From 933b0a9a85d0298481807c116a8ad51a60c6f834 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Thu, 9 Jul 2026 14:20:57 +0200 Subject: [PATCH 001/329] add many pools and multi-zone configuration to infra node pools (#6365) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Mateusz Błażejewski --- cluster/deployment/mock/config.yaml | 5 + cluster/expected/cluster/expected.json | 31 ++++ cluster/pulumi/cluster/src/config.ts | 1 + cluster/pulumi/cluster/src/nodePools.ts | 190 ++++++++++-------------- 4 files changed, 118 insertions(+), 109 deletions(-) diff --git a/cluster/deployment/mock/config.yaml b/cluster/deployment/mock/config.yaml index 0ed9511638..f8a38a6f6b 100644 --- a/cluster/deployment/mock/config.yaml +++ b/cluster/deployment/mock/config.yaml @@ -296,6 +296,11 @@ cluster: minNodes: 0 maxNodes: 1 zones: '*' + additionalInfra: + - nodeType: n4-standard-8 + minNodes: 1 + maxNodes: 3 + zones: '*' gha: githubRepo: "https://github.com/canton-network/splice" runnerVersion: "0.5.10" diff --git a/cluster/expected/cluster/expected.json b/cluster/expected/cluster/expected.json index 8bf405ca80..e6d157bbad 100644 --- a/cluster/expected/cluster/expected.json +++ b/cluster/expected/cluster/expected.json @@ -72,6 +72,37 @@ "id": "", "inputs": { "autoscaling": { + "locationPolicy": "ANY", + "maxNodeCount": 3, + "minNodeCount": 1 + }, + "cluster": "cn-mocknet", + "initialNodeCount": 1, + "nodeConfig": { + "labels": { + "cn_infra": "true" + }, + "loggingVariant": "DEFAULT", + "machineType": "n4-standard-8", + "taints": [ + { + "effect": "NO_SCHEDULE", + "key": "cn_infra", + "value": "true" + } + ] + } + }, + "name": "cn-infra-node-pool-1", + "provider": "", + "type": "gcp:container/nodePool:NodePool" + }, + { + "custom": true, + "id": "", + "inputs": { + "autoscaling": { + "locationPolicy": "ANY", "maxNodeCount": 3, "minNodeCount": 1 }, diff --git a/cluster/pulumi/cluster/src/config.ts b/cluster/pulumi/cluster/src/config.ts index 428dd9e2e1..9a65b4c92f 100644 --- a/cluster/pulumi/cluster/src/config.ts +++ b/cluster/pulumi/cluster/src/config.ts @@ -13,6 +13,7 @@ const GkeNodePoolConfigSchema = z.object({ const GkeClusterConfigSchema = z.object({ nodePools: z.object({ infra: GkeNodePoolConfigSchema, + additionalInfra: z.array(GkeNodePoolConfigSchema).default([]), apps: GkeNodePoolConfigSchema, additionalApps: z.array(GkeNodePoolConfigSchema).default([]), }), diff --git a/cluster/pulumi/cluster/src/nodePools.ts b/cluster/pulumi/cluster/src/nodePools.ts index 276bccadcc..a7392a9463 100644 --- a/cluster/pulumi/cluster/src/nodePools.ts +++ b/cluster/pulumi/cluster/src/nodePools.ts @@ -3,7 +3,6 @@ import * as gcp from '@pulumi/gcp'; import { config, GCP_PROJECT } from '@canton-network/splice-pulumi-common'; -import { hyperdiskSupportConfig } from '../../common/src/config/hyperdiskSupportConfig'; import { gkeClusterConfig, GkeNodePoolConfig } from './config'; export async function installNodePools(): Promise { @@ -14,42 +13,16 @@ export async function installNodePools(): Promise { const zones = await gcp.compute.getZones({ region: config.requireEnv('CLOUDSDK_COMPUTE_REGION'), }); + const nodePoolComputeZone = config.optionalEnv('CLOUDSDK_NODEPOOL_COMPUTE_ZONE'); installAppsNodePools(cluster, zones.names, [ gkeClusterConfig.nodePools.apps, ...gkeClusterConfig.nodePools.additionalApps, ]); - - const nodePoolComputeZone = config.optionalEnv('CLOUDSDK_NODEPOOL_COMPUTE_ZONE'); - new gcp.container.NodePool( - 'cn-infra-node-pool', - { - cluster, - nodeConfig: { - machineType: gkeClusterConfig.nodePools.infra.nodeType, - taints: [ - { - effect: 'NO_SCHEDULE', - key: 'cn_infra', - value: 'true', - }, - ], - labels: { - cn_infra: 'true', - }, - loggingVariant: 'DEFAULT', - }, - nodeLocations: nodePoolComputeZone ? [nodePoolComputeZone] : undefined, - initialNodeCount: 1, - autoscaling: { - minNodeCount: gkeClusterConfig.nodePools.infra.minNodes, - maxNodeCount: gkeClusterConfig.nodePools.infra.maxNodes, - }, - }, - { - replaceOnChanges: ['nodeConfig.machineType'], - } - ); + installInfraNodePools(cluster, zones.names, nodePoolComputeZone, [ + gkeClusterConfig.nodePools.infra, + ...gkeClusterConfig.nodePools.additionalInfra, + ]); new gcp.container.NodePool('gke-node-pool', { cluster, @@ -78,90 +51,89 @@ function installAppsNodePools( allZones: string[], configs: Array ): Array { - const nodepoolLocation = config.optionalEnv('CLOUDSDK_HYPERDISK_NODEPOOL_COMPUTE_ZONE'); + const defaultZone = config.optionalEnv('CLOUDSDK_HYPERDISK_NODEPOOL_COMPUTE_ZONE'); return configs.map((config, index) => { - const zones = - config.zones === '*' - ? allZones - : (config.zones ?? (nodepoolLocation !== undefined ? [nodepoolLocation] : undefined)); - if (hyperdiskSupportConfig.hyperdiskSupport.enabled) { - return hyperdiskNodePool(index, cluster, zones, config); - } else { - return appsNodePool(index, cluster, zones, config); - } - }); -} - -function hyperdiskNodePool( - index: number, - cluster: string, - zones: string[] | undefined, - config: GkeNodePoolConfig -): gcp.container.NodePool { - const name = - index === 0 - ? 'cn-apps-node-pool-hd' // for backwards compat - : `cn-apps-node-pool-${index}-hd`; - return new gcp.container.NodePool(name, { - cluster, - nodeConfig: { - machineType: config.nodeType, - bootDisk: { - diskType: 'hyperdisk-balanced', - sizeGb: config.bootDiskSizeGb || 100, - }, - taints: [ - { - effect: 'NO_SCHEDULE', - key: 'cn_apps', - value: 'true', + const name = + index === 0 + ? 'cn-apps-node-pool-hd' // for backwards compat + : `cn-apps-node-pool-${index}-hd`; + return new gcp.container.NodePool(name, { + cluster, + nodeConfig: { + machineType: config.nodeType, + bootDisk: { + diskType: 'hyperdisk-balanced', + sizeGb: config.bootDiskSizeGb || 100, }, - ], - labels: { - cn_apps: 'hyperdisk', + taints: [ + { + effect: 'NO_SCHEDULE', + key: 'cn_apps', + value: 'true', + }, + ], + labels: { + cn_apps: 'hyperdisk', + }, + loggingVariant: 'DEFAULT', }, - loggingVariant: 'DEFAULT', - }, - nodeLocations: zones, - initialNodeCount: 0, - autoscaling: { - locationPolicy: 'ANY', - minNodeCount: config.minNodes, - maxNodeCount: config.maxNodes, - }, + nodeLocations: + config.zones === '*' + ? allZones + : (config.zones ?? (defaultZone !== undefined ? [defaultZone] : undefined)), + initialNodeCount: 0, + autoscaling: { + locationPolicy: 'ANY', + minNodeCount: config.minNodes, + maxNodeCount: config.maxNodes, + }, + }); }); } -function appsNodePool( - index: number, + +function installInfraNodePools( cluster: string, - zones: string[] | undefined, - appsNodePoolConfig: GkeNodePoolConfig -): gcp.container.NodePool { - const name = - index === 0 - ? 'cn-apps-node-pool' // for backwards compat - : `cn-apps-node-pool-${index}`; - return new gcp.container.NodePool(name, { - cluster, - nodeConfig: { - machineType: appsNodePoolConfig.nodeType, - taints: [ - { - effect: 'NO_SCHEDULE', - key: 'cn_apps', - value: 'true', + allZones: string[], + defaultZone: string | undefined, + configs: Array +): Array { + return configs.map((config, index) => { + const name = + index === 0 + ? 'cn-infra-node-pool' // for backwards compat + : `cn-infra-node-pool-${index}`; + return new gcp.container.NodePool( + name, + { + cluster, + nodeConfig: { + machineType: config.nodeType, + taints: [ + { + effect: 'NO_SCHEDULE', + key: 'cn_infra', + value: 'true', + }, + ], + labels: { + cn_infra: 'true', + }, + loggingVariant: 'DEFAULT', + }, + nodeLocations: + config.zones === '*' + ? allZones + : (config.zones ?? (defaultZone !== undefined ? [defaultZone] : undefined)), + initialNodeCount: 1, + autoscaling: { + locationPolicy: 'ANY', + minNodeCount: config.minNodes, + maxNodeCount: config.maxNodes, }, - ], - labels: { - cn_apps: 'standard', }, - loggingVariant: 'DEFAULT', - }, - initialNodeCount: 0, - autoscaling: { - locationPolicy: 'ANY', - minNodeCount: appsNodePoolConfig.minNodes, - maxNodeCount: appsNodePoolConfig.maxNodes, - }, + { + replaceOnChanges: ['nodeConfig.machineType'], + } + ); }); } From 721ce816669e2ef241705bb3b65f6142786c798b Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Thu, 9 Jul 2026 14:32:53 +0200 Subject: [PATCH 002/329] Support different slack channel for flux alerts (#6366) [static] Signed-off-by: Nicu Reut --- cluster/pulumi/operator/src/flux/config.ts | 16 ++++++++++++++++ cluster/pulumi/operator/src/flux/flux-alerts.ts | 3 ++- 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 cluster/pulumi/operator/src/flux/config.ts diff --git a/cluster/pulumi/operator/src/flux/config.ts b/cluster/pulumi/operator/src/flux/config.ts new file mode 100644 index 0000000000..353d5cbe02 --- /dev/null +++ b/cluster/pulumi/operator/src/flux/config.ts @@ -0,0 +1,16 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { clusterSubConfig, config } from '@canton-network/splice-pulumi-common'; +import { z } from 'zod'; + +const OperatorFluxConfigSchema = z.object({ + flux: z + .object({ + alertSlackChannel: z + .string() + .default(() => config.requireEnv('SLACK_ALERT_NOTIFICATION_CHANNEL_FULL_NAME')), + }) + .prefault({}), +}); + +export const operatorFluxConfig = OperatorFluxConfigSchema.parse(clusterSubConfig('operator')); diff --git a/cluster/pulumi/operator/src/flux/flux-alerts.ts b/cluster/pulumi/operator/src/flux/flux-alerts.ts index 901fad9bac..6c14259a25 100644 --- a/cluster/pulumi/operator/src/flux/flux-alerts.ts +++ b/cluster/pulumi/operator/src/flux/flux-alerts.ts @@ -4,6 +4,7 @@ import * as k8s from '@pulumi/kubernetes'; import { CLUSTER_BASENAME, clusterProdLike, config } from '@canton-network/splice-pulumi-common'; import { namespace } from '../namespace'; +import { operatorFluxConfig } from './config'; import { flux } from './flux'; if (clusterProdLike) { @@ -29,7 +30,7 @@ if (clusterProdLike) { }, spec: { type: 'slack', - channel: config.requireEnv('SLACK_ALERT_NOTIFICATION_CHANNEL_FULL_NAME'), + channel: operatorFluxConfig.flux.alertSlackChannel, address: 'https://slack.com/api/chat.postMessage', secretRef: { name: slackToken.metadata.name }, }, From da42b809e56cda0653c7f83b9d7d9462884ace3e Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Thu, 9 Jul 2026 15:23:46 +0200 Subject: [PATCH 003/329] Bump canton to 3.5.8 (#6364) [ci] Signed-off-by: Nicu Reut --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index 9c294f153b..af13e13295 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.8-snapshot.20260707.19067.0.vc42c3eb4", - "oss_sha256": "sha256:0qgzpgwaaiq7jw7674b9lnrpr7sn8qcr24xbs263iwm8w7v2819i", - "canton_base_image_sha256": "sha256:fd7ec98ace2af1927e52c3db899c0568a3bf6a5e450393e649b7837cac985b2d", - "canton_participant_image_sha256": "sha256:14a1642ee63d4739c64dabbe617f1f5e819a5af2a4f860df055313933564bb4d", - "canton_mediator_image_sha256": "sha256:439d4b2ae0d8b6365f03cddc290c8e2d3c37e898b11f7f6bfac45edd546655c2", - "canton_sequencer_image_sha256": "sha256:53c2b32b3709f9baf057b3a87a812afa162ee5bbe8e38ae415052ede397e1c37" + "version": "3.5.8", + "oss_sha256": "sha256:1f70l5iwy6jhhinihqay9515gcc4s4pgiknxrwg4ycyws69zfil1", + "canton_base_image_sha256": "sha256:4cb2dd84c0f6e18fec98adf46cc90bd6c3c3892ae7e46dd4457668a4ba92a062", + "canton_participant_image_sha256": "sha256:fa5ac29b4632f6ba95c18279bc05d2614f7b743898a40f8e21e8410b26a96263", + "canton_mediator_image_sha256": "sha256:99464e2038bcaf79944bb000f5b9a9caa3bc09b271db1881b8ade48a99a60804", + "canton_sequencer_image_sha256": "sha256:1c4c7e6ac3453031ee17a7d7e37d768b6e487ba626d3f313be63bb5ae4a6dd65" } From a2c076952fc03255eb9c824e37e1aa6cebb1c1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Thu, 9 Jul 2026 15:31:26 +0200 Subject: [PATCH 004/329] add a log ignore for transaction rejection in an unvetting race scenario (#6358) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Błażejewski --- project/ignore-patterns/canton_log.ignore.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project/ignore-patterns/canton_log.ignore.txt b/project/ignore-patterns/canton_log.ignore.txt index 706926c960..92d3377fb2 100644 --- a/project/ignore-patterns/canton_log.ignore.txt +++ b/project/ignore-patterns/canton_log.ignore.txt @@ -198,6 +198,9 @@ Mempool received client request but this node is currently blacklisted, rejectin # This should go away with latest Canton bump Flyway upgrade recommended: PostgreSQL 18.4 is newer than this version of Flyway and support has not been tested. The latest supported version of PostgreSQL is 17 +# TODO(DACH-NY/cn-test-failures#9055) can be removed when it's no longer a warn. +LOCAL_VERDICT_FAILED_MODEL_CONFORMANCE_CHECK.*Rejected transaction due to a failed model conformance check: UnvettedPackages.* + # Make sure to have a trailing newline # TODO(DACH-NY/cn-test-failures#9136) remove once Canton fixes this race condition from not storing packages in dependency order From 12d965a4e2d148c4721ef4775b1e6f525d9e4465 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 9 Jul 2026 15:38:50 +0200 Subject: [PATCH 005/329] Fix external address value in global domain values (#6367) [static] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- apps/app/src/pack/examples/sv-helm/global-domain-values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/pack/examples/sv-helm/global-domain-values.yaml b/apps/app/src/pack/examples/sv-helm/global-domain-values.yaml index e1b5b1c390..a1008a5571 100644 --- a/apps/app/src/pack/examples/sv-helm/global-domain-values.yaml +++ b/apps/app/src/pack/examples/sv-helm/global-domain-values.yaml @@ -12,7 +12,7 @@ sequencer: # Enable when using CantonBFT instead of CometBFT # driver: # type: "cantonbft" - # externalAddress: "sequencer-p2p-15.SERIAL_ID.sv.YOUR_HOSTNAME" + # externalAddress: "sequencer-p2p-SERIAL_ID.sv.YOUR_HOSTNAME" # externalPort: 443 # persistence: # databaseName: sequencer_SERIAL_ID_cantonbft From 4a1fe12e02f4f905abeb2f3e97e8ce89f8b298ac Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Thu, 9 Jul 2026 15:40:34 +0200 Subject: [PATCH 006/329] Unify operator deployment config (#6368) realized we already have a top level field for it, and it was in common [static] Signed-off-by: Nicu Reut --- cluster/pulumi/common/src/operator/config.ts | 19 ++++----------- cluster/pulumi/operator/src/config.ts | 23 +++++++++++++++++++ cluster/pulumi/operator/src/flux/config.ts | 16 ------------- .../pulumi/operator/src/flux/flux-alerts.ts | 4 ++-- cluster/pulumi/operator/src/index.ts | 2 +- 5 files changed, 30 insertions(+), 34 deletions(-) create mode 100644 cluster/pulumi/operator/src/config.ts delete mode 100644 cluster/pulumi/operator/src/flux/config.ts diff --git a/cluster/pulumi/common/src/operator/config.ts b/cluster/pulumi/common/src/operator/config.ts index 28c3af446b..ad36bebb71 100644 --- a/cluster/pulumi/common/src/operator/config.ts +++ b/cluster/pulumi/common/src/operator/config.ts @@ -1,11 +1,6 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { - config, - DeploySvRunbook, - GitReferenceSchema, - K8sResourceSchema, -} from '@canton-network/splice-pulumi-common'; +import { config, DeploySvRunbook, K8sResourceSchema } from '@canton-network/splice-pulumi-common'; import { mustInstallSplitwell, mustInstallValidator1, @@ -63,10 +58,7 @@ function* iterateDefaultProjectFilters(): Generator { } } -export const OperatorDeploymentConfigSchema = z.object({ - operatorDeployment: z.object({ - reference: GitReferenceSchema, - }), +export const DeploymentConfigSchema = z.object({ pulumiStacks: z.record(z.string(), StackConfigSchema).and( z.object({ default: StackConfigSchema, @@ -81,13 +73,10 @@ export const OperatorDeploymentConfigSchema = z.object({ }), }); -export type Config = z.infer; +export type Config = z.infer; export type StackConfig = z.infer; -// eslint-disable-next-line -// @ts-ignore -const fullConfig = OperatorDeploymentConfigSchema.parse(clusterYamlConfig); -export const operatorDeploymentConfig = fullConfig.operatorDeployment; +const fullConfig = DeploymentConfigSchema.parse(clusterYamlConfig); export const deploymentConf = fullConfig.deployment; export const PulumiOperatorGracePeriod = 1800; diff --git a/cluster/pulumi/operator/src/config.ts b/cluster/pulumi/operator/src/config.ts new file mode 100644 index 0000000000..b7f06f9b39 --- /dev/null +++ b/cluster/pulumi/operator/src/config.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { config, GitReferenceSchema } from '@canton-network/splice-pulumi-common'; +import { clusterSubConfig } from '@canton-network/splice-pulumi-common/src/config/config'; +import { z } from 'zod'; + +export const OperatorDeploymentConfigSchema = z.object({ + reference: GitReferenceSchema, + flux: z + .object({ + alertSlackChannel: z + .string() + .default(() => config.requireEnv('SLACK_ALERT_NOTIFICATION_CHANNEL_FULL_NAME')), + }) + .prefault({}), +}); + +export type Config = z.infer; + +export const operatorDeploymentConfig = OperatorDeploymentConfigSchema.parse( + clusterSubConfig('operatorDeployment') +); +export const fluxConfig = operatorDeploymentConfig.flux; diff --git a/cluster/pulumi/operator/src/flux/config.ts b/cluster/pulumi/operator/src/flux/config.ts deleted file mode 100644 index 353d5cbe02..0000000000 --- a/cluster/pulumi/operator/src/flux/config.ts +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 -import { clusterSubConfig, config } from '@canton-network/splice-pulumi-common'; -import { z } from 'zod'; - -const OperatorFluxConfigSchema = z.object({ - flux: z - .object({ - alertSlackChannel: z - .string() - .default(() => config.requireEnv('SLACK_ALERT_NOTIFICATION_CHANNEL_FULL_NAME')), - }) - .prefault({}), -}); - -export const operatorFluxConfig = OperatorFluxConfigSchema.parse(clusterSubConfig('operator')); diff --git a/cluster/pulumi/operator/src/flux/flux-alerts.ts b/cluster/pulumi/operator/src/flux/flux-alerts.ts index 6c14259a25..9015bead27 100644 --- a/cluster/pulumi/operator/src/flux/flux-alerts.ts +++ b/cluster/pulumi/operator/src/flux/flux-alerts.ts @@ -3,8 +3,8 @@ import * as k8s from '@pulumi/kubernetes'; import { CLUSTER_BASENAME, clusterProdLike, config } from '@canton-network/splice-pulumi-common'; +import { fluxConfig } from '../config'; import { namespace } from '../namespace'; -import { operatorFluxConfig } from './config'; import { flux } from './flux'; if (clusterProdLike) { @@ -30,7 +30,7 @@ if (clusterProdLike) { }, spec: { type: 'slack', - channel: operatorFluxConfig.flux.alertSlackChannel, + channel: fluxConfig.alertSlackChannel, address: 'https://slack.com/api/chat.postMessage', secretRef: { name: slackToken.metadata.name }, }, diff --git a/cluster/pulumi/operator/src/index.ts b/cluster/pulumi/operator/src/index.ts index 402316361d..881d272fe3 100644 --- a/cluster/pulumi/operator/src/index.ts +++ b/cluster/pulumi/operator/src/index.ts @@ -4,7 +4,7 @@ import { CLUSTER_BASENAME } from '@canton-network/splice-pulumi-common'; import { gitRepoForRef } from '@canton-network/splice-pulumi-common/src/operator/flux-source'; import { createEnvRefs } from '@canton-network/splice-pulumi-common/src/operator/stack'; -import { operatorDeploymentConfig } from '../../common/src/operator/config'; +import { operatorDeploymentConfig } from './config'; import { flux } from './flux'; import { namespace } from './namespace'; import { installDeploymentStack } from './stacks/deployment'; From 222d9e0f64c76a9f20ac60f54ea736ce2fe861ec Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Thu, 9 Jul 2026 16:39:52 +0200 Subject: [PATCH 007/329] Fix scan disagreement alert for failed consensus (#6370) Signed-off-by: Julien Tinguely --- cluster/expected/observability/expected.json | 2 +- cluster/pulumi/observability/src/observability.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 571499d7a1..70a0a7c2e1 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -88,7 +88,7 @@ "mute_time_intervals.yaml": "apiVersion: 1\nmuteTimes:\n - orgId: 1\n name: sv1-mute\n time_intervals:\n - times:\n - start_time: '04:00'\n end_time: '07:00'\n weekdays:\n - saturday:sunday\n location: UTC\n - orgId: 1\n name: sv-runbook-mute\n time_intervals:\n - times:\n - start_time: '06:00'\n end_time: '10:00'\n location: UTC\n", "notification_policies.yaml": "apiVersion: 1\npolicies:\n - orgId: 1\n receiver: cn-ci-channel-notification\n routes:\n - receiver: cn-ci-channel-notification\n object_matchers:\n - - namespace\n - '='\n - sv1\n mute_time_intervals:\n - sv1-mute\n - receiver: cn-ci-channel-notification\n object_matchers:\n - - namespace\n - '='\n - sv\n mute_time_intervals:\n - sv-runbook-mute\n - receiver: cn-ci-channel-high-prio-notification\n object_matchers:\n - - priority\n - '='\n - high\n group_wait: 30s\n group_interval: 1m\n repeat_interval: 4h\n continue: true\n - receiver: cn-ci-channel-notification\n object_matchers:\n - - team\n - '!='\n - support\n group_wait: 30s\n group_interval: 10m\n repeat_interval: 4h\n", "pruning_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: pruning\n folder: canton-network\n interval: 10m\n rules:\n - uid: ff3sfady2o6bke\n title: Participant pruning\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: daml_pruning_max_event_age\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 720\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 10m\n annotations:\n description: The participant in {{ $labels.namespace }} on migration ID {{ $labels.migration_id }} has not been pruned {{ 30 }} days.\n summary: Participant has not pruned ACS.\n labels:\n \"\": \"\"\n isPaused: false\n - uid: df3sfgne49bswb\n title: Sequencer Pruning\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: daml_sequencer_max_event_age\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 720\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 10m\n annotations:\n description: The sequencer in {{ $labels.namespace }} on migration ID {{ $labels.migration_id }} has not been pruned {{ 30 }} days.\n summary: Sequencer has not been pruned.\n labels:\n \"\": \"\"\n isPaused: false\n - uid: ef3sfj563dq0we\n title: Mediator Pruning\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: daml_mediator_max_event_age\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 720\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 10m\n annotations:\n description: The mediator in {{ $labels.namespace }} on migration ID {{ $labels.migration_id }} has not been pruned {{ 30 }} days.\n summary: Mediator has not been pruned.\n labels:\n \"\": \"\"\n isPaused: false", - "scan_connection_disagreement_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: scan connection consensus\n folder: canton-network\n interval: 10m\n rules:\n - uid: aescandisagreeok1\n title: Scan connection returning successful responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"true\", request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\", http_status!~\"404\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\", http_status!~\"404\"}[30m]))\n instant: true\n intervalMs: 1000\n legendFormat: '{{namespace}}:{{job}}:{{node_name}}:{{scan_connection}}:{{request}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons for this request on this scan connection returned a successful (2xx) response that disagreed with the BFT consensus result over the last 30 minutes.\n severity: critical\n summary: Scan connection {{ $labels.scan_connection }} of job {{ $labels.job }} (node {{ $labels.node_name }}) in namespace {{ $labels.namespace }} returned successful responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons for request {{ $labels.request }} in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n - uid: aescandisagreefail1\n title: Scan connection returning failed responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"false\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{}[30m]))\n instant: true\n intervalMs: 1000\n legendFormat: '{{namespace}}:{{job}}:{{node_name}}:{{scan_connection}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons on this scan connection returned a failed (non-2xx) response that disagreed with the BFT consensus result over the last 30 minutes. This can indicate the scan node is down.\n severity: warning\n summary: Scan connection {{ $labels.scan_connection }} of job {{ $labels.job }} (node {{ $labels.node_name }}) in namespace {{ $labels.namespace }} returned failed responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n", + "scan_connection_disagreement_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: scan connection consensus\n folder: canton-network\n interval: 10m\n rules:\n - uid: aescandisagreeok1\n title: Scan connection returning successful responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"true\", request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\"}[30m]))\n instant: true\n intervalMs: 1000\n legendFormat: '{{namespace}}:{{job}}:{{node_name}}:{{scan_connection}}:{{request}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons for this request on this scan connection returned a successful (2xx) response that disagreed with the BFT consensus result over the last 30 minutes.\n severity: critical\n summary: Scan connection {{ $labels.scan_connection }} of job {{ $labels.job }} (node {{ $labels.node_name }}) in namespace {{ $labels.namespace }} returned successful responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons for request {{ $labels.request }} in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n - uid: aescandisagreefail1\n title: Scan connection returning failed responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"false\", http_status!~\"404\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{http_status!~\"404\"}[30m]))\n instant: true\n intervalMs: 1000\n legendFormat: '{{namespace}}:{{job}}:{{node_name}}:{{scan_connection}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons on this scan connection returned a failed (non-2xx) response that disagreed with the BFT consensus result over the last 30 minutes. This can indicate the scan node is down.\n severity: warning\n summary: Scan connection {{ $labels.scan_connection }} of job {{ $labels.job }} (node {{ $labels.node_name }}) in namespace {{ $labels.namespace }} returned failed responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n", "sequencer_client_delay_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer client delay\n folder: canton\n interval: 1m\n rules:\n - uid: efe3v7bed6134e\n title: Sequencer Client Delay\n condition: client delay threshold exceeded\n data:\n - refId: client delay\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by (namespace, job) (timestamp(daml_sequencer_client_handler_last_sequencing_time_micros) - (daml_sequencer_client_handler_last_sequencing_time_micros>0)/1e6)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: client delay\n - refId: client delay threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 90\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: client delay\n intervalMs: 1000\n maxDataPoints: 43200\n refId: client delay threshold exceeded\n type: threshold\n dashboardUid: ca9df344-c699-4efe-83c2-5fb2639d96d9\n panelId: 3\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: ca9df344-c699-4efe-83c2-5fb2639d96d9\n __panelId__: \"3\"\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting an increased sequencer client delay.\n severity: critical\n labels: {}\n isPaused: false\n", "sequencer_connection_pool_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer connection pool\n folder: canton\n interval: 1m\n rules:\n - uid: dfeb9wma1dk3kc\n title: Health of a connection\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n adhocFilters: []\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (connection, namespace, job) (daml_sequencer_client_sequencer_connection_pool_connection_health{namespace=~\".*\", job=~\".*\"})\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}}:{{job}}:{{connection}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 2\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n panelId: 6\n noDataState: NoData\n execErrState: Error\n for: 1m\n annotations:\n __dashboardUid__: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n __panelId__: \"6\"\n description: A failed connection is periodically retried for availability. A fatal subscription is considered invalid and will never be retried.\n summary: The sequencer connection {{ $labels.connection }} of job {{ $labels.job }} in namespace {{ $labels.namespace }} failed validation.\n isPaused: false\n - uid: dfebau7w2r7cwa\n title: Number of active subscriptions in the subscription pool\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n adhocFilters: []\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (namespace, job) (daml_sequencer_client_sequencer_connection_pool_active_subscriptions{namespace=~\".*\",job=~\".*\"})\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}}:{{job}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: B\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (namespace, job) (daml_sequencer_client_sequencer_connection_pool_subscription_threshold{namespace=~\".*\",job=~\".*\"})\n hide: false\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: $A-$B\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n panelId: 5\n noDataState: NoData\n execErrState: Error\n for: 5m\n annotations:\n __dashboardUid__: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n __panelId__: \"5\"\n description: The liveness margin determines how many subscriptions on different sequencers are continuously maintained, beyond the minimum number defined by the trust threshold. In other words, the subscription pool will strive to maintain at all times (trust threshold + liveness margin)-many subscriptions active. This provides tolerance to subscriptions falling, enabling the node to continue operating while some sequencers are down.\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} has {{ index $values \"C\" }} less active subscriptions than the sum of trust threshold in the subscription pool\n severity: critical\n labels: {}\n isPaused: false\n", "sequencer_rate_limit_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer-rate-limits\n folder: canton\n interval: 1m\n rules:\n - uid: a3f7c8e1d492\n title: Sequencer Throughput Cap Rejections\n condition: rejection_rate_threshold\n data:\n - refId: rejection_rate\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by (namespace, job)(rate(daml_sequencer_throughput_cap_confirmation_request_rejections_total[5m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: rejection_rate\n - refId: rejection_rate_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: rejection_rate\n hide: false\n refId: rejection_rate_threshold\n type: threshold\n dashboardUid: eb26bc60-6c26-408a-b0de-befc19f6915c\n panelId: 3\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eb26bc60-6c26-408a-b0de-befc19f6915c\n __panelId__: \"3\"\n description: Sequencer throughput cap is rejecting confirmation requests on namespace {{ $labels.namespace }} job {{ $labels.job }}. This means members are exceeding their rate limits.\n severity: warning\n summary: Sequencer throughput cap rejections detected on {{ $labels.namespace }} {{ $labels.job }}\n labels: {}\n isPaused: false\n - uid: b4e8d9f2e5a3\n title: Sequencer Circuit Breaker Open\n condition: circuit_breaker_threshold\n data:\n - refId: circuit_breaker_state\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (namespace, job)(daml_sequencer_circuit_breaker_confirmation_request_state)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: circuit_breaker_state\n - refId: circuit_breaker_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.5\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: circuit_breaker_state\n hide: false\n refId: circuit_breaker_threshold\n type: threshold\n dashboardUid: eb26bc60-6c26-408a-b0de-befc19f6915c\n panelId: 10\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eb26bc60-6c26-408a-b0de-befc19f6915c\n __panelId__: \"10\"\n description: Sequencer circuit breaker for confirmation requests is open on namespace {{ $labels.namespace }} job {{ $labels.job }}. The sequencer is rejecting all incoming confirmation requests.\n severity: critical\n summary: Sequencer circuit breaker OPEN on {{ $labels.namespace }} {{ $labels.job }}\n labels: {}\n isPaused: false\n", diff --git a/cluster/pulumi/observability/src/observability.ts b/cluster/pulumi/observability/src/observability.ts index 09edb3b92c..fffc3697fd 100644 --- a/cluster/pulumi/observability/src/observability.ts +++ b/cluster/pulumi/observability/src/observability.ts @@ -743,15 +743,16 @@ function substituteScanConnectionDisagreementAlerts(alert: string): string { if (config.excludedConnections.length > 0) { matchers.push(`scan_connection!~"${config.excludedConnections.join('|')}"`); } - if (config.excludedHttpStatusCodes.length > 0) { - matchers.push(`http_status!~"${config.excludedHttpStatusCodes.join('|')}"`); - } const bareFilter = matchers.join(', '); const filter = bareFilter ? `, ${bareFilter}` : ''; const connectionMatchers: string[] = []; if (config.excludedConnections.length > 0) { connectionMatchers.push(`scan_connection!~"${config.excludedConnections.join('|')}"`); } + if (config.excludedHttpStatusCodes.length > 0) { + matchers.push(`http_status!~"${config.excludedHttpStatusCodes.join('|')}"`); + connectionMatchers.push(`http_status!~"${config.excludedHttpStatusCodes.join('|')}"`); + } const connectionBareFilter = connectionMatchers.join(', '); const connectionFilter = connectionBareFilter ? `, ${connectionBareFilter}` : ''; return alert From c094bfa8fe6569bee059eef3b599170af225658b Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Thu, 9 Jul 2026 13:20:04 -0400 Subject: [PATCH 008/329] Clear release notes for 0.6.12 [static] (#6376) Signed-off-by: Stephen Compall --- docs/src/release_notes_upcoming.rst | 44 ----------------------------- 1 file changed, 44 deletions(-) diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index f48afd779b..5f5ee78c44 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -6,47 +6,3 @@ .. NOTE: add your upcoming release notes below this line. They are included in the `release_notes.rst`. .. release-notes:: Upcoming - - .. note:: - - Next-release notes - - - Deployment - - - Helm - - - Added security contexts for all Helm-based deployments intended for production. - This improves the security of Kubernetes based deployments. - - - Scan - - - Add a metric for the size of the most recent ACS snapshot - - - Token Standard APIs - - - Add the ``accountInputFieldsToShow`` property in the token metadata - API (``token-metadata-v1.yaml``). - This property allows instruments that support only account-ids or only - account providers to inform wallets of this fact. - This change is backwards compatible. - - - Daml - - - Adds support for specifying weight on the ``FeaturedAppRight`` contract as described in - `CIP-0104 amendment `__. - - Fix a bug in ``AmuletAllocation``, which prohibited settling V1 amulet allocations when - using them with the Token Standard V2 feature of setting multiple - executors via metadata. - - - These changes require a Daml upgrade to the following versions: - - ================== ======= - name version - ================== ======= - amulet 0.1.22 - amuletNameService 0.1.23 - dsoGovernance 0.1.28 - validatorLifecycle 0.1.8 - wallet 0.1.23 - walletPayments 0.1.22 - ================== ======= From 6d507a2b5bc5aa9aea6f7d2925581a1b2400430f Mon Sep 17 00:00:00 2001 From: Robert Autenrieth <31539813+rautenrieth-da@users.noreply.github.com> Date: Thu, 9 Jul 2026 19:53:22 +0200 Subject: [PATCH 009/329] Log providers with wrong vetting state (#6357) Signed-off-by: Robert Autenrieth --- .../sv/automation/delegatebased/ProcessRewardsTrigger.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ProcessRewardsTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ProcessRewardsTrigger.scala index 67cb4bd29e..e48067c31f 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ProcessRewardsTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ProcessRewardsTrigger.scala @@ -169,6 +169,11 @@ private[delegatebased] abstract class ProcessRewardsTriggerBase( } .map { supportByProvider => val withWrongVettingState = supportByProvider.collect { case (provider, false) => provider } + if (withWrongVettingState.nonEmpty) { + logger.info( + s"Providers with wrong vetting state for batch: ${withWrongVettingState.mkString(", ")}" + ) + } damlSetOf(withWrongVettingState) } } From 70cce8ddb48c4ab597b824f57d94d5a1f6754da6 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Fri, 10 Jul 2026 09:54:14 +0200 Subject: [PATCH 010/329] Expose registry/token: Change istio authorization policies to allow traffic to the registry endpoints (#5970) [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- cluster/configs/shared/base.yaml | 10 +- .../shared/rate-limits/token-registry.yaml | 121 + .../configs/shared/rate-limits/v0-acs.yaml | 9 +- .../scratchneta/config.resolved.yaml | 135 +- .../scratchnetb/config.resolved.yaml | 135 +- .../scratchnetc/config.resolved.yaml | 135 +- .../scratchnetd/config.resolved.yaml | 135 +- .../scratchnete/config.resolved.yaml | 135 +- cluster/expected/canton-network/expected.json | 3236 ++++++++++++++--- cluster/expected/infra/expected.json | 36 + cluster/expected/sv-runbook/expected.json | 1086 +++++- .../pulumi/common/src/config/scanEndpoints.ts | 57 + .../common/src/ratelimit/envoyRateLimiter.ts | 129 +- .../common/src/ratelimit/rateLimitSchema.ts | 2 +- cluster/pulumi/infra/src/cloudArmor.ts | 21 +- cluster/pulumi/infra/src/config.ts | 6 +- cluster/pulumi/infra/src/istio.ts | 40 + 17 files changed, 4777 insertions(+), 651 deletions(-) create mode 100644 cluster/configs/shared/rate-limits/token-registry.yaml diff --git a/cluster/configs/shared/base.yaml b/cluster/configs/shared/base.yaml index 17c3d01f33..08f482f842 100644 --- a/cluster/configs/shared/base.yaml +++ b/cluster/configs/shared/base.yaml @@ -27,6 +27,7 @@ infra: proxyForIstioHttp: false istio: enableIngressAccessLogging: true + enablePublicTokenRegistry: true sequencerFlowControl: # See https://github.com/DACH-NY/canton-network-internal/issues/4901 # The values are somewhat arbitrary so more fine tuning may be appropriate. @@ -226,7 +227,12 @@ cloudArmor: pathPrefix: /api/scan throttleAcrossAllEndpointsAllIps: withinIntervalSeconds: 60 - maxRequestsBeforeHttp429: 0 + maxRequestsBeforeHttp429: 0 # Keeps scan completely closed to the public + tokenRegistry: + pathPrefix: /registry + throttleAcrossAllEndpointsAllIps: + withinIntervalSeconds: 60 + maxRequestsBeforeHttp429: 200 multiValidator: postgresPvcSize: '100Gi' resources: @@ -250,7 +256,7 @@ multiValidator: sv: scan: externalRateLimits: - !include(./rate-limits/v0-acs.yaml;./rate-limits/unlimited.yaml;./rate-limits/public-banned.yaml) + !include(./rate-limits/v0-acs.yaml;./rate-limits/unlimited.yaml;./rate-limits/public-banned.yaml;./rate-limits/token-registry.yaml) globalLimits: maxTokens: 2147483647 tokensPerFill: 2147483647 diff --git a/cluster/configs/shared/rate-limits/token-registry.yaml b/cluster/configs/shared/rate-limits/token-registry.yaml new file mode 100644 index 0000000000..54de5248fc --- /dev/null +++ b/cluster/configs/shared/rate-limits/token-registry.yaml @@ -0,0 +1,121 @@ +rateLimits: + /registry/allocations/v1: + name: registry-allocations + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/metadata/v1/info: + name: registry-metadata-info + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/metadata/v1/instruments: + name: registry-metadata-instruments + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/allocation-instruction/v1/allocation-factory: + name: registry-allocation-factory + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/transfer-instruction/v1: + name: registry-transfer-instruction + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/transfer-instruction/v1/transfer-factory: + name: registry-transfer-factory + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/allocation/v2/settlement-factory: + name: registry-settlement-factory-v2 + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/allocations/v2: + name: registry-allocations-v2 + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/allocation-instruction/v2/allocation-factory: + name: registry-allocation-factory-v2 + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/allocation-instruction/v2: + name: registry-allocation-instruction-v2 + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/transfer-instruction/v2/transfer-factory: + name: registry-transfer-factory-v2 + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s + /registry/transfer-instruction/v2: + name: registry-transfer-instruction-v2 + type: limited + maxTokens: 500 + tokensPerFill: 500 + fillInterval: 60s + perIpLimits: + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s diff --git a/cluster/configs/shared/rate-limits/v0-acs.yaml b/cluster/configs/shared/rate-limits/v0-acs.yaml index f4d90428c0..af956dbdb2 100644 --- a/cluster/configs/shared/rate-limits/v0-acs.yaml +++ b/cluster/configs/shared/rate-limits/v0-acs.yaml @@ -2,7 +2,10 @@ rateLimits: /api/scan/v0/acs: name: acs type: limited - clientIp: true - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 500 # The per-endpoint global limit (shared between all ips) + tokensPerFill: 500 fillInterval: 60s + perIpLimits: # per-IP limit + maxTokens: 10 + tokensPerFill: 5 + fillInterval: 60s diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index f6144013ce..f1d8ba1af9 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -9,6 +9,11 @@ cloudArmor: throttleAcrossAllEndpointsAllIps: maxRequestsBeforeHttp429: 0 withinIntervalSeconds: 60 + tokenRegistry: + pathPrefix: '/registry' + throttleAcrossAllEndpointsAllIps: + maxRequestsBeforeHttp429: 200 + withinIntervalSeconds: 60 cluster: hyperdiskSupport: enabled: true @@ -33,6 +38,7 @@ infra: proxyForIstioHttp: false istio: enableIngressAccessLogging: true + enablePublicTokenRegistry: true sequencerFlowControl: initialConnectionWindowSize: 52428800 initialStreamWindowSize: 524288 @@ -212,11 +218,14 @@ sv: name: 'status' type: 'unlimited' /api/scan/v0/acs: - clientIp: true fillInterval: '60s' - maxTokens: 10 + maxTokens: 500 name: 'acs' - tokensPerFill: 5 + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 type: 'limited' /api/scan/v0/active-synchronizer-serial: name: 'activeSynchronizerSerial' @@ -365,6 +374,126 @@ sv: /api/scan/version: name: 'version' type: 'unlimited' + /registry/allocation-instruction/v1/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation/v2/settlement-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-settlement-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/info: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-info' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/instruments: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-instruments' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' svs: sv: cometbft: diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index f6144013ce..f1d8ba1af9 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -9,6 +9,11 @@ cloudArmor: throttleAcrossAllEndpointsAllIps: maxRequestsBeforeHttp429: 0 withinIntervalSeconds: 60 + tokenRegistry: + pathPrefix: '/registry' + throttleAcrossAllEndpointsAllIps: + maxRequestsBeforeHttp429: 200 + withinIntervalSeconds: 60 cluster: hyperdiskSupport: enabled: true @@ -33,6 +38,7 @@ infra: proxyForIstioHttp: false istio: enableIngressAccessLogging: true + enablePublicTokenRegistry: true sequencerFlowControl: initialConnectionWindowSize: 52428800 initialStreamWindowSize: 524288 @@ -212,11 +218,14 @@ sv: name: 'status' type: 'unlimited' /api/scan/v0/acs: - clientIp: true fillInterval: '60s' - maxTokens: 10 + maxTokens: 500 name: 'acs' - tokensPerFill: 5 + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 type: 'limited' /api/scan/v0/active-synchronizer-serial: name: 'activeSynchronizerSerial' @@ -365,6 +374,126 @@ sv: /api/scan/version: name: 'version' type: 'unlimited' + /registry/allocation-instruction/v1/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation/v2/settlement-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-settlement-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/info: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-info' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/instruments: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-instruments' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' svs: sv: cometbft: diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index f6144013ce..f1d8ba1af9 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -9,6 +9,11 @@ cloudArmor: throttleAcrossAllEndpointsAllIps: maxRequestsBeforeHttp429: 0 withinIntervalSeconds: 60 + tokenRegistry: + pathPrefix: '/registry' + throttleAcrossAllEndpointsAllIps: + maxRequestsBeforeHttp429: 200 + withinIntervalSeconds: 60 cluster: hyperdiskSupport: enabled: true @@ -33,6 +38,7 @@ infra: proxyForIstioHttp: false istio: enableIngressAccessLogging: true + enablePublicTokenRegistry: true sequencerFlowControl: initialConnectionWindowSize: 52428800 initialStreamWindowSize: 524288 @@ -212,11 +218,14 @@ sv: name: 'status' type: 'unlimited' /api/scan/v0/acs: - clientIp: true fillInterval: '60s' - maxTokens: 10 + maxTokens: 500 name: 'acs' - tokensPerFill: 5 + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 type: 'limited' /api/scan/v0/active-synchronizer-serial: name: 'activeSynchronizerSerial' @@ -365,6 +374,126 @@ sv: /api/scan/version: name: 'version' type: 'unlimited' + /registry/allocation-instruction/v1/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation/v2/settlement-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-settlement-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/info: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-info' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/instruments: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-instruments' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' svs: sv: cometbft: diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index f6144013ce..f1d8ba1af9 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -9,6 +9,11 @@ cloudArmor: throttleAcrossAllEndpointsAllIps: maxRequestsBeforeHttp429: 0 withinIntervalSeconds: 60 + tokenRegistry: + pathPrefix: '/registry' + throttleAcrossAllEndpointsAllIps: + maxRequestsBeforeHttp429: 200 + withinIntervalSeconds: 60 cluster: hyperdiskSupport: enabled: true @@ -33,6 +38,7 @@ infra: proxyForIstioHttp: false istio: enableIngressAccessLogging: true + enablePublicTokenRegistry: true sequencerFlowControl: initialConnectionWindowSize: 52428800 initialStreamWindowSize: 524288 @@ -212,11 +218,14 @@ sv: name: 'status' type: 'unlimited' /api/scan/v0/acs: - clientIp: true fillInterval: '60s' - maxTokens: 10 + maxTokens: 500 name: 'acs' - tokensPerFill: 5 + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 type: 'limited' /api/scan/v0/active-synchronizer-serial: name: 'activeSynchronizerSerial' @@ -365,6 +374,126 @@ sv: /api/scan/version: name: 'version' type: 'unlimited' + /registry/allocation-instruction/v1/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation/v2/settlement-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-settlement-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/info: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-info' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/instruments: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-instruments' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' svs: sv: cometbft: diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index f6144013ce..f1d8ba1af9 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -9,6 +9,11 @@ cloudArmor: throttleAcrossAllEndpointsAllIps: maxRequestsBeforeHttp429: 0 withinIntervalSeconds: 60 + tokenRegistry: + pathPrefix: '/registry' + throttleAcrossAllEndpointsAllIps: + maxRequestsBeforeHttp429: 200 + withinIntervalSeconds: 60 cluster: hyperdiskSupport: enabled: true @@ -33,6 +38,7 @@ infra: proxyForIstioHttp: false istio: enableIngressAccessLogging: true + enablePublicTokenRegistry: true sequencerFlowControl: initialConnectionWindowSize: 52428800 initialStreamWindowSize: 524288 @@ -212,11 +218,14 @@ sv: name: 'status' type: 'unlimited' /api/scan/v0/acs: - clientIp: true fillInterval: '60s' - maxTokens: 10 + maxTokens: 500 name: 'acs' - tokensPerFill: 5 + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 type: 'limited' /api/scan/v0/active-synchronizer-serial: name: 'activeSynchronizerSerial' @@ -365,6 +374,126 @@ sv: /api/scan/version: name: 'version' type: 'unlimited' + /registry/allocation-instruction/v1/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation-instruction/v2/allocation-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocation-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocation/v2/settlement-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-settlement-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/allocations/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-allocations-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/info: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-info' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/metadata/v1/instruments: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-metadata-instruments' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v1/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-instruction-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' + /registry/transfer-instruction/v2/transfer-factory: + fillInterval: '60s' + maxTokens: 500 + name: 'registry-transfer-factory-v2' + perIpLimits: + fillInterval: '60s' + maxTokens: 10 + tokensPerFill: 5 + tokensPerFill: 500 + type: 'limited' svs: sv: cometbft: diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index 14b19d1619..dde35ae7bc 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -1599,11 +1599,15 @@ "type": "unlimited" }, "/api/scan/v0/acs": { - "clientIp": true, "fillInterval": "60s", - "maxTokens": 10, + "maxTokens": 500, "name": "acs", - "tokensPerFill": 5, + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, "type": "limited" }, "/api/scan/v0/active-synchronizer-serial": { @@ -1801,6 +1805,150 @@ "/api/scan/version": { "name": "version", "type": "unlimited" + }, + "/registry/allocation-instruction/v1/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocation-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocation-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocation-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocation-instruction/v2/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocation-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocation/v2/settlement-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-settlement-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocations/v1": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocations", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocations/v2": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocations-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/metadata/v1/info": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-metadata-info", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/metadata/v1/instruments": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-metadata-instruments", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v1": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-instruction", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v1/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v2/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" } } }, @@ -1834,11 +1982,15 @@ "type": "unlimited" }, "/api/scan/v0/acs": { - "clientIp": true, "fillInterval": "60s", - "maxTokens": 10, + "maxTokens": 500, "name": "acs", - "tokensPerFill": 5, + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, "type": "limited" }, "/api/scan/v0/active-synchronizer-serial": { @@ -2036,6 +2188,150 @@ "/api/scan/version": { "name": "version", "type": "unlimited" + }, + "/registry/allocation-instruction/v1/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocation-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocation-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocation-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocation-instruction/v2/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocation-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocation/v2/settlement-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-settlement-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocations/v1": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocations", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocations/v2": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocations-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/metadata/v1/info": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-metadata-info", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/metadata/v1/instruments": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-metadata-instruments", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v1": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-instruction", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v1/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v2/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" } } }, @@ -2555,6 +2851,25 @@ "value": { "route": { "rate_limits": [ + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "acs", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/api/scan/v0/acs" + } + } + ] + } + } + ] + }, { "actions": [ { @@ -2579,346 +2894,1235 @@ } } ] - } - ] - }, - "typed_per_filter_config": { - "envoy.filters.http.local_ratelimit": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", - "descriptors": [ - { - "entries": [ - { - "key": "header_match", - "value": "acs" - }, - { - "key": "client_ip" + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 } - } - ], - "filter_enabled": { - "default_value": { - "denominator": "HUNDRED", - "numerator": 100 - }, - "runtime_key": "local_rate_limit_enabled" + ] }, - "filter_enforced": { - "default_value": { - "denominator": "HUNDRED", - "numerator": 100 - }, - "runtime_key": "local_rate_limit_enforced" + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] }, - "response_headers_to_add": [ - { - "append_action": "OVERWRITE_IF_EXISTS_OR_ADD", - "header": { - "key": "x-local-rate-limit", - "value": "true" + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } } - } - ], - "stat_prefix": "http_local_rate_limiter", - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 2147483647, - "tokens_per_fill": 2147483647 - } - } - } - } - } - } - ], - "workloadSelector": { - "labels": { - "app": "scan-app" - } - } - } - }, - "name": "sv-1-scan-app-rate-limit", - "provider": "", - "type": "kubernetes:networking.istio.io/v1alpha3:EnvoyFilter" - }, - { - "custom": true, - "id": "", - "inputs": { - "bigqueryProfile": {}, - "connectionProfileId": "sv-1-scan-bq-cxn", - "displayName": "sv-1-scan-bq-cxn", - "labels": { - "cluster": "mock" - }, - "location": "europe-west6" - }, - "name": "sv-1-scan-bq-cxn", - "provider": "", - "type": "gcp:datastream/connectionProfile:ConnectionProfile" - }, - { - "custom": true, - "id": "", - "inputs": { - "connectionProfileId": "sv-1-scan-update-history-cxn", - "displayName": "sv-1-scan-update-history-cxn", - "labels": { - "cluster": "mock" - }, - "location": "europe-west6", - "postgresqlProfile": { - "database": "scan_sv_1", - "port": 5432, - "username": "bqdatastream" - }, - "privateConnectivity": {} - }, - "name": "sv-1-scan-update-history-cxn", - "provider": "", - "type": "gcp:datastream/connectionProfile:ConnectionProfile" - }, - { - "custom": true, - "id": "", - "inputs": { - "displayName": "sv-1-scan-update-history-datastream-vpc", - "labels": { - "cluster": "mock" - }, - "location": "europe-west6", - "privateConnectionId": "sv-1-scan-update-history-datastream-vpc", - "vpcPeeringConfig": { - "subnet": "1.2.3.16/29", - "vpc": "projects/test-project/global/networks/default" - } - }, - "name": "sv-1-scan-update-history-datastream-vpc", - "provider": "", - "type": "gcp:datastream/privateConnection:PrivateConnection" - }, - { - "custom": true, - "id": "", - "inputs": { - "backfillAll": {}, - "desiredState": "RUNNING", - "destinationConfig": { - "bigqueryDestinationConfig": { - "dataFreshness": "14400s", - "singleTargetDataset": { - "datasetId": "projects/undefined/datasets/mock_da2_scan" - } - } - }, - "displayName": "sv-1-scan-update-history", - "labels": { - "cluster": "mock" - }, - "location": "europe-west6", - "sourceConfig": { - "postgresqlSourceConfig": { - "includeObjects": { - "postgresqlSchemas": [ - { - "postgresqlTables": [ - { - "table": "update_history_creates" - }, - { - "table": "update_history_exercises" - }, - { - "table": "scan_verdict_store" - }, - { - "table": "scan_verdict_transaction_view_store" - }, - { - "table": "app_activity_record_store" - } - ], - "schema": "scan_sv_1" - } - ] - }, - "publication": "update_history_datastream_pub", - "replicationSlot": "update_history_datastream_r_slot" - } - }, - "streamId": "sv-1-scan-update-history" - }, - "name": "sv-1-scan-update-history", - "provider": "", - "type": "gcp:datastream/stream:Stream" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-scan", - "compat": "true", - "maxHistory": 10, - "name": "scan", - "namespace": "sv-1", - "timeout": 600, - "values": { - "additionalEnvVars": [ - { - "name": "CUSTOM_MOCK_ENV_VAR_NAME", - "value": "CUSTOM_MOCK_ENV_VAR_VALUE" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomScanAppJvmFlag", - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ + ] + }, { - "key": "cn_apps", - "operator": "Exists" + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] }, { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + } ] - } - ] - } - ] - } - } - }, - "apiRequestLogLevel": "DEBUG", - "bulkStorage": { - "s3": { - "bucketName": "mock-sv-1-bulk", - "endpoint": "https://storage.googleapis.com", - "region": "europe-west6", - "secretName": "splice-app-bulk-storage-credentials" - } - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "enablePostgresMetrics": true, - "failOnAppVersionMismatch": true, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "isFirstSv": true, - "logAsyncFlush": false, - "logLevel": "WARN", - "metrics": { - "enable": true - }, - "participantAddress": "participant", - "persistence": { - "databaseName": "scan_sv_1", - "port": 5432, - "postgresName": "cn-apps-pg", - "schema": "scan_sv_1", - "secretName": "cn-apps-pg-secrets", - "user": "cnadmin" - }, - "publicUrl": "https://scan.sv-2.mock.global.canton.network.digitalasset.com", - "resources": { - "limits": { - "memory": "2048Mi" - }, - "requests": { - "cpu": "0.5", - "memory": "1536Mi" - } - }, - "spliceInstanceNames": { - "amuletName": "Amulet", - "amuletNameAcronym": "AMT", - "nameServiceName": "Amulet Name Service", - "nameServiceNameAcronym": "ANS", - "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", - "networkName": "Splice" - }, - "synchronizers": { - "current": { - "cantonBft": { - "p2pUrl": "https://sequencer-p2p-9.sv-2.mock.global.canton.network.digitalasset.com" - }, - "mediator": "global-domain-9-mediator", - "sequencer": "global-domain-9-sequencer" - }, - "legacy": { - "mediator": "global-domain-8-mediator", - "sequencer": "global-domain-8-sequencer" - }, - "successor": { - "cantonBft": { - "p2pUrl": "https://sequencer-p2p-10.sv-2.mock.global.canton.network.digitalasset.com" - }, - "mediator": "global-domain-10-mediator", - "sequencer": "global-domain-10-sequencer" - } - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] - }, - "version": "0.3.20" - }, - "name": "sv-1-scan", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-sv-node", - "compat": "true", - "maxHistory": 10, - "name": "sv-app", - "namespace": "sv-1", - "timeout": 600, - "values": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "additionalEnvVars": [ - { - "name": "CUSTOM_MOCK_ENV_VAR_NAME", - "value": "CUSTOM_MOCK_ENV_VAR_VALUE" - }, - { - "name": "ADDITIONAL_CONFIG_MEDIATOR_PRUNING", - "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.mediator.pruning {\n cron = \"0 0 * * *\"\n max-duration = \"30m\"\n retention = \"7d\"\n }" - }, - { - "name": "ADDITIONAL_CONFIG_CANTON_BFT_PRUNING", - "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.sequencer.canton-bft-pruning {\n cron = \"0 /10 * * * ?\"\n max-duration = \"5m\"\n retention = \"15 days\"\n }" - }, - { - "name": "ADDITIONAL_CONFIG_ADDITIONAL_PACKAGES_TO_UNVET", - "value": "canton.sv-apps.sv.additional-packages-to-unvet.\"splice-wallet-payments\" = [\"0.1.15\", \"0.1.16\"]\ncanton.sv-apps.sv.additional-packages-to-unvet.\"splice-amulet\" = [\"0.1.15\"]" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomSvAppJvmFlag", - "affinity": { + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + } + ] + }, + "typed_per_filter_config": { + "envoy.filters.http.local_ratelimit": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", + "descriptors": [ + { + "entries": [ + { + "key": "header_match", + "value": "acs" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "acs" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-instruments" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-instruments" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + } + ], + "filter_enabled": { + "default_value": { + "denominator": "HUNDRED", + "numerator": 100 + }, + "runtime_key": "local_rate_limit_enabled" + }, + "filter_enforced": { + "default_value": { + "denominator": "HUNDRED", + "numerator": 100 + }, + "runtime_key": "local_rate_limit_enforced" + }, + "response_headers_to_add": [ + { + "append_action": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "x-local-rate-limit", + "value": "true" + } + } + ], + "stat_prefix": "http_local_rate_limiter", + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 2147483647, + "tokens_per_fill": 2147483647 + } + } + } + } + } + } + ], + "workloadSelector": { + "labels": { + "app": "scan-app" + } + } + } + }, + "name": "sv-1-scan-app-rate-limit", + "provider": "", + "type": "kubernetes:networking.istio.io/v1alpha3:EnvoyFilter" + }, + { + "custom": true, + "id": "", + "inputs": { + "bigqueryProfile": {}, + "connectionProfileId": "sv-1-scan-bq-cxn", + "displayName": "sv-1-scan-bq-cxn", + "labels": { + "cluster": "mock" + }, + "location": "europe-west6" + }, + "name": "sv-1-scan-bq-cxn", + "provider": "", + "type": "gcp:datastream/connectionProfile:ConnectionProfile" + }, + { + "custom": true, + "id": "", + "inputs": { + "connectionProfileId": "sv-1-scan-update-history-cxn", + "displayName": "sv-1-scan-update-history-cxn", + "labels": { + "cluster": "mock" + }, + "location": "europe-west6", + "postgresqlProfile": { + "database": "scan_sv_1", + "port": 5432, + "username": "bqdatastream" + }, + "privateConnectivity": {} + }, + "name": "sv-1-scan-update-history-cxn", + "provider": "", + "type": "gcp:datastream/connectionProfile:ConnectionProfile" + }, + { + "custom": true, + "id": "", + "inputs": { + "displayName": "sv-1-scan-update-history-datastream-vpc", + "labels": { + "cluster": "mock" + }, + "location": "europe-west6", + "privateConnectionId": "sv-1-scan-update-history-datastream-vpc", + "vpcPeeringConfig": { + "subnet": "1.2.3.16/29", + "vpc": "projects/test-project/global/networks/default" + } + }, + "name": "sv-1-scan-update-history-datastream-vpc", + "provider": "", + "type": "gcp:datastream/privateConnection:PrivateConnection" + }, + { + "custom": true, + "id": "", + "inputs": { + "backfillAll": {}, + "desiredState": "RUNNING", + "destinationConfig": { + "bigqueryDestinationConfig": { + "dataFreshness": "14400s", + "singleTargetDataset": { + "datasetId": "projects/undefined/datasets/mock_da2_scan" + } + } + }, + "displayName": "sv-1-scan-update-history", + "labels": { + "cluster": "mock" + }, + "location": "europe-west6", + "sourceConfig": { + "postgresqlSourceConfig": { + "includeObjects": { + "postgresqlSchemas": [ + { + "postgresqlTables": [ + { + "table": "update_history_creates" + }, + { + "table": "update_history_exercises" + }, + { + "table": "scan_verdict_store" + }, + { + "table": "scan_verdict_transaction_view_store" + }, + { + "table": "app_activity_record_store" + } + ], + "schema": "scan_sv_1" + } + ] + }, + "publication": "update_history_datastream_pub", + "replicationSlot": "update_history_datastream_r_slot" + } + }, + "streamId": "sv-1-scan-update-history" + }, + "name": "sv-1-scan-update-history", + "provider": "", + "type": "gcp:datastream/stream:Stream" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-scan", + "compat": "true", + "maxHistory": 10, + "name": "scan", + "namespace": "sv-1", + "timeout": 600, + "values": { + "additionalEnvVars": [ + { + "name": "CUSTOM_MOCK_ENV_VAR_NAME", + "value": "CUSTOM_MOCK_ENV_VAR_VALUE" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomScanAppJvmFlag", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "apiRequestLogLevel": "DEBUG", + "bulkStorage": { + "s3": { + "bucketName": "mock-sv-1-bulk", + "endpoint": "https://storage.googleapis.com", + "region": "europe-west6", + "secretName": "splice-app-bulk-storage-credentials" + } + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "enablePostgresMetrics": true, + "failOnAppVersionMismatch": true, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "isFirstSv": true, + "logAsyncFlush": false, + "logLevel": "WARN", + "metrics": { + "enable": true + }, + "participantAddress": "participant", + "persistence": { + "databaseName": "scan_sv_1", + "port": 5432, + "postgresName": "cn-apps-pg", + "schema": "scan_sv_1", + "secretName": "cn-apps-pg-secrets", + "user": "cnadmin" + }, + "publicUrl": "https://scan.sv-2.mock.global.canton.network.digitalasset.com", + "resources": { + "limits": { + "memory": "2048Mi" + }, + "requests": { + "cpu": "0.5", + "memory": "1536Mi" + } + }, + "spliceInstanceNames": { + "amuletName": "Amulet", + "amuletNameAcronym": "AMT", + "nameServiceName": "Amulet Name Service", + "nameServiceNameAcronym": "ANS", + "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", + "networkName": "Splice" + }, + "synchronizers": { + "current": { + "cantonBft": { + "p2pUrl": "https://sequencer-p2p-9.sv-2.mock.global.canton.network.digitalasset.com" + }, + "mediator": "global-domain-9-mediator", + "sequencer": "global-domain-9-sequencer" + }, + "legacy": { + "mediator": "global-domain-8-mediator", + "sequencer": "global-domain-8-sequencer" + }, + "successor": { + "cantonBft": { + "p2pUrl": "https://sequencer-p2p-10.sv-2.mock.global.canton.network.digitalasset.com" + }, + "mediator": "global-domain-10-mediator", + "sequencer": "global-domain-10-sequencer" + } + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + }, + "version": "0.3.20" + }, + "name": "sv-1-scan", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-sv-node", + "compat": "true", + "maxHistory": 10, + "name": "sv-app", + "namespace": "sv-1", + "timeout": 600, + "values": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "additionalEnvVars": [ + { + "name": "CUSTOM_MOCK_ENV_VAR_NAME", + "value": "CUSTOM_MOCK_ENV_VAR_VALUE" + }, + { + "name": "ADDITIONAL_CONFIG_MEDIATOR_PRUNING", + "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.mediator.pruning {\n cron = \"0 0 * * *\"\n max-duration = \"30m\"\n retention = \"7d\"\n }" + }, + { + "name": "ADDITIONAL_CONFIG_CANTON_BFT_PRUNING", + "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.sequencer.canton-bft-pruning {\n cron = \"0 /10 * * * ?\"\n max-duration = \"5m\"\n retention = \"15 days\"\n }" + }, + { + "name": "ADDITIONAL_CONFIG_ADDITIONAL_PACKAGES_TO_UNVET", + "value": "canton.sv-apps.sv.additional-packages-to-unvet.\"splice-wallet-payments\" = [\"0.1.15\", \"0.1.16\"]\ncanton.sv-apps.sv.additional-packages-to-unvet.\"splice-amulet\" = [\"0.1.15\"]" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomSvAppJvmFlag", + "affinity": { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { "nodeSelectorTerms": [ @@ -3568,258 +4772,1166 @@ { "matchExpressions": [ { - "key": "cn_apps", - "operator": "Exists" + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "deploymentDetails": { + "configDigest": { + "allowedIpRanges": { + "type": "md5", + "value": "90eedde4a8599204a45dcb972e212c8b" + }, + "approvedSvIdentities": { + "type": "md5", + "value": "5871224b744b45122540483fddbd550f" + } + }, + "network": "test", + "sv": { + "version": "0.3.20" + }, + "synchronizer": { + "current": { + "chainIdSuffix": "4", + "migrationId": 2, + "synchronizerSerialId": 9, + "version": "0.3.20" + }, + "legacy": { + "chainIdSuffix": "4", + "migrationId": 2, + "synchronizerSerialId": 8, + "version": "0.3.20" + }, + "successor": { + "chainIdSuffix": "4", + "migrationId": 2, + "synchronizerSerialId": 10, + "version": "0.3.21" + } + } + }, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "istioVirtualService": { + "gateway": "cluster-ingress/cn-http-gateway", + "host": "info.sv-1.mock.global.canton.network.digitalasset.com" + }, + "runtimeDetails": { + "scanUrl": "http://scan-app.sv-da-1:5012", + "synchronizerSerialId": 9 + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + }, + "version": "0.3.20" + }, + "name": "sv-da-1-info", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-cluster-ingress-runbook", + "compat": "true", + "maxHistory": 10, + "name": "ingress-sv", + "namespace": "sv-da-1", + "timeout": 600, + "values": { + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet", + "svIngressName": "sv-1", + "svNamespace": "sv-da-1" + }, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "ingress": { + "decentralizedSynchronizer": { + "migrationIds": [ + "9", + "8", + "7", + "10" + ] + } + }, + "rateLimit": { + "scan": { + "enable": false + } + }, + "spliceDomainNames": { + "nameServiceDomain": "cns" + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ], + "withSvIngress": true + }, + "version": "0.3.20" + }, + "name": "sv-da-1-ingress-sv", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "EnvoyFilter", + "metadata": { + "annotations": { + "proxy.istio.io/config": "proxyStatsMatcher:\n inclusionRegexps:\n - \".*http_local_rate_limit.*\"" + }, + "name": "scan-app-rate-limit", + "namespace": "sv-da-1" + }, + "spec": { + "configPatches": [ + { + "applyTo": "HTTP_FILTER", + "match": { + "context": "SIDECAR_INBOUND", + "listener": { + "filterChain": { + "filter": { + "name": "envoy.filters.network.http_connection_manager" + } + } + } + }, + "patch": { + "operation": "INSERT_BEFORE", + "value": { + "name": "envoy.filters.http.local_ratelimit", + "typed_config": { + "@type": "type.googleapis.com/udpa.type.v1.TypedStruct", + "type_url": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", + "value": { + "stat_prefix": "http_local_rate_limiter" + } + } + } + } + }, + { + "applyTo": "HTTP_ROUTE", + "match": { + "context": "SIDECAR_INBOUND", + "routeConfiguration": { + "vhost": { + "name": "inbound|http|5012", + "route": { + "action": "ANY" + } + } + } + }, + "patch": { + "operation": "MERGE", + "value": { + "route": { + "rate_limits": [ + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "acs", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/api/scan/v0/acs" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "acs", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/api/scan/v0/acs" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + } + ] }, { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } ] - } - ] - } - ] - } - } - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "deploymentDetails": { - "configDigest": { - "allowedIpRanges": { - "type": "md5", - "value": "90eedde4a8599204a45dcb972e212c8b" - }, - "approvedSvIdentities": { - "type": "md5", - "value": "5871224b744b45122540483fddbd550f" - } - }, - "network": "test", - "sv": { - "version": "0.3.20" - }, - "synchronizer": { - "current": { - "chainIdSuffix": "4", - "migrationId": 2, - "synchronizerSerialId": 9, - "version": "0.3.20" - }, - "legacy": { - "chainIdSuffix": "4", - "migrationId": 2, - "synchronizerSerialId": 8, - "version": "0.3.20" - }, - "successor": { - "chainIdSuffix": "4", - "migrationId": 2, - "synchronizerSerialId": 10, - "version": "0.3.21" - } - } - }, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "istioVirtualService": { - "gateway": "cluster-ingress/cn-http-gateway", - "host": "info.sv-1.mock.global.canton.network.digitalasset.com" - }, - "runtimeDetails": { - "scanUrl": "http://scan-app.sv-da-1:5012", - "synchronizerSerialId": 9 - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] - }, - "version": "0.3.20" - }, - "name": "sv-da-1-info", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-cluster-ingress-runbook", - "compat": "true", - "maxHistory": 10, - "name": "ingress-sv", - "namespace": "sv-da-1", - "timeout": 600, - "values": { - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, { - "key": "cn_apps", - "operator": "Exists" + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + } + ] }, { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } ] - } - ] - } - ] - } - } - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet", - "svIngressName": "sv-1", - "svNamespace": "sv-da-1" - }, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "ingress": { - "decentralizedSynchronizer": { - "migrationIds": [ - "9", - "8", - "7", - "10" - ] - } - }, - "rateLimit": { - "scan": { - "enable": false - } - }, - "spliceDomainNames": { - "nameServiceDomain": "cns" - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ], - "withSvIngress": true - }, - "version": "0.3.20" - }, - "name": "sv-da-1-ingress-sv", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "networking.istio.io/v1alpha3", - "kind": "EnvoyFilter", - "metadata": { - "annotations": { - "proxy.istio.io/config": "proxyStatsMatcher:\n inclusionRegexps:\n - \".*http_local_rate_limit.*\"" - }, - "name": "scan-app-rate-limit", - "namespace": "sv-da-1" - }, - "spec": { - "configPatches": [ - { - "applyTo": "HTTP_FILTER", - "match": { - "context": "SIDECAR_INBOUND", - "listener": { - "filterChain": { - "filter": { - "name": "envoy.filters.network.http_connection_manager" - } - } - } - }, - "patch": { - "operation": "INSERT_BEFORE", - "value": { - "name": "envoy.filters.http.local_ratelimit", - "typed_config": { - "@type": "type.googleapis.com/udpa.type.v1.TypedStruct", - "type_url": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", - "value": { - "stat_prefix": "http_local_rate_limiter" - } - } - } - } - }, - { - "applyTo": "HTTP_ROUTE", - "match": { - "context": "SIDECAR_INBOUND", - "routeConfiguration": { - "vhost": { - "name": "inbound|http|5012", - "route": { - "action": "ANY" - } - } - } - }, - "patch": { - "operation": "MERGE", - "value": { - "route": { - "rate_limits": [ + }, { "actions": [ { "header_value_match": { - "descriptor_value": "acs", + "descriptor_value": "registry-transfer-instruction-v2", "expect_match": true, "headers": [ { "name": ":path", "string_match": { "ignore_case": true, - "prefix": "/api/scan/v0/acs" + "prefix": "/registry/transfer-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2" } } ] } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + } + ] + }, + "typed_per_filter_config": { + "envoy.filters.http.local_ratelimit": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", + "descriptors": [ + { + "entries": [ + { + "key": "header_match", + "value": "acs" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "acs" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-instruments" } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 } - ] - } - ] - }, - "typed_per_filter_config": { - "envoy.filters.http.local_ratelimit": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", - "descriptors": [ + }, { "entries": [ { "key": "header_match", - "value": "acs" + "value": "registry-metadata-instruments" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" }, { "key": "client_ip" diff --git a/cluster/expected/infra/expected.json b/cluster/expected/infra/expected.json index 981e3a4c5f..ea535a388a 100644 --- a/cluster/expected/infra/expected.json +++ b/cluster/expected/infra/expected.json @@ -407,6 +407,42 @@ "provider": "", "type": "kubernetes:telemetry.istio.io/v1alpha1:Telemetry" }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "security.istio.io/v1beta1", + "kind": "AuthorizationPolicy", + "metadata": { + "name": "allow-public-token-registry", + "namespace": "cluster-ingress" + }, + "spec": { + "action": "ALLOW", + "rules": [ + { + "to": [ + { + "operation": { + "paths": [ + "/registry/*" + ] + } + } + ] + } + ], + "selector": { + "matchLabels": { + "app": "istio-ingress" + } + } + } + }, + "name": "allow-public-token-registry", + "provider": "", + "type": "kubernetes:security.istio.io/v1beta1:AuthorizationPolicy" + }, { "custom": true, "id": "", diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index d42ca06b2b..e603488fb4 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -850,11 +850,15 @@ "type": "unlimited" }, "/api/scan/v0/acs": { - "clientIp": true, "fillInterval": "60s", - "maxTokens": 10, + "maxTokens": 500, "name": "acs", - "tokensPerFill": 5, + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, "type": "limited" }, "/api/scan/v0/active-synchronizer-serial": { @@ -1052,6 +1056,150 @@ "/api/scan/version": { "name": "version", "type": "unlimited" + }, + "/registry/allocation-instruction/v1/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocation-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocation-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocation-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocation-instruction/v2/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocation-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocation/v2/settlement-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-settlement-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocations/v1": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocations", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/allocations/v2": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-allocations-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/metadata/v1/info": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-metadata-info", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/metadata/v1/instruments": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-metadata-instruments", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v1": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-instruction", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v1/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/registry/transfer-instruction/v2/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "registry-transfer-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" } } }, @@ -1523,6 +1671,25 @@ "value": { "route": { "rate_limits": [ + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "acs", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/api/scan/v0/acs" + } + } + ] + } + } + ] + }, { "actions": [ { @@ -1547,18 +1714,907 @@ } } ] - } - ] - }, - "typed_per_filter_config": { - "envoy.filters.http.local_ratelimit": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", - "descriptors": [ - { - "entries": [ - { - "key": "header_match", - "value": "acs" + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + } + ] + }, + "typed_per_filter_config": { + "envoy.filters.http.local_ratelimit": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", + "descriptors": [ + { + "entries": [ + { + "key": "header_match", + "value": "acs" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "acs" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-instruments" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-instruments" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" }, { "key": "client_ip" diff --git a/cluster/pulumi/common/src/config/scanEndpoints.ts b/cluster/pulumi/common/src/config/scanEndpoints.ts index 1cd62eb796..79e63b4e56 100644 --- a/cluster/pulumi/common/src/config/scanEndpoints.ts +++ b/cluster/pulumi/common/src/config/scanEndpoints.ts @@ -6,6 +6,39 @@ import { z } from 'zod'; import { readAndParseYaml } from './configLoader'; const scanYamlPath = path.join(__dirname, '../../../../../apps/scan/src/main/openapi/scan.yaml'); +const tokenRegistryYamlPaths = [ + // V1 Specs + path.join( + __dirname, + '../../../../../token-standard/splice-api-token-metadata-v1/openapi/token-metadata-v1.yaml' + ), + path.join( + __dirname, + '../../../../../token-standard/splice-api-token-allocation-v1/openapi/allocation-v1.yaml' + ), + path.join( + __dirname, + '../../../../../token-standard/splice-api-token-allocation-instruction-v1/openapi/allocation-instruction-v1.yaml' + ), + path.join( + __dirname, + '../../../../../token-standard/splice-api-token-transfer-instruction-v1/openapi/transfer-instruction-v1.yaml' + ), + + // V2 Specs + path.join( + __dirname, + '../../../../../token-standard/splice-api-token-allocation-v2/openapi/allocation-v2.yaml' + ), + path.join( + __dirname, + '../../../../../token-standard/splice-api-token-allocation-instruction-v2/openapi/allocation-instruction-v2.yaml' + ), + path.join( + __dirname, + '../../../../../token-standard/splice-api-token-transfer-instruction-v2/openapi/transfer-instruction-v2.yaml' + ), +]; const MinimalOpenApiSchema = z.object({ paths: z.object({}).catchall(z.unknown()).default({}) }); @@ -37,3 +70,27 @@ export function parseScanYamlEndpoints(): string[] { return Array.from(endpoints).sort(); } + +/** + * Read all token registry standard OpenAPI paths into normalized `/registry/...` endpoint prefixes. + */ +export function parseTokenRegistrySpecEndpoints(): string[] { + const endpoints = new Set(); + + for (const yamlPath of tokenRegistryYamlPaths) { + const yaml = MinimalOpenApiSchema.parse(readAndParseYaml(yamlPath)); + const paths = yaml.paths; + + for (let fullPath of Object.keys(paths)) { + const paramIndex = fullPath.indexOf('{'); + if (paramIndex !== -1) { + const lastSlash = fullPath.lastIndexOf('/', paramIndex); + fullPath = fullPath.substring(0, lastSlash); + } + + endpoints.add(fullPath); + } + } + + return Array.from(endpoints).sort(); +} diff --git a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts index 7a47f69d59..2d56ca7e8c 100644 --- a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts +++ b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts @@ -3,7 +3,7 @@ import * as k8s from '@pulumi/kubernetes'; import * as pulumi from '@pulumi/pulumi'; -import { parseScanYamlEndpoints } from '../config/scanEndpoints'; +import { parseScanYamlEndpoints, parseTokenRegistrySpecEndpoints } from '../config/scanEndpoints'; interface Limits { maxTokens: number; @@ -13,7 +13,7 @@ interface Limits { interface MatchedLimits extends Limits { type: 'limited'; - clientIp: boolean; + perIpLimits?: Limits; } interface Banned { @@ -74,7 +74,9 @@ export function extractPathPrefixes( const isBanned = rl.type === 'banned'; return { pathPrefix, isBanned }; }) - .filter(info => info.pathPrefix.startsWith('/api/scan')); + .filter( + info => info.pathPrefix.startsWith('/api/scan') || info.pathPrefix.startsWith('/registry') + ); } function validateEndpointCoverage( @@ -115,16 +117,28 @@ function validateEffectiveRateLimits( const { missing, orphaned } = validateEndpointCoverage(scanEndpoints, configuredScanPrefixes); - if (missing.length > 0 || orphaned.length > 0) { + const tokenRegistryEndpoints = parseTokenRegistrySpecEndpoints(); + + const configuredRegistryPrefixes = Object.keys(args.rateLimits || {}).filter(pathPrefix => + pathPrefix.startsWith('/registry') + ); + + const registryValidation = validateEndpointCoverage( + tokenRegistryEndpoints, + configuredRegistryPrefixes + ); + + const totalMissing = missing.concat(registryValidation.missing); + const totalOrphaned = orphaned.concat(registryValidation.orphaned); + + if (totalMissing.length > 0 || totalOrphaned.length > 0) { const errorParts: string[] = ['Rate limit configuration errors:']; - if (missing.length > 0) { - errorParts.push( - `- Missing rate limit prefixes for scan.yaml endpoints: ${missing.join(', ')}` - ); + if (totalMissing.length > 0) { + errorParts.push(`- Missing rate limit prefixes for endpoints: ${totalMissing.join(', ')}`); } - if (orphaned.length > 0) { + if (totalOrphaned.length > 0) { errorParts.push( - `- Orphaned rate limit prefixes not matching any scan.yaml endpoint: ${orphaned.join(', ')}` + `- Orphaned rate limit prefixes not matching any schema route: ${totalOrphaned.join(', ')}` ); } throw new Error(errorParts.join('\n')); @@ -156,36 +170,44 @@ export class RateLimitEnvoyFilter extends pulumi.ComponentResource { const effectiveRateLimits = validateEffectiveRateLimits(args); const rateLimitActions: unknown[] = - Object.entries(effectiveRateLimits || {}).map(([pathPrefix, rateLimit]) => { - return { - actions: [ - { - header_value_match: { - descriptor_value: rateLimit.name, - expect_match: true, - headers: [ - { - name: ':path', - string_match: { - prefix: pathPrefix, - ignore_case: true, - }, - }, - ], + Object.entries(effectiveRateLimits || {}).flatMap(([pathPrefix, rateLimit]) => { + const actions = []; + + // Action 1: generate the per-endpoint action + const baseAction = { + header_value_match: { + descriptor_value: rateLimit.name, + expect_match: true, + headers: [ + { + name: ':path', + string_match: { + prefix: pathPrefix, + ignore_case: true, + }, }, - }, - ...(rateLimit.clientIp - ? [ - { - request_headers: { - descriptor_key: 'client_ip', - header_name: 'x-forwarded-for', - }, - }, - ] - : []), - ], + ], + }, }; + + actions.push({ actions: [baseAction] }); + + // Action 2: generate the per-IP action if perIpLimits exists + if (rateLimit.perIpLimits) { + actions.push({ + actions: [ + baseAction, + { + request_headers: { + descriptor_key: 'client_ip', + header_name: 'x-forwarded-for', + }, + }, + ], + }); + } + + return actions; }) || []; const enableEnvoyRateLimitMetricsAnnotation = ` @@ -295,21 +317,44 @@ proxyStatsMatcher: // simplified descriptors by combining with actions and requiring all the tokens of an action to be set // a descriptor in practice is a subset of tags from a rate limit // but important to note that for each rate limit only one descriptor can match, if multiple descriptors match, the first one is used - descriptors: Object.values(effectiveRateLimits || {}).map(rateLimit => { - return { + descriptors: Object.values(effectiveRateLimits || {}).flatMap(rateLimit => { + const descs = []; + + // per-endpoint bucket + + descs.push({ entries: [ { key: 'header_match', value: rateLimit.name, }, - ...(rateLimit.clientIp ? [{ key: clientIpEntryKey }] : []), ], token_bucket: { max_tokens: rateLimit.maxTokens, tokens_per_fill: rateLimit.tokensPerFill, fill_interval: rateLimit.fillInterval, }, - }; + }); + + // generate the per-IP bucket if configured + if (rateLimit.perIpLimits) { + descs.push({ + entries: [ + { + key: 'header_match', + value: rateLimit.name, + }, + { key: clientIpEntryKey }, + ], + token_bucket: { + max_tokens: rateLimit.perIpLimits.maxTokens, + tokens_per_fill: rateLimit.perIpLimits.tokensPerFill, + fill_interval: rateLimit.perIpLimits.fillInterval, + }, + }); + } + + return descs; }), }, }, diff --git a/cluster/pulumi/common/src/ratelimit/rateLimitSchema.ts b/cluster/pulumi/common/src/ratelimit/rateLimitSchema.ts index 8fe8ea3514..39fbf163d5 100644 --- a/cluster/pulumi/common/src/ratelimit/rateLimitSchema.ts +++ b/cluster/pulumi/common/src/ratelimit/rateLimitSchema.ts @@ -10,7 +10,7 @@ export const BucketRateLimitSchema = z.object({ const BucketMatchedRateLimitSchema = BucketRateLimitSchema.extend({ type: z.literal('limited'), - clientIp: z.boolean(), + perIpLimits: BucketRateLimitSchema.optional(), }); export const BannedSchema = z.object({ diff --git a/cluster/pulumi/infra/src/cloudArmor.ts b/cluster/pulumi/infra/src/cloudArmor.ts index 9ef7407a92..2ab7f7e286 100644 --- a/cluster/pulumi/infra/src/cloudArmor.ts +++ b/cluster/pulumi/infra/src/cloudArmor.ts @@ -147,9 +147,11 @@ function addThrottleAndBanRules( // this makes the pulumi update cleaner if toggling just one service if (throttleAcrossAllEndpointsAllIps.maxRequestsBeforeHttp429 > 0) { const ruleName = `throttle-all-endpoints-all-ips-${confEntryHead}`; - + const hostnameRegex = hostname + ? _.escapeRegExp(hostname) + : `scan\\.[\\w-]+\\.${_.escapeRegExp(config.clusterHostname)}`; const pathExpr = allowedPathsCondition(scanExternalRateLimits, pathPrefix); - const hostExpr = `request.headers['host'].matches(R"^${_.escapeRegExp(hostname)}(?::[0-9]+)?$")`; + const hostExpr = `request.headers['host'].matches(R"^${hostnameRegex}(?::[0-9]+)?$")`; const matchExpr = `${pathExpr} && ${hostExpr}`; new PolicyRule( @@ -227,15 +229,14 @@ function allowedPathsCondition(scanExternalRateLimits: PerEndpointLimits, pathPr .filter(p => !p.isBanned) .map(p => p.pathPrefix); - // Factor out /api/scan/ prefix (with trailing slash) - const scanPrefix = '/api/scan/'; - const scanPathRxs = pathPrefixes - .filter(p => p.startsWith(scanPrefix)) - .map(p => _.escapeRegExp(p.substring(scanPrefix.length))); // Remove prefix for factoring + const basePrefix = pathPrefix.endsWith('/') ? pathPrefix : `${pathPrefix}/`; + const dynamicPathRxs = pathPrefixes + .filter(p => p.startsWith(basePrefix)) + .map(p => _.escapeRegExp(p.substring(basePrefix.length))); // Build regex pattern - if (scanPathRxs.length > 0) { - const regexPattern = `${scanPrefix}(${scanPathRxs.join('|')})`; + if (dynamicPathRxs.length > 0) { + const regexPattern = `${basePrefix}(${dynamicPathRxs.join('|')})`; const pathExpr = `request.path.matches(R"^${regexPattern}")`; // limit from https://docs.cloud.google.com/armor/quotas#limits @@ -249,7 +250,7 @@ function allowedPathsCondition(scanExternalRateLimits: PerEndpointLimits, pathPr return pathExpr; } else { - // Fallback to simple prefix if no scan paths + // Fallback to simple prefix matching if no paths were resolved return `request.path.startsWith(R"${pathPrefix}")`; } } else { diff --git a/cluster/pulumi/infra/src/config.ts b/cluster/pulumi/infra/src/config.ts index e8fb8b7e37..49184cc908 100644 --- a/cluster/pulumi/infra/src/config.ts +++ b/cluster/pulumi/infra/src/config.ts @@ -27,7 +27,10 @@ const CloudArmorConfigSchema = z.object({ .catchall( z.object({ rulePreviewOnly: z.boolean().default(false), - hostname: z.string().regex(/^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*$/, 'valid DNS hostname'), + hostname: z + .string() + .regex(/^[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)*$/, 'valid DNS hostname') + .optional(), pathPrefix: z.string().regex(/^\/[^"]*$/, 'HTTP request path starting with /'), throttleAcrossAllEndpointsAllIps: z.object({ withinIntervalSeconds: z.number().positive(), @@ -54,6 +57,7 @@ export const InfraConfigSchema = z.object({ istio: z.object({ enableIngressAccessLogging: z.boolean(), enableClusterAccessLogging: z.boolean().default(false), + enablePublicTokenRegistry: z.boolean().default(false), istiodValues: z.object({}).catchall(z.any()).default({}), sequencerFlowControl: z.object({ initialStreamWindowSize: z.int(), diff --git a/cluster/pulumi/infra/src/istio.ts b/cluster/pulumi/infra/src/istio.ts index 4e773d9df0..b726f9ad80 100644 --- a/cluster/pulumi/infra/src/istio.ts +++ b/cluster/pulumi/infra/src/istio.ts @@ -759,6 +759,40 @@ function configurePublicInfo(ingressNs: k8s.core.v1.Namespace): k8s.apiextension : []; } +function configurePublicTokenRegistry( + ingressNs: k8s.core.v1.Namespace +): k8s.apiextensions.CustomResource[] { + return [ + new k8s.apiextensions.CustomResource('allow-public-token-registry', { + apiVersion: istioApiVersion, + kind: 'AuthorizationPolicy', + metadata: { + name: 'allow-public-token-registry', + namespace: ingressNs.metadata.name, + }, + spec: { + selector: { + matchLabels: { + app: 'istio-ingress', + }, + }, + action: 'ALLOW', + rules: [ + { + to: [ + { + operation: { + paths: ['/registry/*'], + }, + }, + ], + }, + ], + }, + }), + ]; +} + function configureSequencerHighPerformanceGrpcDestinationRules( ingressNs: k8s.core.v1.Namespace ): Array { @@ -932,6 +966,11 @@ export function configureIstio( const gateways = configureGateway(ingressNs, gwSvc, cometBftSvc, expectGKEL7Gateway); const docsAndReleases = configureDocsAndReleases(true, gateways); const publicInfo = configurePublicInfo(ingressNs.ns); + + const publicTokenRegistry = infraConfig.istio.enablePublicTokenRegistry + ? configurePublicTokenRegistry(ingressNs.ns) + : []; + const sequencerHighPerformanceGrpcRules = configureSequencerHighPerformanceGrpcDestinationRules( ingressNs.ns ); @@ -941,6 +980,7 @@ export function configureIstio( ...gateways, ...docsAndReleases, ...publicInfo, + ...publicTokenRegistry, ...sequencerHighPerformanceGrpcRules, ...[sequencerFlowControl], ], From f03536b00b7b9a2198a7f383b318a8c13c80ad5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Fri, 10 Jul 2026 13:40:24 +0200 Subject: [PATCH 011/329] make SLACK_ALERT_NOTIFICATION_CHANNEL_FULL_NAME loading lazy to fix operator on non-prod-like clusters (#6381) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Mateusz Błażejewski --- cluster/pulumi/operator/src/config.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/cluster/pulumi/operator/src/config.ts b/cluster/pulumi/operator/src/config.ts index b7f06f9b39..2386386a18 100644 --- a/cluster/pulumi/operator/src/config.ts +++ b/cluster/pulumi/operator/src/config.ts @@ -8,10 +8,16 @@ export const OperatorDeploymentConfigSchema = z.object({ reference: GitReferenceSchema, flux: z .object({ - alertSlackChannel: z - .string() - .default(() => config.requireEnv('SLACK_ALERT_NOTIFICATION_CHANNEL_FULL_NAME')), + alertSlackChannel: z.string().optional(), }) + .transform(flux => ({ + ...flux, + get alertSlackChannel(): string { + return ( + flux.alertSlackChannel ?? config.requireEnv('SLACK_ALERT_NOTIFICATION_CHANNEL_FULL_NAME') + ); + }, + })) .prefault({}), }); From dde95eb5d31979dc6e5cf09c69fa303a4c5f8248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Fri, 10 Jul 2026 14:34:11 +0200 Subject: [PATCH 012/329] remove non-hyperdisk node pool infrastructure code (#6371) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Mateusz Błażejewski --- cluster/configs/shared/base.yaml | 3 - .../scratchneta/config.resolved.yaml | 3 - .../scratchnetb/config.resolved.yaml | 3 - .../scratchnetc/config.resolved.yaml | 3 - .../scratchnetd/config.resolved.yaml | 3 - .../scratchnete/config.resolved.yaml | 3 - .../common-sv/src/synchronizer/cometbft.ts | 12 +--- .../src/config/hyperdiskSupportConfig.ts | 20 ------ cluster/pulumi/common/src/helm.ts | 65 ++++++++----------- cluster/pulumi/common/src/postgres.ts | 12 +--- .../pulumi/common/src/storage/storageClass.ts | 15 ++--- .../pulumi/multi-validator/src/postgres.ts | 9 +-- .../pulumi/observability/src/observability.ts | 9 +-- .../validator-runbook/src/partyAllocator.ts | 6 +- 14 files changed, 41 insertions(+), 125 deletions(-) delete mode 100644 cluster/pulumi/common/src/config/hyperdiskSupportConfig.ts diff --git a/cluster/configs/shared/base.yaml b/cluster/configs/shared/base.yaml index 08f482f842..d46642234d 100644 --- a/cluster/configs/shared/base.yaml +++ b/cluster/configs/shared/base.yaml @@ -1,8 +1,5 @@ # Reference configuration options cluster: - hyperdiskSupport: - enabled: true - enabledForInfra: true nodePools: apps: minNodes: 0 diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index f1d8ba1af9..0ce399c8cb 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -15,9 +15,6 @@ cloudArmor: maxRequestsBeforeHttp429: 200 withinIntervalSeconds: 60 cluster: - hyperdiskSupport: - enabled: true - enabledForInfra: true nodePools: additionalApps: - maxNodes: 20 diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index f1d8ba1af9..0ce399c8cb 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -15,9 +15,6 @@ cloudArmor: maxRequestsBeforeHttp429: 200 withinIntervalSeconds: 60 cluster: - hyperdiskSupport: - enabled: true - enabledForInfra: true nodePools: additionalApps: - maxNodes: 20 diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index f1d8ba1af9..0ce399c8cb 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -15,9 +15,6 @@ cloudArmor: maxRequestsBeforeHttp429: 200 withinIntervalSeconds: 60 cluster: - hyperdiskSupport: - enabled: true - enabledForInfra: true nodePools: additionalApps: - maxNodes: 20 diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index f1d8ba1af9..0ce399c8cb 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -15,9 +15,6 @@ cloudArmor: maxRequestsBeforeHttp429: 200 withinIntervalSeconds: 60 cluster: - hyperdiskSupport: - enabled: true - enabledForInfra: true nodePools: additionalApps: - maxNodes: 20 diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index f1d8ba1af9..0ce399c8cb 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -15,9 +15,6 @@ cloudArmor: maxRequestsBeforeHttp429: 200 withinIntervalSeconds: 60 cluster: - hyperdiskSupport: - enabled: true - enabledForInfra: true nodePools: additionalApps: - maxNodes: 20 diff --git a/cluster/pulumi/common-sv/src/synchronizer/cometbft.ts b/cluster/pulumi/common-sv/src/synchronizer/cometbft.ts index bcf6ada3d1..39646df795 100644 --- a/cluster/pulumi/common-sv/src/synchronizer/cometbft.ts +++ b/cluster/pulumi/common-sv/src/synchronizer/cometbft.ts @@ -23,7 +23,6 @@ import { withAddedDependencies, } from '@canton-network/splice-pulumi-common'; import { CnChartVersion } from '@canton-network/splice-pulumi-common/src/artifacts'; -import { hyperdiskSupportConfig } from '@canton-network/splice-pulumi-common/src/config/hyperdiskSupportConfig'; import { jsonStringify, Output } from '@pulumi/pulumi'; import { svsConfig } from '../config'; @@ -103,14 +102,6 @@ export function installCometBftNode( ? undefined : installCometBftKeysSecret(xns, nodeConfig.validator.keyAddress, migrationId); - let hyperdiskDbValues = {}; - if (hyperdiskSupportConfig.hyperdiskSupport.enabled) { - hyperdiskDbValues = { - pvcName: `cometbft-migration-${migrationId}-hd-pvc`, - volumeStorageClass: standardStorageClassName, - }; - } - const cometbftChartValues = _.mergeWith(cometBftValues, { sv1: nodeConfigs.sv1, istioVirtualService: { @@ -146,7 +137,8 @@ export function installCometBftNode( }, db: { volumeSize: clusterSmallDisk ? '240Gi' : pvcSize || svsConfig?.cometbft?.volumeSize, - ...hyperdiskDbValues, + pvcName: `cometbft-migration-${migrationId}-hd-pvc`, + volumeStorageClass: standardStorageClassName, }, extraLogLevelFlags: svConfiguration.logging?.cometbftExtraLogLevelFlags, serviceAccountName: imagePullServiceAccountName, diff --git a/cluster/pulumi/common/src/config/hyperdiskSupportConfig.ts b/cluster/pulumi/common/src/config/hyperdiskSupportConfig.ts deleted file mode 100644 index 8e83855c8e..0000000000 --- a/cluster/pulumi/common/src/config/hyperdiskSupportConfig.ts +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 -import { z } from 'zod'; - -import { clusterSubConfig } from './config'; - -const HyperdiskSupportConfigSchema = z.object({ - hyperdiskSupport: z - .object({ - enabled: z.boolean(), - enabledForInfra: z.boolean(), - }) - .strict(), -}); - -export type HyperdiskSupportConfig = z.infer; - -export const hyperdiskSupportConfig: HyperdiskSupportConfig = HyperdiskSupportConfigSchema.parse( - clusterSubConfig('cluster') -); diff --git a/cluster/pulumi/common/src/helm.ts b/cluster/pulumi/common/src/helm.ts index 30840b541e..06339a3e42 100644 --- a/cluster/pulumi/common/src/helm.ts +++ b/cluster/pulumi/common/src/helm.ts @@ -9,7 +9,6 @@ import path from 'path'; import { CnChartVersion } from './artifacts'; import { config, imagePullPolicy } from './config'; import { spliceConfig } from './config/config'; -import { hyperdiskSupportConfig } from './config/hyperdiskSupportConfig'; import { activeVersion } from './domainMigration'; import { SplicePlaceholderResource } from './pulumiUtilResources'; import { @@ -225,44 +224,36 @@ function versionStringWithPossibleOverride( } } -export const appsAffinityAndTolerations = getAppsAffinityAndTolerations( - hyperdiskSupportConfig.hyperdiskSupport.enabled -); - -export const nonHyperdiskAppsAffinityAndTolerations = getAppsAffinityAndTolerations(false); - -function getAppsAffinityAndTolerations(hyperdiskSupport: boolean) { - return { - affinity: { - nodeAffinity: { - requiredDuringSchedulingIgnoredDuringExecution: { - nodeSelectorTerms: [ - { - matchExpressions: [ - { - key: 'cn_apps', - operator: 'Exists', - }, - { - key: 'cn_apps', - operator: hyperdiskSupport ? 'In' : 'NotIn', - values: ['hyperdisk'], - }, - ], - }, - ], - }, +export const appsAffinityAndTolerations = { + affinity: { + nodeAffinity: { + requiredDuringSchedulingIgnoredDuringExecution: { + nodeSelectorTerms: [ + { + matchExpressions: [ + { + key: 'cn_apps', + operator: 'Exists', + }, + { + key: 'cn_apps', + operator: 'In', + values: ['hyperdisk'], + }, + ], + }, + ], }, }, - tolerations: [ - { - key: 'cn_apps', - operator: 'Exists', - effect: 'NoSchedule', - }, - ], - }; -} + }, + tolerations: [ + { + key: 'cn_apps', + operator: 'Exists', + effect: 'NoSchedule', + }, + ], +}; export const infraAffinityAndTolerations = { affinity: { diff --git a/cluster/pulumi/common/src/postgres.ts b/cluster/pulumi/common/src/postgres.ts index 8e0b7ecd18..f2141e0df8 100644 --- a/cluster/pulumi/common/src/postgres.ts +++ b/cluster/pulumi/common/src/postgres.ts @@ -10,7 +10,6 @@ import { CnChartVersion } from './artifacts'; import { clusterSmallDisk, CloudSqlConfig, config } from './config'; import { spliceConfig } from './config/config'; import { GcpProject } from './config/gcpConfig'; -import { hyperdiskSupportConfig } from './config/hyperdiskSupportConfig'; import { appsAffinityAndTolerations, infraAffinityAndTolerations, @@ -410,9 +409,6 @@ export class SplicePostgres extends pulumi.ComponentResource implements Postgres // an initial database named cantonnet is created automatically (configured in the Helm chart). const smallDiskSize = clusterSmallDisk ? '240Gi' : undefined; - const supportsHyperdisk = useInfraAffinityAndTolerations - ? hyperdiskSupportConfig.hyperdiskSupport.enabledForInfra - : hyperdiskSupportConfig.hyperdiskSupport.enabled; const pg = installSpliceHelmChart( xns, @@ -423,12 +419,8 @@ export class SplicePostgres extends pulumi.ComponentResource implements Postgres volumeSize: overrideDbSizeFromValues ? values?.db?.volumeSize || smallDiskSize : smallDiskSize, - ...(supportsHyperdisk - ? { - volumeStorageClass: standardStorageClassName, - pvcTemplateName: 'pg-data-hd', - } - : {}), + volumeStorageClass: standardStorageClassName, + pvcTemplateName: 'pg-data-hd', }, persistence: { secretName: this.secretName, diff --git a/cluster/pulumi/common/src/storage/storageClass.ts b/cluster/pulumi/common/src/storage/storageClass.ts index 86d55d46d4..28107308f0 100644 --- a/cluster/pulumi/common/src/storage/storageClass.ts +++ b/cluster/pulumi/common/src/storage/storageClass.ts @@ -1,11 +1,8 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import { spliceConfig } from '../config/config'; -import { hyperdiskSupportConfig } from '../config/hyperdiskSupportConfig'; -export const standardStorageClassName = hyperdiskSupportConfig.hyperdiskSupport.enabled - ? 'hyperdisk-standard-rwo' - : 'standard-rwo'; +export const standardStorageClassName = 'hyperdisk-standard-rwo'; export function persistentHeapDumpsPvc(): { size: string; volumeStorageClass: string } | undefined { return spliceConfig.configuration.persistentHeapDumps @@ -13,11 +10,7 @@ export function persistentHeapDumpsPvc(): { size: string; volumeStorageClass: st : undefined; } -export const infraStandardStorageClassName = hyperdiskSupportConfig.hyperdiskSupport.enabledForInfra - ? 'hyperdisk-standard-rwo' - : 'standard-rwo'; +export const infraStandardStorageClassName = 'hyperdisk-standard-rwo'; -export const infraPremiumStorageClassName = hyperdiskSupportConfig.hyperdiskSupport.enabledForInfra - ? 'hyperdisk-balanced-rwo' - : 'premium-rwo'; -export const pvcSuffix = hyperdiskSupportConfig.hyperdiskSupport.enabled ? 'hd-pvc' : 'pvc'; +export const infraPremiumStorageClassName = 'hyperdisk-balanced-rwo'; +export const pvcSuffix = 'hd-pvc'; diff --git a/cluster/pulumi/multi-validator/src/postgres.ts b/cluster/pulumi/multi-validator/src/postgres.ts index 622416d3c1..a8280160a9 100644 --- a/cluster/pulumi/multi-validator/src/postgres.ts +++ b/cluster/pulumi/multi-validator/src/postgres.ts @@ -15,7 +15,6 @@ import { createVolumeSnapshot, } from '@canton-network/splice-pulumi-common'; -import { hyperdiskSupportConfig } from '../../common/src/config/hyperdiskSupportConfig'; import { multiValidatorConfig } from './config'; export function installPostgres( @@ -45,12 +44,8 @@ export function installPostgres( db: { volumeSize: config.postgresPvcSize, maxConnections: 1000, - ...(hyperdiskSupportConfig.hyperdiskSupport.enabled - ? { - volumeStorageClass: standardStorageClassName, - pvcTemplateName: 'pg-data-hd', - } - : {}), + volumeStorageClass: standardStorageClassName, + pvcTemplateName: 'pg-data-hd', }, resources: config.resources?.postgres, appsAffinityAndTolerations, diff --git a/cluster/pulumi/observability/src/observability.ts b/cluster/pulumi/observability/src/observability.ts index fffc3697fd..a2f0923f95 100644 --- a/cluster/pulumi/observability/src/observability.ts +++ b/cluster/pulumi/observability/src/observability.ts @@ -36,7 +36,6 @@ import { local } from '@pulumi/command'; import { getSecretVersionOutput } from '@pulumi/gcp/secretmanager/getSecretVersion'; import { Input } from '@pulumi/pulumi'; -import { hyperdiskSupportConfig } from '../../common/src/config/hyperdiskSupportConfig'; import { clusterIsResetPeriodically, enableAlertEmailToSupportTeam, @@ -195,9 +194,7 @@ export function configureObservability(namespace: ExactNamespace): pulumi.Resour logFormat: 'json', storage: { volumeClaimTemplate: { - ...(hyperdiskSupportConfig.hyperdiskSupport.enabledForInfra - ? { metadata: { name: 'alertmanager-hd-pvc' } } - : {}), + metadata: { name: 'alertmanager-hd-pvc' }, spec: { storageClassName: infraStandardStorageClassName, accessModes: ['ReadWriteOnce'], @@ -256,9 +253,7 @@ export function configureObservability(namespace: ExactNamespace): pulumi.Resour scrapeNativeHistograms: true, storageSpec: { volumeClaimTemplate: { - ...(hyperdiskSupportConfig.hyperdiskSupport.enabledForInfra - ? { metadata: { name: 'prometheus-hd-pvc' } } - : {}), + metadata: { name: 'prometheus-hd-pvc' }, spec: { storageClassName: infraPremiumStorageClassName, accessModes: ['ReadWriteOnce'], diff --git a/cluster/pulumi/validator-runbook/src/partyAllocator.ts b/cluster/pulumi/validator-runbook/src/partyAllocator.ts index 797c709f84..e97ce99bb9 100644 --- a/cluster/pulumi/validator-runbook/src/partyAllocator.ts +++ b/cluster/pulumi/validator-runbook/src/partyAllocator.ts @@ -13,8 +13,6 @@ import { } from '@canton-network/splice-pulumi-common'; import { PartyAllocatorConfig } from '@canton-network/splice-pulumi-common-validator'; -import { hyperdiskSupportConfig } from '../../common/src/config/hyperdiskSupportConfig'; - export function installPartyAllocator( xns: ExactNamespace, config: PartyAllocatorConfig, @@ -40,9 +38,7 @@ export function installPartyAllocator( pvc: { ...(config.pvcSize ? { size: config.pvcSize } : {}), volumeStorageClass: standardStorageClassName, - name: hyperdiskSupportConfig.hyperdiskSupport.enabled - ? 'party-allocator-keys-hd-pvc' - : 'party-allocator-keys', + name: 'party-allocator-keys-hd-pvc', }, }, activeVersion, From b70c9204e34dcf2bb69a315dfe564ecc50a426f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Fri, 10 Jul 2026 16:35:10 +0200 Subject: [PATCH 013/329] Dedup acceptSubscriptionRequest (#6380) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../splice/wallet/admin/http/HttpWalletHandler.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala index 5219aef9d8..c2d6b3e98f 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala @@ -557,6 +557,11 @@ class HttpWalletHandler( Codec.tryDecodeJavaContractId(subsCodegen.SubscriptionRequest.COMPANION)( contractId ) + val commandId = CommandId( + "org.lfdecentralizedtrust.splice.wallet.acceptSubscriptionRequest", + Seq(userWallet.store.key.endUserParty), + contractId, + ) retryProvider.retryForClientCalls( "accept_subscription", "Accept subscription and make initial payment", @@ -567,6 +572,12 @@ class HttpWalletHandler( d0.AcceptSubscriptionRequestResponse( Codec.encodeContractId(outcome.contractIdValue) ), + dedupConfig = Some( + AmuletOperationDedupConfig( + commandId, + dedupDuration, + ) + ), ), logger, ) From 5336404d77ec97196d52e6214544e2a28d8d380c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Fri, 10 Jul 2026 16:39:41 +0200 Subject: [PATCH 014/329] remove hyperdiskSupport config reading in scripts (#6384) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Mateusz Błażejewski --- cluster/scripts/node-backup.sh | 5 +---- cluster/scripts/node-restore.sh | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/cluster/scripts/node-backup.sh b/cluster/scripts/node-backup.sh index e0acd20ec9..c76c1c6fcd 100755 --- a/cluster/scripts/node-backup.sh +++ b/cluster/scripts/node-backup.sh @@ -295,11 +295,8 @@ function main() { local migration_id=$3 local requested_component="${4:-}" - # Get resolved config and extract hyperdisk support flag - local config - config=$(get_resolved_config) local hyperdisk_enabled - hyperdisk_enabled=$(echo "$config" | yq '.cluster.hyperdiskSupport.enabled // false') + hyperdisk_enabled="true" # TODO(#9361): support multiple domains / non-default-ID'd ones if [ "$1" == "validator" ]; then diff --git a/cluster/scripts/node-restore.sh b/cluster/scripts/node-restore.sh index 616234e5d7..5a5553c8ba 100755 --- a/cluster/scripts/node-restore.sh +++ b/cluster/scripts/node-restore.sh @@ -437,10 +437,8 @@ function main() { local -r migration_id=$2 local -r run_id=$3 - local config - config=$(get_resolved_config) local hyperdisk_enabled - hyperdisk_enabled=$(echo "$config" | yq '.cluster.hyperdiskSupport.enabled // false') + hyperdisk_enabled="true" if [[ "$run_id" == *","* ]]; then _info " ** Validate backup ids ** " From 44b0e35cc7c5c66da24cd89887c62e16ad464dc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Fri, 10 Jul 2026 22:59:21 +0200 Subject: [PATCH 015/329] SV UI Inflight Votes and Vote History counters (#6220) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek Co-authored-by: Stephen Compall --- .../splice/console/ScanAppReference.scala | 13 +- .../splice/console/SvAppReference.scala | 23 ++-- .../tests/SvFrontendIntegrationTest.scala | 11 +- .../SvStateManagementIntegrationTest.scala | 13 +- apps/common/frontend/src/theme/index.ts | 15 +++ .../src/main/openapi/common-internal.yaml | 27 ++++ .../store/DbVotesStoreQueryBuilder.scala | 93 +++++++++----- .../splice/store/VotesStore.scala | 20 ++- apps/scan/src/main/openapi/scan.yaml | 20 +++ .../admin/api/client/BftScanConnection.scala | 24 ++-- .../admin/api/client/ScanConnection.scala | 14 ++- .../api/client/SingleScanConnection.scala | 23 ++-- .../client/commands/HttpScanAppClient.scala | 51 ++++++-- .../scan/admin/http/HttpScanHandler.scala | 37 +++++- .../splice/scan/store/CachingScanStore.scala | 22 ++-- .../splice/scan/store/db/DbScanStore.scala | 30 +++-- .../splice/store/db/ScanStoreTest.scala | 118 ++++++++++-------- .../governance/governance-page.test.tsx | 31 ++++- .../src/__tests__/mocks/handlers/sv-api.ts | 24 ++++ .../src/components/beta/PageSectionHeader.tsx | 14 ++- .../governance/ActionRequiredSection.tsx | 1 + .../governance/ProposalListingSection.tsx | 8 +- .../src/contexts/SvAdminServiceContext.tsx | 11 ++ apps/sv/frontend/src/hooks/index.ts | 2 + .../src/hooks/useVoteRequestResultsCount.ts | 20 +++ apps/sv/frontend/src/routes/governance.tsx | 5 +- apps/sv/src/main/openapi/sv-internal.yaml | 18 +++ .../commands/HttpSvOperatorAppClient.scala | 50 ++++++-- .../sv/admin/http/HttpSvOperatorHandler.scala | 47 ++++++- 29 files changed, 583 insertions(+), 202 deletions(-) create mode 100644 apps/sv/frontend/src/hooks/useVoteRequestResultsCount.ts diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/ScanAppReference.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/ScanAppReference.scala index 11d3a7a6a1..e510e5b33a 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/ScanAppReference.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/ScanAppReference.scala @@ -33,6 +33,7 @@ import org.lfdecentralizedtrust.splice.http.v0.definitions.{ UpdateHistoryItemV2, } import org.lfdecentralizedtrust.splice.scan.{ScanApp, ScanAppBootstrap} +import org.lfdecentralizedtrust.splice.store.VoteResultsFilters import org.lfdecentralizedtrust.splice.scan.automation.ScanAutomationService import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient.TransferContextWithInstances @@ -846,22 +847,14 @@ abstract class ScanAppReference( @Help.Summary("List vote results") def listVoteRequestResults( - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], + filters: VoteResultsFilters, limit: BigInt, pageToken: Option[BigInt] = None, ): (Seq[DsoRules_CloseVoteRequestResult], Option[BigInt]) = { consoleEnvironment.run { httpCommand( HttpScanAppClient.ListVoteRequestResults( - actionName, - accepted, - requester, - effectiveFrom, - effectiveTo, + filters, limit, pageToken, ) diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala index 926b03bbc9..37516b4b28 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/SvAppReference.scala @@ -23,6 +23,7 @@ import org.lfdecentralizedtrust.splice.environment.{ SpliceStatus, } import org.lfdecentralizedtrust.splice.http.v0.definitions +import org.lfdecentralizedtrust.splice.store.VoteResultsFilters import org.lfdecentralizedtrust.splice.sv.{SvApp, SvAppBootstrap, SvAppClientConfig} import org.lfdecentralizedtrust.splice.sv.admin.api.client.commands.{ HttpSvAdminAppClient, @@ -197,22 +198,14 @@ abstract class SvAppReference( } def listVoteRequestResults( - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], + filters: VoteResultsFilters, limit: BigInt, pageToken: Option[BigInt] = None, ): (Seq[DsoRules_CloseVoteRequestResult], Option[BigInt]) = { consoleEnvironment.run { httpCommand( HttpSvOperatorAppClient.ListVoteRequestResults( - actionName, - accepted, - requester, - effectiveFrom, - effectiveTo, + filters, limit, pageToken, ) @@ -220,6 +213,16 @@ abstract class SvAppReference( } } + def countVoteRequestResults( + filters: VoteResultsFilters + ): Long = { + consoleEnvironment.run { + httpCommand( + HttpSvOperatorAppClient.CountVoteRequestResults(filters) + ) + } + } + @Help.Summary("Cast a vote") def castVote( trackingCid: VoteRequest.ContractId, diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala index c23c7114c3..0ed40dd6d3 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala @@ -22,6 +22,7 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.{ import org.lfdecentralizedtrust.splice.config.ConfigTransforms import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.SpliceTestConsoleEnvironment +import org.lfdecentralizedtrust.splice.store.VoteResultsFilters import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.CloseVoteRequestTrigger import org.lfdecentralizedtrust.splice.util.SpliceUtil.defaultDsoRulesConfig import org.lfdecentralizedtrust.splice.util.* @@ -1553,7 +1554,7 @@ class SvFrontendIntegrationTest .listVoteRequests() .filter(_.payload.reason.body == "first request") shouldBe empty sv1Backend - .listVoteRequestResults(None, Some(false), None, None, None, 10) + .listVoteRequestResults(VoteResultsFilters(accepted = Some(false)), 10) ._1 .exists(_.request.reason.body == "first request") shouldBe true }, @@ -1591,7 +1592,7 @@ class SvFrontendIntegrationTest .listVoteRequests() .filter(_.payload.reason.body == "second request") shouldBe empty sv1Backend - .listVoteRequestResults(None, Some(false), None, None, None, 10) + .listVoteRequestResults(VoteResultsFilters(accepted = Some(false)), 10) ._1 .count(r => r.request.reason.body == "first request" || r.request.reason.body == "second request" @@ -1601,7 +1602,7 @@ class SvFrontendIntegrationTest // Verify ordering via backend API: most recently completed first clue("vote results are ordered by completion time descending") { - val (results, _) = sv1Backend.listVoteRequestResults(None, None, None, None, None, 10) + val (results, _) = sv1Backend.listVoteRequestResults(VoteResultsFilters(), 10) val ourResults = results.filter(r => r.request.reason.body == "first request" || r.request.reason.body == "second request" ) @@ -1613,13 +1614,13 @@ class SvFrontendIntegrationTest // Verify cursor-based pagination via backend API with limit=1 clue("pagination returns correct pages") { val (firstPage, firstPageToken) = - sv1Backend.listVoteRequestResults(None, None, None, None, None, 1) + sv1Backend.listVoteRequestResults(VoteResultsFilters(), 1) firstPage.size shouldBe 1 firstPage.head.request.reason.body shouldBe "second request" firstPageToken shouldBe defined val (secondPage, _) = - sv1Backend.listVoteRequestResults(None, None, None, None, None, 1, firstPageToken) + sv1Backend.listVoteRequestResults(VoteResultsFilters(), 1, firstPageToken) secondPage.size shouldBe 1 secondPage.head.request.reason.body shouldBe "first request" } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvStateManagementIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvStateManagementIntegrationTest.scala index dbc5676e16..b48d282e58 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvStateManagementIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvStateManagementIntegrationTest.scala @@ -35,6 +35,7 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.amuletrules_ import org.lfdecentralizedtrust.splice.config.ConfigTransforms import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.SpliceTestConsoleEnvironment +import org.lfdecentralizedtrust.splice.store.VoteResultsFilters import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.CloseVoteRequestTrigger import org.lfdecentralizedtrust.splice.util.{Codec, TriggerTestUtil} @@ -119,10 +120,14 @@ class SvStateManagementIntegrationTest extends SvIntegrationTestBase with Trigge sv1Backend.listVoteRequests() shouldBe empty withClue "VoteRequests" sv1Backend - .listVoteRequestResults(None, Some(false), None, None, None, 1) + .listVoteRequestResults(VoteResultsFilters(accepted = Some(false)), 1) ._1 .loneElement .outcome shouldBe a[VRO_Rejected] + + sv1Backend.countVoteRequestResults( + VoteResultsFilters(accepted = Some(false)) + ) shouldBe 1L withClue "vote result count" }, ) } @@ -160,7 +165,7 @@ class SvStateManagementIntegrationTest extends SvIntegrationTestBase with Trigge _ => { sv1Backend.listVoteRequests() shouldBe empty withClue "VoteRequests" sv1Backend - .listVoteRequestResults(None, Some(false), None, None, None, 1) + .listVoteRequestResults(VoteResultsFilters(accepted = Some(false)), 1) ._1 .loneElement .outcome shouldBe a[VRO_Expired] @@ -201,7 +206,7 @@ class SvStateManagementIntegrationTest extends SvIntegrationTestBase with Trigge _ => { sv1Backend.listVoteRequests() shouldBe empty withClue "VoteRequests" sv1Backend - .listVoteRequestResults(None, Some(false), None, None, None, 1) + .listVoteRequestResults(VoteResultsFilters(accepted = Some(false)), 1) ._1 .loneElement .outcome shouldBe a[VRO_Rejected] @@ -586,7 +591,7 @@ class SvStateManagementIntegrationTest extends SvIntegrationTestBase with Trigge eventually() { val voteResult = sv1Backend - .listVoteRequestResults(None, Some(true), None, None, None, 1) + .listVoteRequestResults(VoteResultsFilters(accepted = Some(true)), 1) ._1 .headOption .value diff --git a/apps/common/frontend/src/theme/index.ts b/apps/common/frontend/src/theme/index.ts index f3dbc9f1db..73c65927f7 100644 --- a/apps/common/frontend/src/theme/index.ts +++ b/apps/common/frontend/src/theme/index.ts @@ -27,6 +27,7 @@ declare module '@mui/material/styles' { } interface Palette { + neutral: Palette['primary']; colors: { neutral: Record; primary: Record; @@ -40,6 +41,7 @@ declare module '@mui/material/styles' { } // allow configuration using `createTheme` interface PaletteOptions { + neutral?: PaletteOptions['primary']; colors?: { neutral?: Record; primary?: Record; @@ -53,6 +55,12 @@ declare module '@mui/material/styles' { } } +declare module '@mui/material/Badge' { + interface BadgePropsColorOverrides { + neutral: true; + } +} + declare module '@mui/material/Button' { interface ButtonPropsVariantOverrides { pill: true; @@ -111,6 +119,13 @@ theme = createTheme(theme, { tertiary: { main: '#875CFF', }, + neutral: theme.palette.augmentColor({ + color: { + main: theme.palette.colors.neutral[25], + contrastText: '#E2E2E2', + }, + name: 'neutral', + }), warning: { main: '#FD8575', }, diff --git a/apps/common/src/main/openapi/common-internal.yaml b/apps/common/src/main/openapi/common-internal.yaml index c39e503c4e..f388a23fd9 100644 --- a/apps/common/src/main/openapi/common-internal.yaml +++ b/apps/common/src/main/openapi/common-internal.yaml @@ -360,6 +360,33 @@ components: Cursor for the next page of results. Pass this as `pageToken` in the request. If absent or `null`, there are no more pages. + CountVoteResultsRequest: + description: | + Filters for counting vote results. Same semantics as the corresponding + fields on `ListVoteResultsRequest`. + type: object + properties: + actionName: + type: string + accepted: + type: boolean + requester: + type: string + effectiveFrom: + type: string + effectiveTo: + type: string + + CountVoteResultsResponse: + type: object + required: + - count + properties: + count: + type: integer + format: int64 + description: Total number of vote results matching the request filters. + PreviousSvRewardWeightRequest: type: object required: diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/DbVotesStoreQueryBuilder.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/DbVotesStoreQueryBuilder.scala index bc602a8b3e..cf5227001a 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/DbVotesStoreQueryBuilder.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/DbVotesStoreQueryBuilder.scala @@ -23,47 +23,25 @@ trait DbVotesTxLogStoreQueryBuilder[TXE] with LimitHelpers with NamedLogging { - def listVoteRequestResultsQuery( - txLogTableName: String, - txLogStoreId: TxLogStoreId, + private def voteRequestResultsConditions( dbType: String3, actionNameColumnName: String, acceptedColumnName: String, requesterNameColumnName: String, - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], - limit: Limit, - after: Option[Long] = None, - ): SqlStreamingAction[Vector[ - TxLogQueries.SelectFromTxLogTableResult - ], TxLogQueries.SelectFromTxLogTableResult, Effect.Read] = { - // Sort key: the vote's effective date, falling back to the result's completedAt for non-accepted votes that have none. - val effectiveAtSortKey = - "coalesce(vote_effective_at, entry_data->'result'->>'completedAt')" - val afterCondition = after match { - case Some(a) => - Some( - // Keyset pagination past the previous page's last entry_number `a`. Lexicographical row comparison, - // expands to: effectiveAt < cursorEffectiveAt OR (effectiveAt = cursorEffectiveAt AND entry_number < a). - sql"""(#$effectiveAtSortKey, entry_number) < ((select #$effectiveAtSortKey from #$txLogTableName where store_id = $txLogStoreId and entry_number = $a), $a)""" - ) - case None => None - } - val actionNameCondition = actionName match { + filters: VoteResultsFilters, + ) = { + val actionNameCondition = filters.actionName match { case Some(actionName) => Some(sql"""#$actionNameColumnName like ${lengthLimited( s"%${lengthLimited(actionName)}%" )}""") case None => None } - val executedCondition = accepted match { + val executedCondition = filters.accepted match { case Some(accepted) => Some(sql"""#$acceptedColumnName = ${accepted}""") case None => None } - val effectivenessCondition = (effectiveFrom, effectiveTo) match { + val effectivenessCondition = (filters.effectiveFrom, filters.effectiveTo) match { case (Some(effectiveFrom), Some(effectiveTo)) => Some(sql"""vote_effective_at between ${lengthLimited( effectiveFrom @@ -76,23 +54,56 @@ trait DbVotesTxLogStoreQueryBuilder[TXE] Some(sql"""vote_effective_at < ${lengthLimited(effectiveTo)}""") case (None, None) => None } - val requesterCondition = requester match { + val requesterCondition = filters.requester match { case Some(requester) => Some(sql"""#$requesterNameColumnName like ${lengthLimited( s"%${lengthLimited(requester)}%" )}""") case None => None } - val conditions = NonEmptyList( + NonEmptyList( sql"""entry_type = ${dbType}""", List( actionNameCondition, executedCondition, requesterCondition, effectivenessCondition, - afterCondition, ).flatten, ) + } + + def listVoteRequestResultsQuery( + txLogTableName: String, + txLogStoreId: TxLogStoreId, + dbType: String3, + actionNameColumnName: String, + acceptedColumnName: String, + requesterNameColumnName: String, + filters: VoteResultsFilters, + limit: Limit, + after: Option[Long] = None, + ): SqlStreamingAction[Vector[ + TxLogQueries.SelectFromTxLogTableResult + ], TxLogQueries.SelectFromTxLogTableResult, Effect.Read] = { + // Sort key: the vote's effective date, falling back to the result's completedAt for non-accepted votes that have none. + val effectiveAtSortKey = + "coalesce(vote_effective_at, entry_data->'result'->>'completedAt')" + val afterCondition = after match { + case Some(a) => + Some( + // Keyset pagination past the previous page's last entry_number `a`. Lexicographical row comparison, + // expands to: effectiveAt < cursorEffectiveAt OR (effectiveAt = cursorEffectiveAt AND entry_number < a). + sql"""(#$effectiveAtSortKey, entry_number) < ((select #$effectiveAtSortKey from #$txLogTableName where store_id = $txLogStoreId and entry_number = $a), $a)""" + ) + case None => None + } + val conditions = voteRequestResultsConditions( + dbType, + actionNameColumnName, + acceptedColumnName, + requesterNameColumnName, + filters, + ) ++ afterCondition.toList val whereClause = conditions.reduceLeft((a, b) => (a ++ sql""" and """ ++ b).toActionBuilder) selectFromTxLogTable( @@ -103,6 +114,26 @@ trait DbVotesTxLogStoreQueryBuilder[TXE] sql"""order by #$effectiveAtSortKey desc, entry_number desc limit ${sqlLimit(limit)}""", ) } + + def countVoteRequestResultsQuery( + txLogTableName: String, + txLogStoreId: TxLogStoreId, + dbType: String3, + actionNameColumnName: String, + acceptedColumnName: String, + requesterNameColumnName: String, + filters: VoteResultsFilters, + ): SqlStreamingAction[Vector[Long], Long, Effect.Read] = { + val whereClause = voteRequestResultsConditions( + dbType, + actionNameColumnName, + acceptedColumnName, + requesterNameColumnName, + filters, + ).reduceLeft((a, b) => (a ++ sql""" and """ ++ b).toActionBuilder) + (sql"""select count(*) from #$txLogTableName where store_id = $txLogStoreId and """ ++ whereClause).toActionBuilder + .as[Long] + } } /** All column names will be unsafely interpolated, as they're expected to be constant strings. diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/VotesStore.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/VotesStore.scala index c293f1be99..7f91f5f958 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/VotesStore.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/VotesStore.scala @@ -124,18 +124,28 @@ trait ActiveVotesStore extends AppStore with DsoRulesStore with HasAmuletRules { } +final case class VoteResultsFilters( + actionName: Option[String] = None, + accepted: Option[Boolean] = None, + requester: Option[String] = None, + effectiveFrom: Option[String] = None, + effectiveTo: Option[String] = None, +) + trait VotesStore extends ActiveVotesStore { def listVoteRequestResults( - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], + filters: VoteResultsFilters, limit: Limit = defaultLimit, after: Option[Long] = None, )(implicit tc: TraceContext ): Future[ResultsPage[DsoRules_CloseVoteRequestResult]] + def countVoteRequestResults( + filters: VoteResultsFilters + )(implicit + tc: TraceContext + ): Future[Long] + } diff --git a/apps/scan/src/main/openapi/scan.yaml b/apps/scan/src/main/openapi/scan.yaml index ea914649c6..527fe5fceb 100644 --- a/apps/scan/src/main/openapi/scan.yaml +++ b/apps/scan/src/main/openapi/scan.yaml @@ -1296,6 +1296,26 @@ paths: schema: "$ref": "../../../../common/src/main/openapi/common-internal.yaml#/components/schemas/ListDsoRulesVoteResultsResponse" + /v0/admin/sv/voteresults/count: + post: + tags: [internal, scan] + x-jvm-package: scan + operationId: "countVoteRequestResults" + description: Count all vote results matching the request filters. + requestBody: + required: true + content: + application/json: + schema: + "$ref": "../../../../common/src/main/openapi/common-internal.yaml#/components/schemas/CountVoteResultsRequest" + responses: + "200": + description: ok + content: + application/json: + schema: + "$ref": "../../../../common/src/main/openapi/common-internal.yaml#/components/schemas/CountVoteResultsResponse" + /v0/admin/sv/previous-sv-reward-weight: post: tags: [internal, scan] diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala index c8959052c8..3c9c689cda 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala @@ -62,7 +62,7 @@ import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAp import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient.DsoScan import org.lfdecentralizedtrust.splice.scan.config.ScanAppClientConfig import org.lfdecentralizedtrust.splice.scan.store.ScanStore -import org.lfdecentralizedtrust.splice.store.DsoRulesStore +import org.lfdecentralizedtrust.splice.store.{DsoRulesStore, VoteResultsFilters} import org.lfdecentralizedtrust.splice.store.HistoryBackfilling.SourceMigrationInfo import org.lfdecentralizedtrust.splice.store.UpdateHistory.UpdateHistoryResponse import org.lfdecentralizedtrust.splice.util.{ @@ -486,11 +486,7 @@ class BftScanConnection( bftCall(_.lookupTransferPreapprovalByParty(receiver), "lookupTransferPreapprovalByParty") override def listVoteRequestResults( - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], + filters: VoteResultsFilters, limit: Int, pageToken: Option[BigInt] = None, )(implicit @@ -498,17 +494,23 @@ class BftScanConnection( tc: TraceContext, ): Future[(Seq[DsoRules_CloseVoteRequestResult], Option[BigInt])] = bftCall( _.listVoteRequestResults( - actionName, - accepted, - requester, - effectiveFrom, - effectiveTo, + filters, limit, pageToken, ), "listVoteRequestResults", ) + override def countVoteRequestResults( + filters: VoteResultsFilters + )(implicit + ec: ExecutionContext, + tc: TraceContext, + ): Future[Long] = bftCall( + _.countVoteRequestResults(filters), + "countVoteRequestResults", + ) + override def getPreviousSvRewardWeight(svParty: String, effectiveBefore: Option[String])(implicit ec: ExecutionContext, tc: TraceContext, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/ScanConnection.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/ScanConnection.scala index 4bd992a4ee..caed44de87 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/ScanConnection.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/ScanConnection.scala @@ -37,6 +37,7 @@ import org.lfdecentralizedtrust.splice.scan.admin.api.client.ScanConnection.* import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient.TransferContextWithInstances import org.lfdecentralizedtrust.splice.scan.config.ScanAppClientConfig +import org.lfdecentralizedtrust.splice.store.VoteResultsFilters import org.lfdecentralizedtrust.splice.util.* import org.lfdecentralizedtrust.splice.util.PrettyInstances.* import com.digitalasset.canton.config.RequireTypes.NonNegativeInt @@ -310,11 +311,7 @@ trait ScanConnection ): Future[Option[ContractWithState[TransferPreapproval.ContractId, TransferPreapproval]]] def listVoteRequestResults( - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], + filters: VoteResultsFilters, limit: Int, pageToken: Option[BigInt] = None, )(implicit @@ -322,6 +319,13 @@ trait ScanConnection tc: TraceContext, ): Future[(Seq[DsoRules_CloseVoteRequestResult], Option[BigInt])] + def countVoteRequestResults( + filters: VoteResultsFilters + )(implicit + ec: ExecutionContext, + tc: TraceContext, + ): Future[Long] + def getPreviousSvRewardWeight(svParty: String, effectiveBefore: Option[String])(implicit ec: ExecutionContext, tc: TraceContext, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/SingleScanConnection.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/SingleScanConnection.scala index 9ccffbd016..f9d818c9d7 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/SingleScanConnection.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/SingleScanConnection.scala @@ -44,6 +44,7 @@ import org.lfdecentralizedtrust.splice.http.v0.definitions.{ import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient import org.lfdecentralizedtrust.splice.scan.config.ScanAppClientConfig import org.lfdecentralizedtrust.splice.store.HistoryBackfilling.SourceMigrationInfo +import org.lfdecentralizedtrust.splice.store.VoteResultsFilters import org.lfdecentralizedtrust.splice.store.UpdateHistory.UpdateHistoryResponse import org.lfdecentralizedtrust.splice.util.{ ChoiceContextWithDisclosures, @@ -567,11 +568,7 @@ class SingleScanConnection private[client] ( ) override def listVoteRequestResults( - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], + filters: VoteResultsFilters, limit: Int, pageToken: Option[BigInt] = None, )(implicit @@ -580,16 +577,22 @@ class SingleScanConnection private[client] ( ): Future[(Seq[DsoRules_CloseVoteRequestResult], Option[BigInt])] = runHttpCmd( config.adminApi.url, HttpScanAppClient.ListVoteRequestResults( - actionName, - accepted, - requester, - effectiveFrom, - effectiveTo, + filters, limit, pageToken, ), ) + override def countVoteRequestResults( + filters: VoteResultsFilters + )(implicit + ec: ExecutionContext, + tc: TraceContext, + ): Future[Long] = runHttpCmd( + config.adminApi.url, + HttpScanAppClient.CountVoteRequestResults(filters), + ) + override def getPreviousSvRewardWeight(svParty: String, effectiveBefore: Option[String])(implicit ec: ExecutionContext, tc: TraceContext, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala index 2ea945a677..28a6bf83d0 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala @@ -53,7 +53,7 @@ import org.lfdecentralizedtrust.splice.scan.admin.http.{ ProtobufJsonScanHttpEncodings, } import org.lfdecentralizedtrust.splice.store.HistoryBackfilling.SourceMigrationInfo -import org.lfdecentralizedtrust.splice.store.MultiDomainAcsStore +import org.lfdecentralizedtrust.splice.store.{MultiDomainAcsStore, VoteResultsFilters} import org.lfdecentralizedtrust.splice.store.UpdateHistory.UpdateHistoryResponse import org.lfdecentralizedtrust.splice.util.{ ChoiceContextWithDisclosures, @@ -3086,11 +3086,7 @@ object HttpScanAppClient { } case class ListVoteRequestResults( - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], + filters: VoteResultsFilters, limit: BigInt, pageToken: Option[BigInt] = None, ) extends InternalBaseCommand[ @@ -3107,13 +3103,13 @@ object HttpScanAppClient { ): EitherT[Future, Either[Throwable, HttpResponse], http.ListVoteRequestResultsResponse] = client.listVoteRequestResults( body = definitions.ListVoteResultsRequest( - actionName, - accepted, - requester, - effectiveFrom, - effectiveTo, - limit, - pageToken, + filters.actionName, + filters.accepted, + requester = filters.requester, + effectiveFrom = filters.effectiveFrom, + effectiveTo = filters.effectiveTo, + limit = limit, + pageToken = pageToken, ), headers = headers, ) @@ -3135,6 +3131,35 @@ object HttpScanAppClient { } } + case class CountVoteRequestResults( + filters: VoteResultsFilters + ) extends InternalBaseCommand[ + http.CountVoteRequestResultsResponse, + Long, + ] { + + override def submitRequest( + client: ScanClient, + headers: List[HttpHeader], + ): EitherT[Future, Either[Throwable, HttpResponse], http.CountVoteRequestResultsResponse] = + client.countVoteRequestResults( + body = definitions.CountVoteResultsRequest( + filters.actionName, + filters.accepted, + requester = filters.requester, + effectiveFrom = filters.effectiveFrom, + effectiveTo = filters.effectiveTo, + ), + headers = headers, + ) + + override def handleOk()(implicit + decoder: TemplateJsonDecoder + ) = { case http.CountVoteRequestResultsResponse.OK(response) => + Right(response.count) + } + } + case class GetPreviousSvRewardWeight( svParty: String, effectiveBefore: Option[String], diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala index 160261e7d0..b5e2302bf0 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala @@ -69,6 +69,7 @@ import org.lfdecentralizedtrust.splice.http.v0.definitions.{ AcsRequest, BatchListVotesByVoteRequestsRequest, DamlValueEncoding, + CountVoteResultsRequest, ErrorResponse, EventHistoryRequest, HoldingsStateRequest, @@ -107,6 +108,7 @@ import org.lfdecentralizedtrust.splice.store.{ AppStoreWithIngestion, PageLimit, SortOrder, + VoteResultsFilters, VotesStore, } import org.lfdecentralizedtrust.splice.store.S3BucketConnection.ObjectKeyAndChecksum @@ -2118,11 +2120,13 @@ class HttpScanHandler( val after = body.pageToken.map(_.longValue) for { page <- votesStore.listVoteRequestResults( - body.actionName, - body.accepted, - body.requester, - body.effectiveFrom, - body.effectiveTo, + VoteResultsFilters( + body.actionName, + body.accepted, + requester = body.requester, + effectiveFrom = body.effectiveFrom, + effectiveTo = body.effectiveTo, + ), limit, after, ) @@ -2149,6 +2153,29 @@ class HttpScanHandler( } } + override def countVoteRequestResults( + respond: ScanResource.CountVoteRequestResultsResponse.type + )( + body: CountVoteResultsRequest + )(extracted: TraceContext): Future[ScanResource.CountVoteRequestResultsResponse] = { + implicit val tc: TraceContext = extracted + withSpan(s"$workflowId.countVoteRequestResults") { _ => _ => + for { + count <- votesStore.countVoteRequestResults( + VoteResultsFilters( + body.actionName, + body.accepted, + requester = body.requester, + effectiveFrom = body.effectiveFrom, + effectiveTo = body.effectiveTo, + ) + ) + } yield ScanResource.CountVoteRequestResultsResponse.OK( + definitions.CountVoteResultsResponse(count) + ) + } + } + override def getPreviousSvRewardWeight( respond: ScanResource.GetPreviousSvRewardWeightResponse.type )( diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/CachingScanStore.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/CachingScanStore.scala index 9206db972d..c3f054b2cc 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/CachingScanStore.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/CachingScanStore.scala @@ -41,6 +41,7 @@ import org.lfdecentralizedtrust.splice.store.{ SynchronizerStore, TxLogStore, UpdateHistory, + VoteResultsFilters, } import org.lfdecentralizedtrust.splice.util.{Contract, ContractWithState} @@ -221,11 +222,7 @@ class CachingScanStore( ) override def listVoteRequestResults( - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], + filters: VoteResultsFilters, limit: Limit, after: Option[Long] = None, )(implicit tc: TraceContext): Future[ResultsPage[DsoRules_CloseVoteRequestResult]] = @@ -235,16 +232,21 @@ class CachingScanStore( store.listVoteRequestResults _ tupled, ).get( ( - actionName, - accepted, - requester, - effectiveFrom, - effectiveTo, + filters, limit, after, ) ) + override def countVoteRequestResults( + filters: VoteResultsFilters + )(implicit tc: TraceContext): Future[Long] = + getCache( + "countVoteRequestResults", + cacheConfig.voteRequests, + (f: VoteResultsFilters) => store.countVoteRequestResults(f), + ).get(filters) + override def listVoteRequestsByTrackingCid( voteRequestCids: Seq[VoteRequest.ContractId], limit: Limit, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbScanStore.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbScanStore.scala index 2f69177fe4..92fbc43bf0 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbScanStore.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbScanStore.scala @@ -59,6 +59,7 @@ import org.lfdecentralizedtrust.splice.store.{ DbVotesAcsStoreQueryBuilder, DbVotesTxLogStoreQueryBuilder, Limit, + VoteResultsFilters, PageLimit, ResultsPage, SortOrder, @@ -633,11 +634,7 @@ class DbScanStore( } override def listVoteRequestResults( - actionName: Option[String], - accepted: Option[Boolean], - requester: Option[String], - effectiveFrom: Option[String], - effectiveTo: Option[String], + filters: VoteResultsFilters, limit: Limit, after: Option[Long] = None, )(implicit tc: TraceContext): Future[ResultsPage[DsoRules_CloseVoteRequestResult]] = { @@ -648,11 +645,7 @@ class DbScanStore( actionNameColumnName = "vote_action_name", acceptedColumnName = "vote_accepted", requesterNameColumnName = "vote_requester_name", - actionName = actionName, - accepted = accepted, - requester = requester, - effectiveFrom = effectiveFrom, - effectiveTo = effectiveTo, + filters = filters, limit = limit, after = after, ) @@ -668,6 +661,23 @@ class DbScanStore( } yield ResultsPage(recentVoteResults, afterToken) } + override def countVoteRequestResults( + filters: VoteResultsFilters + )(implicit tc: TraceContext): Future[Long] = { + val query = countVoteRequestResultsQuery( + txLogTableName = ScanTables.txLogTableName, + txLogStoreId = txLogStoreId, + dbType = EntryType.VoteRequestTxLogEntry, + actionNameColumnName = "vote_action_name", + acceptedColumnName = "vote_accepted", + requesterNameColumnName = "vote_requester_name", + filters = filters, + ) + storage + .query(query, "countVoteRequestResults") + .map(_.headOption.getOrElse(0L)) + } + override def lookupLatestSvRewardWeightChange( svParty: PartyId, effectiveBefore: Option[String], diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala index 220a8d562c..b84bda45f0 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/store/db/ScanStoreTest.scala @@ -515,19 +515,11 @@ abstract class ScanStoreTest _ <- closeVoteRequest(store, 2) _ <- closeVoteRequest(store, 1) page1 <- store.listVoteRequestResults( - None, - None, - None, - None, - None, + VoteResultsFilters(), PageLimit.tryCreate(3), ) page2 <- store.listVoteRequestResults( - None, - None, - None, - None, - None, + VoteResultsFilters(), PageLimit.tryCreate(3), page1.nextPageToken, ) @@ -585,19 +577,11 @@ abstract class ScanStoreTest _ <- closeVoteRequest(store, 2) _ <- closeVoteRequest(store, 6) page1 <- store.listVoteRequestResults( - None, - None, - None, - None, - None, + VoteResultsFilters(), PageLimit.tryCreate(3), ) page2 <- store.listVoteRequestResults( - None, - None, - None, - None, - None, + VoteResultsFilters(), PageLimit.tryCreate(3), page1.nextPageToken, ) @@ -610,6 +594,60 @@ abstract class ScanStoreTest } } + "countVoteRequestResults" should { + + "count vote results matching the filters" in { + val base = Instant.parse("2024-03-01T10:00:00Z") + val accepted = Set(1, 3, 4, 6) + def sortKeyAt(n: Int) = base.plusSeconds(n.toLong) + def recordTime(n: Int) = base.plusSeconds(100L + n.toLong) + val voteRequests = (1 to 6).map { n => + voteRequest( + requester = userParty(n), + votes = Seq( + new Vote(userParty(n).toProtoPrimitive, true, new Reason("", ""), Optional.empty()) + ), + ) + } + val results = + (1 to 6).map(n => + if (accepted(n)) mkVoteRequestResult(voteRequests(n - 1), effectiveAt = sortKeyAt(n)) + else mkRejectedVoteRequestResult(voteRequests(n - 1), completedAt = sortKeyAt(n)) + ) + def closeVoteRequest(store: ScanStore, n: Int) = + dummyDomain.exercise( + contract = dsoRules(dsoParty), + interfaceId = Some(DsoRules.TEMPLATE_ID_WITH_PACKAGE_ID), + choiceName = DsoRulesCloseVoteRequest.choice.name, + choiceArgument = mkCloseVoteRequest(voteRequests(n - 1).contractId), + exerciseResult = results(n - 1).toValue, + recordTime = recordTime(n), + )(store.multiDomainAcsStore) + for { + store <- mkStore() + _ <- MonadUtil.sequentialTraverse(voteRequests)( + dummyDomain.create(_)(store.multiDomainAcsStore) + ) + _ <- MonadUtil.sequentialTraverse(1 to 6)(closeVoteRequest(store, _)) + total <- store.countVoteRequestResults(VoteResultsFilters()) + acceptedCount <- store.countVoteRequestResults( + VoteResultsFilters(accepted = Some(true)) + ) + rejectedCount <- store.countVoteRequestResults( + VoteResultsFilters(accepted = Some(false)) + ) + requesterCount <- store.countVoteRequestResults( + VoteResultsFilters(requester = Some(userParty(1).toProtoPrimitive)) + ) + } yield { + total shouldBe 6L + acceptedCount shouldBe 4L + rejectedCount shouldBe 2L + requesterCount shouldBe 1L + } + } + } + "lookupLatestSvRewardWeightChange" should { "return the weight of the latest accepted UpdateSvRewardWeight before the given time" in { @@ -1302,11 +1340,7 @@ abstract class ScanStoreTest } yield { store .listVoteRequestResults( - Some("AddSv"), - Some(true), - None, - None, - None, + VoteResultsFilters(actionName = Some("AddSv"), accepted = Some(true)), PageLimit.tryCreate(1), ) .futureValue @@ -1315,11 +1349,7 @@ abstract class ScanStoreTest .loneElement shouldBe result2 store .listVoteRequestResults( - Some("SRARC_AddSv"), - Some(false), - None, - None, - None, + VoteResultsFilters(actionName = Some("SRARC_AddSv"), accepted = Some(false)), PageLimit.tryCreate(1), ) .futureValue @@ -1328,11 +1358,7 @@ abstract class ScanStoreTest .size shouldBe (0) store .listVoteRequestResults( - None, - None, - None, - None, - None, + VoteResultsFilters(), PageLimit.tryCreate(1), ) .futureValue @@ -1341,11 +1367,9 @@ abstract class ScanStoreTest .size shouldBe (1) store .listVoteRequestResults( - None, - None, - None, - Some(Instant.now().truncatedTo(ChronoUnit.MICROS).plusSeconds(3600).toString), - None, + VoteResultsFilters(effectiveFrom = + Some(Instant.now().truncatedTo(ChronoUnit.MICROS).plusSeconds(3600).toString) + ), PageLimit.tryCreate(1), ) .futureValue @@ -1354,11 +1378,9 @@ abstract class ScanStoreTest .size shouldBe (0) store .listVoteRequestResults( - None, - None, - None, - Some(Instant.now().truncatedTo(ChronoUnit.MICROS).minusSeconds(3600).toString), - None, + VoteResultsFilters(effectiveFrom = + Some(Instant.now().truncatedTo(ChronoUnit.MICROS).minusSeconds(3600).toString) + ), PageLimit.tryCreate(1), ) .futureValue @@ -2014,11 +2036,7 @@ class DbScanStoreTest // because ingestion in these store tests is simulated by directly interacting with the ingestion sink storeReingest .listVoteRequestResults( - Some("AddSv"), - Some(true), - None, - None, - None, + VoteResultsFilters(actionName = Some("AddSv"), accepted = Some(true)), PageLimit.tryCreate(1), ) .futureValue diff --git a/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx b/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx index c6d3c1dc60..b4e44cba28 100644 --- a/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx @@ -1,11 +1,12 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { render, screen, within } from '@testing-library/react'; +import { render, screen, waitFor, within } from '@testing-library/react'; import { describe, expect, test } from 'vitest'; import { SvConfigProvider } from '../../utils'; import userEvent from '@testing-library/user-event'; import App from '../../App'; import { navigateToGovernancePage } from '../helpers'; +import { voteResultsAmuletRules, voteResultsDsoRules } from '../mocks/constants'; type UserEvent = ReturnType; @@ -95,6 +96,34 @@ describe('Governance Page', () => { expect(true).toBe(true); }); + test('should display total vote history count in the section badge', async () => { + const user = userEvent.setup(); + + render(); + + await navigateToGovernancePage(user); + + const expectedCount = voteResultsAmuletRules.dso_rules_vote_results + .concat(voteResultsDsoRules.dso_rules_vote_results) + .filter( + r => r.outcome.tag !== 'VRO_Accepted' || new Date(r.outcome.value.effectiveAt) < new Date() + ).length; + + const badge = await screen.findByTestId('vote-history-section-badge-count'); + await waitFor(() => expect(badge).toHaveTextContent(`${expectedCount}`)); + }); + + test('should display inflight votes count in the section badge', async () => { + const user = userEvent.setup(); + + render(); + + await navigateToGovernancePage(user); + + const badge = screen.getByTestId('inflight-proposals-section-badge-count'); + expect(badge).toHaveTextContent(''); + }); + test('click on Details link to see Proposal Details (Action Required)', async () => { const user = userEvent.setup(); diff --git a/apps/sv/frontend/src/__tests__/mocks/handlers/sv-api.ts b/apps/sv/frontend/src/__tests__/mocks/handlers/sv-api.ts index c81c8f072c..d184170e42 100644 --- a/apps/sv/frontend/src/__tests__/mocks/handlers/sv-api.ts +++ b/apps/sv/frontend/src/__tests__/mocks/handlers/sv-api.ts @@ -8,6 +8,8 @@ import dayjs from 'dayjs'; import { http, HttpHandler, HttpResponse, PathParams } from 'msw'; import { FeatureSupportResponse, SuccessStatusResponse } from '@canton-network/scan-openapi'; import { + CountVoteResultsRequest, + CountVoteResultsResponse, ErrorResponse, ListDsoRulesVoteRequestsResponse, ListDsoRulesVoteResultsResponse, @@ -159,6 +161,28 @@ export const buildSvMock = (svUrl: string): HttpHandler[] => [ } ), + http.post( + `${svUrl}/v0/admin/sv/voteresults/count`, + ({ request }) => { + return request.json().then(data => { + const count = voteResultsAmuletRules.dso_rules_vote_results + .concat(voteResultsDsoRules.dso_rules_vote_results) + .filter(r => { + const isAccepted = r.outcome.tag === 'VRO_Accepted'; + const acceptedMatch = + data.accepted === undefined || data.accepted === null + ? true + : data.accepted === isAccepted; + const effectiveToMatch = data.effectiveTo + ? isAccepted && dayjs(r.outcome.value.effectiveAt).isBefore(dayjs(data.effectiveTo)) + : true; + return acceptedMatch && effectiveToMatch; + }).length; + return HttpResponse.json({ count }); + }); + } + ), + http.post(`${svUrl}/v0/admin/sv/votes`, () => { return new HttpResponse(null, { status: 201 }); }), diff --git a/apps/sv/frontend/src/components/beta/PageSectionHeader.tsx b/apps/sv/frontend/src/components/beta/PageSectionHeader.tsx index 2f84fafd2f..188ce98ae1 100644 --- a/apps/sv/frontend/src/components/beta/PageSectionHeader.tsx +++ b/apps/sv/frontend/src/components/beta/PageSectionHeader.tsx @@ -5,12 +5,14 @@ import { Badge, Box, Typography } from '@mui/material'; interface PageSectionHeaderProps { title: string; badgeCount?: number; + badgeColor?: 'warning' | 'neutral'; 'data-testid': string; } const PageSectionHeader: React.FC = ({ title, badgeCount, + badgeColor = 'neutral', 'data-testid': testId, }) => ( @@ -19,8 +21,16 @@ const PageSectionHeader: React.FC = ({ diff --git a/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx b/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx index 89d0ff1d4b..36e338bf49 100644 --- a/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx +++ b/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx @@ -41,6 +41,7 @@ export const ActionRequiredSection: React.FC = ( diff --git a/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx b/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx index a9609f88c6..56b25d6bdd 100644 --- a/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx @@ -31,6 +31,7 @@ interface ProposalListingSectionProps { data: ProposalListingData[]; noDataMessage: string; uniqueId: string; + badgeCount?: number; showThresholdDeadline?: boolean; showVoteStats?: boolean; showStatus?: boolean; @@ -77,6 +78,7 @@ export const ProposalListingSection: React.FC = pro data, noDataMessage, uniqueId, + badgeCount, showThresholdDeadline, showVoteStats, showStatus, @@ -105,7 +107,11 @@ export const ProposalListingSection: React.FC = pro return ( - + {sortedData.length === 0 && !hasNextPage ? ( diff --git a/apps/sv/frontend/src/contexts/SvAdminServiceContext.tsx b/apps/sv/frontend/src/contexts/SvAdminServiceContext.tsx index 06934b8e27..6b4e1b8de8 100644 --- a/apps/sv/frontend/src/contexts/SvAdminServiceContext.tsx +++ b/apps/sv/frontend/src/contexts/SvAdminServiceContext.tsx @@ -10,6 +10,7 @@ import BigNumber from 'bignumber.js'; import React, { useContext, useMemo } from 'react'; import { CastVoteRequest, + CountVoteResultsResponse, createConfiguration, CreateVoteRequest, GetPartyToParticipantResponseV1, @@ -63,6 +64,10 @@ export interface SvAdminClient { accepted?: boolean, pageToken?: number ) => Promise; + countVoteRequestResults: ( + accepted?: boolean, + effectiveTo?: string + ) => Promise; getPreviousSvRewardWeight: ( svParty: string, effectiveBefore?: string @@ -163,6 +168,12 @@ export const SvAdminClientProvider: React.FC => { + return await svAdminClient.countVoteRequestResults({ accepted, effectiveTo }); + }, getPreviousSvRewardWeight: async ( svParty: string, effectiveBefore?: string diff --git a/apps/sv/frontend/src/hooks/index.ts b/apps/sv/frontend/src/hooks/index.ts index 44494fdcfd..2542c581fe 100644 --- a/apps/sv/frontend/src/hooks/index.ts +++ b/apps/sv/frontend/src/hooks/index.ts @@ -12,6 +12,7 @@ import { useSequencerStatus } from './useSequencerStatus'; import { useValidatorLicenses } from './useValidatorLicenses'; import { useValidatorOnboardings } from './useValidatorOnboardings'; import { useVoteRequest } from './useVoteRequest'; +import { useVoteRequestResultsCount } from './useVoteRequestResultsCount'; export { useAmuletPriceVotes, @@ -27,4 +28,5 @@ export { useValidatorLicenses, useValidatorOnboardings, useVoteRequest, + useVoteRequestResultsCount, }; diff --git a/apps/sv/frontend/src/hooks/useVoteRequestResultsCount.ts b/apps/sv/frontend/src/hooks/useVoteRequestResultsCount.ts new file mode 100644 index 0000000000..ca27a768fe --- /dev/null +++ b/apps/sv/frontend/src/hooks/useVoteRequestResultsCount.ts @@ -0,0 +1,20 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { useQuery, UseQueryResult } from '@tanstack/react-query'; + +import { useSvAdminClient } from '../contexts/SvAdminServiceContext'; + +export const useVoteRequestResultsCount = (): UseQueryResult => { + const { countVoteRequestResults } = useSvAdminClient(); + return useQuery({ + queryKey: ['voteRequestResultsCount'], + queryFn: async () => { + const [effective, notAccepted] = await Promise.all([ + countVoteRequestResults(true, new Date().toISOString()), + countVoteRequestResults(false), + ]); + return effective.count + notAccepted.count; + }, + }); +}; diff --git a/apps/sv/frontend/src/routes/governance.tsx b/apps/sv/frontend/src/routes/governance.tsx index 0d4086d2d9..196f36fb01 100644 --- a/apps/sv/frontend/src/routes/governance.tsx +++ b/apps/sv/frontend/src/routes/governance.tsx @@ -27,7 +27,7 @@ import { import { SupportedActionTag, ProposalListingData } from '../utils/types'; import { Link as RouterLink } from 'react-router'; import { InfoOutlined, WarningAmberOutlined } from '@mui/icons-material'; -import { useInfiniteVoteRequestResults } from '../hooks'; +import { useInfiniteVoteRequestResults, useVoteRequestResultsCount } from '../hooks'; function getAction(action: ActionRequiringConfirmation): string { switch (action.tag) { @@ -48,6 +48,7 @@ export const Governance: React.FC = () => { const dsoInfosQuery = votesHooks.useDsoInfos(); const listVoteRequestsQuery = votesHooks.useListDsoRulesVoteRequests(); const voteResultsInfiniteQuery = useInfiniteVoteRequestResults(); + const voteResultsCountQuery = useVoteRequestResultsCount(); const voteRequestIds = listVoteRequestsQuery.data ? listVoteRequestsQuery.data.map(v => v.payload.trackingCid || v.contractId) @@ -182,6 +183,7 @@ export const Governance: React.FC = () => { { + Right(response.count) + } + } + case class CastVote( trackingCid: VoteRequest.ContractId, isAccepted: Boolean, diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvOperatorHandler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvOperatorHandler.scala index 822538ca6f..f9937ca058 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvOperatorHandler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvOperatorHandler.scala @@ -34,7 +34,12 @@ import org.lfdecentralizedtrust.splice.http.{ } import org.lfdecentralizedtrust.splice.scan.admin.api.client.ScanConnection import org.lfdecentralizedtrust.splice.scan.config.ScanAppClientConfig -import org.lfdecentralizedtrust.splice.store.{ActiveVotesStore, AppStore, AppStoreWithIngestion} +import org.lfdecentralizedtrust.splice.store.{ + ActiveVotesStore, + AppStore, + AppStoreWithIngestion, + VoteResultsFilters, +} import org.lfdecentralizedtrust.splice.sv.cometbft.CometBftClient import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.store.{SvDsoStore, SvSvStore} @@ -131,11 +136,13 @@ class HttpSvOperatorHandler( for { scanConnection <- scanConnectionF (voteResults, nextPageToken) <- scanConnection.listVoteRequestResults( - body.actionName, - body.accepted, - body.requester, - body.effectiveFrom, - body.effectiveTo, + VoteResultsFilters( + body.actionName, + body.accepted, + requester = body.requester, + effectiveFrom = body.effectiveFrom, + effectiveTo = body.effectiveTo, + ), body.limit.intValue, body.pageToken, ) @@ -162,6 +169,34 @@ class HttpSvOperatorHandler( } } + override def countVoteRequestResults( + respond: r0.CountVoteRequestResultsResponse.type + )( + body: definitions.CountVoteResultsRequest + )( + extracted: ActAsKnownUserRequest + ): Future[r0.CountVoteRequestResultsResponse] = { + implicit val ActAsKnownUserRequest(traceContext) = extracted + withSpan(s"$workflowId.countVoteRequestResults") { _ => _ => + for { + scanConnection <- scanConnectionF + count <- scanConnection.countVoteRequestResults( + VoteResultsFilters( + body.actionName, + body.accepted, + requester = body.requester, + effectiveFrom = body.effectiveFrom, + effectiveTo = body.effectiveTo, + ) + ) + } yield { + r0.CountVoteRequestResultsResponse.OK( + definitions.CountVoteResultsResponse(count) + ) + } + } + } + override def getPreviousSvRewardWeight( respond: r0.GetPreviousSvRewardWeightResponse.type )( From 13ba109e49f24c7bfc2cc92073be59696e63eeb0 Mon Sep 17 00:00:00 2001 From: Divam <681060+dfordivam@users.noreply.github.com> Date: Mon, 13 Jul 2026 15:21:26 +0900 Subject: [PATCH 016/329] start-frontend.sh: wait for direnv to load after 'cd' (#4723) Signed-off-by: Divam --- start-frontends.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/start-frontends.sh b/start-frontends.sh index 94156a9825..f49eb260a4 100755 --- a/start-frontends.sh +++ b/start-frontends.sh @@ -15,7 +15,9 @@ function tmux_cmd() { else tmux new-window -t "$t" -n "$title" fi - tmux send-keys -t "$t" "cd $wd" C-m + local direnv_ready_event="${tmux_session}-cd-done-${tmux_window}" + tmux send-keys -t "$t" "cd $wd && tmux wait-for -S $direnv_ready_event" C-m + tmux wait-for "$direnv_ready_event" tmux send-keys -t "$t" "$cmd" C-m tmux_window=$((tmux_window + 1)) } @@ -75,11 +77,9 @@ function start_frontend() { local log_file="${LOG_DIR}/npm-${app}-${user}.out" tmux_cmd "${app}-${user}" "${frontend_dir}" \ - "trap \"rm -f ${config_file}\" EXIT" - - tmux send-keys -t "${tmux_session}:$((tmux_window - 1))" \ - "BROWSER=none PORT=$port JSON_API_URL=$JSON_API_URL VITE_SPLICE_CONFIG=\"\$(cat $config_file)\" \ - npm start 2>&1 | tee -a $log_file" C-m + "trap \"rm -f ${config_file}\" EXIT && \ + BROWSER=none PORT=$port JSON_API_URL=$JSON_API_URL VITE_SPLICE_CONFIG=\"\$(cat $config_file)\" \ + npm start 2>&1 | tee -a $log_file" } function start_test() { From 280216d69bfc1b53ced8b112c00b1025e58bc42f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Mon, 13 Jul 2026 10:08:35 +0200 Subject: [PATCH 017/329] remove leftover non-hyperdisk script code (#6385) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Błażejewski --- cluster/scripts/node-backup.sh | 66 +++++++++++---------------------- cluster/scripts/node-restore.sh | 33 ++++------------- 2 files changed, 28 insertions(+), 71 deletions(-) diff --git a/cluster/scripts/node-backup.sh b/cluster/scripts/node-backup.sh index c76c1c6fcd..0ff93b226a 100755 --- a/cluster/scripts/node-backup.sh +++ b/cluster/scripts/node-backup.sh @@ -71,18 +71,13 @@ function backup_pvc_postgres() { local namespace=$2 local instance=$3 local migration_id=$4 - local hyperdisk_enabled=$5 _info "** Backup up pvc-based postgres $description **" # Since we only have one replica, it's always 0. replica_index="0" local pvc_name - if [ "$hyperdisk_enabled" = "true" ]; then - pvc_name="pg-data-hd-$instance-$replica_index" - else - pvc_name="pg-data-$instance-$replica_index" - fi + pvc_name="pg-data-hd-$instance-$replica_index" backup_pvc "$description" "$namespace" "$pvc_name" "$migration_id" } @@ -174,14 +169,13 @@ function backup_postgres() { local instance=$3 local migration_id=$4 local stack=$5 - local hyperdisk_enabled=$6 local full_instance="$namespace-$instance" type=$(get_postgres_type "$full_instance" "$stack") if [ "$type" == "canton:network:postgres" ]; then - backup_pvc_postgres "$description" "$namespace" "$instance" "$migration_id" "$hyperdisk_enabled" + backup_pvc_postgres "$description" "$namespace" "$instance" "$migration_id" elif [ "$type" == "canton:cloud:postgres" ]; then backup_cloudsql "$description" "$full_instance" "$stack" elif [ -z "$type" ]; then @@ -197,7 +191,6 @@ function wait_for_postgres_backup() { local instance=$3 local migration_id=$4 local stack=$5 - local hyperdisk_enabled=$6 local full_instance="$namespace-$instance" @@ -207,11 +200,7 @@ function wait_for_postgres_backup() { # Since we only have one replica, it's always 0. replica_index="0" local pvc_name - if [ "$hyperdisk_enabled" = "true" ]; then - pvc_name="pg-data-hd-$instance-$replica_index" - else - pvc_name="pg-data-$instance-$replica_index" - fi + pvc_name="pg-data-hd-$instance-$replica_index" wait_for_pvc_backup "$description" "$namespace" "$pvc_name" elif [ "$type" == "canton:cloud:postgres" ]; then wait_for_cloudsql_backup "$description" "$full_instance" "$stack" @@ -226,7 +215,6 @@ function backup_component() { local component=$2 local requested_component=$3 local migration_id=$4 - local hyperdisk_enabled=$5 local stack stack=$(get_stack_for_namespace_component "$namespace" "$component") @@ -236,16 +224,12 @@ function backup_component() { if [ "$component" == "$requested_component" ] || [ -z "$requested_component" ]; then if [ "$component" == "cometbft-$migration_id" ]; then local cometbft_pvc_name - if [ "$hyperdisk_enabled" = "true" ]; then - cometbft_pvc_name="cometbft-migration-${migration_id}-hd-pvc" - else - cometbft_pvc_name="global-domain-${migration_id}-cometbft-cometbft-data" - fi + cometbft_pvc_name="cometbft-migration-${migration_id}-hd-pvc" backup_pvc "cometBFT" "$namespace" "$cometbft_pvc_name" "$migration_id" else local db_name db_name=$(create_component_instance "$component" "$migration_id" "$namespace") - SPLICE_SV=$namespace SPLICE_MIGRATION_ID=$migration_id backup_postgres "$component" "$namespace" "$db_name-pg" "$migration_id" "$stack" "$hyperdisk_enabled" + SPLICE_SV=$namespace SPLICE_MIGRATION_ID=$migration_id backup_postgres "$component" "$namespace" "$db_name-pg" "$migration_id" "$stack" fi else _info "Skipping backup of $component, not requested" @@ -257,7 +241,6 @@ function wait_for_backup() { local component=$2 local requested_component=$3 local migration_id=$4 - local hyperdisk_enabled=$5 local stack stack=$(get_stack_for_namespace_component "$namespace" "$component") @@ -265,16 +248,12 @@ function wait_for_backup() { if [ "$component" == "$requested_component" ] || [ -z "$requested_component" ]; then if [ "$component" == "cometbft-$migration_id" ]; then local cometbft_pvc_name - if [ "$hyperdisk_enabled" = "true" ]; then - cometbft_pvc_name="cometbft-migration-${migration_id}-hd-pvc" - else - cometbft_pvc_name="global-domain-${migration_id}-cometbft-cometbft-data" - fi + cometbft_pvc_name="cometbft-migration-${migration_id}-hd-pvc" wait_for_pvc_backup "cometBFT" "$namespace" "$cometbft_pvc_name" else local instance instance=$(create_component_instance "$component" "$migration_id" "$namespace") - wait_for_postgres_backup "$component" "$namespace" "$instance-pg" "$migration_id" "$stack" "$hyperdisk_enabled" + wait_for_postgres_backup "$component" "$namespace" "$instance-pg" "$migration_id" "$stack" fi else _info "Skipping waiting for backup of $component, not requested" @@ -295,34 +274,31 @@ function main() { local migration_id=$3 local requested_component="${4:-}" - local hyperdisk_enabled - hyperdisk_enabled="true" - # TODO(#9361): support multiple domains / non-default-ID'd ones if [ "$1" == "validator" ]; then _info "Backing up validator $namespace" - backup_component "$namespace" "validator" "$requested_component" "$migration_id" "$hyperdisk_enabled" - wait_for_backup "$namespace" "validator" "$requested_component" "$migration_id" "$hyperdisk_enabled" + backup_component "$namespace" "validator" "$requested_component" "$migration_id" + wait_for_backup "$namespace" "validator" "$requested_component" "$migration_id" # CN apps must be strictly before participant, so we sync on apps before starting the participant backup - backup_component "$namespace" "participant" "$requested_component" "$migration_id" "$hyperdisk_enabled" - wait_for_backup "$namespace" "participant" "$requested_component" "$migration_id" "$hyperdisk_enabled" + backup_component "$namespace" "participant" "$requested_component" "$migration_id" + wait_for_backup "$namespace" "participant" "$requested_component" "$migration_id" elif [ "$1" == "sv" ]; then _info "Backing up SV node $namespace" - backup_component "$namespace" "cn-apps" "$requested_component" "$migration_id" "$hyperdisk_enabled" - backup_component "$namespace" "mediator" "$requested_component" "$migration_id" "$hyperdisk_enabled" - backup_component "$namespace" "sequencer" "$requested_component" "$migration_id" "$hyperdisk_enabled" - backup_component "$namespace" "cometbft-$migration_id" "$requested_component" "$migration_id" "$hyperdisk_enabled" + backup_component "$namespace" "cn-apps" "$requested_component" "$migration_id" + backup_component "$namespace" "mediator" "$requested_component" "$migration_id" + backup_component "$namespace" "sequencer" "$requested_component" "$migration_id" + backup_component "$namespace" "cometbft-$migration_id" "$requested_component" "$migration_id" - wait_for_backup "$namespace" "cn-apps" "$requested_component" "$migration_id" "$hyperdisk_enabled" + wait_for_backup "$namespace" "cn-apps" "$requested_component" "$migration_id" # CN apps must be strictly before participant, so we sync on apps before starting the participant backup - backup_component "$namespace" "participant" "$requested_component" "$migration_id" "$hyperdisk_enabled" + backup_component "$namespace" "participant" "$requested_component" "$migration_id" - wait_for_backup "$namespace" "participant" "$requested_component" "$migration_id" "$hyperdisk_enabled" - wait_for_backup "$namespace" "mediator" "$requested_component" "$migration_id" "$hyperdisk_enabled" - wait_for_backup "$namespace" "sequencer" "$requested_component" "$migration_id" "$hyperdisk_enabled" - wait_for_backup "$namespace" "cometbft-$migration_id" "$requested_component" "$migration_id" "$hyperdisk_enabled" + wait_for_backup "$namespace" "participant" "$requested_component" "$migration_id" + wait_for_backup "$namespace" "mediator" "$requested_component" "$migration_id" + wait_for_backup "$namespace" "sequencer" "$requested_component" "$migration_id" + wait_for_backup "$namespace" "cometbft-$migration_id" "$requested_component" "$migration_id" else usage exit 1 diff --git a/cluster/scripts/node-restore.sh b/cluster/scripts/node-restore.sh index 5a5553c8ba..f14127908a 100755 --- a/cluster/scripts/node-restore.sh +++ b/cluster/scripts/node-restore.sh @@ -156,17 +156,11 @@ function restore_pvc_postgres() { local -r namespace=$1 local -r component=$2 local -r run_id=$3 - local -r hyperdisk_enabled=$4 local template_name local storage_class - if [ "$hyperdisk_enabled" = "true" ]; then - template_name="pg-data-hd" - storage_class="hyperdisk-standard-rwo" - else - template_name="pg-data" - storage_class="standard-rwo" - fi + template_name="pg-data-hd" + storage_class="hyperdisk-standard-rwo" local -r ss_name="$component-pg" local -r pg_pod_name="$ss_name-0" @@ -263,7 +257,6 @@ function restore_component() { local -r migration_id=$3 local -r run_id=$4 local -r restore_cluster=$5 # cluster to restore into (if different from current) - local -r hyperdisk_enabled=$6 local -r deployment_names=$(component_to_deployments "$component" "$migration_id" "$namespace") local stack @@ -275,20 +268,11 @@ function restore_component() { local cometbft_pvc_name local cometbft_snapshot_name - if [ "$hyperdisk_enabled" = "true" ]; then - cometbft_pvc_name="cometbft-migration-${migration_id}-hd-pvc" - cometbft_snapshot_name="${cometbft_pvc_name}-$run_id" - else - cometbft_pvc_name="global-domain-$migration_id-cometbft-cometbft-data" - cometbft_snapshot_name="${cometbft_pvc_name}-$run_id" - fi + cometbft_pvc_name="cometbft-migration-${migration_id}-hd-pvc" + cometbft_snapshot_name="${cometbft_pvc_name}-$run_id" local cometbft_storage_class - if [ "$hyperdisk_enabled" = "true" ]; then - cometbft_storage_class="hyperdisk-standard-rwo" - else - cometbft_storage_class="premium-rwo" - fi + cometbft_storage_class="hyperdisk-standard-rwo" restore_pvc_from_snapshot "$namespace" "$cometbft_snapshot_name" "$cometbft_pvc_name" "$cometbft_storage_class" kubectl scale deployment -n "$namespace" "${deployment_names}" --replicas=1 @@ -298,7 +282,7 @@ function restore_component() { type=$(get_postgres_type "$namespace-$instance-pg" "$stack") case "$type" in "canton:network:postgres") - restore_pvc_postgres "$namespace" "$instance" "$run_id" "$hyperdisk_enabled" + restore_pvc_postgres "$namespace" "$instance" "$run_id" ;; "canton:cloud:postgres") restore_cloudsql_postgres "$namespace" "$component" "$run_id" "$migration_id" "$restore_cluster" @@ -437,9 +421,6 @@ function main() { local -r migration_id=$2 local -r run_id=$3 - local hyperdisk_enabled - hyperdisk_enabled="true" - if [[ "$run_id" == *","* ]]; then _info " ** Validate backup ids ** " local map_keys @@ -468,7 +449,7 @@ function main() { for component in "${@:4}"; do local component_run_id component_run_id=$(get_component_run_id "$run_id" "$component") - restore_component "$namespace" "$component" "$migration_id" "$component_run_id" "$restore_cluster" "$hyperdisk_enabled" + restore_component "$namespace" "$component" "$migration_id" "$component_run_id" "$restore_cluster" done _info " ** Waiting for all restore operations to finish ** " From 6619c3364f3474610b9263fbb8e9f6dfdaeae625 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Mon, 13 Jul 2026 10:10:48 +0200 Subject: [PATCH 018/329] Make the flux slack channel optional (#6391) [static] Signed-off-by: Nicu Reut --- cluster/pulumi/operator/src/config.ts | 13 ++++--------- cluster/pulumi/operator/src/flux/flux-alerts.ts | 2 +- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/cluster/pulumi/operator/src/config.ts b/cluster/pulumi/operator/src/config.ts index 2386386a18..2f3f7d495f 100644 --- a/cluster/pulumi/operator/src/config.ts +++ b/cluster/pulumi/operator/src/config.ts @@ -8,16 +8,11 @@ export const OperatorDeploymentConfigSchema = z.object({ reference: GitReferenceSchema, flux: z .object({ - alertSlackChannel: z.string().optional(), + alertSlackChannel: z + .string() + .optional() + .prefault(() => config.optionalEnv('SLACK_ALERT_NOTIFICATION_CHANNEL_FULL_NAME')), }) - .transform(flux => ({ - ...flux, - get alertSlackChannel(): string { - return ( - flux.alertSlackChannel ?? config.requireEnv('SLACK_ALERT_NOTIFICATION_CHANNEL_FULL_NAME') - ); - }, - })) .prefault({}), }); diff --git a/cluster/pulumi/operator/src/flux/flux-alerts.ts b/cluster/pulumi/operator/src/flux/flux-alerts.ts index 9015bead27..9ad0d74cd9 100644 --- a/cluster/pulumi/operator/src/flux/flux-alerts.ts +++ b/cluster/pulumi/operator/src/flux/flux-alerts.ts @@ -7,7 +7,7 @@ import { fluxConfig } from '../config'; import { namespace } from '../namespace'; import { flux } from './flux'; -if (clusterProdLike) { +if (clusterProdLike && fluxConfig.alertSlackChannel) { const slackToken = new k8s.core.v1.Secret('slack', { metadata: { name: 'slack', From 85b95b701657d2f35986c26ac22854745524a891 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Mon, 13 Jul 2026 12:37:58 +0200 Subject: [PATCH 019/329] Give some more ram to the LSU in process nodes (#6393) They seem to ocasionally start up really slow and we run 4xmediators and 4x sequencers so hopefully this will help [ci] Signed-off-by: Nicu Reut --- .../splice/integration/tests/LsuIntegrationTest.scala | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LsuIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LsuIntegrationTest.scala index 9c32e60cfe..c6e6388748 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LsuIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LsuIntegrationTest.scala @@ -405,7 +405,9 @@ class LsuIntegrationTest participants = false, enableBftSequencer = true, logSuffix = "global-synchronizer-upgrade", - )() { + )( + ProcessTestUtil.javaToolOptionsKey -> "-Xms8g -Xmx10g" + ) { clue( "Pause traffic transfer trigger on sv2 to simulate a participant that is connected to a non initialized sequencer past upgrade tiem" From 972955c78305fa1156888b1bc1ef228c753256c9 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Mon, 13 Jul 2026 14:55:08 +0200 Subject: [PATCH 020/329] Enable validator unvetting (#6169) Signed-off-by: Julien Tinguely --- .../integration/EnvironmentDefinition.scala | 11 +++ .../tests/AppUpgradeIntegrationTest.scala | 10 +- ...ootstrapPackageConfigIntegrationTest.scala | 10 +- ...pportedPackageVettingIntegrationTest.scala | 93 +++++++++++++++++-- .../splice/config/SpliceConfig.scala | 1 + .../splice/environment/DarResources.scala | 16 +++- .../splice/util/DarResourcesUtil.scala | 14 +-- .../splice/util/PackageVetting.scala | 1 - .../darutils/DarResourcesGenerator.scala | 28 +++++- .../ValidatorAutomationService.scala | 1 + .../ValidatorPackageVettingTrigger.scala | 3 +- docs/src/release_notes_upcoming.rst | 17 ++++ 12 files changed, 168 insertions(+), 37 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/EnvironmentDefinition.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/EnvironmentDefinition.scala index 2b1c85b275..b28ff145d3 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/EnvironmentDefinition.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/EnvironmentDefinition.scala @@ -278,6 +278,17 @@ case class EnvironmentDefinition( ) } + def withReducedAmuletRulesCacheTTL( + duration: NonNegativeFiniteDuration = NonNegativeFiniteDuration.ofSeconds(1) + ): EnvironmentDefinition = + this + .addConfigTransform((_, conf) => + ConfigTransforms.updateAllValidatorAppConfigs_(c => + // Reduce the cache TTL. Otherwise alice validator takes forever to see the new amulet rules version + c.copy(scanClient = c.scanClient.setAmuletRulesCacheTimeToLive(duration)) + )(conf) + ) + /** Use exactly this setup and replace any previously existing setup. */ def withThisSetup(setup: SpliceTestConsoleEnvironment => Unit): EnvironmentDefinition = copy(setup = setup) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AppUpgradeIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AppUpgradeIntegrationTest.scala index 0ec02124a1..e0e1c85eb4 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AppUpgradeIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AppUpgradeIntegrationTest.scala @@ -30,7 +30,6 @@ import com.digitalasset.canton.topology.admin.grpc.TopologyStoreId import com.digitalasset.canton.topology.store.TimeQuery.HeadState import monocle.macros.syntax.lens.* import org.lfdecentralizedtrust.splice.console.ParticipantClientReference -import com.digitalasset.canton.config.NonNegativeFiniteDuration import org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.AmuletRules_SetConfig import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.amuletrules_actionrequiringconfirmation.CRARC_SetConfig @@ -84,14 +83,7 @@ class AppUpgradeIntegrationTest // Makes the test a bit faster and easier to debug. See #11488 ConfigTransforms.useDecentralizedSynchronizerSplitwell()(config) ) - .addConfigTransform((_, conf) => - ConfigTransforms.updateAllValidatorAppConfigs_(c => - // Reduce the cache TTL so package upgrades are picked up quickly. - c.copy(scanClient = - c.scanClient.setAmuletRulesCacheTimeToLive(NonNegativeFiniteDuration.ofSeconds(1)) - ) - )(conf) - ) + .withReducedAmuletRulesCacheTTL() .addConfigTransform((_, config) => { config .focus(_.validatorApps) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala index 33ea2e4f0f..1dec85f5c7 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala @@ -4,7 +4,6 @@ package org.lfdecentralizedtrust.splice.integration.tests import com.digitalasset.canton.admin.api.client.data.TemplateId -import com.digitalasset.canton.config.NonNegativeFiniteDuration import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.topology.{ParticipantId, PartyId} import com.digitalasset.canton.topology.admin.grpc.TopologyStoreId @@ -75,14 +74,7 @@ class BootstrapPackageConfigIntegrationTest _.copy(initialPackageConfig = initialPackageConfig) )(config) ) - .addConfigTransform((_, conf) => - ConfigTransforms.updateAllValidatorAppConfigs_(c => - // Reduce the cache TTL. Otherwise alice validator takes forever to see the new amulet rules version - c.copy(scanClient = - c.scanClient.setAmuletRulesCacheTimeToLive(NonNegativeFiniteDuration.ofSeconds(1)) - ) - )(conf) - ) + .withReducedAmuletRulesCacheTTL() .addConfigTransform((_, config) => ConfigTransforms.useDecentralizedSynchronizerSplitwell()(config) ) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala index e981fbe357..d01c1e364e 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala @@ -29,11 +29,15 @@ import org.lfdecentralizedtrust.splice.util.{ UploadablePackage, WalletTestUtil, } +import org.lfdecentralizedtrust.splice.config.ConfigTransforms.updateAllValidatorConfigs import org.lfdecentralizedtrust.splice.validator.automation.ValidatorPackageVettingTrigger import org.scalatest.concurrent.PatienceConfiguration -import scala.concurrent.duration.DurationInt +import scala.concurrent.duration.DurationInt import scala.concurrent.duration.FiniteDuration +import com.digitalasset.canton.logging.SuppressionRule +import org.lfdecentralizedtrust.splice.config.ConfigTransforms +import org.slf4j.event.Level class UnsupportedPackageVettingIntegrationTest extends IntegrationTest @@ -47,11 +51,24 @@ class UnsupportedPackageVettingIntegrationTest .withoutAliceValidatorConnectingToSplitwell // if other tests run before, packages that break this test might already be vetted .withNoVettedPackages(implicit env => env.validators.local.map(_.participantClient)) + .withReducedAmuletRulesCacheTTL() .addConfigTransforms((_, config) => updateAutomationConfig(ConfigurableApp.Sv)( _.withPausedTrigger[SvPackageVettingTrigger] )(config) ) + .addConfigTransforms((_, config) => + updateAllValidatorConfigs { case (name, c) => + if (name == "aliceValidator" || name == "bobValidator") { + c.copy( + automation = c.automation.withPausedTrigger[ValidatorPackageVettingTrigger] + ) + } else c + }(config) + ) + .addConfigTransforms((_, config) => + ConfigTransforms.useDecentralizedSynchronizerSplitwell()(config) + ) "Unsupported vetted packages are automatically removed by the package vetting trigger for SV and validator" in { implicit env => @@ -78,12 +95,11 @@ class UnsupportedPackageVettingIntegrationTest unsupportedDarsToVetSv, sv1Backend.dsoAutomation.trigger[SvPackageVettingTrigger], ) - // See https://github.com/DACH-NY/canton/issues/29834: set darsUnvettedByAutomation when unvetting works on non-sv validators test( aliceValidatorBackend.appState.participantAdminConnection, synchronizerId, unsupportedDarsToVetValidator, - Seq.empty, + unsupportedDarsToVetValidator, aliceValidatorBackend.validatorAutomation.trigger[ValidatorPackageVettingTrigger], ) } @@ -128,7 +144,7 @@ class UnsupportedPackageVettingIntegrationTest } } - "SVs unvet package versions above the configured PackageConfig, validators do not" in { + "SVs and validators unvet package versions above the configured PackageConfig" in { implicit env => val synchronizerId = sv1Backend.participantClient.synchronizers.list_connected().head.synchronizerId @@ -190,16 +206,81 @@ class UnsupportedPackageVettingIntegrationTest } } - clue("alice validator keeps package versions above the downgraded PackageConfig vetted") { + clue("alice validator unvets package versions above the downgraded PackageConfig") { eventually() { getVettedPackageIds( aliceValidatorBackend.appState.participantAdminConnection, synchronizerId, - ) should contain allElementsOf validatorDarsAbovePackageConfigVersion.map(_.packageId) + ) should contain noElementsOf validatorDarsAbovePackageConfigVersion.map(_.packageId) } eventually(40.seconds) { alicesTapsWithPackageId(DarResources.amulet_0_1_16.packageId) } } } + + "Unvetting amulet does not affect a validator that has splitwell depending on it" in { + implicit env => + val bobValidatorVettingTrigger = + bobValidatorBackend.validatorAutomation.trigger[ValidatorPackageVettingTrigger] + + val synchronizerId = + sv1Backend.participantClient.synchronizers.list_connected().head.synchronizerId + + val bobParticipant = bobValidatorBackend.appState.participantAdminConnection + val splitwellParticipant = splitwellValidatorBackend.appState.participantAdminConnection + + val splitwellDar = DarResources.splitwell_0_1_0 + val amuletDependency = DarResources.amulet_0_1_0 + + actAndCheck( + "bob and splitwell upload and vet splitwell-0.1.0 (which vets amulet-0.1.0 as a dependency)", { + val participants = Seq(bobParticipant, splitwellParticipant) + participants.foreach( + _.uploadDarFiles( + Seq(splitwellDar).map(UploadablePackage.fromResource), + RetryFor.Automation, + ).futureValue + ) + participants.foreach( + _.vetDars(synchronizerId, Seq(splitwellDar), None, None) + .futureValue(timeout = PatienceConfiguration.Timeout(FiniteDuration(40, "seconds"))) + ) + }, + )( + "both splitwell-0.1.0 and amulet-0.1.0 are vetted on bob's participant", + _ => { + val vettedIds = getVettedPackageIds(bobParticipant, synchronizerId) + vettedIds should contain(splitwellDar.packageId) + vettedIds should contain(amuletDependency.packageId) + }, + ) + + clue("amulet-0.1.0 is unvetted on bob") { + loggerFactory.assertEventuallyLogsSeq(SuppressionRule.LevelAndAbove(Level.INFO))( + bobValidatorVettingTrigger.resume(), + entries => { + forAtLeast(1, entries)( + _.message should include regex "Success: dars .*48cac5ba4b6bf78df6c3a952ce05409a1d2ef39c05351074679adc0cf9cd1351.* are removed .*" + ) + }, + ) + } + + clue("splitwell-0.1.0 remains vetted after trigger ran") { + eventually() { + val vettedIds = getVettedPackageIds(bobParticipant, synchronizerId) + vettedIds should contain(splitwellDar.packageId) + vettedIds should not contain amuletDependency.packageId + } + } + + clue("splitwell is still usable on bob") { + onboardWalletUser(bobWalletClient, bobValidatorBackend) + eventually() { + bobSplitwellClient.createInstallRequests() should not be empty + } + } + + } } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala index f0830271d3..f433f9b66c 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala @@ -91,6 +91,7 @@ final case class EnabledFeaturesConfig( // On 3.5 we should be able to set it to false. reconnectOnSynchronizerConfigurationChange: Boolean = true, enableUnsupportedDarsUnvetting: Boolean = true, + enableValidatorDarsUnvetting: Boolean = true, ignorePartyIdWithIgnoredAmulet: Boolean = true, naiveUnresponsivePartiesAutoIgnore: Boolean = true, ) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala index 9184a816b0..ee0a4e30dd 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/DarResources.scala @@ -10067,13 +10067,25 @@ object DarResources { DarResources.splitwell, ) + lazy val corePackageResources: Seq[PackageResource] = + TokenStandard.allPackageResources ++ Seq( + DarResources.amulet, + DarResources.amuletNameService, + DarResources.apiRewardAssignmentV1, + DarResources.dsoGovernance, + DarResources.utilBatchedMarkers, + DarResources.validatorLifecycle, + DarResources.wallet, + DarResources.walletPayments, + ) + lazy val pkgIdToDarResource: Map[String, DarResource] = - packageResources.view.flatMap(_.all).map(resource => resource.packageId -> resource).toMap + corePackageResources.view.flatMap(_.all).map(resource => resource.packageId -> resource).toMap // We don't index the map by PackageMetadata because that type contains some additional // fields that don't matter. lazy val pkgMetadataToDarResource: Map[(PackageName, PackageVersion), DarResource] = - packageResources.view + corePackageResources.view .flatMap(_.all) .map(resource => (resource.metadata.name, resource.metadata.version) -> resource) .toMap diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/DarResourcesUtil.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/DarResourcesUtil.scala index 3cb46ac417..45eef8fabe 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/DarResourcesUtil.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/DarResourcesUtil.scala @@ -9,7 +9,7 @@ import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.daml.lf.data.Ref.{PackageName, PackageVersion} import org.lfdecentralizedtrust.splice.environment.DarResource import org.lfdecentralizedtrust.splice.environment.DarResources.{ - packageResources, + corePackageResources, pkgIdToDarResource, pkgMetadataToDarResource, } @@ -18,12 +18,12 @@ object DarResourcesUtil extends NamedLogging { override protected def loggerFactory: NamedLoggerFactory = NamedLoggerFactory.root - val minimalPackageVersions: Seq[DarResource] = packageResources.flatMap(pkg => + val minimalPackageVersions: Seq[DarResource] = corePackageResources.flatMap(pkg => pkg.all.filter(p => p.metadata.version == pkg.minimumInitialization.metadata.version) ) val supportedPackageVersions: Seq[DarResource] = - packageResources.flatMap(pkg => + corePackageResources.flatMap(pkg => pkg.all.filter(p => p.metadata.version >= pkg.minimumInitialization.metadata.version) ) @@ -37,7 +37,7 @@ object DarResourcesUtil extends NamedLogging { pkgMetadataToDarResource.get((name, version)) def lookupAllPackageVersions(name: PackageName): Seq[DarResource] = - packageResources.view.flatMap(_.all).toSeq.filter(_.metadata.name == name) + corePackageResources.view.flatMap(_.all).toSeq.filter(_.metadata.name == name) // TODO(canton-network/splice#4049): remove `enableUnsupportedDarsUnvetting` once not needed anymore def getRequiredPackageVersions( @@ -61,7 +61,7 @@ object DarResourcesUtil extends NamedLogging { false } } - packageResources.view + corePackageResources.view .flatMap(_.all) .toSeq .filter(_.metadata.name == name) @@ -86,7 +86,7 @@ object DarResourcesUtil extends NamedLogging { packageConfigMap: Map[PackageName, PackageVersion], )(implicit tc: TraceContext): Seq[DarResource] = { val allSupportedVersionsPackageIds = - packageResources + corePackageResources .flatMap { pkg => val versionFromAmuletRules = packageConfigMap.getOrElse( pkg.latest.metadata.name, @@ -107,7 +107,7 @@ object DarResourcesUtil extends NamedLogging { } private def lookupMinimumPackageResource(name: PackageName): DarResource = - packageResources + corePackageResources .find(_.latest.metadata.name == name) .getOrElse(throw new NoSuchElementException(s"Could not find PackageResource for $name.")) .minimumInitialization diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/PackageVetting.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/PackageVetting.scala index 978f396b1f..7936cb0e05 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/PackageVetting.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/PackageVetting.scala @@ -152,7 +152,6 @@ class PackageVetting( .map(_ => ()) } - // See https://github.com/DACH-NY/canton/issues/29834: make it work for non-sv validators as well def unvetPackages( domainId: SynchronizerId, additionalPackagesToUnvet: Map[PackageName, Set[PackageVersion]], diff --git a/apps/dar-resources-generator/src/main/scala/org/lfdecentralizedtrust/splice/darutils/DarResourcesGenerator.scala b/apps/dar-resources-generator/src/main/scala/org/lfdecentralizedtrust/splice/darutils/DarResourcesGenerator.scala index a24ef7f556..cd749f5548 100644 --- a/apps/dar-resources-generator/src/main/scala/org/lfdecentralizedtrust/splice/darutils/DarResourcesGenerator.scala +++ b/apps/dar-resources-generator/src/main/scala/org/lfdecentralizedtrust/splice/darutils/DarResourcesGenerator.scala @@ -59,6 +59,16 @@ object DarResourcesGenerator { "splice-validator-lifecycle", "splice-api-reward-assignment-v1", ) + private val topLevelPackageOrderWithoutSplitwell: Seq[String] = Seq( + "splice-amulet", + "splice-dso-governance", + "splice-util-batched-markers", + "splice-wallet", + "splice-amulet-name-service", + "splice-wallet-payments", + "splice-validator-lifecycle", + "splice-api-reward-assignment-v1", + ) private val tokenStandardProductionPackageOrder: Seq[String] = Seq( "splice-api-token-metadata-v1", "splice-api-token-holding-v1", @@ -147,14 +157,15 @@ object DarResourcesGenerator { indent(2, renderPackage(name, dars, grouped)) } ++ renderPackageResources() ++ + renderCorePackageResources() ++ Seq( """| lazy val pkgIdToDarResource: Map[String, DarResource] = - | packageResources.view.flatMap(_.all).map(resource => resource.packageId -> resource).toMap + | corePackageResources.view.flatMap(_.all).map(resource => resource.packageId -> resource).toMap | | // We don't index the map by PackageMetadata because that type contains some additional | // fields that don't matter. | lazy val pkgMetadataToDarResource: Map[(PackageName, PackageVersion), DarResource] = - | packageResources.view + | corePackageResources.view | .flatMap(_.all) | .map(resource => (resource.metadata.name, resource.metadata.version) -> resource) | .toMap @@ -177,6 +188,19 @@ object DarResourcesGenerator { "", ) + private def renderCorePackageResources(): Seq[String] = + Seq( + " lazy val corePackageResources: Seq[PackageResource] =", + " TokenStandard.allPackageResources ++ Seq(", + ) ++ + topLevelPackageOrderWithoutSplitwell.sorted.map(name => + s" DarResources.${camel(name)}," + ) ++ + Seq( + " )", + "", + ) + private def renderTokenStandard(grouped: Map[String, Seq[DarEntry]]): Seq[String] = { val production = tokenStandardProductionPackageOrder.flatMap(name => renderPackage(name, grouped.getOrElse(name, Nil), grouped) diff --git a/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/ValidatorAutomationService.scala b/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/ValidatorAutomationService.scala index cac4d2676c..a5ea0ab1bb 100644 --- a/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/ValidatorAutomationService.scala +++ b/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/ValidatorAutomationService.scala @@ -241,6 +241,7 @@ class ValidatorAutomationService( maxVettingDelay, latestPackagesOnly, enabledFeatures.enableUnsupportedDarsUnvetting, + enabledFeatures.enableValidatorDarsUnvetting, additionalPackagesToUnvet, ) ) diff --git a/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/ValidatorPackageVettingTrigger.scala b/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/ValidatorPackageVettingTrigger.scala index 999eb92fd3..f59d6c399f 100644 --- a/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/ValidatorPackageVettingTrigger.scala +++ b/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/ValidatorPackageVettingTrigger.scala @@ -23,6 +23,7 @@ class ValidatorPackageVettingTrigger( maxVettingDelay: NonNegativeFiniteDuration, latestPackagesOnly: Boolean, enableUnsupportedDarsUnvetting: Boolean, + enableValidatorDarsUnvetting: Boolean, additionalPackagesToUnvet: Map[PackageName, Set[PackageVersion]], )(implicit override val ec: ExecutionContext, @@ -31,7 +32,7 @@ class ValidatorPackageVettingTrigger( ValidatorPackageVettingTrigger.packages, maxVettingDelay, latestPackagesOnly, - enableUnvetting = false, // Currently only supported by SVs. + enableUnvetting = enableValidatorDarsUnvetting, enableUnsupportedDarsUnvetting, additionalPackagesToUnvet, ) { diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 5f5ee78c44..b8da591f4e 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -6,3 +6,20 @@ .. NOTE: add your upcoming release notes below this line. They are included in the `release_notes.rst`. .. release-notes:: Upcoming + + .. note:: + + Next-release notes + + - Validator + + - Unsupported package versions are now automatically unvetted by the validator package vetting trigger, + aligning validator behavior with SVs. + + You can disable validator unvetting by setting: + + .. code-block:: yaml + + - name: ADDITIONAL_CONFIG_UNSUPPORTED_DARS_UNVETTING + value: | + canton.validator-apps.validator_backend.parameters.enabled-features.enable-validator-dars-unvetting = false From 6e1dcabcc30aeb5edd0b8509303addc5d6d732da Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Mon, 13 Jul 2026 14:56:34 +0200 Subject: [PATCH 021/329] Added token-standard openapi to flux copy (#6394) [ci] Signed-off-by: Pasindu Tennage --- cluster/expected/deployment/expected.json | 28 +++++++++---------- cluster/expected/operator/expected.json | 4 +-- .../pulumi/common/src/operator/flux-source.ts | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/cluster/expected/deployment/expected.json b/cluster/expected/deployment/expected.json index db86445d5c..de04e0dfc3 100644 --- a/cluster/expected/deployment/expected.json +++ b/cluster/expected/deployment/expected.json @@ -943,7 +943,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "interval": "5m", "recurseSubmodules": true, "ref": { @@ -973,7 +973,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "include": [ { "fromPath": "cluster/stacks/prod/sv-runbook/Pulumi.sv-runbook.mock.yaml", @@ -1082,7 +1082,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "interval": "5m", "recurseSubmodules": true, "ref": { @@ -1112,7 +1112,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "include": [ { "fromPath": "cluster/stacks/prod/sv-canton/Pulumi.sv-canton.sv-1-migration-10.mock.yaml", @@ -1165,7 +1165,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "interval": "5m", "recurseSubmodules": true, "ref": { @@ -1195,7 +1195,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "include": [ { "fromPath": "cluster/stacks/prod/sv-canton/Pulumi.sv-canton.sv-1-migration-5.mock.yaml", @@ -1248,7 +1248,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "interval": "5m", "recurseSubmodules": true, "ref": { @@ -1278,7 +1278,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "include": [ { "fromPath": "cluster/stacks/prod/sv-canton/Pulumi.sv-canton.sv-1-migration-6.mock.yaml", @@ -1331,7 +1331,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "interval": "5m", "recurseSubmodules": true, "ref": { @@ -1361,7 +1361,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "include": [ { "fromPath": "cluster/stacks/prod/sv-canton/Pulumi.sv-canton.sv-1-migration-7.mock.yaml", @@ -1414,7 +1414,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "interval": "5m", "recurseSubmodules": true, "ref": { @@ -1444,7 +1444,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "include": [ { "fromPath": "cluster/stacks/prod/sv-canton/Pulumi.sv-canton.sv-1-migration-8.mock.yaml", @@ -1497,7 +1497,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "interval": "5m", "recurseSubmodules": true, "ref": { @@ -1527,7 +1527,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "include": [ { "fromPath": "cluster/stacks/prod/sv-canton/Pulumi.sv-canton.sv-1-migration-9.mock.yaml", diff --git a/cluster/expected/operator/expected.json b/cluster/expected/operator/expected.json index 9b32ac2a92..9b1ffb799e 100644 --- a/cluster/expected/operator/expected.json +++ b/cluster/expected/operator/expected.json @@ -710,7 +710,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "interval": "5m", "recurseSubmodules": true, "ref": { @@ -740,7 +740,7 @@ "namespace": "operator" }, "spec": { - "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard", + "ignore": "**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**", "include": [ { "fromPath": "cluster/stacks/prod/deployment/Pulumi.deployment.mock.yaml", diff --git a/cluster/pulumi/common/src/operator/flux-source.ts b/cluster/pulumi/common/src/operator/flux-source.ts index e4c2ce2ec1..bccce31d96 100644 --- a/cluster/pulumi/common/src/operator/flux-source.ts +++ b/cluster/pulumi/common/src/operator/flux-source.ts @@ -21,7 +21,7 @@ export type StackFromRef = { project: string; stack: string }; // Trim some files to avoid blowing the hardcoded operator size limit of 100mb const repoIgnore = - '**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard'; + '**/splice/canton\n!**/splice/canton/community/app/src/pack/examples/13-observability/grafana/dashboards\n**/daml/dars\n!**/daml/dars/splitwell*\n**/token-standard\n!**/token-standard/*/openapi/**'; function expandGitReference(gitReference: string): { name: string } | { commit: string } { if (gitReference.startsWith('refs/')) { From 0cb7c4f12152c34d41facea9d6e126020d5f6d72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Mon, 13 Jul 2026 17:37:15 +0200 Subject: [PATCH 022/329] Silence slow rolldown plugins warnings (#6190) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- project/ignore-patterns/sbt-output.ignore.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/project/ignore-patterns/sbt-output.ignore.txt b/project/ignore-patterns/sbt-output.ignore.txt index c1d17cbe10..2e69d741b7 100644 --- a/project/ignore-patterns/sbt-output.ignore.txt +++ b/project/ignore-patterns/sbt-output.ignore.txt @@ -137,3 +137,7 @@ WARN: InvalidDefaultArgInFrom Resource.*logback-test.xml.*occurs Setting level of logger.*to WARN Propagating WARN level on Logger + +# rolldown/vite emits a PLUGIN_TIMINGS performance advisory (e.g. for the built-in +# `rolldown:vite-resolve` resolver) on otherwise-successful builds. Not an error. +.*\[PLUGIN_TIMINGS\] Warning:.* From c60508fc59586edc2c05fad094d8c10b2055a497 Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Mon, 13 Jul 2026 12:29:45 -0400 Subject: [PATCH 023/329] bump versions after 0.6.12 (#6378) Signed-off-by: Stephen Compall --- LATEST_RELEASE | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LATEST_RELEASE b/LATEST_RELEASE index 45a346dba8..592e815ea9 100644 --- a/LATEST_RELEASE +++ b/LATEST_RELEASE @@ -1 +1 @@ -0.6.11 +0.6.12 diff --git a/VERSION b/VERSION index 592e815ea9..e196726d2b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.12 +0.6.13 From f125fd4ec2a900582c6ad39e78718c5ab4f546b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Mon, 13 Jul 2026 19:14:18 +0200 Subject: [PATCH 024/329] Update ignored logs entry to match the new canton (#6398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- project/ignore-patterns/canton_log_simtime_extra.ignore.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/ignore-patterns/canton_log_simtime_extra.ignore.txt b/project/ignore-patterns/canton_log_simtime_extra.ignore.txt index dc91549eca..d87108c8f1 100644 --- a/project/ignore-patterns/canton_log_simtime_extra.ignore.txt +++ b/project/ignore-patterns/canton_log_simtime_extra.ignore.txt @@ -23,7 +23,7 @@ Failed to update unsequenced submission.*NOT_SEQUENCED_TIMEOUT SEQUENCER_SUBSCRIPTION_LOST.*: Lost subscription to domain .* Will try to recover automatically. # It seems this happens when a transaction is in-flight and we advance time. We should just retry. -INTERPRETATION_TIME_EXCEEDED.*: Interpretation time exceeds limit of Ledger Effective Time +INTERPRETATION_TIME_EXCEEDED.*: (Interpretation )?[Tt]ime exceeds limit of Ledger Effective Time Could not send a time-advancing message From e7580c5b5dbe36dad774171387d30dfad2a7e09e Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:35:56 +0200 Subject: [PATCH 025/329] Postgres migration guide for migrating off splice-postgres helm chart (#6352) --- .../helm/splice-postgres/Chart-template.yaml | 8 +- .../helm/splice-postgres/templates/NOTES.txt | 15 +- .../helm/splice-postgres/values-template.yaml | 6 + docs/src/release_notes_upcoming.rst | 8 + scripts/test-postgres-migration-k8s.py | 270 ++++++++++++++++++ scripts/test-postgres-migration.py | 252 ++++++++++++++++ 6 files changed, 557 insertions(+), 2 deletions(-) create mode 100755 scripts/test-postgres-migration-k8s.py create mode 100755 scripts/test-postgres-migration.py diff --git a/cluster/helm/splice-postgres/Chart-template.yaml b/cluster/helm/splice-postgres/Chart-template.yaml index b242eb78fb..2095f3381c 100644 --- a/cluster/helm/splice-postgres/Chart-template.yaml +++ b/cluster/helm/splice-postgres/Chart-template.yaml @@ -7,7 +7,13 @@ type: application version: VERSION_NUMBER appVersion: VERSION_NUMBER -description: Splice Self-Hosted PGSQL +description: > + DEPRECATED: This chart is unsupported after 2026-11-12, the PostgreSQL 14 + end-of-life date, and receives no PostgreSQL version upgrades. Run Splice + against a PostgreSQL instance you provision yourself; see the migration guide + at https://docs.canton.network/global-synchronizer/production-operations/validator-postgres-migration + +deprecated: true dependencies: - name: splice-util-lib diff --git a/cluster/helm/splice-postgres/templates/NOTES.txt b/cluster/helm/splice-postgres/templates/NOTES.txt index 0df78ba342..a3bc3ee387 100644 --- a/cluster/helm/splice-postgres/templates/NOTES.txt +++ b/cluster/helm/splice-postgres/templates/NOTES.txt @@ -1 +1,14 @@ -Splice Self-Hosted PGSQL +****************************************************************************** +WARNING: splice-postgres IS DEPRECATED +****************************************************************************** + +This chart is unsupported after 2026-11-12, the PostgreSQL 14 upstream +end-of-life date, and receives no PostgreSQL version upgrades. No new chart +versions are published after 2026-10-12. + +Run Splice against a PostgreSQL instance you provision yourself; a managed +database service such as Amazon RDS or Google Cloud SQL is recommended. + +Migration guide: +https://docs.canton.network/global-synchronizer/production-operations/validator-postgres-migration +****************************************************************************** diff --git a/cluster/helm/splice-postgres/values-template.yaml b/cluster/helm/splice-postgres/values-template.yaml index dc028c6d22..dad72df81d 100644 --- a/cluster/helm/splice-postgres/values-template.yaml +++ b/cluster/helm/splice-postgres/values-template.yaml @@ -1,6 +1,12 @@ # Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. # SPDX-License-Identifier: Apache-2.0 +# DEPRECATED: This chart is unsupported after 2026-11-12, the PostgreSQL 14 +# end-of-life date. A newer PostgreSQL major version does not start on this +# chart's data directory, so do not bump imageName; migrate to an externally +# provisioned PostgreSQL instead. See +# https://docs.canton.network/global-synchronizer/production-operations/validator-postgres-migration + imageRepo: "ghcr.io/digital-asset/decentralized-canton-sync/docker" imageName: "postgres:14" diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index b8da591f4e..566ea5ab04 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -23,3 +23,11 @@ - name: ADDITIONAL_CONFIG_UNSUPPORTED_DARS_UNVETTING value: | canton.validator-apps.validator_backend.parameters.enabled-features.enable-validator-dars-unvetting = false + + - The ``splice-postgres`` Helm chart is deprecated and will not be supported after + 2026-11-12, the PostgreSQL 14 end-of-life date. Published chart versions remain + available, but receive no further updates after that date, and no new chart versions + will be published after 2026-10-12. Run Splice against a PostgreSQL instance you + provision yourself; a managed service such as Amazon RDS or Google Cloud SQL is + recommended. Follow the `migration guide `__ to move the data + of an existing node before that date. diff --git a/scripts/test-postgres-migration-k8s.py b/scripts/test-postgres-migration-k8s.py new file mode 100755 index 0000000000..b4892ccdb1 --- /dev/null +++ b/scripts/test-postgres-migration-k8s.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Replay the Kubernetes section of the PostgreSQL 14 migration guide +(https://docs.canton.network/global-synchronizer/production-operations/validator-postgres-migration) +against a scratch cluster, verifying that the documented procedure works end-to-end. + +Adds the augmentations a scratch cluster needs on top of the documented commands: +tolerations copied from a running pod, service-mesh sidecar injection disabled on +client pods, the source password read from the secret, and values recovered from +the live releases. + +Prerequisites: kubectl context on the cluster, helm; a provisioned target +PostgreSQL (cnadmin user with CREATEDB, cantonnet database, reachable from pods). +The source instance is left untouched (decommissioning stays a manual step), and +wallet-level verification (balances, a fresh transfer) stays manual. + +usage: NAMESPACE=validator1 TARGET_HOST=10.0.0.5 TARGET_PASSWORD=... \ + scripts/test-postgres-migration-k8s.py +""" + +import base64 +import json +import os +import re +import subprocess +import sys +import tempfile +import time + + +def fail(msg: str) -> None: + print(f"FAIL: {msg}") + sys.exit(1) + + +def require_env(name: str, hint: str) -> str: + value = os.environ.get(name) + if not value: + fail(f"set {name} to {hint}") + return value + + +NAMESPACE = require_env("NAMESPACE", "the validator namespace") +TARGET_HOST = require_env("TARGET_HOST", "the target PostgreSQL host or IP") +TARGET_PASSWORD = require_env("TARGET_PASSWORD", "the cnadmin password on the target") +SOURCE_HOST = os.environ.get("SOURCE_HOST", "postgres") # splice-postgres release/service name +SECRET_NAME = os.environ.get("SECRET_NAME", "postgres-secrets") +HELM_REPO = os.environ.get("HELM_REPO", "oci://ghcr.io/digital-asset/decentralized-canton-sync/helm") +PG_CLIENT_IMAGE = os.environ.get("PG_CLIENT_IMAGE", "postgres:17") + + +def run(args: list[str], capture: bool = False, check: bool = True, + stdin_data: str | None = None) -> subprocess.CompletedProcess: + # stdin defaults to /dev/null so attached pods can never consume anything + return subprocess.run( + args, + text=True, + input=stdin_data, + stdin=None if stdin_data is not None else subprocess.DEVNULL, + stdout=subprocess.PIPE if capture else None, + stderr=subprocess.PIPE if capture else None, + check=check, + ) + + +def kubectl_json(*args: str): + out = run(["kubectl", *args, "-o", "json"], capture=True).stdout + return json.loads(out) + + +for tool in ("kubectl", "helm"): + from shutil import which + if which(tool) is None: + fail(f"{tool} not found") + +WORK = tempfile.mkdtemp(prefix="pgmig-k8s-") +print(f"work dir: {WORK}") + +secret = kubectl_json("get", "secret", SECRET_NAME, "-n", NAMESPACE) +POSTGRES_PASSWORD = base64.b64decode(secret["data"]["postgresPassword"]).decode() + +# Tolerations from the running postgres pod; sidecar injection off for client pods. +source_pod = kubectl_json("get", "pod", f"{SOURCE_HOST}-0", "-n", NAMESPACE) +OVERRIDES = json.dumps({ + "metadata": {"annotations": {"sidecar.istio.io/inject": "false"}}, + "spec": {"tolerations": source_pod["spec"].get("tolerations", [])}, +}) + + +def pgpod(name: str, password: str, *cmd: str, env: dict[str, str] | None = None, + check: bool = True) -> subprocess.CompletedProcess: + """Run a one-off client pod; returns the completed process with output captured.""" + env_args = [f"--env=PGPASSWORD={password}"] + for key, value in (env or {}).items(): + env_args.append(f"--env={key}={value}") + result = run( + ["kubectl", "run", name, "--rm", "-i", "--restart=Never", "-n", NAMESPACE, + f"--image={PG_CLIENT_IMAGE}", *env_args, f"--overrides={OVERRIDES}", "--", *cmd], + capture=True, check=False, + ) + if check and result.returncode != 0: + print(result.stdout) + print(result.stderr, file=sys.stderr) + fail(f"pod {name} failed running: {' '.join(cmd[:3])} ...") + return result + + +def pod_lines(result: subprocess.CompletedProcess) -> list[str]: + """Pod stdout minus kubectl chatter and blanks.""" + return [ + line for line in result.stdout.splitlines() + if line.strip() and not line.startswith("pod ") + ] + + +print("### 0. Probe the target (reachability, CREATEDB, connection limit)") +probe = pgpod("pg-client", TARGET_PASSWORD, + "psql", "-h", TARGET_HOST, "-U", "cnadmin", "-d", "cantonnet", + "-c", "CREATE DATABASE probe", "-c", "DROP DATABASE probe", + "-c", "SHOW max_connections") +print(probe.stdout, end="") + +print("### 1. Enumerate the databases") +enum = pgpod("pg-client", POSTGRES_PASSWORD, + "psql", "-h", SOURCE_HOST, "-U", "cnadmin", "-d", "cantonnet", "-tA", + "-c", "SELECT datname FROM pg_database WHERE NOT datistemplate AND datname <> 'postgres'") +databases = pod_lines(enum) +print("\n".join(databases)) +if not databases: + fail("no databases enumerated") + +print("### 2. Stop the applications") +downtime_start = time.monotonic() +run(["kubectl", "scale", "deployment", "--all", "--replicas=0", "-n", NAMESPACE]) +for _ in range(60): + pods = kubectl_json("get", "pods", "-n", NAMESPACE)["items"] + still_running = [ + p["metadata"]["name"] for p in pods + if p["status"].get("phase") == "Running" + and not p["metadata"]["name"].startswith(f"{SOURCE_HOST}-") + ] + if not still_running: + break + time.sleep(5) +else: + fail(f"pods still running after quiesce: {still_running}") + +print("### 3. Create the databases on the target") +for db in databases: + if db == "cantonnet": + continue + result = pgpod("pg-client", TARGET_PASSWORD, + "psql", "-h", TARGET_HOST, "-U", "cnadmin", "-d", "cantonnet", + "-c", f'DROP DATABASE IF EXISTS "{db}" WITH (FORCE)', + "-c", f'CREATE DATABASE "{db}"') + print(result.stdout, end="") + +print("### 4. Copy each database") +for db in databases: + copy_cmd = ( + f"PGPASSWORD=\"$SOURCE_PGPASSWORD\" pg_dump -h {SOURCE_HOST} -U cnadmin -Fc '{db}'" + f" | PGPASSWORD=\"$TARGET_PGPASSWORD\" pg_restore -h {TARGET_HOST} -U cnadmin" + f" --no-owner --no-privileges --exit-on-error -d '{db}'" + ) + result = pgpod("pg-migrate", TARGET_PASSWORD, "bash", "-c", copy_cmd, + env={"SOURCE_PGPASSWORD": POSTGRES_PASSWORD, + "TARGET_PGPASSWORD": TARGET_PASSWORD}, + check=False) + if result.returncode != 0: + print(result.stdout) + print(result.stderr, file=sys.stderr) + fail(f"copy of {db}") + print(f"copied: {db}") + +print("### 4b. Compare table counts per database (source vs target)") +COUNT_SQL = ("SELECT count(*) FROM pg_tables" + " WHERE schemaname NOT IN ('pg_catalog','information_schema')") + + +def count_tables(host: str, password: str, db: str) -> int: + result = pgpod("pg-client", password, + "psql", "-h", host, "-U", "cnadmin", "-d", db, "-tA", "-c", COUNT_SQL) + return int(pod_lines(result)[0]) + + +for db in databases: + src = count_tables(SOURCE_HOST, POSTGRES_PASSWORD, db) + tgt = count_tables(TARGET_HOST, TARGET_PASSWORD, db) + print(f"tables in {db}: source={src} target={tgt}") + if src != tgt: + fail(f"table count mismatch in {db}") + +print("### 5. Point the applications at the target") +# Decide which releases to repoint BEFORE touching the secret: a release +# qualifies when its persistence.host is the source instance, as a bare +# service name or a cluster FQDN (e.g. ..svc.cluster.local). +releases = json.loads(run(["helm", "list", "-n", NAMESPACE, "-o", "json"], capture=True).stdout) +to_repoint = [] +for release in releases: + name, chart = release["name"], release["chart"] + if chart.startswith("splice-postgres-"): + continue + values_raw = run(["helm", "get", "values", name, "-n", NAMESPACE, "-o", "json"], + capture=True).stdout + values = json.loads(values_raw) or {} + host = (values.get("persistence") or {}).get("host", "") + if host == SOURCE_HOST or host.startswith(f"{SOURCE_HOST}."): + to_repoint.append((name, chart, values)) +if not to_repoint: + fail(f"no release has persistence.host pointing at {SOURCE_HOST}") +print("releases to repoint:") +for name, chart, _ in to_repoint: + print(f"{name} {chart}") + +secret_yaml = run(["kubectl", "create", "secret", "generic", SECRET_NAME, + f"--from-literal=postgresPassword={TARGET_PASSWORD}", + "-n", NAMESPACE, "--dry-run=client", "-o", "yaml"], capture=True).stdout +run(["kubectl", "apply", "-f", "-"], stdin_data=secret_yaml) + + +def repoint(name: str, chart: str, values: dict) -> None: + match = re.match(r"^(?P.+)-(?P\d+\.\d+\..+)$", chart) + if not match: + fail(f"cannot parse chart/version from {chart}") + values.setdefault("persistence", {}) + values["persistence"]["host"] = TARGET_HOST + values["persistence"]["port"] = 5432 + values_file = os.path.join(WORK, f"{name}-values.json") # helm accepts JSON values + with open(values_file, "w") as f: + json.dump(values, f, indent=2) + print(f"upgrading {name} ({match['chart']} {match['version']})") + run(["helm", "upgrade", name, f"{HELM_REPO}/{match['chart']}", + "--version", match["version"], "-f", values_file, + "-n", NAMESPACE, "--wait", "--timeout", "10m"]) + + +# participants first, mirroring install order +for name, chart, values in to_repoint: + if chart.startswith("splice-participant-"): + repoint(name, chart, values) +for name, chart, values in to_repoint: + if not chart.startswith("splice-participant-"): + repoint(name, chart, values) + +print("### 6. Verify") +run(["kubectl", "wait", "deployment", "--all", "--for=condition=Available", + "-n", NAMESPACE, "--timeout=600s"]) +print(f"quiesce -> available: {int(time.monotonic() - downtime_start)}s") + +activity = pgpod("pg-client", TARGET_PASSWORD, + "psql", "-h", TARGET_HOST, "-U", "cnadmin", "-d", "cantonnet", + "-c", "SHOW server_version", + "-c", "SELECT datname, count(*) FROM pg_stat_activity" + " WHERE datname <> 'cantonnet' GROUP BY 1") +print(activity.stdout, end="") +if "participant" not in activity.stdout: + fail("no application connections on the target") + +idle = pgpod("pg-client", POSTGRES_PASSWORD, + "psql", "-h", SOURCE_HOST, "-U", "cnadmin", "-d", "cantonnet", "-tA", + "-c", "SELECT count(*) FROM pg_stat_activity" + " WHERE datname NOT IN ('cantonnet','postgres') AND datname IS NOT NULL") +if pod_lines(idle)[0] != "0": + fail(f"{pod_lines(idle)[0]} connection(s) still on the source instance") + +print(f"PASS: applications migrated to {TARGET_HOST} (source instance idle)") +print("manual follow-ups: wallet balance + fresh transfer; scale non-helm deployments") +print("back up; decommission the source per step 7 of the guide once verified.") diff --git a/scripts/test-postgres-migration.py b/scripts/test-postgres-migration.py new file mode 100755 index 0000000000..8cff95baf7 --- /dev/null +++ b/scripts/test-postgres-migration.py @@ -0,0 +1,252 @@ +#!/usr/bin/env python3 +# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""End-to-end test of the PostgreSQL major-version migration documented at +https://docs.canton.network/global-synchronizer/production-operations/validator-postgres-migration, +run against LocalNet. + +Flow: LocalNet on postgres:SRC_PG -> seed wallet data (tap + cross-participant +transfer) -> stop applications -> pg_dump every database with the target-version +client -> fresh postgres:TGT_PG -> pg_restore -> restart -> assert that balances +are preserved and a new transfer succeeds. + +WARNING: tears down any running LocalNet compose project (down -v) at start. + +usage: [SRC_PG=14] [TGT_PG=17] [IMAGE_TAG=0.6.11] scripts/test-postgres-migration.py +""" + +import base64 +import hashlib +import hmac +import json +import os +import subprocess +import sys +import tempfile +import time +import urllib.error +import urllib.request + +SRC_PG = os.environ.get("SRC_PG", "14") +TGT_PG = os.environ.get("TGT_PG", "17") +IMAGE_TAG = os.environ.get("IMAGE_TAG", "0.6.11") +DB_PASSWORD = "supersafe" + +REPO_ROOT = subprocess.run(["git", "rev-parse", "--show-toplevel"], + capture_output=True, text=True, check=True).stdout.strip() +LOCALNET_DIR = os.environ.get("LOCALNET_DIR", f"{REPO_ROOT}/cluster/compose/localnet") +DUMPS = tempfile.mkdtemp(prefix="pgmig-dumps-") + + +def fail(msg: str) -> None: + print(f"FAIL: {msg}") + sys.exit(1) + + +def run(args: list[str], capture: bool = False, check: bool = True, + env: dict[str, str] | None = None) -> subprocess.CompletedProcess: + return subprocess.run( + args, text=True, check=check, + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE if capture else None, + stderr=subprocess.STDOUT if capture else None, + env={**os.environ, **(env or {})}, + ) + + +def compose(*args: str, postgres_version: str, check: bool = True) -> None: + run(["docker", "compose", + "--env-file", f"{LOCALNET_DIR}/compose.env", + "--env-file", f"{LOCALNET_DIR}/env/common.env", + "-f", f"{LOCALNET_DIR}/compose.yaml", + "-f", f"{LOCALNET_DIR}/resource-constraints.yaml", + "--profile", "sv", "--profile", "app-provider", "--profile", "app-user", + *args], + check=check, + env={"IMAGE_TAG": IMAGE_TAG, "LOCALNET_DIR": LOCALNET_DIR, + "POSTGRES_VERSION": postgres_version}) + + +def b64url(data: bytes) -> str: + return base64.urlsafe_b64encode(data).rstrip(b"=").decode() + + +def wallet(port: int, user: str, method: str, path: str, body: dict | None = None): + """Call a LocalNet wallet API through nginx with an unsafe-auth HS256 JWT.""" + header = b64url(b'{"alg":"HS256","typ":"JWT"}') + payload = b64url(json.dumps( + {"sub": user, "aud": "https://canton.network.global", "exp": 4102444800}).encode()) + sig = b64url(hmac.new(b"unsafe", f"{header}.{payload}".encode(), hashlib.sha256).digest()) + request = urllib.request.Request( + f"http://127.0.0.1:{port}{path}", + method=method, + data=json.dumps(body).encode() if body is not None else None, + headers={"Host": "wallet.localhost", + "Authorization": f"Bearer {header}.{payload}.{sig}", + **({"Content-Type": "application/json"} if body is not None else {})}, + ) + with urllib.request.urlopen(request, timeout=30) as response: + return json.loads(response.read()) + + +def retry(what: str, attempts: int, delay: int, fn): + for _ in range(attempts): + try: + result = fn() + if result is not None: + return result + except (urllib.error.URLError, OSError, json.JSONDecodeError, KeyError): + pass + time.sleep(delay) + fail(f"{what} did not succeed after {attempts} attempts") + + +def balance(port: int, user: str) -> str: + return wallet(port, user, "GET", "/api/validator/v0/wallet/balance")["effective_unlocked_qty"] + + +def wait_healthy() -> None: + def all_healthy(): + out = run(["docker", "ps", "--format", "{{.Names}} {{.Status}}"], capture=True).stdout + lines = [ln for ln in out.splitlines() if ln.strip()] + unhealthy = [ln for ln in lines if "(healthy)" not in ln] + return True if len(unhealthy) <= 1 else None # nginx has no healthcheck + retry("containers becoming healthy", 60, 5, all_healthy) + + +def wait_wallet(port: int, user: str) -> None: + # healthy containers do not imply onboarded wallets; wait for the wallet API too + retry(f"wallet for {user}", 60, 5, + lambda: True if wallet(port, user, "GET", "/api/validator/v0/wallet/user-status") + .get("user_wallet_installed") else None) + + +def wait_pg_healthy() -> None: + def healthy(): + out = run(["docker", "inspect", "-f", "{{.State.Health.Status}}", "postgres"], + capture=True, check=False).stdout.strip() + return True if out == "healthy" else None + retry("postgres becoming healthy", 30, 2, healthy) + + +def tap() -> None: + # right after bootstrap taps fail until the first open mining round exists; retry + retry("tap", 30, 10, + lambda: True if "contract_id" in wallet( + 2000, "app-user", "POST", "/api/validator/v0/wallet/tap", {"amount": "1000.0"}) + else None) + + +def transfer(amount: str, tracking_id: str) -> None: + provider = wallet(3000, "app-provider", "GET", + "/api/validator/v0/wallet/user-status")["party_id"] + offer = wallet(2000, "app-user", "POST", "/api/validator/v0/wallet/transfer-offers", + {"receiver_party_id": provider, "amount": amount, + "description": tracking_id, "expires_at": 4102444800000000, + "tracking_id": tracking_id}) + if "offer_contract_id" not in offer: + fail(f"transfer offer {tracking_id} not created: {offer}") + + def find_offer(): # receiver sees the offer only after ingestion + offers = wallet(3000, "app-provider", "GET", + "/api/validator/v0/wallet/transfer-offers")["offers"] + return offers[0]["contract_id"] if offers else None + cid = retry(f"offer visibility for {tracking_id}", 30, 2, find_offer) + accepted = wallet(3000, "app-provider", "POST", + f"/api/validator/v0/wallet/transfer-offers/{cid}/accept", {}) + if "accepted_offer_contract_id" not in accepted: + fail(f"transfer {tracking_id} not accepted: {accepted}") + + +print(f"migrating postgres:{SRC_PG} -> postgres:{TGT_PG} (splice {IMAGE_TAG}); dumps in {DUMPS}") + +print(f"### 0. Clean slate, start LocalNet on postgres:{SRC_PG}") +compose("down", "-v", "--remove-orphans", postgres_version=SRC_PG, check=False) +run(["docker", "volume", "rm", "-f", f"localnet_postgres_pg{SRC_PG}_backup"], check=False) +compose("up", "-d", postgres_version=SRC_PG) +wait_healthy() +run(["docker", "exec", "postgres", "postgres", "--version"]) + +print("### 1. Seed data: tap 1000 USD on app-user, transfer 12345 CC to app-provider") +wait_wallet(2000, "app-user") +wait_wallet(3000, "app-provider") +tap() +time.sleep(3) +transfer("12345.0", "pgmig-pre") +time.sleep(8) +user_bal_before = balance(2000, "app-user") +prov_bal_before = balance(3000, "app-provider") +print(f"pre-migration balances: app-user={user_bal_before} app-provider={prov_bal_before}") + +print("### 2. Quiesce: stop everything except postgres") +compose("stop", postgres_version=SRC_PG) +compose("start", "postgres", postgres_version=SRC_PG) +wait_pg_healthy() +running = run(["docker", "ps", "--format", "{{.Names}}"], capture=True).stdout.split() +if running != ["postgres"]: + fail(f"quiesce incomplete, still running: {running}") + +print(f"### 3. Dump all databases with the postgres:{TGT_PG} client") +databases = run( + ["docker", "exec", "postgres", "psql", "-U", "cnadmin", "-d", "postgres", "-tA", + "-c", "SELECT datname FROM pg_database WHERE NOT datistemplate AND datname <> 'postgres'"], + capture=True).stdout.split() +for db in databases: + run(["docker", "run", "--rm", "--network", "localnet", "-e", f"PGPASSWORD={DB_PASSWORD}", + "-v", f"{DUMPS}:/dumps", f"postgres:{TGT_PG}", + "pg_dump", "-h", "postgres", "-U", "cnadmin", "-Fc", + "-f", f"/dumps/{db}.dump", db]) +run(["ls", "-lh", DUMPS]) + +print(f"### 4. Keep the pg{SRC_PG} volume as rollback point, remove original") +compose("stop", "postgres", postgres_version=SRC_PG) +run(["docker", "rm", "-f", "postgres"]) +run(["docker", "volume", "create", f"localnet_postgres_pg{SRC_PG}_backup"], capture=True) +run(["docker", "run", "--rm", "-v", "localnet_postgres:/from:ro", + "-v", f"localnet_postgres_pg{SRC_PG}_backup:/to", + "alpine", "sh", "-c", "cp -a /from/. /to/"]) +run(["docker", "volume", "rm", "localnet_postgres"]) + +print(f"### 5. Fresh postgres:{TGT_PG} (entrypoint pre-creates empty databases)") +compose("up", "-d", "postgres", postgres_version=TGT_PG) +wait_pg_healthy() +run(["docker", "exec", "postgres", "postgres", "--version"]) +# create databases the entrypoint did not pre-create (all exist on LocalNet; +# mirrors the migration guide, where start.sh-injected names are missing) +for db in databases: + run(["docker", "exec", "postgres", "psql", "-U", "cnadmin", "-d", "postgres", + "-c", f'CREATE DATABASE "{db}"'], capture=True, check=False) + +print("### 6. Restore every dump") +for db in databases: + run(["docker", "run", "--rm", "--network", "localnet", "-e", f"PGPASSWORD={DB_PASSWORD}", + "-v", f"{DUMPS}:/dumps:ro", f"postgres:{TGT_PG}", + "pg_restore", "-h", "postgres", "-U", "cnadmin", + "--no-owner", "--no-privileges", "--exit-on-error", "-d", db, f"/dumps/{db}.dump"]) + print(f"restored: {db}") + +print(f"### 7. Restart the full stack on postgres:{TGT_PG}") +compose("up", "-d", postgres_version=TGT_PG) +wait_healthy() +wait_wallet(2000, "app-user") +wait_wallet(3000, "app-provider") + +print("### 8. Assertions") +user_bal_after = balance(2000, "app-user") +prov_bal_after = balance(3000, "app-provider") +print(f"post-migration balances: app-user={user_bal_after} app-provider={prov_bal_after}") +# balances only grow between the two checks (devnet reward issuance) +if float(user_bal_after) < float(user_bal_before): + fail("app-user balance shrank across migration") +if float(prov_bal_after) < float(prov_bal_before): + fail("app-provider balance shrank across migration") + +transfer("55.0", "pgmig-post") + +errors = run(["docker", "logs", "splice", "--since", "10m"], capture=True).stdout +error_count = errors.count('"level":"ERROR"') +if error_count: + fail(f"{error_count} ERROR lines in splice logs") + +print(f"PASS: migration postgres:{SRC_PG} -> postgres:{TGT_PG} verified") From 348bdc32299fad11b180709b08925f5dfe63e6ac Mon Sep 17 00:00:00 2001 From: Stanislav German-Evtushenko Date: Tue, 14 Jul 2026 09:14:45 +0900 Subject: [PATCH 026/329] helm, info: Make status work after tightening pod security (#6390) * helm, info: Make status work after tightening security Pods run with user id 1001 by default now. Container images may not have a user with such id and thus no home directory. Replace HOME by /tmp to make prom2json work again. Signed-off-by: Stanislav German-Evtushenko * helm, info, status: Do not start the script without prom2json This is to make the script fail on a test cluster when deploying from CI and prom2json is not available so that it's caught before merging. Signed-off-by: Stanislav German-Evtushenko --------- Signed-off-by: Stanislav German-Evtushenko --- cluster/helm/splice-info/scripts/get-status.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cluster/helm/splice-info/scripts/get-status.sh b/cluster/helm/splice-info/scripts/get-status.sh index 5d38e1d116..c726dd613d 100755 --- a/cluster/helm/splice-info/scripts/get-status.sh +++ b/cluster/helm/splice-info/scripts/get-status.sh @@ -23,7 +23,7 @@ CURL_CMD=(curl -fs -m "$CURL_TIMEOUT") prom2json() { P2J_VERSION="1.5.0" P2J_ARCH="linux-amd64" - P2J_BIN="$HOME/.prom2json-$P2J_VERSION" + P2J_BIN="/tmp/.prom2json-$P2J_VERSION" P2J_URL="https://github.com/prometheus/prom2json/releases/download/v$P2J_VERSION/prom2json-$P2J_VERSION.$P2J_ARCH.tar.gz" P2J_EXPECTED_SHA="5935363cc8c88360e3aa275ddc5a754ad95f6bab6b6052978e686300baa5a4d6" @@ -40,7 +40,7 @@ prom2json() { rm -rf "$P2J_TMPDIR" "$P2J_DIST" fi - "$P2J_BIN" + "$P2J_BIN" "$@" } sv_get_status() { @@ -247,6 +247,11 @@ generate_sequencer_metrics_url() { } main() { + if ! prom2json --version &>/dev/null; then + echo "ERROR: prom2json is not installed. Exiting." >&2 + return 1 + fi + if [[ -z "${SEQUENCER_METRICS_URL:-}" ]]; then update_serial_id SEQUENCER_METRICS_URL=$(generate_sequencer_metrics_url) From b6b79fe7bf861e66c765ab5938541643465b59dd Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Tue, 14 Jul 2026 08:45:10 +0200 Subject: [PATCH 027/329] Deduplicate whitelisted IPs (#6401) [static] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- cluster/deployment/mock/config.yaml | 4 ++++ cluster/expected/infra/expected.json | 4 +++- cluster/pulumi/infra/src/config.ts | 9 +++++---- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/cluster/deployment/mock/config.yaml b/cluster/deployment/mock/config.yaml index f8a38a6f6b..f822608625 100644 --- a/cluster/deployment/mock/config.yaml +++ b/cluster/deployment/mock/config.yaml @@ -247,6 +247,10 @@ svs: cantonLogLevel: INFO apiRequestLogLevel: DEBUG infra: + ipWhitelisting: + extraWhitelistedIngress: + - 1.2.3.4/32 + - 2.3.4.5/32 extraCustomResources: deny-onboard-prepare-endpoint: apiVersion: security.istio.io/v1 diff --git a/cluster/expected/infra/expected.json b/cluster/expected/infra/expected.json index ea535a388a..cd85d2e63c 100644 --- a/cluster/expected/infra/expected.json +++ b/cluster/expected/infra/expected.json @@ -1301,6 +1301,7 @@ "9.8.7.6/32", "11.22.33.45/32", "12.34.56.78/32", + "2.3.4.5/32", "10.160.0.0/16" ] } @@ -1476,7 +1477,8 @@ "", "1.2.3.4/32", "5.6.7.8/32", - "11.12.13.14/32" + "11.12.13.14/32", + "2.3.4.5/32" ], "ports": [ { diff --git a/cluster/pulumi/infra/src/config.ts b/cluster/pulumi/infra/src/config.ts index 49184cc908..c677c47c5d 100644 --- a/cluster/pulumi/infra/src/config.ts +++ b/cluster/pulumi/infra/src/config.ts @@ -122,10 +122,11 @@ export function loadIPRanges(svsOnly: boolean = false): pulumi.Output const configWhitelistedIps = infraConfig.ipWhitelisting?.extraWhitelistedIngress || []; const excludedIps = infraConfig.ipWhitelisting?.excludedIps || []; - return internalWhitelistedIps.apply(whitelists => - whitelists + return internalWhitelistedIps.apply(whitelists => { + const ips = whitelists .concat(externalIpRanges) .concat(configWhitelistedIps) - .filter(ip => excludedIps.indexOf(ip) < 0) - ); + .filter(ip => excludedIps.indexOf(ip) < 0); + return [...new Set(ips)]; + }); } From 125f4d6f298be2968b2213c29d2685de4dc9c59e Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Tue, 14 Jul 2026 10:07:17 +0200 Subject: [PATCH 028/329] Move canton bft dashboards to our repo from the fork (#6402) [static] Signed-off-by: Nicu Reut --- .../canton-bft}/bft-ordering-performance.json | 0 .../grafana-dashboards/canton-bft}/bft-ordering.json | 0 cluster/pulumi/observability/src/grafana-dashboards.ts | 5 ----- scripts/copy-canton.sh | 6 ++++++ 4 files changed, 6 insertions(+), 5 deletions(-) rename {canton/community/app/src/pack/examples/13-observability/grafana/dashboards/Canton => cluster/pulumi/observability/grafana-dashboards/canton-bft}/bft-ordering-performance.json (100%) rename {canton/community/app/src/pack/examples/13-observability/grafana/dashboards/Canton => cluster/pulumi/observability/grafana-dashboards/canton-bft}/bft-ordering.json (100%) diff --git a/canton/community/app/src/pack/examples/13-observability/grafana/dashboards/Canton/bft-ordering-performance.json b/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering-performance.json similarity index 100% rename from canton/community/app/src/pack/examples/13-observability/grafana/dashboards/Canton/bft-ordering-performance.json rename to cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering-performance.json diff --git a/canton/community/app/src/pack/examples/13-observability/grafana/dashboards/Canton/bft-ordering.json b/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering.json similarity index 100% rename from canton/community/app/src/pack/examples/13-observability/grafana/dashboards/Canton/bft-ordering.json rename to cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering.json diff --git a/cluster/pulumi/observability/src/grafana-dashboards.ts b/cluster/pulumi/observability/src/grafana-dashboards.ts index 57005cf906..03ef9195ec 100644 --- a/cluster/pulumi/observability/src/grafana-dashboards.ts +++ b/cluster/pulumi/observability/src/grafana-dashboards.ts @@ -11,11 +11,6 @@ export function createGrafanaDashboards(namespace: Input): void { namespace, `${SPLICE_ROOT}/cluster/pulumi/observability/grafana-dashboards/` ); - createConfigMapForFolder( - namespace, - `${SPLICE_ROOT}/canton/community/app/src/pack/examples/13-observability/grafana/dashboards/Canton/`, - 'canton-bft' - ); } function createdNestedConfigMapForFolder(namespace: Input, folderPath: string) { diff --git a/scripts/copy-canton.sh b/scripts/copy-canton.sh index 576c9ba6f7..a803b0253d 100755 --- a/scripts/copy-canton.sh +++ b/scripts/copy-canton.sh @@ -27,3 +27,9 @@ rsync -av --delete --exclude version.sbt --exclude community-build.sbt --exclude canton/ # remove any broken symlinks after the copy find -L canton/ -type l -exec rm {} + + +canton_bft_src="$1/community/app/src/pack/examples/13-observability/grafana/dashboards/Canton" +canton_bft_dest="cluster/pulumi/observability/grafana-dashboards/canton-bft" +rm -rf "$canton_bft_dest" +mkdir -p "$canton_bft_dest" +cp "$canton_bft_src"/*.json "$canton_bft_dest/" From 0ee121ba58ca928d56ac341dcee914128677f3df Mon Sep 17 00:00:00 2001 From: Puneet Bharti <124160444+puneetfinoa@users.noreply.github.com> Date: Tue, 14 Jul 2026 14:27:32 +0200 Subject: [PATCH 029/329] Implement SV UI Redesign Look & Feel: Component: ActionSelection and Dropdown (#6374) Signed-off-by: Puneet Bharti --- apps/common/frontend/src/theme/index.ts | 2 +- apps/sv/frontend/index.html | 7 +- .../governance/create-proposal.test.tsx | 3 - apps/sv/frontend/src/__tests__/sv.test.tsx | 2 +- .../src/components/forms/SelectAction.tsx | 199 +++++++++-------- .../governance/ProposalVoteForm.tsx | 2 - .../frontend/src/components/ui/Dropdown.tsx | 211 ++++++++++++++++++ .../sv/frontend/src/routes/createProposal.tsx | 4 +- 8 files changed, 326 insertions(+), 104 deletions(-) create mode 100644 apps/sv/frontend/src/components/ui/Dropdown.tsx diff --git a/apps/common/frontend/src/theme/index.ts b/apps/common/frontend/src/theme/index.ts index 73c65927f7..382e6a43fe 100644 --- a/apps/common/frontend/src/theme/index.ts +++ b/apps/common/frontend/src/theme/index.ts @@ -157,7 +157,7 @@ theme = createTheme(theme, { }, }); -// Based on the Major Third type scale: https://typescale.com/?size=16&scale=1.250&text=A%20Visual%20Type%20Scale&font=Lato&fontweight=400&bodyfont=body_font_default&bodyfontweight=400&lineheight=1.75&backgroundcolor=%23ffffff&fontcolor=%23000000&preview=false +// Based on the Major Third type scale: https://typescale.com/?size=16&scale=1.250&text=A%20Visual%20Type%20Scale&font=Inter&fontweight=400&bodyfont=body_font_default&bodyfontweight=400&lineheight=1.75&backgroundcolor=%23ffffff&fontcolor=%23000000&preview=false const TYPE_SCALE = 1.25; theme = createTheme(theme, { diff --git a/apps/sv/frontend/index.html b/apps/sv/frontend/index.html index 8cf03993b2..c2f95b8233 100644 --- a/apps/sv/frontend/index.html +++ b/apps/sv/frontend/index.html @@ -5,16 +5,13 @@ + - + diff --git a/apps/sv/frontend/src/__tests__/governance/create-proposal.test.tsx b/apps/sv/frontend/src/__tests__/governance/create-proposal.test.tsx index 599a7fcbb3..6277063c12 100644 --- a/apps/sv/frontend/src/__tests__/governance/create-proposal.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/create-proposal.test.tsx @@ -92,9 +92,6 @@ describe('Create Proposal', () => { ); - const actionSelectionTitle = screen.getByText('Select an Action'); - expect(actionSelectionTitle).toBeDefined(); - const actionDropdown = screen.getByTestId('select-action'); expect(actionDropdown).toBeDefined(); diff --git a/apps/sv/frontend/src/__tests__/sv.test.tsx b/apps/sv/frontend/src/__tests__/sv.test.tsx index 99dc546dce..13e57012cd 100644 --- a/apps/sv/frontend/src/__tests__/sv.test.tsx +++ b/apps/sv/frontend/src/__tests__/sv.test.tsx @@ -372,7 +372,7 @@ describe('SetAmuletRules', () => { (calledWithBody.action as any).value.amuletRulesAction.value.newConfig.transferConfig .transferFee.steps // the second element is gone - ).toStrictEqual(initialSteps.filter((_, i) => i !== 1)); + ).toStrictEqual(initialSteps.filter((_: unknown, i: number) => i !== 1)); }, { timeout: 20000 } ); diff --git a/apps/sv/frontend/src/components/forms/SelectAction.tsx b/apps/sv/frontend/src/components/forms/SelectAction.tsx index affab2d451..a6d2d49c5d 100644 --- a/apps/sv/frontend/src/components/forms/SelectAction.tsx +++ b/apps/sv/frontend/src/components/forms/SelectAction.tsx @@ -1,20 +1,44 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { - Box, - Button, - FormControl, - MenuItem, - Paper, - Select, - SelectChangeEvent, - Typography, -} from '@mui/material'; +import { Box, Button } from '@mui/material'; import { useForm } from '@tanstack/react-form'; import { useNavigate } from 'react-router'; +import { Dropdown } from '../ui/Dropdown'; import { createProposalActions } from '../../utils/governance'; +const CARD_CONTENT_WIDTH = 833; +const CARD_BG = '#1b1b1b'; +const CARD_VERTICAL_PADDING = '60px'; +const PLACEHOLDER_TEXT = 'Select proposal type'; + +const pillButtonSx = { + height: '39px', + px: '16px', + py: '10px', +}; + +const cancelButtonSx = { + ...pillButtonSx, + bgcolor: 'transparent', + '&:hover': { bgcolor: 'transparent' }, +}; + +const nextButtonSx = () => ({ + ...pillButtonSx, + '&:disabled': { + bgcolor: '#696969', + color: '#363636', + border: 'none', + }, +}); + +const dropdownOptions = createProposalActions.map(action => ({ + value: action.value, + label: action.name, + testId: action.value, +})); + export const SelectAction: React.FC = () => { const navigate = useNavigate(); @@ -33,92 +57,85 @@ export const SelectAction: React.FC = () => { }; return ( - - - - - Select an Action - - -
{ - e.preventDefault(); - e.stopPropagation(); - form.handleSubmit(); + + + { + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(); + }} + > + { + const res = createProposalActions.find(a => a.value === value); + return res ? undefined : 'Invalid action'; + }, }} + children={field => ( + + )} + /> + + - { - const res = createProposalActions.find(a => a.value === value); - return res ? undefined : 'Invalid action'; - }, - }} - children={field => ( - - - + Next + + )} /> - - - state.canSubmit} - children={canSubmit => ( - <> - - - - - )} - /> - - - -
+
+ +
); }; diff --git a/apps/sv/frontend/src/components/governance/ProposalVoteForm.tsx b/apps/sv/frontend/src/components/governance/ProposalVoteForm.tsx index bce3e45608..46790d07bf 100644 --- a/apps/sv/frontend/src/components/governance/ProposalVoteForm.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalVoteForm.tsx @@ -126,7 +126,6 @@ export const ProposalVoteForm: React.FC = props => { '& .MuiFilledInput-root': { borderRadius: 1, paddingTop: 1, - fontFamily: 'Lato', '&:before, &:after': { display: 'none', }, @@ -180,7 +179,6 @@ export const ProposalVoteForm: React.FC = props => { sx={{ '& .MuiFilledInput-root': { borderRadius: 1, - fontFamily: 'Lato', '&:before, &:after': { display: 'none', }, diff --git a/apps/sv/frontend/src/components/ui/Dropdown.tsx b/apps/sv/frontend/src/components/ui/Dropdown.tsx new file mode 100644 index 0000000000..4fb48c3521 --- /dev/null +++ b/apps/sv/frontend/src/components/ui/Dropdown.tsx @@ -0,0 +1,211 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { + Box, + FormControl, + FormHelperText, + MenuItem, + Select, + SelectChangeEvent, + SxProps, + Theme, + Typography, +} from '@mui/material'; + +/** + * Source of truth: Figma Dev Mode node `3870:3442` ("Dropdown fields") and + * [#2652](https://github.com/canton-network/splice/issues/2652) reference. + * Prop surface mirrors `components.md` Input/Select: label, required, + * placeholder, helperText, hasDropdown (always true here), state. + */ + +export type DropdownState = 'default' | 'disabled' | 'error'; + +export interface DropdownOption { + value: string; + label: string; + /** Optional per-option test id (defaults to value). */ + testId?: string; +} + +export interface DropdownProps { + options: DropdownOption[]; + value: string; + onChange: (value: string) => void; + onBlur?: () => void; + label?: string; + required?: boolean; + placeholder?: string; + helperText?: string; + state?: DropdownState; + id?: string; + labelId?: string; + testId?: string; + disabled?: boolean; + error?: boolean; + fullWidth?: boolean; + renderValue?: (selected: string, options: DropdownOption[]) => React.ReactNode; + sx?: SxProps; +} + +/** Dev Mode: `background: var(--grey54, #363636)` */ +const FIELD_BG = 'var(--grey54, #363636)'; + +/** Figma "Body M" on nodes `3870:3442` / `1724:3506`: Inter 14px/400/22px. */ +const valueTextSx = { + fontFamily: "'Inter', sans-serif", + fontSize: '14px', + fontWeight: 400, + lineHeight: '22px', + color: '#E2E2E2', + fontFeatureSettings: "'liga' off, 'clig' off", +}; + +/** Empty-state placeholder node `I3870:3442;120:415`: Inter 14px, grey105. */ +const placeholderTextSx = { + ...valueTextSx, + color: '#696969', +}; + +/** Figma "FIELD H": Inter Semi Bold 12px uppercase, grey226. */ +const labelSx = { + fontFamily: "'Inter', sans-serif", + fontSize: '12px', + fontWeight: 600, + lineHeight: '22px', + textTransform: 'uppercase' as const, + color: '#E2E2E2', + mb: '8px', +}; + +const ChevronDownIcon: React.FC> = props => ( + + + +); + +const resolveLabelId = ( + labelId: string | undefined, + id: string | undefined, + label: string | undefined +): string | undefined => labelId ?? (label ? `${id ?? 'dropdown'}-label` : undefined); + +export const Dropdown: React.FC = ({ + options, + value, + onChange, + onBlur, + label, + required = false, + placeholder, + helperText, + state = 'default', + id, + labelId: labelIdProp, + testId, + disabled: disabledProp, + error: errorProp, + fullWidth = true, + renderValue, + sx, +}) => { + const isDisabled = disabledProp ?? state === 'disabled'; + const isError = errorProp ?? state === 'error'; + const resolvedId = id ?? testId ?? 'dropdown'; + const resolvedLabelId = resolveLabelId(labelIdProp, resolvedId, label); + const showLabel = Boolean(label); + + const defaultRenderValue = (selected: string) => { + if (!selected) { + return placeholder ? ( + + {placeholder} + + ) : null; + } + const option = options.find(o => o.value === selected); + return ( + + {option?.label ?? selected} + + ); + }; + + return ( + + {showLabel && ( + + {label} + {required && ( + + )} + + )} + + + + {helperText && ( + {helperText} + )} + + ); +}; diff --git a/apps/sv/frontend/src/routes/createProposal.tsx b/apps/sv/frontend/src/routes/createProposal.tsx index b8a6c4a9c0..63ee6fb92c 100644 --- a/apps/sv/frontend/src/routes/createProposal.tsx +++ b/apps/sv/frontend/src/routes/createProposal.tsx @@ -38,13 +38,15 @@ const ProposalForm: React.FC<{ action: SupportedActionTag }> = ({ action }) => { } }; +const CREATE_PROPOSAL_MAX_WIDTH = 1583; + export const CreateProposal: React.FC = () => { const [searchParams, _] = useSearchParams(); const action = searchParams.get('action'); const selectedAction = createProposalActions.find(a => a.value === action); return ( - + {selectedAction ? ( ) : ( From c28973322fc80560414b38281eeccbef4fe3ea90 Mon Sep 17 00:00:00 2001 From: Jagath Weerasinghe Date: Tue, 14 Jul 2026 14:57:31 +0200 Subject: [PATCH 030/329] Add ignore log patterns (#6403) -ignore log patterns for perf test infra Signed-off-by: Jagath Weerasinghe --- .../tests/BaseStorePerformanceTest.scala | 13 +++++-------- .../canton_network_test_log.ignore.txt | 5 +++++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/performance/tests/BaseStorePerformanceTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/performance/tests/BaseStorePerformanceTest.scala index 95a5fec347..1814c36e31 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/performance/tests/BaseStorePerformanceTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/performance/tests/BaseStorePerformanceTest.scala @@ -117,14 +117,11 @@ abstract class BaseStorePerformanceTest( case Left(err) => throw new RuntimeException(s"Failed to create storage: $err") } - /** Suppress Flyway ClassPathScanner warnings about unloadable test jars (apps-app_2.13-0.1.0-SNAPSHOT-tests.jar) - * TODO(#4790): This is a temporary workaround, w/o adding ignored logs. - */ - org.slf4j.LoggerFactory - .getLogger("org.flywaydb.core.internal.scanner.classpath.ClassPathScanner") - .asInstanceOf[ch.qos.logback.classic.Logger] - .setLevel(ch.qos.logback.classic.Level.ERROR) - + // Running unforked puts sbt's test jar on the classpath Flyway scans. + // So, unlike in forked approach, Flyway finds the jar but can't read it. + // Flyway skips it with a WARN. "Skipping unloadable jar file: ...apps-app_*-tests.jar" WARN. + // That log line is handled via an ignore pattern in project/ignore-patterns/canton_network_test_log.ignore.txt + // Migrations aren't affected — the actual migration files are read from the main resources. new DbMigrations(storage.dbConfig, false, timeouts, loggerFactory) .migrateDatabase() .map(_ => storage) diff --git a/project/ignore-patterns/canton_network_test_log.ignore.txt b/project/ignore-patterns/canton_network_test_log.ignore.txt index dbd7354be7..5e0358dad8 100644 --- a/project/ignore-patterns/canton_network_test_log.ignore.txt +++ b/project/ignore-patterns/canton_network_test_log.ignore.txt @@ -169,3 +169,8 @@ Ryuk has been disabled # Calls to this have retries; if the retries were not enough, we'll get a louder error. api/sv/v0/onboard/validator \(POST\) resulted in a timeout + +# In BaseStorePerformanceTest, we run migrations in an unforked sbt JVM, so Flyway +# finds the sbt test jar (apps-app_*-tests.jar). But, Flyway cannot open it, +# so it skips it with this WARN. It contains no migrations, so this is harmless and unrelated to DB migrations. +Skipping unloadable jar file:.*FlywayExecutor From bda9733fc1aa89d0e5ceafb124466961360c0ae6 Mon Sep 17 00:00:00 2001 From: Jagath Weerasinghe Date: Tue, 14 Jul 2026 14:58:13 +0200 Subject: [PATCH 031/329] Bump runner container hooks (#6399) Signed-off-by: Jagath Weerasinghe --- .github/runners/runner-container-hooks | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/runners/runner-container-hooks b/.github/runners/runner-container-hooks index 32b74210ef..8472c0f706 160000 --- a/.github/runners/runner-container-hooks +++ b/.github/runners/runner-container-hooks @@ -1 +1 @@ -Subproject commit 32b74210efb8c288ffd9e994ee6a90337ce156d3 +Subproject commit 8472c0f7060e0f2d956185eace05812c33bacbb5 From e2e7076fc6b4199d4ba46cad7b8d71d916ebb115 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Tue, 14 Jul 2026 16:05:46 +0200 Subject: [PATCH 032/329] Bump canton to 3.5.9-snapshot.20260714.19082.0.vd76d1db5 (#6406) [ci] Signed-off-by: Nicu Reut --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index af13e13295..04a1ec9877 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.8", - "oss_sha256": "sha256:1f70l5iwy6jhhinihqay9515gcc4s4pgiknxrwg4ycyws69zfil1", - "canton_base_image_sha256": "sha256:4cb2dd84c0f6e18fec98adf46cc90bd6c3c3892ae7e46dd4457668a4ba92a062", - "canton_participant_image_sha256": "sha256:fa5ac29b4632f6ba95c18279bc05d2614f7b743898a40f8e21e8410b26a96263", - "canton_mediator_image_sha256": "sha256:99464e2038bcaf79944bb000f5b9a9caa3bc09b271db1881b8ade48a99a60804", - "canton_sequencer_image_sha256": "sha256:1c4c7e6ac3453031ee17a7d7e37d768b6e487ba626d3f313be63bb5ae4a6dd65" + "version": "3.5.9-snapshot.20260714.19082.0.vd76d1db5", + "oss_sha256": "sha256:07q7a0p1b3r1ihvsfl10y77ssad8hds7zddfs2r14g4z8jwn3x4z", + "canton_base_image_sha256": "sha256:1688d1886eabc3e44e8dee5971dca330df5b3b2e0a03ddbadca02859878d438e", + "canton_participant_image_sha256": "sha256:0ccc22e60f570d9050f058420cc0652318a9abadfba5b666b1fe8880aebb9cf5", + "canton_mediator_image_sha256": "sha256:19edb1fd3b4d7ca5a07d0cfe534080d7abcbe1e0e28bd6eb85a11159501decc3", + "canton_sequencer_image_sha256": "sha256:8b34ed10fba56699fc744a62ba62e13737ce9cd696defd69e2d26fbdb642b795" } From 4d2473e85e9211cf0174b6e8023d47ef9609a608 Mon Sep 17 00:00:00 2001 From: Tim Date: Tue, 14 Jul 2026 16:40:10 +0200 Subject: [PATCH 033/329] Redesign JSON diff accordion on governance config forms and reviews (#6379) Signed-off-by: Tim Pelzer --- .../forms/set-amulet-rules-form.test.tsx | 23 +- .../forms/set-dso-rules-form.test.tsx | 23 +- .../proposal-details-content.test.tsx | 12 +- .../src/components/forms/FormLayout.tsx | 8 +- .../forms/SetAmuletConfigRulesForm.tsx | 2 +- .../forms/SetDsoConfigRulesForm.tsx | 2 +- .../governance/JsonDiffAccordion.tsx | 273 +++++++++++++++++- .../governance/ProposalDetailsContent.tsx | 4 +- 8 files changed, 312 insertions(+), 35 deletions(-) diff --git a/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx index 728ca1538c..d4a4a2380a 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx @@ -80,7 +80,11 @@ describe('Set Amulet Config Rules Form', () => { { timeout: 1000 } ); - expect(screen.getByTestId('json-diffs-details')).toBeInTheDocument(); + const jsonDiffsToggle = screen.getByTestId('json-diff-toggle'); + expect(screen.getByText('JSON')).toBeInTheDocument(); + expect(jsonDiffsToggle).toHaveTextContent('Show JSON'); + expect(jsonDiffsToggle).toHaveAttribute('aria-expanded', 'false'); + expect(screen.getByTestId('json-diffs-details')).not.toBeVisible(); }); test( @@ -275,6 +279,8 @@ describe('Set Amulet Config Rules Form', () => { await user.click(submitButton); expect(screen.getByText(PROPOSAL_SUMMARY_TITLE)).toBeInTheDocument(); + expect(screen.queryByText('JSON')).not.toBeInTheDocument(); + expect(screen.getByTestId('json-diff-toggle')).toHaveTextContent('Show JSON'); }); test('should show error on form if submission fails', { timeout: 10000 }, async () => { @@ -376,19 +382,22 @@ describe('Set Amulet Config Rules Form', () => { const c2Input = screen.getByTestId('config-field-transferConfigTransferFeeInitialRate'); await user.type(c2Input, '9.99'); - const jsonDiffs = screen.getByText('JSON Diffs'); - expect(jsonDiffs).toBeInTheDocument(); + const jsonDiffsToggle = screen.getByTestId('json-diff-toggle'); + expect(jsonDiffsToggle).toHaveTextContent('Show JSON'); + expect(jsonDiffsToggle).toHaveAttribute('aria-expanded', 'false'); - await user.click(jsonDiffs); - expect(await screen.findByTestId('config-diffs-display')).toBeInTheDocument(); + await user.click(jsonDiffsToggle); + expect(await screen.findByTestId('config-diffs-display')).toBeVisible(); + expect(jsonDiffsToggle).toHaveTextContent('Hide JSON'); + expect(jsonDiffsToggle).toHaveAttribute('aria-expanded', 'true'); const reviewButton = screen.getByTestId('submit-button'); await waitFor(async () => { expect(reviewButton.getAttribute('disabled')).toBeNull(); }); - expect(jsonDiffs).toBeInTheDocument(); - await user.click(jsonDiffs); + expect(jsonDiffsToggle).toBeInTheDocument(); + await user.click(jsonDiffsToggle); expect(await screen.findByTestId('config-diffs-display')).toBeInTheDocument(); }); diff --git a/apps/sv/frontend/src/__tests__/governance/forms/set-dso-rules-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/set-dso-rules-form.test.tsx index 25753a34fd..77bbedba50 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/set-dso-rules-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/set-dso-rules-form.test.tsx @@ -73,7 +73,11 @@ describe('Set DSO Config Rules Form', () => { /Unable to find an element/ ); - expect(screen.getByTestId('json-diffs-details')).toBeInTheDocument(); + const jsonDiffsToggle = screen.getByTestId('json-diff-toggle'); + expect(screen.getByText('JSON')).toBeInTheDocument(); + expect(jsonDiffsToggle).toHaveTextContent('Show JSON'); + expect(jsonDiffsToggle).toHaveAttribute('aria-expanded', 'false'); + expect(screen.getByTestId('json-diffs-details')).not.toBeVisible(); }); test('should render errors when submit button is clicked on new form', async () => { @@ -264,6 +268,8 @@ describe('Set DSO Config Rules Form', () => { await user.click(submitButton); expect(screen.getByText('Proposal Summary')).toBeInTheDocument(); + expect(screen.queryByText('JSON')).not.toBeInTheDocument(); + expect(screen.getByTestId('json-diff-toggle')).toHaveTextContent('Show JSON'); }); test('should show error on form if submission fails', async () => { @@ -374,19 +380,22 @@ describe('Set DSO Config Rules Form', () => { const c2Input = screen.getByTestId('config-field-voteCooldownTime'); await user.type(c2Input, '9999'); - const jsonDiffs = screen.getByText('JSON Diffs'); - expect(jsonDiffs).toBeInTheDocument(); + const jsonDiffsToggle = screen.getByTestId('json-diff-toggle'); + expect(jsonDiffsToggle).toHaveTextContent('Show JSON'); + expect(jsonDiffsToggle).toHaveAttribute('aria-expanded', 'false'); - await user.click(jsonDiffs); - expect(await screen.findByTestId('config-diffs-display')).toBeInTheDocument(); + await user.click(jsonDiffsToggle); + expect(await screen.findByTestId('config-diffs-display')).toBeVisible(); + expect(jsonDiffsToggle).toHaveTextContent('Hide JSON'); + expect(jsonDiffsToggle).toHaveAttribute('aria-expanded', 'true'); const reviewButton = screen.getByTestId('submit-button'); await waitFor(async () => { expect(reviewButton.getAttribute('disabled')).not.toBeInTheDocument(); }); - expect(jsonDiffs).toBeInTheDocument(); - await user.click(jsonDiffs); + expect(jsonDiffsToggle).toBeInTheDocument(); + await user.click(jsonDiffsToggle); expect(await screen.findByTestId('config-diffs-display')).toBeInTheDocument(); }); diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx index 1de4bb9c29..de7f2a7398 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx @@ -417,7 +417,11 @@ describe('Proposal Details Content', () => { const maxNumInputsNewValue = within(changes[1]).getByTestId('config-change-new-value'); expect(maxNumInputsNewValue.textContent).toBe('4'); - expect(screen.getByTestId('json-diffs-details')).toBeInTheDocument(); + const jsonDiffsToggle = screen.getByTestId('json-diff-toggle'); + expect(jsonDiffsToggle).toHaveTextContent('Show JSON'); + expect(jsonDiffsToggle).toHaveAttribute('aria-expanded', 'false'); + expect(screen.queryByText('JSON')).not.toBeInTheDocument(); + expect(screen.getByTestId('json-diffs-details')).not.toBeVisible(); }); test('should render dso rules config changes', () => { @@ -498,7 +502,11 @@ describe('Proposal Details Content', () => { ); expect(dsoNumUnclaimedRewardsThresholdNewValue.textContent).toBe('20'); - expect(screen.getByTestId('json-diffs-details')).toBeInTheDocument(); + const jsonDiffsToggle = screen.getByTestId('json-diff-toggle'); + expect(jsonDiffsToggle).toHaveTextContent('Show JSON'); + expect(jsonDiffsToggle).toHaveAttribute('aria-expanded', 'false'); + expect(screen.queryByText('JSON')).not.toBeInTheDocument(); + expect(screen.getByTestId('json-diffs-details')).not.toBeVisible(); }); }); diff --git a/apps/sv/frontend/src/components/forms/FormLayout.tsx b/apps/sv/frontend/src/components/forms/FormLayout.tsx index 78e72cd99f..baca47f788 100644 --- a/apps/sv/frontend/src/components/forms/FormLayout.tsx +++ b/apps/sv/frontend/src/components/forms/FormLayout.tsx @@ -24,7 +24,7 @@ export const FormLayout: React.FC = props => { alignItems: 'center', }} > - +
{ e.preventDefault(); @@ -32,7 +32,11 @@ export const FormLayout: React.FC = props => { form.handleSubmit(); }} > - {children} + + {children} +
diff --git a/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx b/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx index 2cf14577aa..0af20b6170 100644 --- a/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx +++ b/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx @@ -293,7 +293,7 @@ export const SetAmuletConfigRulesForm: () => JSX.Element = () => { )} - + {amuletConfigToCompareWith && amuletConfigToCompareWith[1] ? ( JSX.Element = () => { )} - + {dsoConfigToCompareWith[1] ? ( div': { + width: '100%', + minWidth: 0, + maxWidth: '100%', + }, + + '& [data-testid="stringify-display"], & [data-testid="config-diffs-display"]': { + width: '100%', + }, + + '& [data-testid="stringify-display"]': { + ...jsonDiffMonoSx, + color: 'common.white', + whiteSpace: 'pre-wrap', + wordBreak: 'break-word', + overflowWrap: 'break-word', + }, + + [`${DIFF} .jsondiffpatch-delta`]: { + ...jsonDiffMonoSx, + color: 'common.white', + display: 'block', + maxWidth: '100%', + boxSizing: 'border-box', + }, + + [`${DIFF} > .jsondiffpatch-delta`]: { + padding: 0, + }, + + [`${DIFF} .jsondiffpatch-delta pre`]: { + ...jsonDiffMonoSx, + whiteSpace: 'pre-wrap', + wordBreak: 'break-word', + overflowWrap: 'break-word', + }, + + // Unchanged rows inherit grey onto keys and values (do not set color on pre/property-name directly). + [`${DIFF} .jsondiffpatch-unchanged, ${DIFF} .jsondiffpatch-movedestination`]: { + color: 'gray', + }, + + [`${DIFF} .jsondiffpatch-delta ul, ${DIFF} ul.jsondiffpatch-delta`]: { + listStyleType: 'none', + padding: '0 0 0 20px', + margin: 0, + }, + + [`${DIFF} li`]: { + display: 'block', + }, + + [`${DIFF} .jsondiffpatch-added .jsondiffpatch-value pre::after, ${DIFF} .jsondiffpatch-modified .jsondiffpatch-right-value pre::after, ${DIFF} .jsondiffpatch-deleted .jsondiffpatch-value pre::after`]: + { + content: '""', + padding: 0, + }, + + [`${DIFF} li.jsondiffpatch-added:not(:last-child) > .jsondiffpatch-value::after, ${DIFF} li.jsondiffpatch-deleted:not(:last-child) > .jsondiffpatch-value::after, ${DIFF} li.jsondiffpatch-modified:not(:last-child) > .jsondiffpatch-right-value::after`]: + { + content: '","', + color: 'common.white', + padding: 0, + }, + + [`${DIFF} .jsondiffpatch-modified .jsondiffpatch-right-value`]: { + marginLeft: 0, + }, + [`${DIFF} .jsondiffpatch-modified .jsondiffpatch-right-value::before`]: { + content: '" -> "', + }, +} as const; + +interface JsonToggleButtonProps { + expanded: boolean; + onClick: () => void; +} + +const JsonToggleButton: React.FC = ({ expanded, onClick }) => ( + + + {expanded ? 'Hide JSON' : 'Show JSON'} + + + +); + +export interface JsonDiffAccordionProps { + children: React.ReactNode; + /** `form` — JSON label left, toggle right; `review` — toggle only. */ + variant?: 'form' | 'review'; +} + +export const JsonDiffAccordion: React.FC = ({ + children, + variant = 'review', +}) => { + const [expanded, setExpanded] = useState(false); + const toggle = ( + setExpanded(current => !current)} /> + ); + + const content = ( + + + {children} + + + ); -export const JsonDiffAccordion = ({ children }: { children: React.ReactNode }): JSX.Element => { return ( - - - } - aria-controls="json-diff-content" - id="json-diff-header" - > - JSON Diffs - - {children} - + + {variant === 'form' ? ( + + + JSON + + {toggle} + + ) : ( + {toggle} + )} + {content} ); }; diff --git a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx index e12d28c38b..b16e551b9a 100644 --- a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx @@ -247,7 +247,7 @@ export const ProposalDetailsContent: React.FC = pro label="Proposed Changes" value={} /> - + {amuletConfigToCompareWith ? ( = pro label="Proposed Changes" value={} /> - + {dsoConfigToCompareWith?.[1] ? ( Date: Tue, 14 Jul 2026 17:08:31 +0200 Subject: [PATCH 034/329] Add debugging for null view payload in TSv2 parser (#6408) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oriol Muñoz --- token-standard/cli/src/txparse/parserv2.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/token-standard/cli/src/txparse/parserv2.ts b/token-standard/cli/src/txparse/parserv2.ts index e05539ac83..1008d8734c 100644 --- a/token-standard/cli/src/txparse/parserv2.ts +++ b/token-standard/cli/src/txparse/parserv2.ts @@ -166,10 +166,16 @@ export class V2TransactionParser { return null; } - const result = holdingViewToResult( - createdEvent.contractId, - Holding.decoder.runWithException(holdingView.viewValue), - ); + let decodedPayload: Holding; + try { + decodedPayload = Holding.decoder.runWithException(holdingView.viewValue); + } catch (err) { + console.error( + `Failed to decode Holding. View: ${holdingView}. Error: ${err}`, + ); + throw err; + } + const result = holdingViewToResult(createdEvent.contractId, decodedPayload); return { holding: result, From 650fa3538e774bc0b9eac2f8018f2cd2e9f14346 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Tue, 14 Jul 2026 18:07:42 +0200 Subject: [PATCH 035/329] Adjusted Scan/Registry ratelimits (#6405) [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../shared/rate-limits/token-registry.yaml | 90 ++--- .../scratchneta/config.resolved.yaml | 88 ++--- .../scratchnetb/config.resolved.yaml | 88 ++--- .../scratchnetc/config.resolved.yaml | 88 ++--- .../scratchnetd/config.resolved.yaml | 88 ++--- .../scratchnete/config.resolved.yaml | 88 ++--- cluster/expected/canton-network/expected.json | 352 +++++++++--------- cluster/expected/sv-runbook/expected.json | 176 ++++----- 8 files changed, 529 insertions(+), 529 deletions(-) diff --git a/cluster/configs/shared/rate-limits/token-registry.yaml b/cluster/configs/shared/rate-limits/token-registry.yaml index 54de5248fc..6aaa5e0a01 100644 --- a/cluster/configs/shared/rate-limits/token-registry.yaml +++ b/cluster/configs/shared/rate-limits/token-registry.yaml @@ -2,102 +2,102 @@ rateLimits: /registry/allocations/v1: name: registry-allocations type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s /registry/metadata/v1/info: name: registry-metadata-info type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s /registry/metadata/v1/instruments: name: registry-metadata-instruments type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s /registry/allocation-instruction/v1/allocation-factory: name: registry-allocation-factory type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s /registry/transfer-instruction/v1: name: registry-transfer-instruction type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s /registry/transfer-instruction/v1/transfer-factory: name: registry-transfer-factory type: limited maxTokens: 500 - tokensPerFill: 500 + tokensPerFill: 500 # Higher limit: transfer-factory generates the most registry traffic fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 fillInterval: 60s /registry/allocation/v2/settlement-factory: name: registry-settlement-factory-v2 type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s /registry/allocations/v2: name: registry-allocations-v2 type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s /registry/allocation-instruction/v2/allocation-factory: name: registry-allocation-factory-v2 type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s /registry/allocation-instruction/v2: name: registry-allocation-instruction-v2 type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s /registry/transfer-instruction/v2/transfer-factory: name: registry-transfer-factory-v2 @@ -106,16 +106,16 @@ rateLimits: tokensPerFill: 500 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 fillInterval: 60s /registry/transfer-instruction/v2: name: registry-transfer-instruction-v2 type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 200 + tokensPerFill: 200 fillInterval: 60s perIpLimits: - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 30 + tokensPerFill: 30 fillInterval: 60s diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index 0ce399c8cb..0ddb928aa1 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -373,93 +373,93 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' @@ -467,19 +467,19 @@ sv: name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' @@ -487,8 +487,8 @@ sv: name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' svs: diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index 0ce399c8cb..0ddb928aa1 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -373,93 +373,93 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' @@ -467,19 +467,19 @@ sv: name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' @@ -487,8 +487,8 @@ sv: name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' svs: diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index 0ce399c8cb..0ddb928aa1 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -373,93 +373,93 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' @@ -467,19 +467,19 @@ sv: name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' @@ -487,8 +487,8 @@ sv: name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' svs: diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index 0ce399c8cb..0ddb928aa1 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -373,93 +373,93 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' @@ -467,19 +467,19 @@ sv: name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' @@ -487,8 +487,8 @@ sv: name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' svs: diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index 0ce399c8cb..0ddb928aa1 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -373,93 +373,93 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' @@ -467,19 +467,19 @@ sv: name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 500 + maxTokens: 200 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 - tokensPerFill: 500 + maxTokens: 30 + tokensPerFill: 30 + tokensPerFill: 200 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' @@ -487,8 +487,8 @@ sv: name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 10 - tokensPerFill: 5 + maxTokens: 50 + tokensPerFill: 50 tokensPerFill: 500 type: 'limited' svs: diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index dde35ae7bc..62d864a9ac 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -1808,110 +1808,110 @@ }, "/registry/allocation-instruction/v1/allocation-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocation-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocation-instruction/v2": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocation-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocation-instruction/v2/allocation-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocation-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocation/v2/settlement-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-settlement-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocations/v1": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocations", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocations/v2": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocations-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/metadata/v1/info": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-metadata-info", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/metadata/v1/instruments": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-metadata-instruments", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/transfer-instruction/v1": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-transfer-instruction", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/transfer-instruction/v1/transfer-factory": { @@ -1920,22 +1920,22 @@ "name": "registry-transfer-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 50, + "tokensPerFill": 50 }, "tokensPerFill": 500, "type": "limited" }, "/registry/transfer-instruction/v2": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-transfer-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/transfer-instruction/v2/transfer-factory": { @@ -1944,8 +1944,8 @@ "name": "registry-transfer-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 50, + "tokensPerFill": 50 }, "tokensPerFill": 500, "type": "limited" @@ -2191,110 +2191,110 @@ }, "/registry/allocation-instruction/v1/allocation-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocation-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocation-instruction/v2": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocation-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocation-instruction/v2/allocation-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocation-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocation/v2/settlement-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-settlement-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocations/v1": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocations", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocations/v2": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocations-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/metadata/v1/info": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-metadata-info", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/metadata/v1/instruments": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-metadata-instruments", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/transfer-instruction/v1": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-transfer-instruction", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/transfer-instruction/v1/transfer-factory": { @@ -2303,22 +2303,22 @@ "name": "registry-transfer-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 50, + "tokensPerFill": 50 }, "tokensPerFill": 500, "type": "limited" }, "/registry/transfer-instruction/v2": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-transfer-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/transfer-instruction/v2/transfer-factory": { @@ -2327,8 +2327,8 @@ "name": "registry-transfer-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 50, + "tokensPerFill": 50 }, "tokensPerFill": 500, "type": "limited" @@ -3467,8 +3467,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3483,8 +3483,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -3496,8 +3496,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3512,8 +3512,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -3525,8 +3525,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3541,8 +3541,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -3554,8 +3554,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3570,8 +3570,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -3583,8 +3583,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3599,8 +3599,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -3628,8 +3628,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 50, + "tokens_per_fill": 50 } }, { @@ -3641,8 +3641,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3657,8 +3657,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -3670,8 +3670,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3686,8 +3686,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -3699,8 +3699,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3715,8 +3715,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -3728,8 +3728,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3744,8 +3744,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -3773,8 +3773,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 50, + "tokens_per_fill": 50 } }, { @@ -3786,8 +3786,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -3802,8 +3802,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } } ], @@ -5604,8 +5604,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5620,8 +5620,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -5633,8 +5633,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5649,8 +5649,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -5662,8 +5662,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5678,8 +5678,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -5691,8 +5691,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5707,8 +5707,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -5720,8 +5720,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5736,8 +5736,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -5765,8 +5765,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 50, + "tokens_per_fill": 50 } }, { @@ -5778,8 +5778,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5794,8 +5794,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -5807,8 +5807,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5823,8 +5823,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -5836,8 +5836,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5852,8 +5852,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -5865,8 +5865,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5881,8 +5881,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -5910,8 +5910,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 50, + "tokens_per_fill": 50 } }, { @@ -5923,8 +5923,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -5939,8 +5939,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } } ], diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index e603488fb4..10c5d40af4 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -1059,110 +1059,110 @@ }, "/registry/allocation-instruction/v1/allocation-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocation-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocation-instruction/v2": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocation-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocation-instruction/v2/allocation-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocation-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocation/v2/settlement-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-settlement-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocations/v1": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocations", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/allocations/v2": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-allocations-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/metadata/v1/info": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-metadata-info", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/metadata/v1/instruments": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-metadata-instruments", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/transfer-instruction/v1": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-transfer-instruction", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/transfer-instruction/v1/transfer-factory": { @@ -1171,22 +1171,22 @@ "name": "registry-transfer-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 50, + "tokensPerFill": 50 }, "tokensPerFill": 500, "type": "limited" }, "/registry/transfer-instruction/v2": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 200, "name": "registry-transfer-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 30, + "tokensPerFill": 30 }, - "tokensPerFill": 500, + "tokensPerFill": 200, "type": "limited" }, "/registry/transfer-instruction/v2/transfer-factory": { @@ -1195,8 +1195,8 @@ "name": "registry-transfer-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 + "maxTokens": 50, + "tokensPerFill": 50 }, "tokensPerFill": 500, "type": "limited" @@ -2287,8 +2287,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2303,8 +2303,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -2316,8 +2316,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2332,8 +2332,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -2345,8 +2345,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2361,8 +2361,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -2374,8 +2374,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2390,8 +2390,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -2403,8 +2403,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2419,8 +2419,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -2448,8 +2448,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 50, + "tokens_per_fill": 50 } }, { @@ -2461,8 +2461,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2477,8 +2477,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -2490,8 +2490,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2506,8 +2506,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -2519,8 +2519,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2535,8 +2535,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -2548,8 +2548,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2564,8 +2564,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } }, { @@ -2593,8 +2593,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 50, + "tokens_per_fill": 50 } }, { @@ -2606,8 +2606,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 200, + "tokens_per_fill": 200 } }, { @@ -2622,8 +2622,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 + "max_tokens": 30, + "tokens_per_fill": 30 } } ], From 2074158f82c9980939695ef0d7a11cfe2b64b65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Tue, 14 Jul 2026 19:58:21 +0200 Subject: [PATCH 036/329] Fix parserv2.ts logging (#6411) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oriol Muñoz --- token-standard/cli/src/txparse/parserv2.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token-standard/cli/src/txparse/parserv2.ts b/token-standard/cli/src/txparse/parserv2.ts index 1008d8734c..04820bf3f8 100644 --- a/token-standard/cli/src/txparse/parserv2.ts +++ b/token-standard/cli/src/txparse/parserv2.ts @@ -171,7 +171,7 @@ export class V2TransactionParser { decodedPayload = Holding.decoder.runWithException(holdingView.viewValue); } catch (err) { console.error( - `Failed to decode Holding. View: ${holdingView}. Error: ${err}`, + `Failed to decode Holding. View: ${JSON.stringify(holdingView)}. Error: ${JSON.stringify(err)}`, ); throw err; } From 8584d291bcf33bd0efc097cbae9ffa40a9eee3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Tue, 14 Jul 2026 20:53:32 +0200 Subject: [PATCH 037/329] Pause ReceiveSvRewardCouponTrigger in TBAR tests (#6386) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../tests/TrafficBasedRewardsTimeBasedIntegrationTest.scala | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsTimeBasedIntegrationTest.scala index 48c7e1cda3..78309a9912 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsTimeBasedIntegrationTest.scala @@ -39,6 +39,7 @@ import org.lfdecentralizedtrust.splice.sv.automation.confirmation.{ CalculateRewardsDryRunTrigger, } import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.ExpiredAmuletTransferInstructionTrigger +import org.lfdecentralizedtrust.splice.sv.automation.singlesv.ReceiveSvRewardCouponTrigger import org.lfdecentralizedtrust.splice.util.{ AmuletConfigUtil, ChoiceContextWithDisclosures, @@ -117,6 +118,11 @@ abstract class TrafficBasedRewardsTimeBasedIntegrationTestBase .withPausedTrigger[CollectRewardsAndMergeAmuletsTrigger] )(config) ) + .addConfigTransform((_, config) => + updateAutomationConfig(ConfigurableApp.Sv)( + _.withPausedTrigger[ReceiveSvRewardCouponTrigger] + )(config) + ) "CIP-104 reward accounting pipeline works" in { implicit env => val aliceParty = onboardWalletUser(aliceWalletClient, aliceValidatorBackend) From 3a31b33e69d746c9201fc7d7255063cca28ae3be Mon Sep 17 00:00:00 2001 From: Divam <681060+dfordivam@users.noreply.github.com> Date: Wed, 15 Jul 2026 09:50:26 +0900 Subject: [PATCH 038/329] SV UI: support activity weights for GrantFeaturedAppRight (#6387) Signed-off-by: Divam --- .../tests/SvFrontendIntegrationTest.scala | 9 +- .../grant-revoke-featured-app-form.test.tsx | 135 ++++++++++++++++++ .../governance/proposal-summary.test.tsx | 7 + .../forms/GrantRevokeFeaturedAppForm.tsx | 33 ++++- .../src/components/forms/formValidators.ts | 18 +++ .../governance/ProposalDetailsContent.tsx | 14 +- .../components/governance/ProposalSummary.tsx | 10 +- .../votes/actions/GrantFeaturedAppRight.tsx | 25 +++- apps/sv/frontend/src/utils/governance.ts | 15 +- apps/sv/frontend/src/utils/types.ts | 1 + docs/src/release_notes_upcoming.rst | 4 + 11 files changed, 260 insertions(+), 11 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala index 0ed40dd6d3..5591a73d02 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala @@ -31,6 +31,7 @@ import org.openqa.selenium.support.ui.Select import org.slf4j.event.Level import scala.jdk.CollectionConverters.* +import scala.jdk.OptionConverters.* import java.util.Optional class SvFrontendIntegrationTest @@ -1421,6 +1422,7 @@ class SvFrontendIntegrationTest "NEW UI: Grant and Revoke Featured App Right" in { implicit env => val providerParty = sv3Backend.getDsoInfo().svParty val providerPartyId = providerParty.toProtoPrimitive + val activityWeight = BigDecimal("2.5") // First, create a Grant proposal for the provider. val grantProposalContractId = assertCreateProposal( @@ -1428,6 +1430,7 @@ class SvFrontendIntegrationTest "grant-featured-app", ) { implicit webDriver => fillOutTextField("grant-featured-app-idValue", providerPartyId) + fillOutTextField("grant-featured-app-activityWeight", activityWeight.toString) } clue("vote the grant request to execution before creating revoke request") { @@ -1463,7 +1466,11 @@ class SvFrontendIntegrationTest } eventually() { - sv1ScanBackend.lookupFeaturedAppRight(providerParty) shouldBe a[Some[?]] + val featuredAppRight = sv1ScanBackend.lookupFeaturedAppRight(providerParty) + featuredAppRight shouldBe a[Some[?]] + featuredAppRight.value.payload.activityWeight.toScala.map( + BigDecimal(_) + ) shouldBe Some(activityWeight) } } diff --git a/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx index e13c9d4aaf..5a35681575 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx @@ -214,6 +214,141 @@ describe('Grant Featured App Form', () => { expect(screen.getByText(PROPOSAL_SUMMARY_TITLE)).toBeInTheDocument(); }); + + test('activity weight is optional and is sent to backend as null when left blank', async () => { + let requestBody = ''; + server.use( + http.post(`${svUrl}/v0/admin/sv/voterequest/create`, async ({ request }) => { + requestBody = await request.text(); + return HttpResponse.json({}); + }) + ); + + const user = userEvent.setup(); + + render( + + + + ); + + const activityWeightInput = screen.getByTestId('grant-featured-app-activityWeight'); + expect(activityWeightInput.getAttribute('value')).toBe(''); + + const actionInput = screen.getByTestId('grant-featured-app-action'); + const submitButton = screen.getByTestId('submit-button'); + + const summaryInput = screen.getByTestId('grant-featured-app-summary'); + await user.type(summaryInput, 'Summary of the proposal'); + + const urlInput = screen.getByTestId('grant-featured-app-url'); + await user.type(urlInput, 'https://example.com'); + + const providerInput = screen.getByTestId('grant-featured-app-idValue'); + await user.type(providerInput, 'a-party-id::1014912492'); + + await user.click(activityWeightInput); + + await user.click(actionInput); // using this to trigger the onBlur event which triggers the validation + + await waitFor(() => { + expect(screen.queryByText('Validating provider...')).not.toBeInTheDocument(); + }); + + await waitFor(async () => { + expect(submitButton).not.toBeDisabled(); + }); + + await user.click(submitButton); // review proposal + + expect(screen.getByTestId('grantRightActivityWeight-field').textContent).toBe(''); + + await user.click(submitButton); // submit proposal + + await waitFor(() => { + expect(requestBody).toContain('"activityWeight":null'); + }); + }); + + test('should send explicit activity weight to backend when provided', async () => { + let requestBody = ''; + server.use( + http.post(`${svUrl}/v0/admin/sv/voterequest/create`, async ({ request }) => { + requestBody = await request.text(); + return HttpResponse.json({}); + }) + ); + + const user = userEvent.setup(); + + render( + + + + ); + + const actionInput = screen.getByTestId('grant-featured-app-action'); + const submitButton = screen.getByTestId('submit-button'); + + const summaryInput = screen.getByTestId('grant-featured-app-summary'); + await user.type(summaryInput, 'Summary of the proposal'); + + const urlInput = screen.getByTestId('grant-featured-app-url'); + await user.type(urlInput, 'https://example.com'); + + const providerInput = screen.getByTestId('grant-featured-app-idValue'); + await user.type(providerInput, 'a-party-id::1014912492'); + + const activityWeightInput = screen.getByTestId('grant-featured-app-activityWeight'); + await user.type(activityWeightInput, '2.5'); + + await user.click(actionInput); // using this to trigger the onBlur event which triggers the validation + + await waitFor(() => { + expect(screen.queryByText('Validating provider...')).not.toBeInTheDocument(); + }); + + await waitFor(async () => { + expect(submitButton).not.toBeDisabled(); + }); + + await user.click(submitButton); // review proposal + await user.click(submitButton); // submit proposal + + await waitFor(() => { + expect(requestBody).toContain('"activityWeight":"2.5"'); + }); + }); + + test('activity weight rejects negative numbers and more than 10 decimal places', async () => { + const user = userEvent.setup(); + + render( + + + + ); + + const activityWeightInput = screen.getByTestId('grant-featured-app-activityWeight'); + const activityWeightError = screen.getByTestId('grant-featured-app-activityWeight-error'); + + await user.type(activityWeightInput, '-1'); + await waitFor(() => { + expect(activityWeightError.textContent).toBe('Weight must be a valid non-negative number'); + }); + + await user.clear(activityWeightInput); + await user.type(activityWeightInput, '1.1234567891'); + await waitFor(() => { + expect(activityWeightError.textContent).toBe(''); + }); + + await user.clear(activityWeightInput); + await user.type(activityWeightInput, '1.12345678912'); + await waitFor(() => { + expect(activityWeightError.textContent).toBe('Weight can have at most 10 decimal places'); + }); + }); }); describe('Revoke Featured App Form', () => { diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx index 8922fa5003..cbbd9b3f9e 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx @@ -116,6 +116,7 @@ describe('Review Proposal Component', () => { test('should render review proposal component for feature application', () => { const actionName = 'Feature Application'; const provider = 'Digital-Asset-Eng-2'; + const activityWeight = '2.5'; render( { effectiveDate={effectiveDate} formType="grant-right" grantRight={provider} + activityWeight={activityWeight} onEdit={() => {}} onSubmit={() => {}} /> @@ -148,6 +150,11 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('grantRight-title').textContent).toBe('Provider Party ID'); expect(screen.getByTestId('grantRight-field').textContent).toBe(provider); + + expect(screen.getByTestId('grantRightActivityWeight-title').textContent).toBe( + 'Activity Weight' + ); + expect(screen.getByTestId('grantRightActivityWeight-field').textContent).toBe(activityWeight); }); test('should render review proposal component for unfeature application', () => { diff --git a/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx b/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx index ca7a9fd0b7..8598837054 100644 --- a/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx +++ b/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx @@ -5,7 +5,11 @@ import { ActionRequiringConfirmation } from '@daml.js/splice-dso-governance/lib/ import { useSearchParams } from 'react-router'; import { useDsoInfos } from '../../contexts/SvContext'; import dayjs from 'dayjs'; -import { createProposalActions, getInitialExpiration } from '../../utils/governance'; +import { + activityWeightToOptional, + createProposalActions, + getInitialExpiration, +} from '../../utils/governance'; import { dateTimeFormatISO } from '@canton-network/splice-common-frontend-utils'; import { useAppForm } from '../../hooks/form'; import { useStore } from '@tanstack/react-form'; @@ -14,6 +18,7 @@ import { CommonProposalFormData } from '../../utils/types'; import { ContractId } from '@daml/types'; import { FeaturedAppRight } from '@daml.js/splice-amulet/lib/Splice/Amulet'; import { + validateActivityWeight, validateEffectiveDate, validateExpiration, validateExpiryEffectiveDate, @@ -38,6 +43,7 @@ interface ExtraFormField { idValue: ProviderId; partyId: ProviderId; rightCid: FeaturedAppRightId; + activityWeight: string; } export type GrantRevokeFeaturedAppFormData = CommonProposalFormData & ExtraFormField; @@ -137,6 +143,7 @@ export const GrantRevokeFeaturedAppForm: React.FC setShowConfirmation(false)} @@ -247,6 +258,24 @@ export const GrantRevokeFeaturedAppForm: React.FC )} + {formAction === 'SRARC_GrantFeaturedAppRight' && ( + validateActivityWeight(value), + onChange: ({ value }) => validateActivityWeight(value), + }} + > + {field => ( + + )} + + )} + {formAction === 'SRARC_RevokeFeaturedAppRight' && ( <> v === '' || /^\d+(\.\d+)?$/.test(v), { + message: 'Weight must be a valid non-negative number', + }) + .refine( + v => { + const dotIndex = v.indexOf('.'); + return dotIndex === -1 || v.length - dotIndex - 1 <= 10; + }, + { message: 'Weight can have at most 10 decimal places' } + ); + export const validateWeight = (value: string): string | false => { const result = svWeightSchema.safeParse(value); return result.success ? false : result.error.issues[0].message; @@ -76,6 +89,11 @@ export const validateRewardAmount = (value: string): string | false => { return result.success ? false : result.error.issues[0].message; }; +export const validateActivityWeight = (value: string): string | false => { + const result = activityWeightSchema.safeParse(value); + return result.success ? false : result.error.issues[0].message; +}; + export const validateSvSelection = (value: string): string | false => { const result = svSelectionSchema.safeParse(value); return result.success ? false : result.error.issues[0].message; diff --git a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx index b16e551b9a..be7d87ac26 100644 --- a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx @@ -218,7 +218,10 @@ export const ProposalDetailsContent: React.FC = pro )} {proposalDetails.action === 'SRARC_GrantFeaturedAppRight' && ( - + )} {proposalDetails.action === 'SRARC_RevokeFeaturedAppRight' && ( @@ -576,9 +579,10 @@ const OffboardMemberSection = ({ memberPartyId }: OffboardMemberSectionProps) => interface FeatureAppSectionProps { provider: string; + activityWeight: string; } -const FeatureAppSection = ({ provider }: FeatureAppSectionProps) => { +const FeatureAppSection = ({ provider, activityWeight }: FeatureAppSectionProps) => { return ( { } labelId="proposal-details-feature-app-label" /> + ); }; diff --git a/apps/sv/frontend/src/components/governance/ProposalSummary.tsx b/apps/sv/frontend/src/components/governance/ProposalSummary.tsx index f3e8c525ae..18d365d058 100644 --- a/apps/sv/frontend/src/components/governance/ProposalSummary.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalSummary.tsx @@ -31,6 +31,7 @@ type ProposalSummaryProps = BaseProposalSummaryProps & | { formType: 'grant-right'; grantRight: string; + activityWeight: string; } | { formType: 'revoke-right'; @@ -105,7 +106,14 @@ export const ProposalSummary: React.FC = props => { )} {formType === 'grant-right' && ( - + <> + + + )} {formType === 'revoke-right' && ( diff --git a/apps/sv/frontend/src/components/votes/actions/GrantFeaturedAppRight.tsx b/apps/sv/frontend/src/components/votes/actions/GrantFeaturedAppRight.tsx index d0a65f0a15..6a8bdee801 100644 --- a/apps/sv/frontend/src/components/votes/actions/GrantFeaturedAppRight.tsx +++ b/apps/sv/frontend/src/components/votes/actions/GrantFeaturedAppRight.tsx @@ -8,12 +8,14 @@ import { FormControl, Stack, TextField, Typography } from '@mui/material'; import { ActionRequiringConfirmation } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules/module'; import { useDsoInfos } from '../../../contexts/SvContext'; +import { activityWeightToOptional } from '../../../utils/governance'; const GrantFeaturedAppRight: React.FC<{ chooseAction: (action: ActionRequiringConfirmation) => void; }> = ({ chooseAction }) => { const dsoInfosQuery = useDsoInfos(); const [provider, setProvider] = useState(''); + const [activityWeight, setActivityWeight] = useState(''); if (dsoInfosQuery.isLoading) { return ; @@ -23,19 +25,28 @@ const GrantFeaturedAppRight: React.FC<{ return

Error: {JSON.stringify(dsoInfosQuery.error)}

; } - function setProviderAction(provider: string) { - setProvider(provider); + function chooseGrantAction(provider: string, activityWeight: string) { chooseAction({ tag: 'ARC_DsoRules', value: { dsoAction: { tag: 'SRARC_GrantFeaturedAppRight', - value: { provider: provider, activityWeight: null }, + value: { provider: provider, activityWeight: activityWeightToOptional(activityWeight) }, }, }, }); } + function setProviderAction(provider: string) { + setProvider(provider); + chooseGrantAction(provider, activityWeight); + } + + function setActivityWeightAction(activityWeight: string) { + setActivityWeight(activityWeight); + chooseGrantAction(provider, activityWeight); + } + return ( Provider @@ -46,6 +57,14 @@ const GrantFeaturedAppRight: React.FC<{ value={provider} /> + Activity Weight + + setActivityWeightAction(e.target.value)} + value={activityWeight} + /> + ); }; diff --git a/apps/sv/frontend/src/utils/governance.ts b/apps/sv/frontend/src/utils/governance.ts index 0fdf4bd4aa..9432ee4312 100644 --- a/apps/sv/frontend/src/utils/governance.ts +++ b/apps/sv/frontend/src/utils/governance.ts @@ -134,7 +134,10 @@ export function buildProposal(action: ActionRequiringConfirmation, dsoInfo?: Dso dsoAction.value.expiresAt ); case 'SRARC_GrantFeaturedAppRight': - return createGrantFeatureAppProposal(dsoAction.value.provider); + return createGrantFeatureAppProposal( + dsoAction.value.provider, + dsoAction.value.activityWeight ?? '' + ); case 'SRARC_RevokeFeaturedAppRight': return createRevokeFeatureAppProposal(dsoAction.value.rightCid); case 'SRARC_SetConfig': @@ -156,9 +159,13 @@ function createOffboardMemberProposal(memberToOffboard: string): OffBoardMemberP return { memberToOffboard }; } -function createGrantFeatureAppProposal(provider: string): FeatureAppProposal { +function createGrantFeatureAppProposal( + provider: string, + activityWeight: string +): FeatureAppProposal { return { provider: provider, + activityWeight: activityWeight, }; } @@ -274,6 +281,10 @@ export function getSvRewardWeight(svs: [string, SvInfo][], svPartyId: string): s return svInfo ? svInfo[1].svRewardWeight : ''; } +export function activityWeightToOptional(weight: string): string | null { + return weight.trim() === '' ? null : weight; +} + export function buildPendingConfigFields( proposals: Contract[] | undefined ): PendingConfigFieldInfo[] { diff --git a/apps/sv/frontend/src/utils/types.ts b/apps/sv/frontend/src/utils/types.ts index b54b5c6797..09a4460252 100644 --- a/apps/sv/frontend/src/utils/types.ts +++ b/apps/sv/frontend/src/utils/types.ts @@ -22,6 +22,7 @@ export interface OffBoardMemberProposal { export interface FeatureAppProposal { provider: string; + activityWeight: string; } export interface UnfeatureAppProposal { diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 566ea5ab04..04fa075f12 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -31,3 +31,7 @@ provision yourself; a managed service such as Amazon RDS or Google Cloud SQL is recommended. Follow the `migration guide `__ to move the data of an existing node before that date. + + - SV app + + - Add support for specifying weight in ``GrantFeaturedAppRight`` governance voting UI. From 73dd8aba8c8b9c7b47dfb0d7bff8237103bfdead Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Wed, 15 Jul 2026 07:36:53 +0200 Subject: [PATCH 039/329] Fix cantonbft threshold alert param (#6409) [static] Signed-off-by: Nicu Reut --- cluster/expected/observability/expected.json | 2 +- cluster/pulumi/observability/src/observability.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 70a0a7c2e1..8eee62d285 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -77,7 +77,7 @@ "acs_commitment_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: acs commitment\n folder: canton\n interval: 1m\n rules:\n - uid: ffghab4ms1ds0b\n title: ACS Commitment Checkpoint Delay\n condition: acs commitment checkpoint delay threshold exceeded\n data:\n - refId: acs commitment checkpoint delay\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by (namespace, job) (timestamp(daml_participant_sync_commitments_last_locally_checkpointed) - (daml_participant_sync_commitments_last_locally_checkpointed > 0) / 1e6)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: acs commitment checkpoint delay\n - refId: acs commitment checkpoint delay threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1200\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: acs commitment checkpoint delay\n intervalMs: 1000\n maxDataPoints: 43200\n refId: acs commitment checkpoint delay threshold exceeded\n type: threshold\n dashboardUid: eesa90lstfk00b\n panelId: 11\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eesa90lstfk00b\n __panelId__: \"11\"\n severity: critical\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting an ACS commitment checkpoint delay\n labels: {}\n isPaused: false\n - uid: ffghab4ms1ds0c\n title: ACS Commitment Delay\n condition: acs commitment delay threshold exceeded\n data:\n - refId: acs commitment delay\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by (namespace, job) (timestamp(daml_participant_sync_commitments_last_locally_completed) - (daml_participant_sync_commitments_last_locally_completed > 0) / 1e6)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: acs commitment delay\n - refId: acs commitment delay threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 3600\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: acs commitment delay\n intervalMs: 1000\n maxDataPoints: 43200\n refId: acs commitment delay threshold exceeded\n type: threshold\n dashboardUid: eesa90lstfk00b\n panelId: 10\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eesa90lstfk00b\n __panelId__: \"10\"\n severity: critical\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting an ACS commitment delay\n labels: {}\n isPaused: false\n - uid: ffghab4ms1ds0d\n title: ACS Commitment Compute Duration\n condition: acs commitment compute duration threshold exceeded\n data:\n - refId: acs commitment compute duration\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: histogram_quantile(0.99, sum(rate(daml_participant_sync_commitments_compute_duration_seconds[5m])) by (namespace, job, le))\n instant: false\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: true\n refId: acs commitment compute duration\n - refId: acs commitment compute duration max\n datasourceUid: __expr__\n model:\n datasource:\n type: __expr__\n uid: __expr__\n expression: acs commitment compute duration\n intervalMs: 1000\n maxDataPoints: 43200\n reducer: max\n refId: acs commitment compute duration max\n type: reduce\n - refId: acs commitment compute duration threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1200\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: acs commitment compute duration max\n intervalMs: 1000\n maxDataPoints: 43200\n refId: acs commitment compute duration threshold exceeded\n type: threshold\n dashboardUid: eesa90lstfk00b\n panelId: 2\n noDataState: NoData\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eesa90lstfk00b\n __panelId__: \"2\"\n severity: warning\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting a high ACS commitment compute duration (p99)\n labels: {}\n isPaused: false\n", "acs_snapshots_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: acs-snapshots\n folder: canton-network\n interval: 5m\n rules:\n - uid: adwt1yr5xuscge\n title: Saving ACS snapshot taking too long\n condition: too_long\n data:\n - refId: latency\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n # The metric only has one sample every ~3h (more if catching up), the 10m range in the expression\n # has to be longer than the alert evaluation+pending interval.\n expr: histogram_quantile(0.99, rate(splice_history_acs_snapshots_latency_save_duration_seconds[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: latency\n - refId: too_long\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 5400\n - 0\n type: gt\n operator:\n type: and\n query:\n params: [ ]\n reducer:\n params: [ ]\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: latency\n intervalMs: 1000\n maxDataPoints: 43200\n refId: too_long\n type: threshold\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: The time to save an incremental snapshot was too high. This step scales with the size of the ACS.\n runbook_url: \"\"\n severity: warning\n summary: Saving an ACS snapshot took longer than {{ humanizeDuration 5400 }} in {{ index $labels \"namespace\" }}'s Scan\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A\n isPaused: false\n - uid: t2ln0sn2bdfmcd\n title: Updating ACS snapshot taking too long\n condition: too_long\n data:\n - refId: latency\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n # The metric has one sample every ~30sec (more if catching up).\n # Here we alert on the average update latency, as the 99th percentile is overly noisy.\n expr: histogram_avg(rate(splice_history_acs_snapshots_latency_update_duration_seconds[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: latency\n - refId: too_long\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 3\n - 0\n type: gt\n operator:\n type: and\n query:\n params: [ ]\n reducer:\n params: [ ]\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: latency\n intervalMs: 1000\n maxDataPoints: 43200\n refId: too_long\n type: threshold\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: The time to update an incremental snapshot was too high. This step scales with the TPS.\n runbook_url: \"\"\n severity: warning\n summary: Updating an ACS snapshot took longer than {{ humanizeDuration 3 }} on average over the past 10min in {{ index $labels \"namespace\" }}'s Scan\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A\n isPaused: false\n", "automation_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: automation\n folder: canton-network\n interval: 5m\n rules:\n - uid: fe73c0e7-dcb3-4975-a7d1-04ed8da087be\n title: Automation Failures\n condition: threshold\n data:\n - refId: total\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by(namespace, node_type, trigger_name, migration) (delta(splice_trigger_completed_total{trigger_name=~\".+\", outcome=~\".+\"}[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total\n - refId: failures\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum by(namespace, node_type, trigger_name, migration) (delta(splice_trigger_completed_total{trigger_name=~\".+\", outcome=~\"failure\"}[10m])) or on() vector(0)\n hide: false\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: failures\n - refId: failure_pct\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: ${failures} / ${total} * 100\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: failure_pct\n type: math\n - refId: threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: failure_pct\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: threshold\n dashboardUid: a3e1385f-6f03-46d9-908c-34aca0f507a6\n panelId: 14\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: a3e1385f-6f03-46d9-908c-34aca0f507a6\n __panelId__: \"14\"\n description: The {{ index $labels \"trigger_name\" }} for the {{ index $labels \"node_type\" }} app in the {{ index $labels \"namespace\" }} namespace on migration id {{ index $labels \"migration\" }} experienced {{ index $values \"failure_pct\" }}% failures in the last 10 minutes.\n severity: |-\n {{- if (gt $values.failure_pct.Value 50.0) -}}\n critical\n {{- else -}}\n warning\n {{- end -}}\n summary: '{{ index $values \"failure_pct\" }}% fatal errors occurred in {{ index $labels \"namespace\" }} - {{ index $labels \"trigger_name\" }} automation trigger'\n labels:\n gcloud_filter: 'resource.labels.namespace_name=%22{{ index $labels \"namespace\" }}%22%0A%22{{ index $labels \"trigger_name\" }}%22'\n isPaused: false\n - uid: ady2ks9ehbw1sb\n title: Busy task-based automation\n condition: threshold\n data:\n - refId: runs\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by(namespace, node_type, node_name, job, trigger_name, migration, party) (rate(splice_trigger_completed_total{trigger_name!~\"ScanVerdictStoreIngestion|ScanHistoryBackfillingTrigger|AcsSnapshotTrigger|ScanBackfillAggregatesTrigger|TxLogBackfillingTrigger|ReportValidatorLicenseMetricsExportTrigger\"}[5m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: runs\n - refId: threshold\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: runs\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: threshold\n dashboardUid: a3e1385f-6f03-46d9-908c-34aca0f507a6\n panelId: 14\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: a3e1385f-6f03-46d9-908c-34aca0f507a6\n __panelId__: \"14\"\n description: The {{ index $labels \"trigger_name\" }} for the {{ index $labels \"node_type\" }} app in the {{ index $labels \"namespace\" }} namespace on migration id {{ index $labels \"migration\" }} experienced {{ index $values \"runs\" }} runs per second in the last 5 minutes.\n runbook_url: \"\"\n severity: |-\n {{- if and $values.runs (gt $values.runs.Value 2.0) -}}\n critical\n {{- else -}}\n warning\n {{- end -}}\n summary: '{{ index $values \"runs\" }} trigger runs per second occurred in {{ index $labels \"namespace\" }} - {{ index $labels \"trigger_name\" }} automation trigger'\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A%22{{ index \"trigger_name\" }}%22\n isPaused: false\n - uid: edz6eq1kc543ke\n title: Busy polling-based automation\n condition: threshold\n data:\n - refId: runs\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by(namespace, node_type, node_name, job, trigger_name, migration, party) (rate(splice_trigger_iterations_total{trigger_name!~\"ScanVerdictStoreIngestion|ScanHistoryBackfillingTrigger|AcsSnapshotTrigger|ScanBackfillAggregatesTrigger|TxLogBackfillingTrigger\"}[5m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: runs\n - refId: threshold\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: runs\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: threshold\n dashboardUid: a3e1385f-6f03-46d9-908c-34aca0f507a6\n panelId: 14\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: a3e1385f-6f03-46d9-908c-34aca0f507a6\n __panelId__: \"14\"\n description: The {{ index $labels \"trigger_name\" }} for the {{ index $labels \"node_type\" }} app in the {{ index $labels \"namespace\" }} namespace on migration id {{ index $labels \"migration\" }} experienced {{ index $values \"runs\" }} runs per second in the last 5 minutes.\n runbook_url: \"\"\n severity: |-\n {{- if and $values.runs (gt $values.runs.Value 2.0) -}}\n critical\n {{- else -}}\n warning\n {{- end -}}\n summary: '{{ index $values \"runs\" }} trigger runs per second occurred in {{ index $labels \"namespace\" }} - {{ index $labels \"trigger_name\" }} automation trigger'\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A%22{{ index \"trigger_name\" }}%22\n isPaused: false\n - uid: fe12i7xur3eo0d\n title: Backfilling not progressing\n condition: C\n data:\n - refId: Max (rate vs completed)\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by(namespace)(rate(splice_history_backfilling_transaction_count[5m]) > 0 or splice_history_backfilling_completed)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Max (rate vs completed)\n - refId: C\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1e-11\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: [ ]\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Max (rate vs completed)\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n description: \"\"\n runbook_url: \"\"\n summary: History backfilling is not making any progress in {{ index $labels \"namespace\" }}\n labels:\n \"\": \"\"\n isPaused: false\n - uid: bel66uf182ha8e\n title: TxLog backfilling not progressing\n condition: C\n data:\n - refId: Max (rate vs completed)\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: |-\n max by(namespace)(\n rate(splice_history_txlog_backfilling_transaction_count[5m]) > 0 or\n splice_history_txlog_backfilling_completed or\n # TxLog backfilling can't start until history backfilling is completed\n absent(splice_history_backfilling_completed) or\n # Note: \"== bool\" is a boolean operator, \"==\" is a filter\n (splice_history_backfilling_completed == bool 0)\n )\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Max (rate vs completed)\n - refId: C\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1e-11\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: [ ]\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Max (rate vs completed)\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n description: \"\"\n runbook_url: \"\"\n summary: TxLog backfilling is not making any progress in {{ index $labels \"namespace\" }}\n labels:\n \"\": \"\"\n isPaused: false\n - uid: fentlrcbcrsaoa\n title: Delegateless trigger contention\n condition: threshold\n data:\n - refId: total_attempts\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum(rate(splice_trigger_attempted_total{isDsoDelegateTrigger=\"true\", node_type=\"sv\"}[30m])) by (namespace)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total_attempts\n - refId: total_contention\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(splice_trigger_attempted_total{statusCode!~\"OK\", isDsoDelegateTrigger=\"true\", node_type=\"sv\", contentionFailure=\"true\"}[30m])) by (namespace)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total_contention\n - refId: threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: [ ]\n reducer:\n params: [ ]\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: (${total_contention} > 0.2 * ${total_attempts}) && (${total_contention} > 0)\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: math\n # Alert also reports noData when there is no contention.\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: '{{ index $labels \"namespace\" }} had more than 20% contention in the last 30 minutes.'\n isPaused: false\n - uid: fentlrcbcrsaob\n title: Delegateless trigger non-local contention errors\n condition: threshold\n data:\n - refId: total_attempts\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum(rate(splice_trigger_attempted_total{isDsoDelegateTrigger=\"true\", node_type=\"sv\"}[30m])) by (namespace)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total_attempts\n - refId: total_non_local_contention_errors\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(splice_trigger_attempted_total{statusCode!~\"OK\", isDsoDelegateTrigger=\"true\", node_type=\"sv\", contentionFailure=\"true\", errorCodeId!~\"UNKNOWN_CONTRACT_SYNCHRONIZERS|LOCAL_.*\"}[30m])) by (namespace)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total_non_local_contention_errors\n - refId: threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: [ ]\n reducer:\n params: [ ]\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: (${total_non_local_contention_errors} > 0.05 * ${total_attempts}) && (${total_non_local_contention_errors} > 0)\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: math\n # Alert also reports noData when there is no contention.\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: '{{ index $labels \"namespace\" }} had more than 5% non-local contention errors in the last 30 minutes.'\n isPaused: false\n - uid: df3t10f23cm68c\n title: 'Ingestion saturation'\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: histogram_avg(sum by (le, namespace,job,store_name,store_party,synchronizer_id) (rate(splice_store_ingestion_batch_size{namespace=~\"sv\"}[20m])))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 80\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: [ ]\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: OK\n execErrState: OK\n for: 30m\n annotations:\n description: |\n The ingestion batch size has exceeded 80 entries/batch for a prolonged time in {{ index $labels \"namespace\" }}'s {{ index $labels \"node_type\" }}.\n This might mean that ingestion is close to not being able to keep up with ledger activity.\n This can also happen when an SV was just onboarded and is catching up with the ACS.\n summary: The ingestion batch size has exceeded the expected size for a prolonged time.\n isPaused: false\n - uid: bgh7m2a8cy3x4a\n title: Automation service unhealthy\n condition: threshold\n data:\n - refId: health\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by(namespace, node_type, automation_service) (splice_automation_background_service_health)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: health\n - refId: threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - threshold\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: health\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: threshold\n dashboardUid: a3e1385f-6f03-46d9-908c-34aca0f507a6\n panelId: 32\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: a3e1385f-6f03-46d9-908c-34aca0f507a6\n __panelId__: \"32\"\n description: The {{ index $labels \"automation_service\" }} background service of the {{ index $labels \"node_type\" }} app in the {{ index $labels \"namespace\" }} namespace has been continuously reporting unhealthy for more than 5 minutes.\n summary: '{{ index $labels \"namespace\" }} - {{ index $labels \"node_type\" }} - {{ index $labels \"automation_service\" }} automation service is unhealthy'\n labels:\n gcloud_filter: 'resource.labels.namespace_name=%22{{ index $labels \"namespace\" }}%22'\n isPaused: false\n", - "cantonbft_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: canton-bft\n folder: canton\n interval: 1m\n rules:\n - uid: c7d1e9f4a6b2\n title: CantonBft Blacklisted Member\n condition: blacklisted_threshold\n data:\n - refId: blacklisted_epochs\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (blacklist_sequencer)(daml_sequencer_bftordering_blacklist_sequencer)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: blacklisted_epochs\n - refId: blacklisted_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: blacklisted_epochs\n hide: false\n refId: blacklisted_threshold\n type: threshold\n dashboardUid: \"\"\n panelId: 0\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: The CantonBft member {{ $labels.blacklist_sequencer }} is blacklisted.\n severity: warning\n summary: CantonBft member {{ $labels.blacklist_sequencer }} is blacklisted for {{ index $values \"blacklisted_epochs\" }} epochs\n labels: {}\n isPaused: false\n - uid: e2a9c7b4f1d6\n title: CantonBft Ingress Requests Queued\n condition: mempool_size_threshold\n data:\n - refId: mempool_size\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (namespace, job)(daml_sequencer_bftordering_ingress_requests_queued)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: mempool_size\n - refId: mempool_size_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - $CANTON_BFT_MEMPOOL_SIZE_THRESHOLD\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: mempool_size\n hide: false\n refId: mempool_size_threshold\n type: threshold\n dashboardUid: \"\"\n panelId: 0\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: The number of requests in the mempool of CantonBft for the namespace {{ $labels.namespace }} is above the configured threshold.\n severity: warning\n summary: CantonBft mempool size {{ index $values \"mempool_size\" }} in namespace {{ $labels.namespace }}\n labels: {}\n isPaused: false\n", + "cantonbft_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: canton-bft\n folder: canton\n interval: 1m\n rules:\n - uid: c7d1e9f4a6b2\n title: CantonBft Blacklisted Member\n condition: blacklisted_threshold\n data:\n - refId: blacklisted_epochs\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (blacklist_sequencer)(daml_sequencer_bftordering_blacklist_sequencer)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: blacklisted_epochs\n - refId: blacklisted_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: blacklisted_epochs\n hide: false\n refId: blacklisted_threshold\n type: threshold\n dashboardUid: \"\"\n panelId: 0\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: The CantonBft member {{ $labels.blacklist_sequencer }} is blacklisted.\n severity: warning\n summary: CantonBft member {{ $labels.blacklist_sequencer }} is blacklisted for {{ index $values \"blacklisted_epochs\" }} epochs\n labels: {}\n isPaused: false\n - uid: e2a9c7b4f1d6\n title: CantonBft Ingress Requests Queued\n condition: mempool_size_threshold\n data:\n - refId: mempool_size\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (namespace, job)(daml_sequencer_bftordering_ingress_requests_queued)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: mempool_size\n - refId: mempool_size_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 100\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: mempool_size\n hide: false\n refId: mempool_size_threshold\n type: threshold\n dashboardUid: \"\"\n panelId: 0\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: The number of requests in the mempool of CantonBft for the namespace {{ $labels.namespace }} is above the configured threshold.\n severity: warning\n summary: CantonBft mempool size {{ index $values \"mempool_size\" }} in namespace {{ $labels.namespace }}\n labels: {}\n isPaused: false\n", "cometbft_deleted_alerts.yaml": "apiVersion: 1\ndeleteRules:\n - orgId: 1\n # Cometbft Consensus\n uid: fa4b8a18-e4be-4d19-a5af-90c7326fd935\n - orgId: 1\n # Cometbft Height Not Advancing\n uid: a3b47fca-0982-491b-80bb-0c70f04dcd8b\n - orgId: 1\n # Cometbft Not Voting\n uid: a3f23df0-40d5-41d1-8b4b-03aa8c62c030\n - orgId: 1\n # Reached Expected Maximum CometBFT Block Rate\n uid: adl2npxte6u4gd\n - orgId: 1\n # CometBFT is not pruning old blocks\n uid: ddlrp7f1f8l4wf\n", "deleted_alerts.yaml": "apiVersion: 1\ndeleteRules:\n - orgId: 1\n # SV Status - mediator -falling behind\n uid: f15539f2-f6ea-4de4-8f7c-c56434d5bd52\n - orgId: 1\n # wasted traffic (sustained)\n uid: adw5rd048zf9cb\n - orgId: 1\n # wasted traffic\n uid: adw5rd048zf9ca\n - orgId: 1\n # Confirmation Requests By Member (replaced by sequencer caps, #2917)\n uid: 88b8827c8d09\n - orgId: 1\n # Total Confirmation Requests (replaced by sequencer caps, #2917)\n uid: 5dcddc9a5487\n - orgId: 1\n uid: aescandisagree1\ndeleteContactPoints:\n - uid: grafana-default-email\n orgId: 1\n", "deployment_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: deployments\n folder: canton-network\n interval: 1m\n rules:\n - uid: adkhl6u18pqtce\n title: Failing Stacks\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n exemplar: true\n expr: splice_deployment_pulumi_stack_status{state=\"failed\"}\n format: time_series\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: stacks_active\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: D\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - D\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: QP_wDqDnz\n panelId: 11\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"11\"\n description: The pulumi operator failed to update the {{ $labels.stack }} stack. Check the logs for the deployment.\n runbook_url: \"\"\n severity: critical\n summary: '{{ $labels.stack }} stack failed to update'\n labels:\n \"\": \"\"\n isPaused: false\n - uid: bdndg5g3x4kxsf\n title: Deployments running\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: splice_deployment_pulumi_stack_condition{type=\"Reconciling\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n dashboardUid: QP_wDqDnz\n panelId: 27\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"27\"\n description: A deployment is currently running for the {{ $labels.stack }} stack.\n runbook_url: \"\"\n severity: info\n summary: Stack {{ $labels.stack }} is being updated\n labels:\n \"\": \"\"\n isPaused: false\n", diff --git a/cluster/pulumi/observability/src/observability.ts b/cluster/pulumi/observability/src/observability.ts index a2f0923f95..9f37f5f68d 100644 --- a/cluster/pulumi/observability/src/observability.ts +++ b/cluster/pulumi/observability/src/observability.ts @@ -11,7 +11,6 @@ import { CLUSTER_NAME, clusterProdLike, commandScriptPath, - createVolumeSnapshot, DecentralizedSynchronizerUpgradeConfig, ExactNamespace, GCP_PROJECT, @@ -993,7 +992,7 @@ function createGrafanaAlerting(namespace: Input) { 'cantonbft_alerts.yaml': readGrafanaAlertingFile( 'cantonbft_alerts.yaml' ).replaceAll( - '$BFT_ORDERING_INGRESS_REQUESTS_QUEUED_THRESHOLD', + '$CANTON_BFT_MEMPOOL_SIZE_THRESHOLD', monitoringConfig.alerting.alerts.cantonBft.mempoolMaxSizeThreshold.toString() ), } From 1662ad21bad5c2ad3a47f676602c0c25f8290b05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Wed, 15 Jul 2026 10:29:42 +0200 Subject: [PATCH 040/329] implement split-SV deployment (#6396) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Błażejewski --- cluster/expected/canton-network/expected.json | 44 ----- cluster/pulumi/canton-network/src/dso.ts | 152 ++---------------- cluster/pulumi/canton-network/src/index.ts | 4 +- .../canton-network/src/installCluster.ts | 57 ++----- cluster/pulumi/common-sv/src/config.ts | 2 +- cluster/pulumi/common-sv/src/sv.ts | 124 +++++++++++++- .../common/src/config/migrationSchema.ts | 1 + cluster/pulumi/sv/src/installNode.ts | 9 +- cluster/scripts/utils.source | 5 + 9 files changed, 159 insertions(+), 239 deletions(-) diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index 62d864a9ac..0a40202ff3 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -413,50 +413,6 @@ "version": "0.3.21" } } - }, - "disableOnboardingParticipantPromotionDelay": false, - "expectedValidatorOnboardings": [ - { - "expiresIn": "24h", - "name": "splitwell2", - "secret": "splitwellsecret2" - }, - { - "expiresIn": "24h", - "name": "validator12", - "secret": "validator1secret2" - } - ], - "identitiesBackupLocation": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "bucket": { - "bucketName": "da-cn-data-dumps", - "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", - "projectId": "da-cn-devnet", - "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" - } - } - }, - "isDevNet": false, - "periodicBackupConfig": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "backupInterval": "10m", - "location": { - "bucket": { - "bucketName": "da-cn-data-dumps", - "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", - "projectId": "da-cn-devnet", - "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" - } - } - } - }, - "splitPostgresInstances": true, - "topupConfig": { - "minTopupInterval": "1m", - "targetThroughput": 0 } }, "name": "dso", diff --git a/cluster/pulumi/canton-network/src/dso.ts b/cluster/pulumi/canton-network/src/dso.ts index 457e928b73..dcecc2fa0c 100644 --- a/cluster/pulumi/canton-network/src/dso.ts +++ b/cluster/pulumi/canton-network/src/dso.ts @@ -2,44 +2,24 @@ // SPDX-License-Identifier: Apache-2.0 import * as pulumi from '@pulumi/pulumi'; import { - activeVersion, Auth0Client, - BucketConfig, - BucketLocation, - BootstrappingDumpConfig, CnInput, - config, DecentralizedSynchronizerMigrationConfig, - ExpectedValidatorOnboarding, - SvCometBftGovernanceKey, - svCometBftGovernanceKeyFromSecret, - SvIdKey, - svKeyFromSecret, - ValidatorTopupConfig, + exactNamespace, } from '@canton-network/splice-pulumi-common'; import { - approvedSvIdentities, configForSv, coreSvsToDeploy, - initialRound, - StaticCometBftConfigWithNodeName, StaticSvConfig, - SvOnboarding, } from '@canton-network/splice-pulumi-common-sv'; -import { InstalledSv, installSvNode } from '@canton-network/splice-pulumi-common-sv/src/sv'; +import { + InstalledSv, + installSvNodeStandalone, +} from '@canton-network/splice-pulumi-common-sv/src/sv'; interface DsoArgs { auth0Client: Auth0Client; - expectedValidatorOnboardings: ExpectedValidatorOnboarding[]; // Only used by the sv1 - isDevNet: boolean; - periodicBackupConfig?: BucketConfig; - identitiesBackupLocation: BucketLocation; - bootstrappingDumpConfig?: BootstrappingDumpConfig; - topupConfig?: ValidatorTopupConfig; - splitPostgresInstances: boolean; decentralizedSynchronizerUpgradeConfig: DecentralizedSynchronizerMigrationConfig; - onboardingPollingInterval?: string; - disableOnboardingParticipantPromotionDelay: boolean; } export class Dso extends pulumi.ComponentResource { @@ -47,59 +27,17 @@ export class Dso extends pulumi.ComponentResource { sv1: Promise; allSvs: Promise; - private joinViaSv1(sv1: pulumi.Resource, keys: CnInput): SvOnboarding { - return { - type: 'join-with-key', - sponsorApiUrl: `http://sv-app.sv-1:5014`, - sponsorScanUrl: `http://scan-app.sv-1:5012`, - sponsorRelease: sv1, - keys, - }; - } - private async installSvNode( svConf: StaticSvConfig, - onboarding: SvOnboarding, - nodeConfigs: { - sv1: StaticCometBftConfigWithNodeName; - peers: StaticCometBftConfigWithNodeName[]; - }, - expectedValidatorOnboardings: ExpectedValidatorOnboarding[], - isFirstSv = false, - cometBftGovernanceKey: CnInput | undefined = undefined, extraDependsOn: CnInput[] = [] - ) { + ): Promise { + const xns = exactNamespace(svConf.nodeName, true); const dynamicConfig = configForSv(svConf.nodeName); - return installSvNode( - { - isFirstSv, - nodeName: svConf.nodeName, - ingressName: svConf.ingressName, - onboardingName: svConf.onboardingName, - nodeConfigs, - cometBft: svConf.cometBft, - validatorWalletUser: svConf.validatorWalletUser, - auth0ValidatorAppName: svConf.auth0ValidatorAppName, - auth0SvAppName: svConf.auth0SvAppName, - onboarding, - auth0Client: this.args.auth0Client, - expectedValidatorOnboardings, - isDevNet: this.args.isDevNet, - periodicBackupConfig: this.args.periodicBackupConfig, - identitiesBackupLocation: this.args.identitiesBackupLocation, - bootstrappingDumpConfig: this.args.bootstrappingDumpConfig, - topupConfig: this.args.topupConfig, - splitPostgresInstances: this.args.splitPostgresInstances, - disableOnboardingParticipantPromotionDelay: - this.args.disableOnboardingParticipantPromotionDelay, - onboardingPollingInterval: this.args.onboardingPollingInterval, - sweep: svConf.sweep, - cometBftGovernanceKey, - initialRound: initialRound?.toString(), - version: dynamicConfig.versionOverride ?? activeVersion, - ...dynamicConfig, - }, - this.args.decentralizedSynchronizerUpgradeConfig, + return await installSvNodeStandalone( + xns, + svConf, + dynamicConfig, + this.args.auth0Client, extraDependsOn ); } @@ -108,60 +46,7 @@ export class Dso extends pulumi.ComponentResource { const relevantSvConfs = coreSvsToDeploy; const [sv1Conf, ...restSvConfs] = relevantSvConfs; - const svIdKeys = restSvConfs.reduce>>((acc, conf) => { - const secretName = conf.svIdKeySecretName ?? conf.nodeName.replaceAll('-', '') + '-id'; - return { - ...acc, - [conf.onboardingName]: svKeyFromSecret(secretName), - }; - }, {}); - - const cometBftGovernanceKeys = relevantSvConfs - .filter(conf => configForSv(conf.nodeName)?.participant?.kms) - .reduce>>((acc, conf) => { - const secretName = - conf.cometBftGovernanceKeySecretName ?? - conf.nodeName.replaceAll('-', '') + '-cometbft-governance-key'; - return { - ...acc, - [conf.onboardingName]: svCometBftGovernanceKeyFromSecret(secretName), - }; - }, {}); - - const sv1CometBftConf = { - ...sv1Conf.cometBft, - nodeName: sv1Conf.nodeName, - ingressName: sv1Conf.ingressName, - }; - const peerCometBftConfs = restSvConfs.map(conf => ({ - ...conf.cometBft, - nodeName: conf.nodeName, - ingressName: conf.ingressName, - })); - - const sv1SvRewardWeightBps = (() => { - const found = approvedSvIdentities().find( - identity => identity.name == sv1Conf.onboardingName - ); - return found ? found.rewardWeightBps : 10000; - })(); - - const sv1 = await this.installSvNode( - sv1Conf, - { - type: 'found-dso', - sv1SvRewardWeightBps, - roundZeroDuration: config.optionalEnv('ROUND_ZERO_DURATION'), - initialRound: initialRound?.toString(), - }, - { - sv1: sv1CometBftConf, - peers: peerCometBftConfs, - }, - this.args.expectedValidatorOnboardings, - true, - cometBftGovernanceKeys[sv1Conf.onboardingName] - ); + const sv1 = await this.installSvNode(sv1Conf); // TODO(#893): long-term CantonBFT deployments should be robust enough to onboard in parallel again? const incrementalOnboarding = @@ -177,19 +62,8 @@ export class Dso extends pulumi.ComponentResource { } const [conf, ...remainingConfigs] = configs; - const onboarding: SvOnboarding = this.joinViaSv1(sv1.svApp, svIdKeys[conf.onboardingName]); - const cometBft = { - sv1: sv1CometBftConf, - peers: peerCometBftConfs.filter(c => c.id !== conf.cometBft.id), // remove self from peer list - }; - const newSv = await this.installSvNode( conf, - onboarding, - cometBft, - [], - false, - cometBftGovernanceKeys[conf.onboardingName], incrementalOnboarding ? previousSvs.map(sv => sv.svApp) : [] ); return installSvNodes(remainingConfigs, [...previousSvs, newSv]); diff --git a/cluster/pulumi/canton-network/src/index.ts b/cluster/pulumi/canton-network/src/index.ts index 69b6466f48..9c8e97c3a7 100644 --- a/cluster/pulumi/canton-network/src/index.ts +++ b/cluster/pulumi/canton-network/src/index.ts @@ -11,11 +11,9 @@ async function auth0CacheAndInstallCluster(auth0Fetch: Auth0Fetch) { installClusterVersion(); - const cluster = await installCluster(auth0Fetch); + await installCluster(auth0Fetch); await auth0Fetch.saveAuth0Cache(); - - return cluster; } async function main() { diff --git a/cluster/pulumi/canton-network/src/installCluster.ts b/cluster/pulumi/canton-network/src/installCluster.ts index ae2cd3fdf2..5dde062edc 100644 --- a/cluster/pulumi/canton-network/src/installCluster.ts +++ b/cluster/pulumi/canton-network/src/installCluster.ts @@ -4,20 +4,9 @@ import { Auth0Client, config, DecentralizedSynchronizerUpgradeConfig, - ExpectedValidatorOnboarding, isDevNet, - svOnboardingPollingInterval, - svValidatorTopupConfig, + spliceConfig, } from '@canton-network/splice-pulumi-common'; -import { readBackupConfig } from '@canton-network/splice-pulumi-common-validator/src/backup'; -import { - mustInstallSplitwell, - mustInstallValidator1, - splitwellOnboarding, - standaloneValidatorOnboarding, - validator1Onboarding, -} from '@canton-network/splice-pulumi-common-validator/src/validators'; -import { SplitPostgresInstances } from '@canton-network/splice-pulumi-common/src/config/configs'; import { Resource } from '@pulumi/pulumi'; import { activeVersion } from '../../common'; @@ -31,45 +20,21 @@ console.error(`Launching with isDevNet: ${isDevNet}`); const enableChaosMesh = config.envFlag('ENABLE_CHAOS_MESH'); -const disableOnboardingParticipantPromotionDelay = config.envFlag( - 'DISABLE_ONBOARDING_PARTICIPANT_PROMOTION_DELAY', - false -); - -export async function installCluster( - auth0Client: Auth0Client -): Promise<{ dso: Dso; validator1?: Resource }> { +export async function installCluster(auth0Client: Auth0Client): Promise { console.error( activeVersion.type === 'local' ? 'Using locally built charts by default' : `Using charts from the container registry by default, version ${activeVersion.version}` ); - const backupConfig = await readBackupConfig(); - const expectedValidatorOnboardings: ExpectedValidatorOnboarding[] = []; - if (mustInstallSplitwell) { - expectedValidatorOnboardings.push(splitwellOnboarding); - } - if (mustInstallValidator1) { - expectedValidatorOnboardings.push(validator1Onboarding); - } - if (standaloneValidatorOnboarding) { - expectedValidatorOnboardings.push(standaloneValidatorOnboarding); - } - - const dso = new Dso('dso', { - auth0Client, - expectedValidatorOnboardings, - isDevNet, - ...backupConfig, - topupConfig: svValidatorTopupConfig, - splitPostgresInstances: SplitPostgresInstances, - decentralizedSynchronizerUpgradeConfig: DecentralizedSynchronizerUpgradeConfig, - onboardingPollingInterval: svOnboardingPollingInterval, - disableOnboardingParticipantPromotionDelay, - }); + const dso = spliceConfig.configuration.synchronizerMigration.splitSvDeploymentEnabled + ? undefined + : new Dso('dso', { + auth0Client, + decentralizedSynchronizerUpgradeConfig: DecentralizedSynchronizerUpgradeConfig, + }); - const allSvs = await dso.allSvs; + const allSvs = (await dso?.allSvs) ?? []; const svDependencies = allSvs.flatMap(sv => [sv.scan, sv.svApp, sv.validatorApp, sv.ingress]); @@ -78,8 +43,4 @@ export async function installCluster( if (enableChaosMesh) { installChaosMesh({ dependsOn: svDependencies }); } - - return { - dso, - }; } diff --git a/cluster/pulumi/common-sv/src/config.ts b/cluster/pulumi/common-sv/src/config.ts index e7dc0e549f..275c06f91c 100644 --- a/cluster/pulumi/common-sv/src/config.ts +++ b/cluster/pulumi/common-sv/src/config.ts @@ -36,7 +36,7 @@ export type SvOnboarding = | { type: 'join-with-key'; keys: CnInput; - sponsorRelease: pulumi.Resource; + sponsorRelease?: pulumi.Resource; sponsorApiUrl: string; sponsorScanUrl: string; }; diff --git a/cluster/pulumi/common-sv/src/sv.ts b/cluster/pulumi/common-sv/src/sv.ts index 61422d7de9..ae605556ed 100644 --- a/cluster/pulumi/common-sv/src/sv.ts +++ b/cluster/pulumi/common-sv/src/sv.ts @@ -4,17 +4,20 @@ import * as postgres from '@canton-network/splice-pulumi-common/src/postgres'; import * as k8s from '@pulumi/kubernetes'; import * as pulumi from '@pulumi/pulumi'; import { + activeVersion, ansDomainPrefix, appsAffinityAndTolerations, + Auth0Client, btoa, ChartValues, CLUSTER_BASENAME, CLUSTER_HOSTNAME, CnInput, + config as envConfig, daContactPoint, DecentralizedSynchronizerMigrationConfig, + DecentralizedSynchronizerUpgradeConfig, ExactNamespace, - exactNamespace, failOnAppVersionMismatch, fetchAndInstallParticipantBootstrapDump, getAdditionalJvmOptions, @@ -26,14 +29,19 @@ import { installSpliceHelmChart, installSvAppSecrets, installValidatorOnboardingSecret, + isDevNet, networkWideConfig, participantBootstrapDumpSecretName, PersistenceConfig, persistentHeapDumpsPvc, sanitizedForPostgres, spliceInstanceNames, + svCometBftGovernanceKeyFromSecret, svCometBftGovernanceKeySecret, SvIdKey, + svKeyFromSecret, + svOnboardingPollingInterval, + svValidatorTopupConfig, svUserIds, validatorOnboardingSecretName, } from '@canton-network/splice-pulumi-common'; @@ -41,15 +49,27 @@ import { approvedSvIdentities, CantonBftSynchronizerNode, configForSv, + coreSvsToDeploy, DecentralizedSynchronizerNode, + initialRound, installScanBulkStorage, installSvLoopback, + SingleSvConfiguration, + StaticSvConfig, SynchronizerNodes, valuesForSvApp, valuesForSvValidatorApp, } from '@canton-network/splice-pulumi-common-sv'; import { SvConfig, svsConfig } from '@canton-network/splice-pulumi-common-sv/src/config'; +import { readBackupConfig } from '@canton-network/splice-pulumi-common-validator/src/backup'; import { installValidatorApp } from '@canton-network/splice-pulumi-common-validator/src/validator'; +import { + mustInstallSplitwell, + mustInstallValidator1, + splitwellOnboarding, + standaloneValidatorOnboarding, + validator1Onboarding, +} from '@canton-network/splice-pulumi-common-validator/src/validators'; import { delegatelessAutomationExpectedTaskDuration, delegatelessAutomationExpiredRewardCouponBatchSize, @@ -60,15 +80,113 @@ import { installBucketSecret, } from '@canton-network/splice-pulumi-common/src/buckets'; import { spliceConfig } from '@canton-network/splice-pulumi-common/src/config/config'; +import { SplitPostgresInstances } from '@canton-network/splice-pulumi-common/src/config/configs'; import { initialAmuletPrice } from '@canton-network/splice-pulumi-common/src/initialAmuletPrice'; import { Postgres } from '@canton-network/splice-pulumi-common/src/postgres'; import { installRateLimits } from '@canton-network/splice-pulumi-common/src/ratelimit/rateLimit'; import { topologySnapshotConfig } from '@canton-network/splice-pulumi-common/src/topology-snapshot'; import { Resource } from '@pulumi/pulumi'; +import pick from 'lodash/pick'; import { configureScanBigQuery } from './bigQuery'; import { installInfo } from './info'; +export async function installSvNodeStandalone( + xns: ExactNamespace, + staticConfig: StaticSvConfig, + config: SingleSvConfiguration, + auth0Client: Auth0Client, + extraDependsOn: CnInput[] = [] +): Promise { + const nodeName = staticConfig.nodeName; + const [sv1StaticConfig, ...otherSvsStaticConfigs] = coreSvsToDeploy; + const isFoundingSv = nodeName === sv1StaticConfig.nodeName; + const disableOnboardingParticipantPromotionDelay = envConfig.envFlag( + 'DISABLE_ONBOARDING_PARTICIPANT_PROMOTION_DELAY', + false + ); + return await installSvNode( + xns, + { + isFirstSv: isFoundingSv, + ...pick(staticConfig, [ + 'nodeName', + 'ingressName', + 'onboardingName', + 'cometBft', + 'validatorWalletUser', + 'auth0ValidatorAppName', + 'auth0SvAppName', + 'sweep', + ]), + nodeConfigs: { + sv1: { + ...sv1StaticConfig.cometBft, + ...pick(sv1StaticConfig, ['nodeName', 'ingressName']), + }, + peers: otherSvsStaticConfigs + .filter(config => config.nodeName !== nodeName) + .map(config => ({ + ...config.cometBft, + ...pick(config, ['nodeName', 'ingressName']), + })), + }, + onboarding: isFoundingSv + ? { + type: 'found-dso', + sv1SvRewardWeightBps: + approvedSvIdentities().find( + identity => identity.name == sv1StaticConfig.onboardingName + )?.rewardWeightBps ?? 10_000, + roundZeroDuration: envConfig.optionalEnv('ROUND_ZERO_DURATION'), + initialRound: initialRound?.toString(), + } + : { + type: 'join-with-key', + sponsorApiUrl: `http://sv-app.sv-1:5014`, + sponsorScanUrl: `http://scan-app.sv-1:5012`, + keys: svKeyFromSecret( + staticConfig.svIdKeySecretName ?? `${nodeName.replaceAll('-', '')}-id` + ), + }, + auth0Client, + expectedValidatorOnboardings: isFoundingSv + ? [ + ...(function* () { + if (mustInstallSplitwell) { + yield splitwellOnboarding; + } + if (mustInstallValidator1) { + yield validator1Onboarding; + } + if (standaloneValidatorOnboarding !== undefined) { + yield standaloneValidatorOnboarding; + } + })(), + ] + : [], + isDevNet, + ...(await readBackupConfig()), + topupConfig: svValidatorTopupConfig, + splitPostgresInstances: SplitPostgresInstances, + disableOnboardingParticipantPromotionDelay, + onboardingPollingInterval: svOnboardingPollingInterval, + cometBftGovernanceKey: + config.participant?.kms !== undefined + ? svCometBftGovernanceKeyFromSecret( + staticConfig.cometBftGovernanceKeySecretName ?? + `${nodeName.replaceAll('-', '')}-cometbft-governance-key` + ) + : undefined, + initialRound: initialRound?.toString(), + version: config.versionOverride ?? activeVersion, + ...config, + }, + DecentralizedSynchronizerUpgradeConfig, + extraDependsOn + ); +} + export function installSvKeySecret( xns: ExactNamespace, keys: CnInput @@ -124,11 +242,11 @@ export type InstalledSv = { }; export async function installSvNode( + xns: ExactNamespace, baseConfig: SvConfig, decentralizedSynchronizerUpgradeConfig: DecentralizedSynchronizerMigrationConfig, extraDependsOn: CnInput[] = [] ): Promise { - const xns = exactNamespace(baseConfig.nodeName, true); const loopback = installSvLoopback(xns, decentralizedSynchronizerUpgradeConfig.usesCometbft()); const imagePullDeps = imagePullSecret(xns); @@ -199,7 +317,7 @@ export async function installSvNode( ) .concat( config.onboarding.type == 'join-with-key' && - config.onboarding.sponsorRelease && + config.onboarding.sponsorRelease !== undefined && spliceConfig.pulumiProjectConfig.interAppsDependencies ? [config.onboarding.sponsorRelease] : [] diff --git a/cluster/pulumi/common/src/config/migrationSchema.ts b/cluster/pulumi/common/src/config/migrationSchema.ts index 879475b52f..b1ffcf27a7 100644 --- a/cluster/pulumi/common/src/config/migrationSchema.ts +++ b/cluster/pulumi/common/src/config/migrationSchema.ts @@ -60,5 +60,6 @@ export const SynchronizerMigrationSchema = z activeDatabaseId: z.number().optional(), attachPvc: z.boolean().default(true), frozenMigrationId: z.number(), + splitSvDeploymentEnabled: z.boolean().default(false), }) .strict(); diff --git a/cluster/pulumi/sv/src/installNode.ts b/cluster/pulumi/sv/src/installNode.ts index a5fea169f3..9415760155 100644 --- a/cluster/pulumi/sv/src/installNode.ts +++ b/cluster/pulumi/sv/src/installNode.ts @@ -7,6 +7,7 @@ import { exactNamespace, imagePullSecretWithNonDefaultServiceAccount, installLedgerApiUserSecret, + spliceConfig, } from '@canton-network/splice-pulumi-common'; import { configForSv, @@ -14,18 +15,24 @@ import { svConfigs, svRunbookConfig, } from '@canton-network/splice-pulumi-common-sv'; +import { installSvNodeStandalone } from '@canton-network/splice-pulumi-common-sv/src/sv'; import { installParticipant } from './participant'; export async function installNode(sv: string, auth0Client: Auth0Client): Promise { + const splitSvDeploymentEnabled = + spliceConfig.configuration.synchronizerMigration.splitSvDeploymentEnabled; const staticConfig = findStaticConfigOrFail(sv); const config = configForSv(staticConfig.nodeName); - const xns = exactNamespace(staticConfig.nodeName, true, true); + const xns = exactNamespace(staticConfig.nodeName, true, !splitSvDeploymentEnabled); const serviceAccountName = 'sv'; const imagePullDeps = imagePullSecretWithNonDefaultServiceAccount(xns, serviceAccountName); const auth0Config = auth0Client.getCfg(); const ledgerApiUserSecret = installLedgerApiUserSecret(auth0Client, xns, 'sv', 'sv'); const ledgerApiUserSecretSource = auth0UserNameEnvVarSource('sv', true); + if (splitSvDeploymentEnabled && staticConfig.nodeName !== svRunbookConfig.nodeName) { + await installSvNodeStandalone(xns, staticConfig, config, auth0Client); + } await installParticipant( { xns, diff --git a/cluster/scripts/utils.source b/cluster/scripts/utils.source index b870b56410..0018dc9f2d 100755 --- a/cluster/scripts/utils.source +++ b/cluster/scripts/utils.source @@ -33,6 +33,9 @@ function get_cloudsql_id() { function get_stack_for_namespace_component() { local namespace=$1 local component=$2 + + local split_sv_deployment + split_sv_deployment=$(get_resolved_config | yq '.synchronizerMigration.splitSvDeploymentEnabled // false') local stack="" if [[ "${namespace}" =~ sv.* ]]; then @@ -42,6 +45,8 @@ function get_stack_for_namespace_component() { stack="sv-canton" elif [[ "${component}" == "mediator" ]]; then stack="sv-canton" + elif [[ "${split_sv_deployment}" == "true" ]]; then + stack="sv" else stack="canton-network" fi From 28514e2223cd8327c67a849da1274583b43a793b Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Wed, 15 Jul 2026 11:05:37 +0200 Subject: [PATCH 041/329] Enable dabft for the upgrade during the LSU (#6037) * Enable dabft for the upgrade during the LSU [static] Signed-off-by: Nicu Reut --- ...unbookSvPreflightIntegrationTestBase.scala | 34 ----- build-tools/cncluster | 1 - .../lib/hard-domain-migration-commands | 137 ------------------ .../lib/logical-synchronizer-upgrade-commands | 105 ++++++++++++++ cluster/scripts/find-recent-backup.sh | 19 ++- cluster/scripts/node-backup.sh | 22 ++- cluster/scripts/node-restore.sh | 33 ++++- 7 files changed, 170 insertions(+), 181 deletions(-) delete mode 100644 build-tools/lib/hard-domain-migration-commands diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/runbook/RunbookSvPreflightIntegrationTestBase.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/runbook/RunbookSvPreflightIntegrationTestBase.scala index 9425e906d0..3ff56d669b 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/runbook/RunbookSvPreflightIntegrationTestBase.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/runbook/RunbookSvPreflightIntegrationTestBase.scala @@ -52,40 +52,6 @@ abstract class RunbookSvPreflightIntegrationTestBase } } - "CometBFT is working" in { _ => - val svUiUrl = s"https://sv.sv.${sys.env("NETWORK_APPS_ADDRESS")}/"; - - withFrontEnd("sv") { implicit webDriver => - actAndCheck( - s"Logging in to SV UI at: ${svUiUrl}", { - completeAuth0LoginWithAuthorization( - svUiUrl, - svUsername, - svPassword, - () => find(id("logout-button")) should not be empty withClue "'Logout' button", - ) - - eventuallyClickOn(id("information-tab-cometBft-debug")) - }, - )( - s"We see all other SVs as peers", - _ => { - inside(find(id("comet-bft-debug-network"))) { case Some(e) => - if (isDevNet) { - forAll(Range(1, 5)) { _ => - e.text should include(s"\"moniker\": \"${getSvName(1)}\"") - } - } else { - forAll(Range(1, 2)) { _ => - e.text should include(s"\"moniker\": \"Digital-Asset-2\"") - } - } - } - }, - ) - } - } - "The SV can log in to their wallet" in { implicit env => withFrontEnd("sv") { implicit webDriver => actAndCheck( diff --git a/build-tools/cncluster b/build-tools/cncluster index 23c58d86e0..706f585319 100755 --- a/build-tools/cncluster +++ b/build-tools/cncluster @@ -312,7 +312,6 @@ declare -A subcommand_whitelist source "${TOOLS_LIB}/pulumi-helpers" source "${TOOLS_LIB}/pulumi-commands" -source "${TOOLS_LIB}/hard-domain-migration-commands" source "${TOOLS_LIB}/logical-synchronizer-upgrade-commands" source "${TOOLS_LIB}/disaster-recovery-commands" diff --git a/build-tools/lib/hard-domain-migration-commands b/build-tools/lib/hard-domain-migration-commands deleted file mode 100644 index c17efbec6c..0000000000 --- a/build-tools/lib/hard-domain-migration-commands +++ /dev/null @@ -1,137 +0,0 @@ -# -*- shell-script -*- - -subcommand_whitelist[hard_domain_migration_trigger]='Propose a (nearly immediate) hard domain migration start and vote with SVs 1-4' - -function subcmd_hard_domain_migration_trigger() { - _prompt_to_confirm - _cluster_must_exist - - subcmd_preflight_global_domain_upgrade -} - -subcommand_whitelist[update_config]='Upgrade the hard domain migration config' - -function subcmd_update_config() { - local migration_type - local migration_id - if [ "$#" -ge 2 ]; then - migration_type="$1" - migration_id="$2" - shift 2 - else - _error "Usage: $0 $SCRIPTNAME update_config [] []" - fi - local migration_config - migration_config=$(cat < [] []" - fi - git_reference="$1" - shift - migration_config+=$(cat <] [migration]" - fi - # yq doesn't like the version being unquoted so we quote it here - _update_cluster_config "\"$1\"" "synchronizerMigration.$2.version" - if [ -z "${TARGET_CLUSTER-}" ]; then - cluster_directory="$(pwd)" - else - cluster_directory="${DEPLOYMENT_DIR}/${TARGET_CLUSTER}" - fi -} - -subcommand_whitelist[update_active_version]='Upgrade the version of the active migration id' - -function subcmd_update_active_version() { - if [ "$#" -ne 1 ]; then - _error "Usage: $0 $SCRIPTNAME []" - fi - update_version "$1" "active" -} - -subcommand_whitelist[update_upgrade_version]='Upgrade the version of the upgrade migration id' - -function subcmd_update_upgrade_version() { - if [ "$#" -ne 1 ]; then - _error "Usage: $0 $SCRIPTNAME []" - fi - if [ -z "${TARGET_CLUSTER-}" ]; then - configFile="config.yaml" - else - configFile="${DEPLOYMENT_DIR}/${TARGET_CLUSTER}/config.yaml" - fi - if yq -e '.synchronizerMigration.upgrade' "$configFile" > /dev/null 2>&1 - then - update_version "$1" "upgrade" - else - echo "No upgrade version" - fi -} - -subcommand_whitelist[update_config_to_migrate]='Upgrade the cluster config.yaml to migrate cluster' - -function subcmd_update_config_to_migrate() { - local configFile - if [ -z "${TARGET_CLUSTER-}" ]; then - configFile="config.yaml" - else - configFile="${DEPLOYMENT_DIR}/${TARGET_CLUSTER}/config.yaml" - fi - - yq e 'with(.synchronizerMigration; .legacy = .active | .active = .upgrade | del(.upgrade))' -i $configFile - yq e '.synchronizerMigration.active.migratingFrom = 0' -i $configFile - yq e 'del(.synchronizerMigration.legacy.releaseReference)' -i $configFile - "${SPLICE_ROOT}/cluster/scripts/resolve-config.sh" -} - -subcommand_whitelist[update_config_to_archive_legacy_migration]='Upgrade the cluster config.yaml to archive legacy migration' - -function subcmd_update_config_to_archive_legacy_migration() { - local configFile - if [ -z "${TARGET_CLUSTER-}" ]; then - configFile="config.yaml" - else - configFile="${DEPLOYMENT_DIR}/${TARGET_CLUSTER}/config.yaml" - fi - - yq e '.synchronizerMigration.archived = [ .synchronizerMigration.legacy ] | del(.synchronizerMigration.legacy)' -i $configFile - "${SPLICE_ROOT}/cluster/scripts/resolve-config.sh" -} - -subcommand_whitelist[update_config_to_remove_migrating_from]='Upgrade the cluster config.yaml to remove migratingFrom field from active migration' - -function subcmd_update_config_to_remove_migrating_from() { - local configFile - if [ -z "${TARGET_CLUSTER-}" ]; then - configFile="config.yaml" - else - configFile="${DEPLOYMENT_DIR}/${TARGET_CLUSTER}/config.yaml" - fi - - yq e 'del(.synchronizerMigration.active.migratingFrom)' -i $configFile - "${SPLICE_ROOT}/cluster/scripts/resolve-config.sh" -} diff --git a/build-tools/lib/logical-synchronizer-upgrade-commands b/build-tools/lib/logical-synchronizer-upgrade-commands index 60a27537a6..b7d696e061 100644 --- a/build-tools/lib/logical-synchronizer-upgrade-commands +++ b/build-tools/lib/logical-synchronizer-upgrade-commands @@ -40,3 +40,108 @@ function subcmd_retire_old_physical_synchronizer() { yq e 'del(.synchronizerMigration.legacy.releaseReference)' -i $configFile "${SPLICE_ROOT}/cluster/scripts/resolve-config.sh" } + +subcommand_whitelist[update_config]='Upgrade the hard domain migration config' + +function subcmd_update_config() { + local migration_type + local migration_id + if [ "$#" -ge 2 ]; then + migration_type="$1" + migration_id="$2" + shift 2 + else + _error "Usage: $0 $SCRIPTNAME update_config [] []" + fi + local migration_config + migration_config=$(cat < [] []" + fi + git_reference="$1" + shift + migration_config+=$(cat <] [migration]" + fi + # yq doesn't like the version being unquoted so we quote it here + _update_cluster_config "\"$1\"" "synchronizerMigration.$2.version" + if [ -z "${TARGET_CLUSTER-}" ]; then + cluster_directory="$(pwd)" + else + cluster_directory="${DEPLOYMENT_DIR}/${TARGET_CLUSTER}" + fi +} + +subcommand_whitelist[update_active_version]='Upgrade the version of the active migration id' + +function subcmd_update_active_version() { + if [ "$#" -ne 1 ]; then + _error "Usage: $0 $SCRIPTNAME []" + fi + update_version "$1" "active" +} + +subcommand_whitelist[update_upgrade_version]='Upgrade the version of the upgrade migration id' + +function subcmd_update_upgrade_version() { + if [ "$#" -ne 1 ]; then + _error "Usage: $0 $SCRIPTNAME []" + fi + if [ -z "${TARGET_CLUSTER-}" ]; then + configFile="config.yaml" + else + configFile="${DEPLOYMENT_DIR}/${TARGET_CLUSTER}/config.yaml" + fi + if yq -e '.synchronizerMigration.upgrade' "$configFile" > /dev/null 2>&1 + then + update_version "$1" "upgrade" + else + echo "No upgrade version" + fi +} + +subcommand_whitelist[update_config_to_archive_legacy_migration]='Upgrade the cluster config.yaml to archive legacy migration' + +function subcmd_update_config_to_archive_legacy_migration() { + local configFile + if [ -z "${TARGET_CLUSTER-}" ]; then + configFile="config.yaml" + else + configFile="${DEPLOYMENT_DIR}/${TARGET_CLUSTER}/config.yaml" + fi + + yq e '.synchronizerMigration.archived = [ .synchronizerMigration.legacy ] | del(.synchronizerMigration.legacy)' -i $configFile + "${SPLICE_ROOT}/cluster/scripts/resolve-config.sh" +} diff --git a/cluster/scripts/find-recent-backup.sh b/cluster/scripts/find-recent-backup.sh index 4ed3d89caa..4bdf540fc1 100755 --- a/cluster/scripts/find-recent-backup.sh +++ b/cluster/scripts/find-recent-backup.sh @@ -43,7 +43,8 @@ function latest_full_backup_run_id_kube() { local is_sv=$3 local expected_components=$4 local before_timestamp=$5 - if [ "$is_sv" == "true" ]; then + local include_cometbft=$6 + if [ "$is_sv" == "true" ] && [ "$include_cometbft" == "true" ]; then expected_components="$expected_components cometbft" fi @@ -138,6 +139,20 @@ function main() { before_timestamp=$(date +%s) fi + local config + config=$(get_resolved_config) + local bft_sequencer_enabled + bft_sequencer_enabled=$(echo "$config" | yq " + ([.synchronizerMigration.active, .synchronizerMigration.upgrade, .synchronizerMigration.legacy] + + (.synchronizerMigration.archived // []) + + (.synchronizerMigration.additionalLegacy // [])) + | map(select(.id == $migration_id)) + | .[0].sequencer.enableBftSequencer // false") + local include_cometbft="true" + if [ "$bft_sequencer_enabled" == "true" ]; then + include_cometbft="false" + fi + case "$namespace" in sv|sv-[0-9]|sv-[0-9][0-9]|sv-da-*) is_sv=true @@ -156,7 +171,7 @@ function main() { type=$(get_postgres_type "$full_instance" "$stack") # We only check the postgres type of one component and assume other components have the same type. if [ "$type" == "canton:network:postgres" ]; then - backup_run_id=$(latest_full_backup_run_id_kube "$namespace" "$migration_id" "$is_sv" "$expected_components" "$before_timestamp") + backup_run_id=$(latest_full_backup_run_id_kube "$namespace" "$migration_id" "$is_sv" "$expected_components" "$before_timestamp" "$include_cometbft") echo "$backup_run_id" elif [ "$type" == "canton:cloud:postgres" ]; then backup_map_id=$(latest_full_backup_run_id_gcloud "$namespace" "$migration_id" "$is_sv" "$expected_components" "$before_timestamp") diff --git a/cluster/scripts/node-backup.sh b/cluster/scripts/node-backup.sh index 0ff93b226a..533f2d1bf4 100755 --- a/cluster/scripts/node-backup.sh +++ b/cluster/scripts/node-backup.sh @@ -274,6 +274,18 @@ function main() { local migration_id=$3 local requested_component="${4:-}" + local config + config=$(get_resolved_config) + + # Determine whether the BFT sequencer is enabled for the migration being backed up. + local bft_sequencer_enabled + bft_sequencer_enabled=$(echo "$config" | yq " + ([.synchronizerMigration.active, .synchronizerMigration.upgrade, .synchronizerMigration.legacy] + + (.synchronizerMigration.archived // []) + + (.synchronizerMigration.additionalLegacy // [])) + | map(select(.id == $migration_id)) + | .[0].sequencer.enableBftSequencer // false") + # TODO(#9361): support multiple domains / non-default-ID'd ones if [ "$1" == "validator" ]; then _info "Backing up validator $namespace" @@ -288,7 +300,11 @@ function main() { backup_component "$namespace" "cn-apps" "$requested_component" "$migration_id" backup_component "$namespace" "mediator" "$requested_component" "$migration_id" backup_component "$namespace" "sequencer" "$requested_component" "$migration_id" - backup_component "$namespace" "cometbft-$migration_id" "$requested_component" "$migration_id" + if [ "$bft_sequencer_enabled" == "true" ]; then + _info "BFT sequencer is enabled for migration $migration_id, skipping CometBFT backup" + else + backup_component "$namespace" "cometbft-$migration_id" "$requested_component" "$migration_id" + fi wait_for_backup "$namespace" "cn-apps" "$requested_component" "$migration_id" @@ -298,7 +314,9 @@ function main() { wait_for_backup "$namespace" "participant" "$requested_component" "$migration_id" wait_for_backup "$namespace" "mediator" "$requested_component" "$migration_id" wait_for_backup "$namespace" "sequencer" "$requested_component" "$migration_id" - wait_for_backup "$namespace" "cometbft-$migration_id" "$requested_component" "$migration_id" + if [ "$bft_sequencer_enabled" != "true" ]; then + wait_for_backup "$namespace" "cometbft-$migration_id" "$requested_component" "$migration_id" + fi else usage exit 1 diff --git a/cluster/scripts/node-restore.sh b/cluster/scripts/node-restore.sh index f14127908a..d2000ed4b8 100755 --- a/cluster/scripts/node-restore.sh +++ b/cluster/scripts/node-restore.sh @@ -421,6 +421,19 @@ function main() { local -r migration_id=$2 local -r run_id=$3 + local config + config=$(get_resolved_config) + + # Determine whether the BFT sequencer is enabled for the migration being restored. + # When it is, CometBFT is not used and there is nothing to restore for it. + local bft_sequencer_enabled + bft_sequencer_enabled=$(echo "$config" | yq " + ([.synchronizerMigration.active, .synchronizerMigration.upgrade, .synchronizerMigration.legacy] + + (.synchronizerMigration.archived // []) + + (.synchronizerMigration.additionalLegacy // [])) + | map(select(.id == $migration_id)) + | .[0].sequencer.enableBftSequencer // false") + if [[ "$run_id" == *","* ]]; then _info " ** Validate backup ids ** " local map_keys @@ -432,34 +445,44 @@ function main() { fi fi + # Build the list of components to restore, dropping CometBFT when the BFT sequencer is enabled. + local -a components=() for component in "${@:4}"; do + if [ "$component" == "cometbft" ] && [ "$bft_sequencer_enabled" == "true" ]; then + _info "BFT sequencer is enabled for migration $migration_id, skipping CometBFT restore" + continue + fi + components+=("$component") + done + + for component in "${components[@]}"; do component_to_deployments "$component" "$migration_id" "$namespace" done _info " ** Scaling down ** " - for component in "${@:4}"; do + for component in "${components[@]}"; do down "$namespace" "$component" "$migration_id" done - for component in "${@:4}"; do + for component in "${components[@]}"; do wait_down "$namespace" "$component" "$migration_id" done _info " ** Restoring ** " - for component in "${@:4}"; do + for component in "${components[@]}"; do local component_run_id component_run_id=$(get_component_run_id "$run_id" "$component") restore_component "$namespace" "$component" "$migration_id" "$component_run_id" "$restore_cluster" done _info " ** Waiting for all restore operations to finish ** " - for component in "${@:4}"; do + for component in "${components[@]}"; do wait_restore_component "$namespace" "$component" done _info " ** Scaling up ** " - for component in "${@:4}"; do + for component in "${components[@]}"; do up "$namespace" "$component" "$migration_id" done From 9be3ee7f9e11868514fb9a93e777dca5826708e5 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Wed, 15 Jul 2026 11:20:39 +0200 Subject: [PATCH 042/329] Fix dashboard for mediator verdict record time (#6418) [static] The unless seems needlessly complicated. The != 0 check is incorrect, that is excluding unix epoch but the problematic part here is CantonTimestamp.MinValue. I don't think we care about anything smaller than unix epoch though so just > 0 seems good enough. Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- cluster/expected/observability/expected.json | 2 +- .../splice-stores/mediator-verdicts-ingestion.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 8eee62d285..fc57706327 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -350,7 +350,7 @@ "acs-snapshots.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 5,\n \"panels\": [],\n \"title\": \"Incremental ACS snapshot generation\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"splice_history_acs_snapshots_latest_record_time_update{namespace=~\\\"$namespace\\\"} / 1000\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Record time of last update\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 1\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"splice_history_acs_snapshots_latest_record_time_save{namespace=~\\\"$namespace\\\"} / 1000\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Record time of last save\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to update an incremental snapshot, i.e., the time to update an incremental snapshot by processing ~30sec worth of update history.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"builder\",\n \"expr\": \"histogram_avg(rate(splice_history_acs_snapshots_latency_update_duration_seconds{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Update latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to save an incremental snapshot, i.e., the time to copy an incremental snapshot to the the big table containing all historical snapshots.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 9\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"builder\",\n \"expr\": \"histogram_avg(rate(splice_history_acs_snapshots_latency_save_duration_seconds{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Save latency\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_history_acs_snapshots_latest_record_time_update,namespace)\",\n \"includeAll\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_acs_snapshots_latest_record_time_update,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"ACS snapshots\",\n \"uid\": \"cnqnrp4\",\n \"version\": 3,\n \"weekStart\": \"\"\n}\n", "bulk-storage.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_bulk_storage_latest_acs_snapshot{namespace=~\\\"$namespace\\\"} >-62135596800000) / 1000.0)\",\n \"legendFormat\": \"{{namespace}} ACS Snapshots\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_bulk_storage_latest_updates_segment{namespace=~\\\"$namespace\\\"} >-62135596800000) / 1000.0)\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} Updates\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Last record time stored\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"splice_history_bulk_storage_object_count{namespace=~\\\"$namespace\\\"}\",\n \"legendFormat\": \"{{namespace}} {{object_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Num S3 objects stored\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_history_bulk_storage_updates_count,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_bulk_storage_updates_count,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-2d\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Bulk Storage\",\n \"uid\": \"cnxl9bn\",\n \"version\": 3,\n \"weekStart\": \"\"\n}\n", "history-backfilling.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 1387,\n \"links\": [],\n \"panels\": [\n {\n \"fieldConfig\": {\n \"defaults\": {},\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 21,\n \"options\": {\n \"code\": {\n \"language\": \"plaintext\",\n \"showLineNumbers\": false,\n \"showMiniMap\": false\n },\n \"content\": \"There are three backfilling background tasks:\\n\\n1. `ScanHistoryBackfillingTrigger` backfills regular updates\\n - Data is loaded from peer scan apps using BFT network calls\\n1. `ScanHistoryBackfillingTrigger` backfills import updates\\n - Data is loaded from peer scan apps using BFT network calls\\n1. `TxLogBackfillingTrigger` backfills txlog entries\\n - Data is loaded from the local UpdateHistory\\n\\nThe tasks are completed sequentially in the above order.\\nE.g., txlog backfilling won't start until update history backfilling is complete.\",\n \"mode\": \"markdown\"\n },\n \"pluginVersion\": \"12.1.1\",\n \"title\": \"Overview\",\n \"type\": \"text\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 7\n },\n \"id\": 7,\n \"panels\": [],\n \"title\": \"Updates (scan)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 8\n },\n \"id\": 17,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_backfilling_transaction_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Backfilled transactions count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 8\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace)(rate(splice_history_backfilling_transaction_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate backfilled transactions\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 16\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_backfilling_event_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Backfilled events count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 16\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(rate(splice_history_backfilling_event_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate backfilled events\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"axisPlacement\": \"auto\",\n \"fillOpacity\": 70,\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"mappings\": [],\n \"max\": 1,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"green\",\n \"value\": 1\n }\n ]\n },\n \"unit\": \"bool_yes_no\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 24\n },\n \"id\": 15,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"auto\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_backfilling_completed{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Backfilling completed\",\n \"type\": \"state-timeline\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"unit\",\n \"value\": \"dateTimeAsIso\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 24\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_backfilling_latest_record_time{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}>-62135596800000)/1000)\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"Value\"\n }\n ],\n \"title\": \"Last record time backfilled\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 16,\n \"panels\": [],\n \"title\": \"Import Updates (scan)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 33\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_import_updates_backfilling_contract_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Backfilled import contracts count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 33\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace)(rate(splice_history_import_updates_backfilling_contract_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate backfilled contracts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"axisPlacement\": \"auto\",\n \"fillOpacity\": 70,\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"mappings\": [],\n \"max\": 1,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"green\",\n \"value\": 1\n }\n ]\n },\n \"unit\": \"bool_yes_no\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 41\n },\n \"id\": 19,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"auto\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_import_updates_backfilling_completed{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Import update backfilling completed\",\n \"type\": \"state-timeline\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"unit\",\n \"value\": \"\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 41\n },\n \"id\": 18,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_import_updates_backfilling_latest_migration_id{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}>-1))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"Value\"\n }\n ],\n \"title\": \"Last migration id backfilled\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 49\n },\n \"id\": 8,\n \"panels\": [],\n \"title\": \"TxLog entries (all apps)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 50\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_txlog_backfilling_transaction_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Processed transactions count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 50\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace)(rate(splice_history_txlog_backfilling_transaction_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate processed transactions\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 58\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_txlog_backfilling_event_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Processed events count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 58\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(rate(splice_history_txlog_backfilling_event_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}{{service}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate processed events\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"axisPlacement\": \"auto\",\n \"fillOpacity\": 70,\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"mappings\": [],\n \"max\": 1,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"green\",\n \"value\": 1\n }\n ]\n },\n \"unit\": \"bool_yes_no\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 12,\n \"x\": 0,\n \"y\": 66\n },\n \"id\": 13,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"auto\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace, service) (splice_history_txlog_backfilling_completed{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"})\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"TxLog backfilling completed\",\n \"type\": \"state-timeline\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"unit\",\n \"value\": \"dateTimeAsIso\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 12,\n \"x\": 12,\n \"y\": 66\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_txlog_backfilling_latest_record_time{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"}>-62135596800000)/1000)\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"Value\"\n }\n ],\n \"title\": \"Last record time backfilled\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"\",\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_history_txlog_backfilling_latest_record_time,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_txlog_backfilling_latest_record_time,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_history_txlog_backfilling_latest_record_time,migration)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"migration\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_txlog_backfilling_latest_record_time,migration)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"definition\": \"label_values(splice_history_txlog_backfilling_completed,service)\",\n \"includeAll\": true,\n \"name\": \"service\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_txlog_backfilling_completed,service)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"History backfiling\",\n \"uid\": \"fe12ejqgwdlvke\",\n \"version\": 4\n}\n", - "mediator-verdicts-ingestion.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of ingestion of mediator verdicts per second based on ingestion_count_total\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"rate(splice_scan_verdict_ingestion_count_total{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", pod=~\\\"$pod_name\\\"}[$__rate_interval])\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": false,\n \"legendFormat\": \"{{namespace}} {{job}} {{pod}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Verdicts Ingestion Rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested verdict record time and the current wall clock time.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\", pod_name=~\\\"$pod_name\\\"}) - (splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\", pod_name=~\\\"$pod_name\\\"} > 0) / 1e6)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{pod}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Last Ingested Verdict Time Lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 9\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\", pod_name=~\\\"$pod_name\\\"} / 1000 unless (splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\", pod_name=~\\\"$pod_name\\\"} / 1000 == 0)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{pod}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Last Ingested Verdict Record Time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Average and 99th quantile batch size.\\n\\nVerdicts are streamed in from the mediator, then batched in the scan app. For each batch we do one lookup for traffic summaries in the sequencer, and submit one SQL transaction to store all app activity data.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 17\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_avg(rate(splice_scan_verdict_ingestion_batch_size{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} avg\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.99, rate(splice_scan_verdict_ingestion_batch_size{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} 99th\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Batch size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Average and 99th percentile time to ingest one batch.\\n\\nIncludes data processing and waiting for the SQL transaction to complete.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 25\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_avg(rate(splice_scan_verdict_ingestion_latency_duration_seconds{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} avg\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.99, rate(splice_scan_verdict_ingestion_latency_duration_seconds{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} 99th\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Batch latency\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"definition\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,node_name)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"node_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,node_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,pod)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"pod_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,pod)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-12h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Mediator Verdicts Ingestion\",\n \"uid\": \"86f70b02-f283-469e-bf90-033dc9f55888\",\n \"version\": 10,\n \"weekStart\": \"\"\n}\n", + "mediator-verdicts-ingestion.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of ingestion of mediator verdicts per second based on ingestion_count_total\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"rate(splice_scan_verdict_ingestion_count_total{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", pod=~\\\"$pod_name\\\"}[$__rate_interval])\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": false,\n \"legendFormat\": \"{{namespace}} {{job}} {{pod}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Verdicts Ingestion Rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested verdict record time and the current wall clock time.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\", pod_name=~\\\"$pod_name\\\"}) - (splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\", pod_name=~\\\"$pod_name\\\"} > 0) / 1e6)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{pod}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Last Ingested Verdict Time Lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 9\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"(splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\", pod_name=~\\\"$pod_name\\\"} > 0) / 1e3\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{pod}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Last Ingested Verdict Record Time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Average and 99th quantile batch size.\\n\\nVerdicts are streamed in from the mediator, then batched in the scan app. For each batch we do one lookup for traffic summaries in the sequencer, and submit one SQL transaction to store all app activity data.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 17\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_avg(rate(splice_scan_verdict_ingestion_batch_size{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} avg\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.99, rate(splice_scan_verdict_ingestion_batch_size{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} 99th\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Batch size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Average and 99th percentile time to ingest one batch.\\n\\nIncludes data processing and waiting for the SQL transaction to complete.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 25\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_avg(rate(splice_scan_verdict_ingestion_latency_duration_seconds{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} avg\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.99, rate(splice_scan_verdict_ingestion_latency_duration_seconds{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} 99th\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Batch latency\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"definition\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,node_name)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"node_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,node_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,pod)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"pod_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_scan_verdict_ingestion_last_record_time_us,pod)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-12h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Mediator Verdicts Ingestion\",\n \"uid\": \"86f70b02-f283-469e-bf90-033dc9f55888\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", "store-ingestion-performance.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": { \"type\": \"grafana\", \"uid\": \"-- Grafana --\" },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 2,\n \"id\": null,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": { \"h\": 1, \"w\": 24, \"x\": 0, \"y\": 0 },\n \"id\": 1,\n \"repeat\": \"test\",\n \"repeatDirection\": \"h\",\n \"title\": \"$test\",\n \"type\": \"row\"\n },\n {\n \"title\": \"Total Items\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 0, \"y\": 1 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"short\",\n \"color\": { \"mode\": \"fixed\", \"fixedColor\": \"green\" },\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_ingestion_total_items{test=\\\"${test}\\\"}\",\n \"legendFormat\": \"Total Items\",\n \"range\": true, \"refId\": \"A\"\n }\n ]\n },\n {\n \"title\": \"Total Batches\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 12, \"y\": 1 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"short\",\n \"color\": { \"mode\": \"fixed\", \"fixedColor\": \"orange\" },\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_ingestion_total_batches{test=\\\"${test}\\\"}\",\n \"legendFormat\": \"Total Batches\",\n \"range\": true, \"refId\": \"A\"\n }\n ]\n },\n {\n \"title\": \"Total Time\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 0, \"y\": 9 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"s\",\n \"color\": { \"mode\": \"fixed\", \"fixedColor\": \"purple\" },\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_ingestion_total_time_ns{test=\\\"${test}\\\"} / 1e9\",\n \"legendFormat\": \"Total Time (s)\",\n \"range\": true, \"refId\": \"A\"\n }\n ]\n },\n {\n \"title\": \"Avg Item Time\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 12, \"y\": 9 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"ms\",\n \"color\": { \"mode\": \"fixed\", \"fixedColor\": \"blue\" },\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_ingestion_avg_item_time_ns{test=\\\"${test}\\\"} / 1e6\",\n \"legendFormat\": \"Avg Item Time (ms)\",\n \"range\": true, \"refId\": \"A\"\n }\n ]\n },\n {\n \"title\": \"CPU-to-Wall-Clock Ratio\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 0, \"y\": 17 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"none\",\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n },\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n { \"color\": \"blue\", \"value\": null },\n { \"color\": \"green\", \"value\": 0.5 },\n { \"color\": \"orange\", \"value\": 1.0 },\n { \"color\": \"red\", \"value\": 2.0 }\n ]\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_ingestion_cpu_to_wall_clock_ratio{test=\\\"${test}\\\"}\",\n \"legendFormat\": \"CPU/Wall-clock Ratio (>0.7: CPU-bound, 0.7~0.25: balanced, <0.25:I/O-bound)\",\n \"range\": true, \"refId\": \"A\"\n }\n ]\n },\n {\n \"title\": \"Peak Heap Memory\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 12, \"y\": 17 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"decbytes\",\n \"color\": { \"mode\": \"fixed\", \"fixedColor\": \"semi-dark-purple\" },\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_ingestion_peak_heap_bytes{test=\\\"${test}\\\"}\",\n \"legendFormat\": \"Peak Heap\",\n \"range\": true, \"refId\": \"A\"\n }\n ]\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": { \"text\": \"All\", \"value\": \"$__all\" },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"definition\": \"label_values(splice_perf_ingestion_total_items, test)\",\n \"hide\": 2,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"test\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_perf_ingestion_total_items, test)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": { \"from\": \"now-30d\", \"to\": \"now\" },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Store Ingestion Performance\",\n \"uid\": \"splice-perf-ingestion\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", "store-ingestion.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 1141,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.95, sum by(le, namespace,job,store_name,store_party,synchronizer_id) (rate(splice_store_batch_ingestion_latency_duration_seconds{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", store_name=~\\\"$store_name\\\", store_party=~\\\"$store_party\\\"}[$__rate_interval])))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": false,\n \"legendFormat\": \"95% - {{namespace}} {{job}} {{store_name}} {{store_party}} {{synchronizer_id}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"histogram_avg(rate(splice_store_batch_ingestion_latency_duration_seconds{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", store_name=~\\\"$store_name\\\", store_party=~\\\"$store_party\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": false,\n \"legendFormat\": \"avg - {{namespace}} {{job}} {{store_name}} {{store_party}} {{synchronizer_id}}\",\n \"range\": true,\n \"refId\": \"B\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Ingestion timing\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"histogram_count(rate(splice_store_batch_ingestion_latency_duration_seconds{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\",store_name=~\\\"$store_name\\\",store_party=~\\\"$store_party\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} {{job}} {{store_name}} {{store_party}} {{synchronizer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Ingestion rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 14\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.99, sum by (le, namespace,job,store_name,store_party,synchronizer_id) (rate(splice_store_ingestion_batch_size{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", store_name=~\\\"$store_name\\\", store_party=~\\\"$store_party\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"99% - {{namespace}} {{job}} {{store_name}} {{store_party}} {{synchronizer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_avg(sum by (le, namespace,job,store_name,store_party,synchronizer_id) (rate(splice_store_ingestion_batch_size{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", store_name=~\\\"$store_name\\\", store_party=~\\\"$store_party\\\"}[$__rate_interval])))\",\n \"hide\": false,\n \"instant\": false,\n \"legendFormat\": \"avg - {{namespace}} {{job}} {{store_name}} {{store_party}} {{synchronizer_id}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Batch size\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 23\n },\n \"id\": 12,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"How many events (created / archived) were ingested per $rate_interval\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 12,\n \"x\": 0,\n \"y\": 77\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_store_event_count{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", store_name=~\\\"$store_name\\\", store_party=~\\\"$store_party\\\", event_type=\\\"archived\\\"}[$__rate_interval])\",\n \"hide\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_store_event_count{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", store_name=~\\\"$store_name\\\", store_party=~\\\"$store_party\\\", event_type=\\\"created\\\"}[$__rate_interval])\",\n \"hide\": false,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Ingestion rate events\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Events\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 24\n },\n \"id\": 7,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested record time and the current wallclock time.\\n\\nNote that the last ingested record time metric only updates when the store ingests a new transaction so if there is no activity, the last ingested record time will not advance. For a party performing reward collection, e.g., the validator operator party you expect at least one transaction every round so the lag should not go above 20min.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 25\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\",store_name=~\\\"$store_name\\\",store_party=~\\\"$store_party\\\"}) - (splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\",store_name=~\\\"$store_name\\\",store_party=~\\\"$store_party\\\"} > 0) / 1e3)\",\n \"hide\": false,\n \"legendFormat\": \"{{namespace}} {{job}} {{store_name}} {{store_party}} {{synchronizer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Last Ingested Record Time Lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The last ingested record time for each store.\\n\\nNote that the last ingested record time metric only updates when the store ingests a new transaction so if no transaction is ingested the last ingested time will not advance. For a party performing reward collection, e.g., the validator operator party you expect at least one transaction every round so the last ingested time should be at most 20min ago.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 25\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"asc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\",store_name=~\\\"$store_name\\\",store_party=~\\\"$store_party\\\"}\",\n \"legendFormat\": \"{{namespace}} {{job}} {{store_name}} {{store_party}} {{synchronizer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Last Ingested Record Time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last seen record time and the current wallclock time.\\n\\nThe last seen record time updates regardless of whether an update was ingested or filtered out.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_store_last_seen_record_time_ms{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\",store_name=~\\\"$store_name\\\",store_party=~\\\"$store_party\\\"}) - (splice_store_last_seen_record_time_ms{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\",store_name=~\\\"$store_name\\\",store_party=~\\\"$store_party\\\"} > 0) / 1e3)\",\n \"hide\": false,\n \"legendFormat\": \"{{namespace}} {{job}} {{store_name}} {{store_party}} {{synchronizer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Last Seen Record Time Lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The most recent record time seen by this store for each synchronizer in milliseconds.\\n\\nThis updates for every entry seen, regardless of whether it was ingested or filtered out.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 38\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"asc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"splice_store_last_seen_record_time_ms{namespace=~\\\"$namespace\\\",node_name=~\\\"$node_name\\\",store_name=~\\\"$store_name\\\",store_party=~\\\"$store_party\\\"}\",\n \"legendFormat\": \"{{namespace}} {{job}} {{store_name}} {{store_party}} {{synchronizer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Last Seen Record Time\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Record time metrics\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 25\n },\n \"id\": 9,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"How many txlog entries were ingested per $rate_interval\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 102\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_store_ingested_tx_log_entries_total{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", store_name=~\\\"$store_name\\\", store_party=~\\\"$store_party\\\"}[$__rate_interval])\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Ingestion rate txlog entries\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"TxLog\",\n \"type\": \"row\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"1m\",\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"definition\": \"label_values(splice_store_last_ingested_record_time_ms,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_store_last_ingested_record_time_ms,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(splice_store_last_ingested_record_time_ms,node_name)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"node_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_store_last_ingested_record_time_ms,node_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(splice_store_last_ingested_record_time_ms,store_name)\",\n \"description\": \"The name of the store\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"store_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_store_last_ingested_record_time_ms,store_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(splice_store_last_ingested_record_time_ms,store_party)\",\n \"description\": \"The party that the store is ingesting data for\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"store_party\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_store_last_ingested_record_time_ms,store_party)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Splice Stores Ingestion\",\n \"uid\": \"dec17fpqzdwcge\",\n \"version\": 10\n}\n", "store-read-performance.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": { \"type\": \"grafana\", \"uid\": \"-- Grafana --\" },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 2,\n \"id\": null,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": { \"h\": 1, \"w\": 24, \"x\": 0, \"y\": 0 },\n \"id\": 1,\n \"repeat\": \"test\",\n \"repeatDirection\": \"h\",\n \"title\": \"$test\",\n \"type\": \"row\"\n },\n {\n \"title\": \"Num Updates\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 0, \"y\": 1 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"none\",\n \"color\": { \"mode\": \"fixed\", \"fixedColor\": \"green\" },\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_read_num_updates{test=\\\"${test}\\\"}\",\n \"legendFormat\": \"Num Updates\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ]\n },\n {\n \"title\": \"Update Size\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 12, \"y\": 1 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"decbytes\",\n \"color\": { \"mode\": \"fixed\", \"fixedColor\": \"blue\" },\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_read_update_size_bytes{test=\\\"${test}\\\"}\",\n \"legendFormat\": \"Update Size\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ]\n },\n {\n \"title\": \"Total Read Time\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 0, \"y\": 9 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"s\",\n \"color\": { \"mode\": \"fixed\", \"fixedColor\": \"purple\" },\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_read_total_time_ns{test=\\\"${test}\\\"} / 1e9\",\n \"legendFormat\": \"Total Read Time\",\n \"range\": true, \"refId\": \"A\"\n }\n ]\n },\n {\n \"title\": \"CPU-to-Wall-Clock Ratio\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 12, \"y\": 9 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"none\",\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n },\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n { \"color\": \"blue\", \"value\": null },\n { \"color\": \"green\", \"value\": 0.5 },\n { \"color\": \"orange\", \"value\": 1.0 },\n { \"color\": \"red\", \"value\": 2.0 }\n ]\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_read_cpu_to_wall_clock_ratio{test=\\\"${test}\\\"}\",\n \"legendFormat\": \"CPU/Wall-clock Ratio\",\n \"range\": true, \"refId\": \"A\"\n }\n ]\n },\n {\n \"title\": \"Peak Heap Memory\",\n \"type\": \"timeseries\",\n \"gridPos\": { \"h\": 8, \"w\": 12, \"x\": 0, \"y\": 17 },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"fieldConfig\": {\n \"defaults\": {\n \"unit\": \"decbytes\",\n \"color\": { \"mode\": \"fixed\", \"fixedColor\": \"semi-dark-purple\" },\n \"custom\": {\n \"drawStyle\": \"line\",\n \"lineWidth\": 2,\n \"pointSize\": 6,\n \"showPoints\": \"always\",\n \"fillOpacity\": 5,\n \"spanNulls\": false\n }\n },\n \"overrides\": []\n },\n \"options\": {\n \"legend\": { \"calcs\": [\"mean\", \"max\", \"lastNotNull\"], \"displayMode\": \"table\", \"placement\": \"bottom\", \"showLegend\": true },\n \"tooltip\": { \"mode\": \"single\", \"sort\": \"none\" }\n },\n \"targets\": [\n {\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"expr\": \"splice_perf_read_peak_heap_bytes{test=\\\"${test}\\\"}\",\n \"legendFormat\": \"Peak Heap\",\n \"range\": true, \"refId\": \"A\"\n }\n ]\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": { \"text\": \"All\", \"value\": \"$__all\" },\n \"datasource\": { \"type\": \"prometheus\", \"uid\": \"prometheus\" },\n \"definition\": \"label_values(splice_perf_read_total_time_ns, test)\",\n \"hide\": 2,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"test\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_perf_read_total_time_ns, test)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": { \"from\": \"now-30d\", \"to\": \"now\" },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Store Read Performance\",\n \"uid\": \"splice-perf-read\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", diff --git a/cluster/pulumi/observability/grafana-dashboards/splice-stores/mediator-verdicts-ingestion.json b/cluster/pulumi/observability/grafana-dashboards/splice-stores/mediator-verdicts-ingestion.json index 680b6e6877..0c0f3f4668 100644 --- a/cluster/pulumi/observability/grafana-dashboards/splice-stores/mediator-verdicts-ingestion.json +++ b/cluster/pulumi/observability/grafana-dashboards/splice-stores/mediator-verdicts-ingestion.json @@ -311,7 +311,7 @@ { "disableTextWrap": false, "editorMode": "code", - "expr": "splice_scan_verdict_ingestion_last_record_time_us{namespace=~\"$namespace\",node_name=~\"$node_name\", pod_name=~\"$pod_name\"} / 1000 unless (splice_scan_verdict_ingestion_last_record_time_us{namespace=~\"$namespace\",node_name=~\"$node_name\", pod_name=~\"$pod_name\"} / 1000 == 0)", + "expr": "(splice_scan_verdict_ingestion_last_record_time_us{namespace=~\"$namespace\",node_name=~\"$node_name\", pod_name=~\"$pod_name\"} > 0) / 1e3", "fullMetaSearch": false, "includeNullMetadata": true, "legendFormat": "{{namespace}} {{job}} {{pod}}", @@ -631,6 +631,6 @@ "timezone": "", "title": "Mediator Verdicts Ingestion", "uid": "86f70b02-f283-469e-bf90-033dc9f55888", - "version": 10, + "version": 1, "weekStart": "" } From 141c2d5691363f3460d8a84cad110d75ac45e8b7 Mon Sep 17 00:00:00 2001 From: Jagath Weerasinghe Date: Wed, 15 Jul 2026 11:31:28 +0200 Subject: [PATCH 043/329] Fix performance readme (#6419) Signed-off-by: Jagath Weerasinghe --- PERFORMANCE.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/PERFORMANCE.md b/PERFORMANCE.md index 255ed6a142..6909d8572d 100644 --- a/PERFORMANCE.md +++ b/PERFORMANCE.md @@ -7,10 +7,10 @@ See the [design document](https://docs.google.com/document/d/1rvAec6BuKx61TdJ6sY The performance tests cover three stores. The Tests ingest update data relevant to each store and measure the time taken for the ingestion process. -| Store | Description | Content | -|---------------------|-------------|-------------------| -| **`SvDsoStore`** | DSO's internal governance data | ACS contracts | -| **`ScanStore`** | DSO's public queryable data | ACS contracts | +| Store | Description | Content | +|---------------------|-------------|---------------------------------------------------| +| **`SvDsoStore`** | DSO's internal governance data | ACS contracts | +| **`ScanStore`** | DSO's public queryable data | ACS contracts and Tx Log | | **`UpdateHistory`** | DSO's append-only audit log | Updates and associated events (creates/exercises) | # How to run a test on branch From ca937f08d5db905769b57f2715efd4e3f0f9ffe9 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Wed, 15 Jul 2026 12:13:21 +0200 Subject: [PATCH 044/329] Bumped registry rate limits globally (#6422) [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- .../shared/rate-limits/token-registry.yaml | 96 ++--- .../scratchneta/config.resolved.yaml | 96 ++--- .../scratchnetb/config.resolved.yaml | 96 ++--- .../scratchnetc/config.resolved.yaml | 96 ++--- .../scratchnetd/config.resolved.yaml | 96 ++--- .../scratchnete/config.resolved.yaml | 96 ++--- cluster/expected/canton-network/expected.json | 384 +++++++++--------- cluster/expected/sv-runbook/expected.json | 192 ++++----- 8 files changed, 576 insertions(+), 576 deletions(-) diff --git a/cluster/configs/shared/rate-limits/token-registry.yaml b/cluster/configs/shared/rate-limits/token-registry.yaml index 6aaa5e0a01..7997521b60 100644 --- a/cluster/configs/shared/rate-limits/token-registry.yaml +++ b/cluster/configs/shared/rate-limits/token-registry.yaml @@ -2,120 +2,120 @@ rateLimits: /registry/allocations/v1: name: registry-allocations type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/metadata/v1/info: name: registry-metadata-info type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/metadata/v1/instruments: name: registry-metadata-instruments type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/allocation-instruction/v1/allocation-factory: name: registry-allocation-factory type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/transfer-instruction/v1: name: registry-transfer-instruction type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/transfer-instruction/v1/transfer-factory: name: registry-transfer-factory type: limited - maxTokens: 500 - tokensPerFill: 500 # Higher limit: transfer-factory generates the most registry traffic + maxTokens: 730 + tokensPerFill: 730 fillInterval: 60s perIpLimits: - maxTokens: 50 - tokensPerFill: 50 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/allocation/v2/settlement-factory: name: registry-settlement-factory-v2 type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/allocations/v2: name: registry-allocations-v2 type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/allocation-instruction/v2/allocation-factory: name: registry-allocation-factory-v2 type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/allocation-instruction/v2: name: registry-allocation-instruction-v2 type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/transfer-instruction/v2/transfer-factory: name: registry-transfer-factory-v2 type: limited - maxTokens: 500 - tokensPerFill: 500 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 50 - tokensPerFill: 50 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s /registry/transfer-instruction/v2: name: registry-transfer-instruction-v2 type: limited - maxTokens: 200 - tokensPerFill: 200 + maxTokens: 720 + tokensPerFill: 720 fillInterval: 60s perIpLimits: - maxTokens: 30 - tokensPerFill: 30 + maxTokens: 120 + tokensPerFill: 120 fillInterval: 60s diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index 0ddb928aa1..a6a986c5c5 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -373,123 +373,123 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 730 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 730 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 720 name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' svs: sv: diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index 0ddb928aa1..a6a986c5c5 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -373,123 +373,123 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 730 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 730 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 720 name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' svs: sv: diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index 0ddb928aa1..a6a986c5c5 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -373,123 +373,123 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 730 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 730 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 720 name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' svs: sv: diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index 0ddb928aa1..a6a986c5c5 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -373,123 +373,123 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 730 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 730 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 720 name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' svs: sv: diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index 0ddb928aa1..a6a986c5c5 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -373,123 +373,123 @@ sv: type: 'unlimited' /registry/allocation-instruction/v1/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation-instruction/v2/allocation-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocation-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocation/v2/settlement-factory: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-settlement-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/allocations/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-allocations-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/info: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-info' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/metadata/v1/instruments: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-metadata-instruments' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 730 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 730 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' - maxTokens: 200 + maxTokens: 720 name: 'registry-transfer-instruction-v2' perIpLimits: fillInterval: '60s' - maxTokens: 30 - tokensPerFill: 30 - tokensPerFill: 200 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' /registry/transfer-instruction/v2/transfer-factory: fillInterval: '60s' - maxTokens: 500 + maxTokens: 720 name: 'registry-transfer-factory-v2' perIpLimits: fillInterval: '60s' - maxTokens: 50 - tokensPerFill: 50 - tokensPerFill: 500 + maxTokens: 120 + tokensPerFill: 120 + tokensPerFill: 720 type: 'limited' svs: sv: diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index 0a40202ff3..c623ea3c3a 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -1764,146 +1764,146 @@ }, "/registry/allocation-instruction/v1/allocation-factory": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocation-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocation-instruction/v2": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocation-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocation-instruction/v2/allocation-factory": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocation-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocation/v2/settlement-factory": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-settlement-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocations/v1": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocations", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocations/v2": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocations-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/metadata/v1/info": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-metadata-info", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/metadata/v1/instruments": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-metadata-instruments", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/transfer-instruction/v1": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-transfer-instruction", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/transfer-instruction/v1/transfer-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 730, "name": "registry-transfer-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 50, - "tokensPerFill": 50 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 500, + "tokensPerFill": 730, "type": "limited" }, "/registry/transfer-instruction/v2": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-transfer-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/transfer-instruction/v2/transfer-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 720, "name": "registry-transfer-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 50, - "tokensPerFill": 50 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 500, + "tokensPerFill": 720, "type": "limited" } } @@ -2147,146 +2147,146 @@ }, "/registry/allocation-instruction/v1/allocation-factory": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocation-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocation-instruction/v2": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocation-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocation-instruction/v2/allocation-factory": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocation-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocation/v2/settlement-factory": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-settlement-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocations/v1": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocations", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocations/v2": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocations-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/metadata/v1/info": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-metadata-info", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/metadata/v1/instruments": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-metadata-instruments", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/transfer-instruction/v1": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-transfer-instruction", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/transfer-instruction/v1/transfer-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 730, "name": "registry-transfer-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 50, - "tokensPerFill": 50 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 500, + "tokensPerFill": 730, "type": "limited" }, "/registry/transfer-instruction/v2": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-transfer-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/transfer-instruction/v2/transfer-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 720, "name": "registry-transfer-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 50, - "tokensPerFill": 50 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 500, + "tokensPerFill": 720, "type": "limited" } } @@ -3423,8 +3423,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3439,8 +3439,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3452,8 +3452,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3468,8 +3468,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3481,8 +3481,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3497,8 +3497,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3510,8 +3510,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3526,8 +3526,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3539,8 +3539,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3555,8 +3555,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3568,8 +3568,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 730, + "tokens_per_fill": 730 } }, { @@ -3584,8 +3584,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 50, - "tokens_per_fill": 50 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3597,8 +3597,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3613,8 +3613,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3626,8 +3626,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3642,8 +3642,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3655,8 +3655,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3671,8 +3671,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3684,8 +3684,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3700,8 +3700,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3713,8 +3713,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3729,8 +3729,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 50, - "tokens_per_fill": 50 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -3742,8 +3742,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -3758,8 +3758,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } } ], @@ -5560,8 +5560,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5576,8 +5576,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5589,8 +5589,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5605,8 +5605,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5618,8 +5618,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5634,8 +5634,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5647,8 +5647,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5663,8 +5663,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5676,8 +5676,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5692,8 +5692,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5705,8 +5705,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 730, + "tokens_per_fill": 730 } }, { @@ -5721,8 +5721,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 50, - "tokens_per_fill": 50 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5734,8 +5734,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5750,8 +5750,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5763,8 +5763,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5779,8 +5779,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5792,8 +5792,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5808,8 +5808,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5821,8 +5821,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5837,8 +5837,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5850,8 +5850,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5866,8 +5866,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 50, - "tokens_per_fill": 50 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -5879,8 +5879,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -5895,8 +5895,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } } ], diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index 10c5d40af4..a149bccb94 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -1059,146 +1059,146 @@ }, "/registry/allocation-instruction/v1/allocation-factory": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocation-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocation-instruction/v2": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocation-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocation-instruction/v2/allocation-factory": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocation-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocation/v2/settlement-factory": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-settlement-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocations/v1": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocations", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/allocations/v2": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-allocations-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/metadata/v1/info": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-metadata-info", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/metadata/v1/instruments": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-metadata-instruments", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/transfer-instruction/v1": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-transfer-instruction", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/transfer-instruction/v1/transfer-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 730, "name": "registry-transfer-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 50, - "tokensPerFill": 50 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 500, + "tokensPerFill": 730, "type": "limited" }, "/registry/transfer-instruction/v2": { "fillInterval": "60s", - "maxTokens": 200, + "maxTokens": 720, "name": "registry-transfer-instruction-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 30, - "tokensPerFill": 30 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 200, + "tokensPerFill": 720, "type": "limited" }, "/registry/transfer-instruction/v2/transfer-factory": { "fillInterval": "60s", - "maxTokens": 500, + "maxTokens": 720, "name": "registry-transfer-factory-v2", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 50, - "tokensPerFill": 50 + "maxTokens": 120, + "tokensPerFill": 120 }, - "tokensPerFill": 500, + "tokensPerFill": 720, "type": "limited" } } @@ -2287,8 +2287,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2303,8 +2303,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2316,8 +2316,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2332,8 +2332,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2345,8 +2345,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2361,8 +2361,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2374,8 +2374,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2390,8 +2390,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2403,8 +2403,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2419,8 +2419,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2432,8 +2432,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 730, + "tokens_per_fill": 730 } }, { @@ -2448,8 +2448,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 50, - "tokens_per_fill": 50 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2461,8 +2461,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2477,8 +2477,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2490,8 +2490,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2506,8 +2506,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2519,8 +2519,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2535,8 +2535,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2548,8 +2548,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2564,8 +2564,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2577,8 +2577,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2593,8 +2593,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 50, - "tokens_per_fill": 50 + "max_tokens": 120, + "tokens_per_fill": 120 } }, { @@ -2606,8 +2606,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 200, - "tokens_per_fill": 200 + "max_tokens": 720, + "tokens_per_fill": 720 } }, { @@ -2622,8 +2622,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 30, - "tokens_per_fill": 30 + "max_tokens": 120, + "tokens_per_fill": 120 } } ], From 2eb2616a0c2ff8637a96e273be91c78e69f97524 Mon Sep 17 00:00:00 2001 From: Itai Segall Date: Wed, 15 Jul 2026 07:07:24 -0400 Subject: [PATCH 045/329] helm & pulumi support for staging bulk storage (#6334) Signed-off-by: Itai Segall --- cluster/expected/canton-network/expected.json | 122 +++++++++++++++--- cluster/helm/splice-scan/templates/scan.yaml | 55 +++++--- cluster/helm/splice-scan/tests/scan_test.yaml | 28 +++- cluster/pulumi/common-sv/src/bulkStorage.ts | 75 ++++++++--- cluster/pulumi/common-sv/src/config.ts | 4 +- cluster/pulumi/common-sv/src/sv.ts | 31 +++-- cluster/pulumi/pulumiUp.ts | 6 +- 7 files changed, 248 insertions(+), 73 deletions(-) diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index c623ea3c3a..ce454feac0 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -1100,23 +1100,46 @@ { "custom": true, "id": "", - "inputs": {}, - "name": "mock-sv-1-bulk-hmac", + "inputs": { + "bucket": "mock-sv-1-bulk-committed", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectCreator" + }, + "name": "mock-sv-1-bulk-committed-sa-role-creator", "provider": "", - "type": "gcp:storage/hmacKey:HmacKey" + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" }, { "custom": true, "id": "", "inputs": { - "bucket": "mock-sv-1-bulk", + "bucket": "mock-sv-1-bulk-committed", "member": "serviceAccount:undefined", - "role": "roles/storage.objectUser" + "role": "roles/storage.objectViewer" }, - "name": "mock-sv-1-bulk-sa-role", + "name": "mock-sv-1-bulk-committed-sa-role-reader", "provider": "", "type": "gcp:storage/bucketIAMMember:BucketIAMMember" }, + { + "custom": true, + "id": "", + "inputs": { + "location": "europe-west6", + "name": "mock-sv-1-bulk-committed" + }, + "name": "mock-sv-1-bulk-committed", + "provider": "", + "type": "gcp:storage/bucket:Bucket" + }, + { + "custom": true, + "id": "", + "inputs": {}, + "name": "mock-sv-1-bulk-sa-hmac", + "provider": "", + "type": "gcp:storage/hmacKey:HmacKey" + }, { "custom": true, "id": "", @@ -1128,37 +1151,72 @@ "provider": "", "type": "gcp:serviceaccount/account:Account" }, + { + "custom": true, + "id": "", + "inputs": { + "bucket": "mock-sv-1-bulk-staging", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectUser" + }, + "name": "mock-sv-1-bulk-staging-sa-role", + "provider": "", + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" + }, { "custom": true, "id": "", "inputs": { "location": "europe-west6", - "name": "mock-sv-1-bulk" + "name": "mock-sv-1-bulk-staging" }, - "name": "mock-sv-1-bulk", + "name": "mock-sv-1-bulk-staging", "provider": "", "type": "gcp:storage/bucket:Bucket" }, { "custom": true, "id": "", - "inputs": {}, - "name": "mock-sv-da-1-bulk-hmac", + "inputs": { + "bucket": "mock-sv-da-1-bulk-committed", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectCreator" + }, + "name": "mock-sv-da-1-bulk-committed-sa-role-creator", "provider": "", - "type": "gcp:storage/hmacKey:HmacKey" + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" }, { "custom": true, "id": "", "inputs": { - "bucket": "mock-sv-da-1-bulk", + "bucket": "mock-sv-da-1-bulk-committed", "member": "serviceAccount:undefined", - "role": "roles/storage.objectUser" + "role": "roles/storage.objectViewer" }, - "name": "mock-sv-da-1-bulk-sa-role", + "name": "mock-sv-da-1-bulk-committed-sa-role-reader", "provider": "", "type": "gcp:storage/bucketIAMMember:BucketIAMMember" }, + { + "custom": true, + "id": "", + "inputs": { + "location": "europe-west6", + "name": "mock-sv-da-1-bulk-committed" + }, + "name": "mock-sv-da-1-bulk-committed", + "provider": "", + "type": "gcp:storage/bucket:Bucket" + }, + { + "custom": true, + "id": "", + "inputs": {}, + "name": "mock-sv-da-1-bulk-sa-hmac", + "provider": "", + "type": "gcp:storage/hmacKey:HmacKey" + }, { "custom": true, "id": "", @@ -1170,14 +1228,26 @@ "provider": "", "type": "gcp:serviceaccount/account:Account" }, + { + "custom": true, + "id": "", + "inputs": { + "bucket": "mock-sv-da-1-bulk-staging", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectUser" + }, + "name": "mock-sv-da-1-bulk-staging-sa-role", + "provider": "", + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" + }, { "custom": true, "id": "", "inputs": { "location": "europe-west6", - "name": "mock-sv-da-1-bulk" + "name": "mock-sv-da-1-bulk-staging" }, - "name": "mock-sv-da-1-bulk", + "name": "mock-sv-da-1-bulk-staging", "provider": "", "type": "gcp:storage/bucket:Bucket" }, @@ -3963,8 +4033,14 @@ }, "apiRequestLogLevel": "DEBUG", "bulkStorage": { - "s3": { - "bucketName": "mock-sv-1-bulk", + "committed": { + "bucketName": "mock-sv-1-bulk-committed", + "endpoint": "https://storage.googleapis.com", + "region": "europe-west6", + "secretName": "splice-app-bulk-storage-credentials" + }, + "staging": { + "bucketName": "mock-sv-1-bulk-staging", "endpoint": "https://storage.googleapis.com", "region": "europe-west6", "secretName": "splice-app-bulk-storage-credentials" @@ -5989,8 +6065,14 @@ }, "apiRequestLogLevel": "DEBUG", "bulkStorage": { - "s3": { - "bucketName": "mock-sv-da-1-bulk", + "committed": { + "bucketName": "mock-sv-da-1-bulk-committed", + "endpoint": "https://storage.googleapis.com", + "region": "europe-west6", + "secretName": "splice-app-bulk-storage-credentials" + }, + "staging": { + "bucketName": "mock-sv-da-1-bulk-staging", "endpoint": "https://storage.googleapis.com", "region": "europe-west6", "secretName": "splice-app-bulk-storage-credentials" diff --git a/cluster/helm/splice-scan/templates/scan.yaml b/cluster/helm/splice-scan/templates/scan.yaml index 0aa7d5db18..53b219e3e5 100644 --- a/cluster/helm/splice-scan/templates/scan.yaml +++ b/cluster/helm/splice-scan/templates/scan.yaml @@ -143,30 +143,55 @@ spec: - name: ADDITIONAL_CONFIG_BULK_STORAGE value: | canton.scan-apps.scan-app.bulk-storage { - s-3 = { - endpoint = "{{ .s3.endpoint }}" - region = "{{ .s3.region }}" - bucket-name = "{{ .s3.bucketName }}" - access-key-id = ${SPLICE_APP_BULK_STORAGE_S3_ACCESS_KEY_ID} - secret-access-key = ${SPLICE_APP_BULK_STORAGE_S3_SECRET_ACCESS_KEY} + staging = { + endpoint = "{{ .staging.endpoint }}" + region = "{{ .staging.region }}" + bucket-name = "{{ .staging.bucketName }}" + access-key-id = ${SPLICE_APP_BULK_STORAGE_STAGING_ACCESS_KEY_ID} + secret-access-key = ${SPLICE_APP_BULK_STORAGE_STAGING_SECRET_ACCESS_KEY} + } + committed = { + endpoint = "{{ .committed.endpoint }}" + region = "{{ .committed.region }}" + bucket-name = "{{ .committed.bucketName }}" + access-key-id = ${SPLICE_APP_BULK_STORAGE_COMMITTED_ACCESS_KEY_ID} + secret-access-key = ${SPLICE_APP_BULK_STORAGE_COMMITTED_SECRET_ACCESS_KEY} } } - - name: SPLICE_APP_BULK_STORAGE_S3_ACCESS_KEY_ID - {{- if (($.Values.bulkStorage.s3).secretOverrides).accessKey }} - value: {{ $.Values.bulkStorage.s3.secretOverrides.accessKey | quote }} + - name: SPLICE_APP_BULK_STORAGE_STAGING_ACCESS_KEY_ID + {{- if (($.Values.bulkStorage.staging).secretOverrides).accessKey }} + value: {{ $.Values.bulkStorage.staging.secretOverrides.accessKey | quote }} + {{- else }} + valueFrom: + secretKeyRef: + name: {{ $.Values.bulkStorage.staging.secretName }} + key: accessKey + {{- end }} + - name: SPLICE_APP_BULK_STORAGE_STAGING_SECRET_ACCESS_KEY + {{- if (($.Values.bulkStorage.staging).secretOverrides).secretAccessKey }} + value: {{ $.Values.bulkStorage.staging.secretOverrides.secretAccessKey | quote }} {{- else }} valueFrom: secretKeyRef: - name: {{ $.Values.bulkStorage.s3.secretName }} + name: {{ $.Values.bulkStorage.staging.secretName }} + key: secretAccessKey + {{- end }} + - name: SPLICE_APP_BULK_STORAGE_COMMITTED_ACCESS_KEY_ID + {{- if (($.Values.bulkStorage.committed).secretOverrides).accessKey }} + value: {{ $.Values.bulkStorage.committed.secretOverrides.accessKey | quote }} + {{- else }} + valueFrom: + secretKeyRef: + name: {{ $.Values.bulkStorage.committed.secretName }} key: accessKey - {{ end }} - - name: SPLICE_APP_BULK_STORAGE_S3_SECRET_ACCESS_KEY - {{- if (($.Values.bulkStorage.s3).secretOverrides).secretAccessKey }} - value: {{ $.Values.bulkStorage.s3.secretOverrides.secretAccessKey | quote }} + {{- end }} + - name: SPLICE_APP_BULK_STORAGE_COMMITTED_SECRET_ACCESS_KEY + {{- if (($.Values.bulkStorage.committed).secretOverrides).secretAccessKey }} + value: {{ $.Values.bulkStorage.committed.secretOverrides.secretAccessKey | quote }} {{- else }} valueFrom: secretKeyRef: - name: {{ $.Values.bulkStorage.s3.secretName }} + name: {{ $.Values.bulkStorage.committed.secretName }} key: secretAccessKey {{- end }} {{- end }} diff --git a/cluster/helm/splice-scan/tests/scan_test.yaml b/cluster/helm/splice-scan/tests/scan_test.yaml index e0fd0105e0..7f1c413a63 100644 --- a/cluster/helm/splice-scan/tests/scan_test.yaml +++ b/cluster/helm/splice-scan/tests/scan_test.yaml @@ -466,14 +466,22 @@ tests: secretOverrides: postgresPassword: "vault:kv/data/scan/postgres-password" bulkStorage: - s3: + staging: endpoint: "mock-endpoint" region: "mock-region" bucketName: "mock-bucket" secretName: "mock-secret" secretOverrides: - accessKey: "vault:kv/data/scan/s3-access-key" - secretAccessKey: "vault:kv/data/scan/s3-secret-key" + accessKey: "vault:kv/data/scan/staging-access-key" + secretAccessKey: "vault:kv/data/scan/staging-secret-key" + committed: + endpoint: "mock-endpoint" + region: "mock-region" + bucketName: "mock-bucket" + secretName: "mock-secret" + secretOverrides: + accessKey: "vault:kv/data/scan/committed-access-key" + secretAccessKey: "vault:kv/data/scan/committed-secret-key" auth: jwksUrl: "https://mock.com/.well-known/jwks.json" audience: "mock_audience" @@ -494,11 +502,17 @@ tests: path: spec.template.spec.initContainers[?(@.name=='scan-init')].env[?(@.name=='PGPASSWORD')].value pattern: "vault:kv/data/scan/postgres-password" - matchRegex: - path: spec.template.spec.containers[?(@.name=='scan-app')].env[?(@.name=='SPLICE_APP_BULK_STORAGE_S3_ACCESS_KEY_ID')].value - pattern: "vault:kv/data/scan/s3-access-key" + path: spec.template.spec.containers[?(@.name=='scan-app')].env[?(@.name=='SPLICE_APP_BULK_STORAGE_STAGING_ACCESS_KEY_ID')].value + pattern: "vault:kv/data/scan/staging-access-key" + - matchRegex: + path: spec.template.spec.containers[?(@.name=='scan-app')].env[?(@.name=='SPLICE_APP_BULK_STORAGE_STAGING_SECRET_ACCESS_KEY')].value + pattern: "vault:kv/data/scan/staging-secret-key" + - matchRegex: + path: spec.template.spec.containers[?(@.name=='scan-app')].env[?(@.name=='SPLICE_APP_BULK_STORAGE_COMMITTED_ACCESS_KEY_ID')].value + pattern: "vault:kv/data/scan/committed-access-key" - matchRegex: - path: spec.template.spec.containers[?(@.name=='scan-app')].env[?(@.name=='SPLICE_APP_BULK_STORAGE_S3_SECRET_ACCESS_KEY')].value - pattern: "vault:kv/data/scan/s3-secret-key" + path: spec.template.spec.containers[?(@.name=='scan-app')].env[?(@.name=='SPLICE_APP_BULK_STORAGE_COMMITTED_SECRET_ACCESS_KEY')].value + pattern: "vault:kv/data/scan/committed-secret-key" - notExists: path: spec.template.spec.containers[?(@.name=='scan-app')].env[?(@.valueFrom.secretKeyRef)] - matchRegex: diff --git a/cluster/pulumi/common-sv/src/bulkStorage.ts b/cluster/pulumi/common-sv/src/bulkStorage.ts index 2596410bfb..1efd3d6eff 100644 --- a/cluster/pulumi/common-sv/src/bulkStorage.ts +++ b/cluster/pulumi/common-sv/src/bulkStorage.ts @@ -11,42 +11,76 @@ import { import { BulkStorageConfig } from './singleSvConfig'; -export type BulkStorageBucket = { +type BulkStorageBucket = { bucket: gcp.storage.Bucket; region: string; secret: k8s.core.v1.Secret; }; +export type BulkStorageBuckets = { + staging: BulkStorageBucket; + committed: BulkStorageBucket; +}; + export function installScanBulkStorage( xns: ExactNamespace, bulkStorageConfig: BulkStorageConfig -): BulkStorageBucket | undefined { +): BulkStorageBuckets | undefined { if (!bulkStorageConfig.enabled) { return; } - const bucketName = `${ClusterBasename}-${xns.logicalName}-bulk`; + const stagingBucketName = `${ClusterBasename}-${xns.logicalName}-bulk-staging`; + const committedBucketName = `${ClusterBasename}-${xns.logicalName}-bulk-committed`; + const saName = `${ClusterBasename}-${xns.logicalName}-bulk-sa`; + // TODO(#3429): review other bucket configs - const bucket = new gcp.storage.Bucket(bucketName, { name: bucketName, location: GcpRegion }); - const bucketServiceAccount = new gcp.serviceaccount.Account(`${bucketName}-sa`, { - accountId: `${bucketName}-sa`, + const bucketServiceAccount = new gcp.serviceaccount.Account(saName, { + accountId: saName, displayName: 'Service Account for Bulk-Storage Bucket Read/Write Access', }); + const hmacKey = new gcp.storage.HmacKey( + `${saName}-hmac`, + { + serviceAccountEmail: bucketServiceAccount.email, + }, + { dependsOn: [bucketServiceAccount] } + ); + + const staging = new gcp.storage.Bucket(stagingBucketName, { + name: stagingBucketName, + location: GcpRegion, + }); new gcp.storage.BucketIAMMember( - `${bucketName}-sa-role`, + `${stagingBucketName}-sa-role`, { - bucket: bucket.name, + bucket: staging.name, role: 'roles/storage.objectUser', member: pulumi.interpolate`serviceAccount:${bucketServiceAccount.email}`, }, - { dependsOn: [bucket, bucketServiceAccount] } + { dependsOn: [staging, bucketServiceAccount] } ); - const hmacKey = new gcp.storage.HmacKey( - `${bucketName}-hmac`, + const committed = new gcp.storage.Bucket(committedBucketName, { + name: committedBucketName, + location: GcpRegion, + }); + new gcp.storage.BucketIAMMember( + `${committedBucketName}-sa-role-creator`, { - serviceAccountEmail: bucketServiceAccount.email, + bucket: committed.name, + role: 'roles/storage.objectCreator', + member: pulumi.interpolate`serviceAccount:${bucketServiceAccount.email}`, }, - { dependsOn: [bucketServiceAccount] } + { dependsOn: [committed, bucketServiceAccount] } + ); + new gcp.storage.BucketIAMMember( + `${committedBucketName}-sa-role-reader`, + { + bucket: committed.name, + role: 'roles/storage.objectViewer', + member: pulumi.interpolate`serviceAccount:${bucketServiceAccount.email}`, + }, + { dependsOn: [committed, bucketServiceAccount] } ); const accessKey = hmacKey.accessId; @@ -72,8 +106,15 @@ export function installScanBulkStorage( ); return { - region: GcpRegion, - bucket, - secret, - } as BulkStorageBucket; + staging: { + bucket: staging, + region: GcpRegion, + secret, + }, + committed: { + bucket: committed, + region: GcpRegion, + secret, + }, + }; } diff --git a/cluster/pulumi/common-sv/src/config.ts b/cluster/pulumi/common-sv/src/config.ts index 275c06f91c..5022305712 100644 --- a/cluster/pulumi/common-sv/src/config.ts +++ b/cluster/pulumi/common-sv/src/config.ts @@ -18,7 +18,7 @@ import { SweepConfig } from '@canton-network/splice-pulumi-common-validator'; import { clusterYamlConfig } from '@canton-network/splice-pulumi-common/src/config/config'; import { z } from 'zod'; -import { BulkStorageBucket } from './bulkStorage'; +import { BulkStorageBuckets } from './bulkStorage'; import { SingleSvConfiguration } from './singleSvConfig'; import { StaticCometBftConfig, @@ -84,7 +84,7 @@ export interface SvConfig extends StaticSvConfig, SingleSvConfiguration { initialRound?: string; periodicTopologySnapshotConfig?: CnInput; version: CnChartVersion; - bulkStorageBucket?: BulkStorageBucket; + bulkStorageBuckets?: BulkStorageBuckets; } export const TopologySnapshotSchema = z.object({ diff --git a/cluster/pulumi/common-sv/src/sv.ts b/cluster/pulumi/common-sv/src/sv.ts index ae605556ed..fac7b413d4 100644 --- a/cluster/pulumi/common-sv/src/sv.ts +++ b/cluster/pulumi/common-sv/src/sv.ts @@ -280,7 +280,7 @@ export async function installSvNode( prefix: baseConfig.identitiesBackupLocation.prefix || `${CLUSTER_BASENAME}/${xns.logicalName}`, }; - const bulkStorageBucket = svConfig.scanApp?.bulkStorage + const bulkStorageBuckets = svConfig.scanApp?.bulkStorage ? installScanBulkStorage(xns, svConfig.scanApp.bulkStorage) : undefined; @@ -288,7 +288,7 @@ export async function installSvNode( ...baseConfig, periodicBackupConfig, identitiesBackupLocation, - bulkStorageBucket, + bulkStorageBuckets, }; const identitiesBackupConfigSecret = installBucketSecret( @@ -338,7 +338,16 @@ export async function installSvNode( ? svCometBftGovernanceKeySecret(xns, config.cometBftGovernanceKey) : [] ) - .concat(bulkStorageBucket ? [bulkStorageBucket.secret, bulkStorageBucket.bucket] : []) + .concat( + bulkStorageBuckets + ? [ + bulkStorageBuckets.staging.secret, + bulkStorageBuckets.staging.bucket, + bulkStorageBuckets.committed.secret, + bulkStorageBuckets.committed.bucket, + ] + : [] + ) .concat(extraDependsOn); const defaultPostgres = config.splitPostgresInstances @@ -718,14 +727,20 @@ function installScan( logAsyncFlush: config.logging?.appsAsync, additionalEnvVars: config.scanApp?.additionalEnvVars || [], resources: config.scanApp?.resources, - ...(config.bulkStorageBucket + ...(config.bulkStorageBuckets ? { bulkStorage: { - s3: { - region: config.bulkStorageBucket.region, - bucketName: config.bulkStorageBucket.bucket.name, + staging: { + region: config.bulkStorageBuckets.staging.region, + bucketName: config.bulkStorageBuckets.staging.bucket.name, + endpoint: 'https://storage.googleapis.com', // gcs endpoint for s3 + secretName: config.bulkStorageBuckets.staging.secret.metadata.name, + }, + committed: { + region: config.bulkStorageBuckets.committed.region, + bucketName: config.bulkStorageBuckets.committed.bucket.name, endpoint: 'https://storage.googleapis.com', // gcs endpoint for s3 - secretName: config.bulkStorageBucket.secret.metadata.name, + secretName: config.bulkStorageBuckets.committed.secret.metadata.name, }, }, } diff --git a/cluster/pulumi/pulumiUp.ts b/cluster/pulumi/pulumiUp.ts index a61152971d..ac536b515e 100644 --- a/cluster/pulumi/pulumiUp.ts +++ b/cluster/pulumi/pulumiUp.ts @@ -6,9 +6,7 @@ import { mustInstallValidator1, } from '@canton-network/splice-pulumi-common-validator/src/validators'; import { runSvCantonForAllMigrations } from '@canton-network/splice-pulumi-sv-canton/pulumi'; -import { - runSvProjectForAllSvs, -} from '@canton-network/splice-pulumi-sv/pulumi'; +import { runSvProjectForAllSvs } from '@canton-network/splice-pulumi-sv/pulumi'; import { awaitAllOrThrowAllExceptions, Operation, PulumiAbortController, stack } from './pulumi'; import { upOperation, upStack } from './pulumiOperations'; @@ -60,7 +58,7 @@ async function runAllStacksUp() { runAllStacksUp().catch((err: unknown) => { console.error( `\nPulumi up finished with errors. See the summary above for details.\n` + - (err instanceof Error ? err.message : String(err)) + (err instanceof Error ? err.message : String(err)) ); process.exit(1); }); From eed7b5be9de377b444610fcad657010697214c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Wed, 15 Jul 2026 13:38:09 +0200 Subject: [PATCH 046/329] Add 30s to maxSequencerTime in LSU DR Test (#6424) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../integration/tests/RollForwardLsuDRIntegrationTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/RollForwardLsuDRIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/RollForwardLsuDRIntegrationTest.scala index 7c358bf7bb..12e65d9f18 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/RollForwardLsuDRIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/RollForwardLsuDRIntegrationTest.scala @@ -85,7 +85,7 @@ class RollForwardLsuDRIntegrationTest val trafficExportTimeFile = File.newTemporaryFile() // This needs to be long enough in the future that we can start Canton and initialize for SVs. - val maxSequencingTime = CantonTimestamp.now().plusSeconds(210) + val maxSequencingTime = CantonTimestamp.now().plusSeconds(240) // 5s chosen by fair dice roll val lowerBoundSequencingTimeExclusive = maxSequencingTime.plusSeconds(5) val upgradeTime = lowerBoundSequencingTimeExclusive From b292879af818915119e8fd1073ed6258a51b9c6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 15 Jul 2026 13:54:28 +0200 Subject: [PATCH 047/329] Prevent 'DSO Party Missed Confirmations' from firing nSV times (#6426) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Oriol Muñoz --- cluster/configs/shared/base.yaml | 2 +- cluster/deployment/scratchneta/config.resolved.yaml | 2 +- cluster/deployment/scratchnetb/config.resolved.yaml | 2 +- cluster/deployment/scratchnetc/config.resolved.yaml | 2 +- cluster/deployment/scratchnetd/config.resolved.yaml | 2 +- cluster/deployment/scratchnete/config.resolved.yaml | 2 +- cluster/expected/observability/expected.json | 2 +- .../grafana-alerting/dso_missed_confirmations_alerts.yaml | 4 ++-- cluster/pulumi/observability/src/observability.ts | 1 - 9 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cluster/configs/shared/base.yaml b/cluster/configs/shared/base.yaml index d46642234d..cc7c63bdbc 100644 --- a/cluster/configs/shared/base.yaml +++ b/cluster/configs/shared/base.yaml @@ -121,7 +121,7 @@ monitoring: rate: 25 overMinutes: 5 dsoMissedConfirmations: - threshold: 0.01 + threshold: 0 # alert as soon as there's any confirmation missing windowMinutes: 10 cloudSql: maintenance: false diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index a6a986c5c5..01d1ba8bf3 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -74,7 +74,7 @@ monitoring: deployment: pendingPeriodMinutes: 5 dsoMissedConfirmations: - threshold: 0.01 + threshold: 0 windowMinutes: 10 gcpQuotas: excludedApproachingMetrics: [] diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index a6a986c5c5..01d1ba8bf3 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -74,7 +74,7 @@ monitoring: deployment: pendingPeriodMinutes: 5 dsoMissedConfirmations: - threshold: 0.01 + threshold: 0 windowMinutes: 10 gcpQuotas: excludedApproachingMetrics: [] diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index a6a986c5c5..01d1ba8bf3 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -74,7 +74,7 @@ monitoring: deployment: pendingPeriodMinutes: 5 dsoMissedConfirmations: - threshold: 0.01 + threshold: 0 windowMinutes: 10 gcpQuotas: excludedApproachingMetrics: [] diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index a6a986c5c5..01d1ba8bf3 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -74,7 +74,7 @@ monitoring: deployment: pendingPeriodMinutes: 5 dsoMissedConfirmations: - threshold: 0.01 + threshold: 0 windowMinutes: 10 gcpQuotas: excludedApproachingMetrics: [] diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index a6a986c5c5..01d1ba8bf3 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -74,7 +74,7 @@ monitoring: deployment: pendingPeriodMinutes: 5 dsoMissedConfirmations: - threshold: 0.01 + threshold: 0 windowMinutes: 10 gcpQuotas: excludedApproachingMetrics: [] diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index fc57706327..264a98fed5 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -81,7 +81,7 @@ "cometbft_deleted_alerts.yaml": "apiVersion: 1\ndeleteRules:\n - orgId: 1\n # Cometbft Consensus\n uid: fa4b8a18-e4be-4d19-a5af-90c7326fd935\n - orgId: 1\n # Cometbft Height Not Advancing\n uid: a3b47fca-0982-491b-80bb-0c70f04dcd8b\n - orgId: 1\n # Cometbft Not Voting\n uid: a3f23df0-40d5-41d1-8b4b-03aa8c62c030\n - orgId: 1\n # Reached Expected Maximum CometBFT Block Rate\n uid: adl2npxte6u4gd\n - orgId: 1\n # CometBFT is not pruning old blocks\n uid: ddlrp7f1f8l4wf\n", "deleted_alerts.yaml": "apiVersion: 1\ndeleteRules:\n - orgId: 1\n # SV Status - mediator -falling behind\n uid: f15539f2-f6ea-4de4-8f7c-c56434d5bd52\n - orgId: 1\n # wasted traffic (sustained)\n uid: adw5rd048zf9cb\n - orgId: 1\n # wasted traffic\n uid: adw5rd048zf9ca\n - orgId: 1\n # Confirmation Requests By Member (replaced by sequencer caps, #2917)\n uid: 88b8827c8d09\n - orgId: 1\n # Total Confirmation Requests (replaced by sequencer caps, #2917)\n uid: 5dcddc9a5487\n - orgId: 1\n uid: aescandisagree1\ndeleteContactPoints:\n - uid: grafana-default-email\n orgId: 1\n", "deployment_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: deployments\n folder: canton-network\n interval: 1m\n rules:\n - uid: adkhl6u18pqtce\n title: Failing Stacks\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n exemplar: true\n expr: splice_deployment_pulumi_stack_status{state=\"failed\"}\n format: time_series\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: stacks_active\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: D\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - D\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: QP_wDqDnz\n panelId: 11\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"11\"\n description: The pulumi operator failed to update the {{ $labels.stack }} stack. Check the logs for the deployment.\n runbook_url: \"\"\n severity: critical\n summary: '{{ $labels.stack }} stack failed to update'\n labels:\n \"\": \"\"\n isPaused: false\n - uid: bdndg5g3x4kxsf\n title: Deployments running\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: splice_deployment_pulumi_stack_condition{type=\"Reconciling\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n dashboardUid: QP_wDqDnz\n panelId: 27\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"27\"\n description: A deployment is currently running for the {{ $labels.stack }} stack.\n runbook_url: \"\"\n severity: info\n summary: Stack {{ $labels.stack }} is being updated\n labels:\n \"\": \"\"\n isPaused: false\n", - "dso_missed_confirmations_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: dso party missed confirmations\n folder: canton-network\n interval: 1m\n rules:\n - uid: ddso1miss0conf0a\n title: DSO Party Missed Confirmations\n condition: missed confirmations threshold exceeded\n data:\n - refId: missed confirmation rate\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by (namespace) (increase(daml_mediator_timeout_non_responsive_participants_total{party=~\"DSO::.*\"}[10m])) / sum by (namespace) (increase(daml_mediator_requests_total[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: missed confirmation rate\n - refId: missed confirmations threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.01\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: missed confirmation rate\n intervalMs: 1000\n maxDataPoints: 43200\n refId: missed confirmations threshold exceeded\n type: threshold\n dashboardUid: cnck4jp\n panelId: 1\n noDataState: OK\n execErrState: Alerting\n for: 0m\n annotations:\n __dashboardUid__: cnck4jp\n __panelId__: \"1\"\n severity: critical\n summary: The DSO party in namespace {{ $labels.namespace }} is missing more than 1% of its confirmations over the last 10 minutes.\n labels: {}\n isPaused: false\n", + "dso_missed_confirmations_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: dso party missed confirmations\n folder: canton-network\n interval: 1m\n rules:\n - uid: ddso1miss0conf0a\n title: DSO Party Missed Confirmations\n condition: missed confirmations threshold exceeded\n data:\n - refId: missed confirmation rate\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (party) (increase(daml_mediator_timeout_non_responsive_participants_total{party=~\"DSO::.*\"}[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: missed confirmation rate\n - refId: missed confirmations threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: missed confirmation rate\n intervalMs: 1000\n maxDataPoints: 43200\n refId: missed confirmations threshold exceeded\n type: threshold\n dashboardUid: cnck4jp\n panelId: 1\n noDataState: OK\n execErrState: Alerting\n for: 0m\n annotations:\n __dashboardUid__: cnck4jp\n __panelId__: \"1\"\n severity: critical\n summary: The DSO party in namespace {{ $labels.namespace }} missed more than 0 of its confirmations over the last 10 minutes.\n labels: {}\n isPaused: false\n", "extra_k8s_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: storage\n folder: k8s\n interval: 5m\n rules:\n - uid: adlrbu5kog0sga\n title: KubePersistentVolumeTooFull\n condition: free_space_below_threshold\n data:\n - refId: free_space\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: kubelet_volume_stats_available_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"} / kubelet_volume_stats_capacity_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: free_space\n - refId: free_space_below_threshold\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.15\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: free_space\n intervalMs: 1000\n maxDataPoints: 43200\n refId: free_space_below_threshold\n type: threshold\n dashboardUid: 919b92a8e8041bd567af9edab12c840c\n panelId: 2\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: 919b92a8e8041bd567af9edab12c840c\n __panelId__: \"2\"\n severity: warning\n description: The PersistentVolume claimed by {{ index $labels \"persistentvolumeclaim\" }} in Namespace {{ index $labels \"namespace\" }} is running out of disk space. Currently {{ humanizePercentage (index $values \"free_space\").Value }} is available.\n runbook_url: https://runbooks.prometheus-operator.dev/runbooks/kubernetes/kubepersistentvolumefillingup\n summary: PersistentVolume is too full.\n isPaused: false\n", "load-tester_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: load-tester\n folder: canton-network\n interval: 1m\n rules:\n - uid: a1dbc4e8-7941-4351-9a14-f8573fd2be2b\n title: K6 Request Failure Rate Exceeded\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: B\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total{expected_response=\"false\"}) or on() vector(0)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: ($B / $A) * 100\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 50\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 1m\n annotations:\n description: ''\n runbook_url: ''\n summary: The k6 load tester experienced HTTP request failures at a rate past the acceptable threshold\n labels:\n gcloud_filter: ''\n isPaused: false\n - uid: ddogbytusmz9cb\n title: K6 Throughput Below Threshold\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: rate(k6_transfers_completed_total{scenario=\"generate_load\"}[$__rate_interval])\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.123\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: B\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n - refId: B\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n hide: false\n reducer: mean\n refId: B\n type: reduce\n dashboardUid: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n panelId: 28\n noDataState: Alerting\n execErrState: Alerting\n for: 15m # give some time for restarts\n annotations:\n __dashboardUid__: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n __panelId__: \"28\"\n severity: warning\n description: ''\n runbook_url: ''\n summary: Transaction rate from the k6 load tester is lower than expected threshold\n labels:\n '': ''\n isPaused: false\n", "mining-rounds_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: mining-rounds\n folder: canton-network\n interval: 1m\n rules:\n - uid: ae74w21qca2o0e\n title: Open Mining Rounds Not Advancing\n condition: No new round\n data:\n - refId: Round number diff\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: max(max(max_over_time(splice_sv_dso_store_latest_open_mining_round[30m])) by (namespace) - min(min_over_time(splice_sv_dso_store_latest_open_mining_round[30m])) by (namespace))\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Round number diff\n useBackend: false\n - refId: No new round\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.9\n type: gt\n datasource:\n type: __expr__\n uid: __expr__\n expression: Round number diff\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No new round\n type: threshold\n dashboardUid: ed94a332-4fa7-47f8-982b-fc997381175b\n panelId: 1\n noDataState: Alerting\n execErrState: OK\n for: 1m\n annotations:\n __dashboardUid__: ed94a332-4fa7-47f8-982b-fc997381175b\n __panelId__: \"1\"\n runbook_url: \"\"\n summary: \"The open mining rounds have not advanced in the last 30m\"\n description: 'None of our SV apps have seen the open mining round advancing in the last 30m. Either all of our SVs or a high enough number of SVs to break BFT guarantees are failing'\n severity: critical\n labels:\n priority: high\n isPaused: false\n - uid: ae74w21qca2o0d\n title: Issuing Rounds Not Advancing\n condition: No new round\n data:\n - refId: Round number diff\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: max(max(max_over_time(splice_sv_dso_store_latest_issuing_mining_round[30m])) by (namespace) - min(min_over_time(splice_sv_dso_store_latest_issuing_mining_round[30m])) by (namespace))\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Round number diff\n useBackend: false\n - refId: No new round\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.9\n type: gt\n datasource:\n type: __expr__\n uid: __expr__\n expression: Round number diff\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No new round\n type: threshold\n dashboardUid: ed94a332-4fa7-47f8-982b-fc997381175b\n panelId: 1\n noDataState: Alerting\n execErrState: OK\n for: 1m\n annotations:\n __dashboardUid__: ed94a332-4fa7-47f8-982b-fc997381175b\n __panelId__: \"2\"\n runbook_url: \"\"\n summary: \"The issuing mining rounds have not advanced in the last 30m\"\n description: 'None of our SV apps have seen the issuing mining round advancing in the last 30m. Either all of our SVs or a high enough number of SVs to break BFT guarantees are failing'\n severity: critical\n labels:\n priority: high\n isPaused: false\n", diff --git a/cluster/pulumi/observability/grafana-alerting/dso_missed_confirmations_alerts.yaml b/cluster/pulumi/observability/grafana-alerting/dso_missed_confirmations_alerts.yaml index 9ebde7b920..b7e5f678eb 100644 --- a/cluster/pulumi/observability/grafana-alerting/dso_missed_confirmations_alerts.yaml +++ b/cluster/pulumi/observability/grafana-alerting/dso_missed_confirmations_alerts.yaml @@ -16,7 +16,7 @@ groups: datasourceUid: prometheus model: editorMode: code - expr: sum by (namespace) (increase(daml_mediator_timeout_non_responsive_participants_total{party=~"DSO::.*"}[$DSO_MISSED_CONFIRMATIONS_WINDOW_MINUTESm])) / sum by (namespace) (increase(daml_mediator_requests_total[$DSO_MISSED_CONFIRMATIONS_WINDOW_MINUTESm])) + expr: max by (party) (increase(daml_mediator_timeout_non_responsive_participants_total{party=~"DSO::.*"}[$DSO_MISSED_CONFIRMATIONS_WINDOW_MINUTESm])) instant: true intervalMs: 1000 legendFormat: __auto @@ -57,6 +57,6 @@ groups: __dashboardUid__: cnck4jp __panelId__: "1" severity: critical - summary: The DSO party in namespace {{ $labels.namespace }} is missing more than $DSO_MISSED_CONFIRMATIONS_THRESHOLD_PERCENT% of its confirmations over the last $DSO_MISSED_CONFIRMATIONS_WINDOW_MINUTES minutes. + summary: The DSO party in namespace {{ $labels.namespace }} missed more than $DSO_MISSED_CONFIRMATIONS_THRESHOLD of its confirmations over the last $DSO_MISSED_CONFIRMATIONS_WINDOW_MINUTES minutes. labels: {} isPaused: false diff --git a/cluster/pulumi/observability/src/observability.ts b/cluster/pulumi/observability/src/observability.ts index 9f37f5f68d..e9ad3c76ce 100644 --- a/cluster/pulumi/observability/src/observability.ts +++ b/cluster/pulumi/observability/src/observability.ts @@ -764,7 +764,6 @@ function substituteScanConnectionDisagreementAlerts(alert: string): string { function substituteDsoMissedConfirmationsAlerts(alert: string): string { const config = monitoringConfig.alerting.alerts.dsoMissedConfirmations; return alert - .replaceAll('$DSO_MISSED_CONFIRMATIONS_THRESHOLD_PERCENT', (config.threshold * 100).toString()) .replaceAll('$DSO_MISSED_CONFIRMATIONS_THRESHOLD', config.threshold.toString()) .replaceAll('$DSO_MISSED_CONFIRMATIONS_WINDOW_SECONDS', (config.windowMinutes * 60).toString()) .replaceAll('$DSO_MISSED_CONFIRMATIONS_WINDOW_MINUTES', config.windowMinutes.toString()); From b6379f1ef379b0cba3723c30441168e90aec4753 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Wed, 15 Jul 2026 15:04:17 +0200 Subject: [PATCH 048/329] Add pulumi config to disable sequencer anti affinity (#6421) disable in scratches it no longer really makes sense for non prod clusters will also disable in cilr as we moved to fairly big nodes there [static] Signed-off-by: Nicu Reut --- cluster/configs/shared/scratchnet-sv.yaml | 1 + .../scratchneta/config.resolved.yaml | 18 ++++++++++++++++++ .../scratchnetb/config.resolved.yaml | 18 ++++++++++++++++++ .../scratchnetc/config.resolved.yaml | 18 ++++++++++++++++++ .../scratchnetd/config.resolved.yaml | 18 ++++++++++++++++++ .../scratchnete/config.resolved.yaml | 18 ++++++++++++++++++ cluster/expected/sv-canton/expected.json | 12 ++++++++++++ .../src/physicalSynchronizerConfig.ts | 1 + .../src/decentralizedSynchronizerNode.ts | 1 + 9 files changed, 105 insertions(+) diff --git a/cluster/configs/shared/scratchnet-sv.yaml b/cluster/configs/shared/scratchnet-sv.yaml index 4cd0d393e9..b07cb0aa5e 100644 --- a/cluster/configs/shared/scratchnet-sv.yaml +++ b/cluster/configs/shared/scratchnet-sv.yaml @@ -22,6 +22,7 @@ participant: requests: cpu: "1" sequencer: + enableAntiAffinity: false # formerly the effect of `SEQUENCER_LOW_RESOURCES=true` resources: limits: diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index 01d1ba8bf3..a01269d394 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -542,6 +542,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -616,6 +617,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -690,6 +692,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -764,6 +767,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -838,6 +842,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -912,6 +917,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -986,6 +992,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1060,6 +1067,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1134,6 +1142,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1208,6 +1217,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1282,6 +1292,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1356,6 +1367,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1430,6 +1442,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1504,6 +1517,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1578,6 +1592,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1652,6 +1667,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1726,6 +1742,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1803,6 +1820,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index 01d1ba8bf3..a01269d394 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -542,6 +542,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -616,6 +617,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -690,6 +692,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -764,6 +767,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -838,6 +842,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -912,6 +917,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -986,6 +992,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1060,6 +1067,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1134,6 +1142,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1208,6 +1217,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1282,6 +1292,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1356,6 +1367,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1430,6 +1442,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1504,6 +1517,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1578,6 +1592,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1652,6 +1667,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1726,6 +1742,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1803,6 +1820,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index 01d1ba8bf3..a01269d394 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -542,6 +542,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -616,6 +617,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -690,6 +692,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -764,6 +767,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -838,6 +842,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -912,6 +917,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -986,6 +992,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1060,6 +1067,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1134,6 +1142,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1208,6 +1217,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1282,6 +1292,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1356,6 +1367,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1430,6 +1442,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1504,6 +1517,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1578,6 +1592,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1652,6 +1667,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1726,6 +1742,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1803,6 +1820,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index 01d1ba8bf3..a01269d394 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -542,6 +542,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -616,6 +617,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -690,6 +692,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -764,6 +767,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -838,6 +842,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -912,6 +917,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -986,6 +992,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1060,6 +1067,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1134,6 +1142,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1208,6 +1217,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1282,6 +1292,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1356,6 +1367,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1430,6 +1442,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1504,6 +1517,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1578,6 +1592,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1652,6 +1667,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1726,6 +1742,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1803,6 +1820,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index 01d1ba8bf3..a01269d394 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -542,6 +542,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -616,6 +617,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -690,6 +692,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -764,6 +767,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -838,6 +842,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -912,6 +917,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -986,6 +992,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1060,6 +1067,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1134,6 +1142,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1208,6 +1217,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1282,6 +1292,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1356,6 +1367,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1430,6 +1442,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1504,6 +1517,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1578,6 +1592,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1652,6 +1667,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1726,6 +1742,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' @@ -1803,6 +1820,7 @@ svs: cpu: '2' memory: '2Gi' sequencer: + enableAntiAffinity: false resources: limits: cpu: '3' diff --git a/cluster/expected/sv-canton/expected.json b/cluster/expected/sv-canton/expected.json index 3bd648d617..90e0975394 100644 --- a/cluster/expected/sv-canton/expected.json +++ b/cluster/expected/sv-canton/expected.json @@ -1941,6 +1941,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -2076,6 +2077,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -2207,6 +2209,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -2338,6 +2341,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -5515,6 +5519,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -5650,6 +5655,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -5781,6 +5787,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -5912,6 +5919,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -8375,6 +8383,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -8510,6 +8519,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -8641,6 +8651,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, @@ -8772,6 +8783,7 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, + "enableAntiAffinity": true, "enablePostgresMetrics": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "logAsyncFlush": false, diff --git a/cluster/pulumi/common-sv/src/physicalSynchronizerConfig.ts b/cluster/pulumi/common-sv/src/physicalSynchronizerConfig.ts index 99cee3b64b..fd89fe8721 100644 --- a/cluster/pulumi/common-sv/src/physicalSynchronizerConfig.ts +++ b/cluster/pulumi/common-sv/src/physicalSynchronizerConfig.ts @@ -28,6 +28,7 @@ export const SvSequencerConfigSchema = z additionalJvmOptions: z.string().optional(), cloudSql: CloudSqlWithOverrideConfigSchema, resources: K8sResourceSchema, + enableAntiAffinity: z.boolean().default(true), }) .strict(); export type SvSequencerConfig = z.infer; diff --git a/cluster/pulumi/sv-canton/src/decentralizedSynchronizerNode.ts b/cluster/pulumi/sv-canton/src/decentralizedSynchronizerNode.ts index f537fb307b..0dd2a463fa 100644 --- a/cluster/pulumi/sv-canton/src/decentralizedSynchronizerNode.ts +++ b/cluster/pulumi/sv-canton/src/decentralizedSynchronizerNode.ts @@ -177,6 +177,7 @@ abstract class InStackDecentralizedSynchronizerNode ), pvc: persistentHeapDumpsPvc(), serviceAccountName: imagePullServiceAccountName, + enableAntiAffinity: physicalSynchronizerConfig.sequencer.enableAntiAffinity, }, }, this.version, From 952f466332d98234655348098c1b7d8212a69a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 15 Jul 2026 15:46:00 +0200 Subject: [PATCH 049/329] Disable token-standard sanity check in unvetting test (#6429) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci] Signed-off-by: Oriol Muñoz --- .../tests/UnsupportedPackageVettingIntegrationTest.scala | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala index d01c1e364e..6699fec30d 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala @@ -45,6 +45,11 @@ class UnsupportedPackageVettingIntegrationTest with AmuletConfigUtil with WalletTestUtil { + // Prevent failures due to: + // NO_VETTED_INTERFACE_IMPLEMENTATION_PACKAGE(9,f5ce331d): + // No vetted package for rendering the interface view for package-name 'splice-amulet' + override protected def runTokenStandardCliSanityCheck: Boolean = false + override def environmentDefinition: SpliceEnvironmentDefinition = EnvironmentDefinition .simpleTopology1Sv(this.getClass.getSimpleName) From 6d0b4a9e5b9873896783490c1c884913617ccd68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 15 Jul 2026 15:55:01 +0200 Subject: [PATCH 050/329] Fix missing TransferInstruction in TestTokenV2SettlementIntegrationTest (#6428) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Signed-off-by: Oriol Muñoz --- .../tests/TestTokenV2SettlementIntegrationTest.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala index d83ae7c2e9..65c2930413 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala @@ -252,7 +252,7 @@ class TestTokenV2SettlementIntegrationTest ) // Bob accepts - val transferInstruction = + val transferInstruction = eventually() { Contract .fromCreatedEvent(transferinstructionv2.TransferInstruction.INTERFACE)( CreatedEvent.fromProto( @@ -275,6 +275,7 @@ class TestTokenV2SettlementIntegrationTest ) ) .valueOrFail("Failed to read transferinstructionv2.TransferInstruction") + } val acceptContext = registry.getContext( transferInstruction.payload.transfer.inputHoldingCids.asScala.toSeq From 11cb06b28abfef60c550f6fcb9e4b57aea819b84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 15 Jul 2026 16:38:10 +0200 Subject: [PATCH 051/329] Fix usage of archived ExternalPartyConfigState in TestTokenV2SettlementIntegrationTest (#6432) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oriol Muñoz --- ...TestTokenV2SettlementIntegrationTest.scala | 234 +++++++++--------- 1 file changed, 120 insertions(+), 114 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala index 65c2930413..df4ed66108 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala @@ -626,126 +626,132 @@ class TestTokenV2SettlementIntegrationTest val (settleTradeTx, _) = actAndCheck( "Venue settles the trade", { - val allAllocations = { - venueValidator.participantClientWithAdminToken.ledger_api.state.acs.of_party( - party = venueParty, - filterInterfaces = Seq(allocationv2.Allocation.TEMPLATE_ID).map(templateId => - TemplateId( - templateId.getPackageId, - templateId.getModuleName, - templateId.getEntityName, - ) + // UpdateExternalPartyConfigStateTrigger might run concurrently and cause a LOCAL_VERDICT_INACTIVE_CONTRACTS + // because of the ExternalPartyConfigState being updated. + // In the real world, we expect the venue to also just retry re-fetching all contexts + eventuallySucceeds() { + val allAllocations = { + venueValidator.participantClientWithAdminToken.ledger_api.state.acs.of_party( + party = venueParty, + filterInterfaces = Seq(allocationv2.Allocation.TEMPLATE_ID).map(templateId => + TemplateId( + templateId.getPackageId, + templateId.getModuleName, + templateId.getEntityName, + ) + ), + includeCreatedEventBlob = true, + ) + } + // sanity check + (bobAllocationCids ++ aliceAllocationCids).foreach { cid => + allAllocations + .find(_.contractId == cid.contractId) + .valueOrFail(s"No allocation found for cid $cid") + } + val amuletAllocations = + allAllocations.filter(_.event.signatories.contains(dsoParty.toProtoPrimitive)) + val usdAllocations = + allAllocations.filter(_.event.signatories.contains(ttAdminParty.toProtoPrimitive)) + val settleBatch = new allocationv2.SettlementFactory_SettleBatch( + new allocationv2.SettlementInfo( + java.util.List.of(venueParty.toProtoPrimitive), + "OTCTrade", + java.util.Optional + .of(new metadatav1.AnyContract.ContractId(otcTrade.id.contractId)), + emptyMetadata, ), - includeCreatedEventBlob = true, - ) - } - // sanity check - (bobAllocationCids ++ aliceAllocationCids).foreach { cid => - allAllocations - .find(_.contractId == cid.contractId) - .valueOrFail(s"No allocation found for cid $cid") - } - val amuletAllocations = - allAllocations.filter(_.event.signatories.contains(dsoParty.toProtoPrimitive)) - val usdAllocations = - allAllocations.filter(_.event.signatories.contains(ttAdminParty.toProtoPrimitive)) - val settleBatch = new allocationv2.SettlementFactory_SettleBatch( - new allocationv2.SettlementInfo( - java.util.List.of(venueParty.toProtoPrimitive), - "OTCTrade", - java.util.Optional.of(new metadatav1.AnyContract.ContractId(otcTrade.id.contractId)), - emptyMetadata, - ), - transferLegsFromTrade(otcTrade).asJava, - allAllocations - .map(alloc => - new allocationv2.FinalizedAllocation( - new allocationv2.Allocation.ContractId(alloc.contractId), - java.util.List.of(), - java.util.Optional.empty[java.util.Map[String, java.math.BigDecimal]](), + transferLegsFromTrade(otcTrade).asJava, + allAllocations + .map(alloc => + new allocationv2.FinalizedAllocation( + new allocationv2.Allocation.ContractId(alloc.contractId), + java.util.List.of(), + java.util.Optional.empty[java.util.Map[String, java.math.BigDecimal]](), + ) ) - ) - .asJava, - /*actors = */ java.util.List.of(venueParty.toProtoPrimitive), - emptyExtraArgs, - ) - val amuletContext = sv1ScanBackend.getSettlementFactoryV2(settleBatch) - val bobUsdcHoldings = getHoldings(bobParty, bobValidatorBackend) - .map(_.contractId) - .map(id => new holdingv2.Holding.ContractId(id)) - val usdcContext = registry.getContext( - bobUsdcHoldings - ) - venueValidator.participantClientWithAdminToken.ledger_api_extensions.commands - .submitJava( - actAs = Seq(venueParty), - commands = otcTrade.id - .exerciseOTCTrade_Settle( - Map[String, tradingappv2.SettlementBatch]( - dsoParty.toProtoPrimitive -> new SettlementBatchV2( - amuletAllocations - .map(alloc => new allocationv2.Allocation.ContractId(alloc.contractId)) - .asJava, - java.util.List.of(), - amuletContext.factoryId, - amuletContext.args.extraArgs, - ), - ttAdminParty.toProtoPrimitive -> new SettlementBatchV2( - usdAllocations - .map(alloc => new allocationv2.Allocation.ContractId(alloc.contractId)) - .asJava, - java.util.List.of( - new tradingappv2.MissingAllocation( - java.util.Optional.empty(), - tokenRulesId.toInterface( - allocationinstructionv2.AllocationFactory.INTERFACE - ), - new allocationinstructionv2.AllocationFactory_Allocate( - new allocationv2.SettlementInfo( - java.util.List.of(venueParty.toProtoPrimitive), - "OTCTradeProposal", - java.util.Optional.of( - new metadatav1.AnyContract.ContractId(otcTrade.id.contractId) - ), - emptyMetadata, + .asJava, + /*actors = */ java.util.List.of(venueParty.toProtoPrimitive), + emptyExtraArgs, + ) + val amuletContext = sv1ScanBackend.getSettlementFactoryV2(settleBatch) + val bobUsdcHoldings = getHoldings(bobParty, bobValidatorBackend) + .map(_.contractId) + .map(id => new holdingv2.Holding.ContractId(id)) + val usdcContext = registry.getContext( + bobUsdcHoldings + ) + venueValidator.participantClientWithAdminToken.ledger_api_extensions.commands + .submitJava( + actAs = Seq(venueParty), + commands = otcTrade.id + .exerciseOTCTrade_Settle( + Map[String, tradingappv2.SettlementBatch]( + dsoParty.toProtoPrimitive -> new SettlementBatchV2( + amuletAllocations + .map(alloc => new allocationv2.Allocation.ContractId(alloc.contractId)) + .asJava, + java.util.List.of(), + amuletContext.factoryId, + amuletContext.args.extraArgs, + ), + ttAdminParty.toProtoPrimitive -> new SettlementBatchV2( + usdAllocations + .map(alloc => new allocationv2.Allocation.ContractId(alloc.contractId)) + .asJava, + java.util.List.of( + new tradingappv2.MissingAllocation( + java.util.Optional.empty(), + tokenRulesId.toInterface( + allocationinstructionv2.AllocationFactory.INTERFACE ), - new allocationv2.AllocationSpecification( - ttAdminParty.toProtoPrimitive, - basicAccount(venueParty), - java.util.List.of( - new allocationv2.TransferLegSide( - "alicetovenue0.2USDC", - allocationv2.TransferSide.RECEIVERSIDE, - basicAccount(aliceParty), - BigDecimal(0.2).bigDecimal, - usdcInstrumentName, - emptyMetadata, - ) + new allocationinstructionv2.AllocationFactory_Allocate( + new allocationv2.SettlementInfo( + java.util.List.of(venueParty.toProtoPrimitive), + "OTCTradeProposal", + java.util.Optional.of( + new metadatav1.AnyContract.ContractId(otcTrade.id.contractId) + ), + emptyMetadata, + ), + new allocationv2.AllocationSpecification( + ttAdminParty.toProtoPrimitive, + basicAccount(venueParty), + java.util.List.of( + new allocationv2.TransferLegSide( + "alicetovenue0.2USDC", + allocationv2.TransferSide.RECEIVERSIDE, + basicAccount(aliceParty), + BigDecimal(0.2).bigDecimal, + usdcInstrumentName, + emptyMetadata, + ) + ), + java.util.Optional.empty(), + java.util.Optional.empty(), + false, + emptyMetadata, ), - java.util.Optional.empty(), - java.util.Optional.empty(), - false, - emptyMetadata, + Instant.now(), + java.util.List.of(), + new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), + java.util.List.of(venueParty.toProtoPrimitive), ), - Instant.now(), - java.util.List.of(), - new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), - java.util.List.of(venueParty.toProtoPrimitive), - ), - ) + ) + ), + new allocationv2.SettlementFactory.ContractId(tokenRulesId.contractId), + new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), ), - new allocationv2.SettlementFactory.ContractId(tokenRulesId.contractId), - new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), - ), - ).asJava, - java.util.List.of(), - ) - .commands() - .asScala - .toSeq, - disclosedContracts = - usdcContext.disclosedContracts ++ amuletContext.disclosedContracts, - ) + ).asJava, + java.util.List.of(), + ) + .commands() + .asScala + .toSeq, + disclosedContracts = + usdcContext.disclosedContracts ++ amuletContext.disclosedContracts, + ) + } }, )( "The balances are updated", From ca1ce96850564e64f1b78d29de90ff61690cd20f Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Wed, 15 Jul 2026 17:37:27 +0200 Subject: [PATCH 052/329] Improve removal of CantonBFT p2p connections (#6425) In particular this can now remove connections with wrong sequencer ids in some cases it didn't before. I personally also just find it easier to follow what is going on. Note that this does not fix the issues we encountered still sadly, we do now issue a remove before add. But that's not sufficient due to https://github.com/DACH-NY/canton/issues/34191. In combination with a restart it does work but that's obviously meh. [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- .../SequencerBftPeerReconciler.scala | 72 +++++++++---------- .../SequencerBftPeerReconcilerSpec.scala | 11 ++- 2 files changed, 38 insertions(+), 45 deletions(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconciler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconciler.scala index 019a1f175b..0c1dc07e07 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconciler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconciler.scala @@ -79,10 +79,10 @@ abstract class SequencerBftPeerReconciler( .listConfiguredPeerEndpoints() peersToAdd = dsoSequencerEndpoints .filterNot(endpoint => configuredPeers.exists(_.id == endpoint.id)) - candidatePeersToRemove = configuredPeers + peersWithNoDsoRulesEndpoint = configuredPeers .filterNot(peer => dsoSequencerEndpoints.exists(_.id == peer.id)) peersToRemove <- computePeersToRemove( - candidatePeersToRemove, + configuredPeers, dsoSequencersWithEndpoint, ) } yield { @@ -99,47 +99,43 @@ abstract class SequencerBftPeerReconciler( } yield result } - /** If all DSO sequencers have an associated peer endpoint advertised by scan, any configured peer - * that does not correspond to one of those endpoints is stale and safe to remove. - * - * Otherwise we cannot rely on scan alone (as some scans can be unavailable), so we cross-check the peer network status to find the - * sequencer id backing each candidate endpoint. Removal is only safe if that sequencer id is no - * longer part of the DSO sequencers, or if it is now associated with a different endpoint. If no - * sequencer id can be found for a candidate endpoint we keep it and log a warning. - */ private def computePeersToRemove( - candidatePeersToRemove: Seq[P2PEndpoint], + configuredPeers: Seq[P2PEndpoint], dsoSequencersWithEndpoint: Seq[(SequencerId, Option[P2PEndpoint])], )(implicit tc: TraceContext, ec: ExecutionContext): Future[Seq[P2PEndpoint]] = { - val allDsoSequencersHaveEndpoint = dsoSequencersWithEndpoint.forall { case (_, endpoint) => - endpoint.isDefined - } - if (candidatePeersToRemove.isEmpty || allDsoSequencersHaveEndpoint) { - Future.successful(candidatePeersToRemove) - } else { - sequencerAdminConnection.listCurrentPeerEndpoints().map { networkStatus => - candidatePeersToRemove.filter { peer => - networkStatus.collectFirst { - case (Some(sequencerId), Some(endpointId)) if endpointId == peer.id => sequencerId - } match { - case Some(sequencerId) => - val sequencerNoLongerInDso = - !dsoSequencersWithEndpoint.exists { case (dsoSequencerId, _) => - dsoSequencerId == sequencerId - } - val sequencerMovedToDifferentEndpoint = - dsoSequencersWithEndpoint.exists { case (dsoSequencerId, endpoint) => - dsoSequencerId == sequencerId && endpoint.exists(_.id != peer.id) - } - sequencerNoLongerInDso || sequencerMovedToDifferentEndpoint - case None => - logger.warn( - s"Could not find a sequencer id for the configured peer endpoint ${peer.id} in the peer network status; not removing it to be safe." - ) - false - } + sequencerAdminConnection.listCurrentPeerEndpoints().map { networkStatus => + val configuredPeersWithSequencerId = configuredPeers.map { peer => + peer -> networkStatus.collectFirst { + case (Some(sequencerId), Some(endpoint)) if endpoint == peer.id => sequencerId } } + val peersWithWrongSequencerId = configuredPeersWithSequencerId.filter { + case (peer, Some(sequencerId)) => + !dsoSequencersWithEndpoint.exists({ case (dsoSequencerId, _) => + sequencerId == dsoSequencerId + }) + case _ => false + } + val peersWithChangedEndpoint = configuredPeersWithSequencerId.filter { + case (peer, Some(sequencerId)) => + dsoSequencersWithEndpoint.exists({ case (dsoSequencerId, endpoint) => + sequencerId == dsoSequencerId && endpoint.exists(_.id != peer.id) + }) + case _ => false + } + // we only remove connections for which we don't have a sequencer id when we have been able to query all scans to get connections. otherwise a temporary scan issue could result in us removing the peer. + val unknownPeers = + if (dsoSequencersWithEndpoint.forall { case (_, endpoint) => endpoint.isDefined }) { + configuredPeers.filter(peer => + !dsoSequencersWithEndpoint.exists { case (_, endpoint) => + endpoint.exists(_.id == peer.id) + } + ) + } else Seq.empty + + (peersWithWrongSequencerId.map(_._1) ++ peersWithChangedEndpoint.map( + _._1 + ) ++ unknownPeers).distinct } } diff --git a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconcilerSpec.scala b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconcilerSpec.scala index f6efa519cc..c2f25430ce 100644 --- a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconcilerSpec.scala +++ b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconcilerSpec.scala @@ -91,6 +91,8 @@ class SequencerBftPeerReconcilerSpec extends AnyFlatSpec with BaseTest with HasR ) ) + withNetworkStatus() + withScanSequencers( BftSequencer( serialId, @@ -389,7 +391,7 @@ class SequencerBftPeerReconcilerSpec extends AnyFlatSpec with BaseTest with HasR result.toRemove should contain only sequencer1Host } - it should "keep a configured peer and log a warning when no sequencer id can be found for it in the network status" in { + it should "keep a configured peer when no sequencer id can be found for it in the network status" in { withConfiguredDsoSequencers( Seq( createSequencerConfig(sequencer1Id), @@ -419,12 +421,7 @@ class SequencerBftPeerReconcilerSpec extends AnyFlatSpec with BaseTest with HasR (Some(sequencer1Id), Some(sequencer1Host)) ) - val result = loggerFactory.assertLogs( - reconciler.diffDsoRulesWithTopology().futureValue, - _.warningMessage should include( - s"Could not find a sequencer id for the configured peer endpoint ${sequencer2Host}" - ), - ) + val result = reconciler.diffDsoRulesWithTopology().futureValue result should be(empty) } From 486b55bb63c487a089170685d062fdae4d34b926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Wed, 15 Jul 2026 17:55:59 +0200 Subject: [PATCH 053/329] Silence SEQUENCER_SUBMISSION_AFTER_UPGRADE_TIME in LSU tests (#6433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- project/ignore-patterns/canton_log.ignore.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project/ignore-patterns/canton_log.ignore.txt b/project/ignore-patterns/canton_log.ignore.txt index 92d3377fb2..a4acd0c5ca 100644 --- a/project/ignore-patterns/canton_log.ignore.txt +++ b/project/ignore-patterns/canton_log.ignore.txt @@ -177,7 +177,7 @@ Missing successor information for the following sequencers.*sv4 Skipping insertion of pending onboarding flag clearance operation as no party ID was provided # LSU: submissions on the old physical synchronizer after the upgrade time has passed are rejected -Request failed for server-DefaultSequencer-0.\\n GrpcRequestRefusedByServer: FAILED_PRECONDITION/SEQUENCER_SUBMISSION_AFTER_UPGRADE_TIME +GrpcRequestRefusedByServer: FAILED_PRECONDITION/SEQUENCER_SUBMISSION_AFTER_UPGRADE_TIME # LSU SV4 migrates late SEQ::sv4.*Sequencer can't take requests because it is behind on processing events Sequencer can't take requests because it is behind on processing events.*sequencer=>sv4StandaloneSequencer From f39c0ed59c89ca479b3be4f921c72804a5102192 Mon Sep 17 00:00:00 2001 From: Jagath Weerasinghe Date: Wed, 15 Jul 2026 18:04:49 +0200 Subject: [PATCH 054/329] Add ignore log patterns covering both FlywayExecutor and ClassPathScanner (#6434) Signed-off-by: Jagath Weerasinghe --- project/ignore-patterns/canton_network_test_log.ignore.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/project/ignore-patterns/canton_network_test_log.ignore.txt b/project/ignore-patterns/canton_network_test_log.ignore.txt index 5e0358dad8..ab044cad67 100644 --- a/project/ignore-patterns/canton_network_test_log.ignore.txt +++ b/project/ignore-patterns/canton_network_test_log.ignore.txt @@ -173,4 +173,6 @@ api/sv/v0/onboard/validator \(POST\) resulted in a timeout # In BaseStorePerformanceTest, we run migrations in an unforked sbt JVM, so Flyway # finds the sbt test jar (apps-app_*-tests.jar). But, Flyway cannot open it, # so it skips it with this WARN. It contains no migrations, so this is harmless and unrelated to DB migrations. -Skipping unloadable jar file:.*FlywayExecutor +# The same warning is emitted by both the FlywayExecutor and the ClassPathScanner loggers. +# a single pattern matching the message (the unloadable sbt tests jar) covers both. +Skipping unloadable jar file: file:.*-tests\.jar From b72e00efa880f83c814e1e3768383cadb7ed52b8 Mon Sep 17 00:00:00 2001 From: Divam <681060+dfordivam@users.noreply.github.com> Date: Thu, 16 Jul 2026 11:59:37 +0900 Subject: [PATCH 055/329] ProcessRewardsTrigger fix for batches with parties with mixed package vetting state (#6416) Signed-off-by: Divam --- ...wardCouponV2TimeBasedIntegrationTest.scala | 199 +++++++++++++++--- .../delegatebased/ProcessRewardsTrigger.scala | 41 +++- 2 files changed, 204 insertions(+), 36 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest.scala index 1c313bf995..613362b3e4 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest.scala @@ -28,12 +28,14 @@ import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.{ IntegrationTestWithIsolatedEnvironment, SpliceTestConsoleEnvironment, } -import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.ExpireRewardCouponV2Trigger +import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.{ + ExpireRewardCouponV2Trigger, + UnhideRewardCouponV2Trigger, +} import org.lfdecentralizedtrust.splice.sv.config.InitialRewardConfig import org.lfdecentralizedtrust.splice.util.{ ChoiceContextWithDisclosures, TimeTestUtil, - TriggerTestUtil, UploadablePackage, WalletTestUtil, } @@ -54,26 +56,36 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest extends IntegrationTestWithIsolatedEnvironment with HasExecutionContext with WalletTestUtil - with TriggerTestUtil with TimeTestUtil { - private val v2AmuletVersion = DarResources.amulet_0_1_19.metadata.version + // Version where V2 was introduced, or the current minimum initialization version if higher + private val minV2AmuletVersion = + Ordering[PackageVersion].max( + DarResources.amulet.minimumInitialization.metadata.version, + DarResources.amulet_0_1_19.metadata.version, + ) + + private val minV2AmuletPackageId = + DarResources.amulet.getPackageIdWithVersion(minV2AmuletVersion.toString).value - private val previousAmuletPackageId = - DarResources.amulet.others - .filter(_.metadata.version < v2AmuletVersion) - .maxBy(_.metadata.version) - .packageId + private val latestAmuletDar: DarResource = DarResources.amulet.latest private val v2CapableAmuletPackageIds: Seq[String] = DarResources.amulet.all - .filter(_.metadata.version >= v2AmuletVersion) + .filter(_.metadata.version >= minV2AmuletVersion) + .map(_.packageId) + .distinct + + private val amuletVersionsAboveOldestV2: Seq[String] = + DarResources.amulet.all + .filter(_.metadata.version > minV2AmuletVersion) .map(_.packageId) .distinct - // Set of packages alice must not vet to have wrong vetting state for v2 coupons - private val v2CapableDarsUnvettedOnAlice: Seq[DarResource] = { - val v2CapableAmuletIds = v2CapableAmuletPackageIds.toSet + // Only the latest is unvetted, as this would still cause + // ProcessRewardsTrigger to create hidden coupons + private val darsUnvettedOnAliceAtStart: Seq[DarResource] = { + val latestAmuletIds = Set(latestAmuletDar.packageId) Seq( DarResources.amulet, DarResources.amuletNameService, @@ -82,8 +94,8 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest DarResources.walletPayments, ).flatMap(_.all) .filter(d => - v2CapableAmuletIds.contains(d.packageId) || - d.dependencyPackageIds.exists(v2CapableAmuletIds.contains) + latestAmuletIds.contains(d.packageId) || + d.dependencyPackageIds.exists(latestAmuletIds.contains) ) .distinctBy(d => (d.metadata.name, d.metadata.version)) } @@ -99,7 +111,7 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest (aliceValidator -> config .validatorApps(aliceValidator) .copy( - additionalPackagesToUnvet = v2CapableDarsUnvettedOnAlice + additionalPackagesToUnvet = darsUnvettedOnAliceAtStart .groupBy(_.metadata.name) .map { case (name, resources) => name -> resources.map(_.metadata.version).toSet @@ -121,6 +133,11 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest _.withPausedTrigger[AcceptedTransferOfferTrigger] )(config) ) + .addConfigTransform((_, config) => + updateAutomationConfig(ConfigurableApp.Sv)( + _.withPausedTrigger[UnhideRewardCouponV2Trigger] + )(config) + ) .addConfigTransform((_, config) => ConfigTransforms.updateAllSvAppConfigs_(svConfig => svConfig.copy( @@ -135,7 +152,7 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest "Unhide and expire of RewardCouponV2" in { implicit env => val aliceParticipantId = aliceValidatorBackend.appState.participantAdminConnection.getParticipantId().futureValue - assertAliceVettedBelowV2(aliceParticipantId) + assertAliceVettedBelowLatest(aliceParticipantId) val (aliceParty, bobParty) = onboardAliceAndBobWithFeaturedRights() @@ -216,6 +233,10 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest clue("UnhideRewardCouponV2Trigger unhides Alice's coupons once she is re-vetted") { eventually() { + sv1Backend.dsoDelegateBasedAutomation + .trigger[UnhideRewardCouponV2Trigger] + .runOnce() + .futureValue val coupons = aliceCoupons coupons should not be empty coupons.filterNot(_.payload.providerIsObserver) shouldBe empty @@ -223,6 +244,112 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest } } + // Scenario for #6372, both providers have V2 capable versions vetted, but they lack a common vetted version. + clue( + "ProcessRewardsTrigger handles a batch where providers have jointly-incompatible vetting states" + ) { + val bobParticipantId = + bobValidatorBackend.appState.participantAdminConnection.getParticipantId().futureValue + + // Alice unvets minV2AmuletVersion; Bob keeps only minV2AmuletVersion, nothing after it. + actAndCheck( + s"Unvet $minV2AmuletPackageId on Alice and $amuletVersionsAboveOldestV2 on Bob", { + aliceValidatorBackend.participantClient.topology.vetted_packages.propose_delta( + aliceParticipantId, + removes = Seq(PackageId.assertFromString(minV2AmuletPackageId)), + force = ForceFlags(ForceFlag.AllowUnvettedDependencies), + store = TopologyStoreId.Synchronizer(decentralizedSynchronizerId), + ) + bobValidatorBackend.participantClient.topology.vetted_packages.propose_delta( + bobParticipantId, + removes = amuletVersionsAboveOldestV2.map(PackageId.assertFromString), + force = ForceFlags(ForceFlag.AllowUnvettedDependencies), + store = TopologyStoreId.Synchronizer(decentralizedSynchronizerId), + ) + }, + )( + "sv1's participant observes Alice no longer has minV2AmuletVersion vetted, and Bob's vetting is capped at minV2AmuletVersion", + _ => { + vettedPackagesOnSv1View(aliceParticipantId) should not contain + minV2AmuletPackageId + vettedPackagesOnSv1View(bobParticipantId) + .intersect(amuletVersionsAboveOldestV2) shouldBe empty + + val aliceVettedAboveMin = + vettedPackagesOnSv1View(aliceParticipantId).intersect(amuletVersionsAboveOldestV2) + val bobVettedAboveMin = + vettedPackagesOnSv1View(bobParticipantId).intersect(amuletVersionsAboveOldestV2) + aliceVettedAboveMin.intersect(bobVettedAboveMin) shouldBe empty + }, + ) + + val (round, _) = actAndCheck( + "Generate activity", { + doTransfer() + val round = oldestOpenRound + advanceRoundsToNextRoundOpening + round + }, + )( + "ProcessRewardsTrigger issues coupons for the round", + round => { + val newAliceCoupons = aliceCoupons.filter(_.payload.round.number == round) + val newBobCoupons = bobUnassignedCoupons.filter(_.payload.round.number == round) + newAliceCoupons should not be empty + newAliceCoupons.foreach(_.payload.providerIsObserver shouldBe true) + newBobCoupons should not be empty + newBobCoupons.foreach(_.payload.providerIsObserver shouldBe false) + }, + ) + + clue("UnhideRewardCouponV2Trigger can unhide Bob's coupon before he is fully re-vetted") { + eventually() { + sv1Backend.dsoDelegateBasedAutomation + .trigger[UnhideRewardCouponV2Trigger] + .runOnce() + .futureValue + val coupons = bobUnassignedCoupons.filter(_.payload.round.number == round) + coupons should not be empty + coupons.filterNot(_.payload.providerIsObserver) shouldBe empty + } + } + + clue("Restore Alice's and Bob's full vetting") { + actAndCheck( + s"Re-vet the minV2AmuletVersion package on Alice and $amuletVersionsAboveOldestV2 on Bob", { + aliceValidatorBackend.participantClient.topology.vetted_packages.propose_delta( + aliceParticipantId, + adds = Seq( + VettedPackage( + PackageId.assertFromString(minV2AmuletPackageId), + None, + None, + ) + ), + store = TopologyStoreId.Synchronizer(decentralizedSynchronizerId), + ) + bobValidatorBackend.participantClient.topology.vetted_packages.propose_delta( + bobParticipantId, + adds = amuletVersionsAboveOldestV2.map(id => + VettedPackage(PackageId.assertFromString(id), None, None) + ), + store = TopologyStoreId.Synchronizer(decentralizedSynchronizerId), + ) + }, + )( + "sv1's participant observes Alice and Bob are fully vetted again", + _ => { + v2CapableAmuletPackageIds.toSet.subsetOf( + vettedPackagesOnSv1View(aliceParticipantId).toSet + ) shouldBe true + v2CapableAmuletPackageIds.toSet.subsetOf( + vettedPackagesOnSv1View(bobParticipantId).toSet + ) shouldBe true + }, + ) + } + } + clue( "RewardCouponV2 can be assigned after vetting, even when beneficiary is offline" ) { @@ -344,22 +471,27 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest } } - private def aliceVettedPackagesOnSv1View( - aliceParticipantId: ParticipantId + private def vettedPackagesOnSv1View( + participantId: ParticipantId )(implicit env: SpliceTestConsoleEnvironment): Seq[String] = sv1ValidatorBackend.appState.participantAdminConnection - .listVettedPackages(aliceParticipantId, decentralizedSynchronizerId, AuthorizedState) + .listVettedPackages(participantId, decentralizedSynchronizerId, AuthorizedState) .futureValue .flatMap(_.mapping.packages.map(_.packageId)) - private def assertAliceVettedBelowV2( + private def assertAliceVettedBelowLatest( aliceParticipantId: ParticipantId )(implicit env: SpliceTestConsoleEnvironment): Unit = - clue("Alice's validator vets the highest amulet below the V2 but none at/above it") { + clue("Alice's validator vets the second-latest amulet version but not the latest") { eventually() { - val vetted = aliceVettedPackagesOnSv1View(aliceParticipantId) - vetted should contain(previousAmuletPackageId) - vetted.intersect(v2CapableAmuletPackageIds) shouldBe empty + val vetted = vettedPackagesOnSv1View(aliceParticipantId) + vetted should contain( + DarResources.amulet.others + .filter(_.metadata.version < latestAmuletDar.metadata.version) + .maxBy(_.metadata.version) + .packageId + ) + vetted should not contain latestAmuletDar.packageId } } @@ -402,18 +534,18 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest val aliceAdminConnection = aliceValidatorBackend.appState.participantAdminConnection aliceAdminConnection .uploadDarFiles( - v2CapableDarsUnvettedOnAlice.map(UploadablePackage.fromResource), + darsUnvettedOnAliceAtStart.map(UploadablePackage.fromResource), RetryFor.Automation, ) .futureValue aliceAdminConnection - .vetDars(decentralizedSynchronizerId, v2CapableDarsUnvettedOnAlice, None, None) + .vetDars(decentralizedSynchronizerId, darsUnvettedOnAliceAtStart, None, None) .futureValue }, )( "sv1's participant observes Alice has the correct vetting state for RewardAccountingV2", _ => - aliceVettedPackagesOnSv1View(aliceParticipantId) should contain( + vettedPackagesOnSv1View(aliceParticipantId) should contain( DarResources.amulet.latest.packageId ), ) @@ -432,7 +564,7 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest )( "sv1's participant observes Alice is in the wrong vetting state", _ => - aliceVettedPackagesOnSv1View(aliceParticipantId) + vettedPackagesOnSv1View(aliceParticipantId) .intersect(v2CapableAmuletPackageIds) shouldBe empty, ) @@ -454,9 +586,9 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest "sv1's participant observes Alice has the correct vetting state again", _ => { v2CapableAmuletPackageIds.toSet - .subsetOf(aliceVettedPackagesOnSv1View(aliceParticipantId).toSet) shouldBe true + .subsetOf(vettedPackagesOnSv1View(aliceParticipantId).toSet) shouldBe true aliceLedgerApiAmuletVersionOnSv1View(aliceParty).exists( - _ >= v2AmuletVersion + _ >= minV2AmuletVersion ) shouldBe true }, ) @@ -488,6 +620,11 @@ class UnhideAndExpireRewardCouponV2TimeBasedIntegrationTest } } + private def oldestOpenRound(implicit env: SpliceTestConsoleEnvironment): Long = { + val (openRounds, _) = sv1ScanBackend.getOpenAndIssuingMiningRounds() + openRounds.map(_.contract.payload.round.number.toLong).min + } + private def assertOldestOpenRound( expected: Long )(implicit env: SpliceTestConsoleEnvironment): Unit = { diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ProcessRewardsTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ProcessRewardsTrigger.scala index e48067c31f..67131b8551 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ProcessRewardsTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ProcessRewardsTrigger.scala @@ -20,6 +20,8 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet.rewardaccounti BatchOfBatches, BatchOfMintingAllowances, } +import org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.AmuletRules +import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.http.v0.definitions.{ GetRewardAccountingBatchResponse, RewardAccountingMintingAllowance, @@ -27,12 +29,13 @@ import org.lfdecentralizedtrust.splice.http.v0.definitions.{ import org.lfdecentralizedtrust.splice.scan.admin.api.client.{BftScanConnection, ScanConnection} import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.store.PageLimit -import org.lfdecentralizedtrust.splice.util.AssignedContract +import org.lfdecentralizedtrust.splice.util.{AmuletConfigSchedule, AssignedContract, Contract} import org.lfdecentralizedtrust.splice.util.PrettyInstances.* import com.daml.metrics.api.{MetricInfo, MetricName, MetricsContext} import com.daml.metrics.api.MetricsContext.Implicits.empty import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} import com.digitalasset.canton.tracing.TraceContext +import com.digitalasset.daml.lf.language.Ast import io.grpc.Status import io.opentelemetry.api.trace.Tracer import org.lfdecentralizedtrust.splice.codegen.java.da.set.types.Set as DamlSet @@ -82,11 +85,16 @@ private[delegatebased] abstract class ProcessRewardsTriggerBase( val batchHash = processRewards.payload.batchHash.value val batchF = fetchBatch(round, batchHash) val dsoRulesF = store.getDsoRules() + val amuletRulesF = store.getAmuletRules() for { batch <- batchF dsoRules <- dsoRulesF + amuletRules <- amuletRulesF damlBatch = convertBatch(batch) - providersWithWrongVettingState <- determineProvidersWithWrongVettingState(batch) + providersWithWrongVettingState <- determineProvidersWithWrongVettingState( + batch, + amuletRules, + ) choiceArg = new ProcessRewardsV2_ProcessBatch( damlBatch, providersWithWrongVettingState, @@ -149,7 +157,8 @@ private[delegatebased] abstract class ProcessRewardsTriggerBase( } private def determineProvidersWithWrongVettingState( - batch: GetRewardAccountingBatchResponse + batch: GetRewardAccountingBatchResponse, + amuletRules: Contract[AmuletRules.ContractId, AmuletRules], )(implicit tc: TraceContext): Future[DamlSet[String]] = { val providers = batch match { case GetRewardAccountingBatchResponse.members.RewardAccountingBatchOfMintingAllowances( @@ -160,12 +169,34 @@ private[delegatebased] abstract class ProcessRewardsTriggerBase( Vector.empty[String] } val now = context.clock.now + + // Checking for vetting state on the active amulet version is conservative, + // but it ensures that we don't hit issues where the common version vetted by + // all providers in a batch is below the version where V2 was introduced. (see #6372) + // + // Being conservative here is OK, as this keeps our vetting state checking + // simple, while avoiding potential issues in the submission of ProcessBatch. + // And also because the UnhideRewardCouponV2Trigger would make the coupons + // visible based on the vetting state of each party. + // In practice we expect most providers to have vetted the active amulet version. + val activeAmuletVersionMetadata = Ast.PackageMetadata( + PackageIdResolver.Package.SpliceAmulet.packageName, + PackageIdResolver.readPackageVersion( + AmuletConfigSchedule(amuletRules).getConfigAsOf(now).packageConfig, + PackageIdResolver.Package.SpliceAmulet, + ), + None, + ) Future .traverse(providers) { provider => val partyId = PartyId.tryFromProtoPrimitive(provider) svTaskContext.packageVersionSupport - .supportsTrafficBasedAppRewards(Seq(partyId), now) - .map(support => provider -> support.supported) + .isPackageSupported( + Seq(PackageIdResolver.Package.SpliceAmulet -> Seq(store.key.dsoParty, partyId)), + now, + activeAmuletVersionMetadata, + ) + .map(supported => provider -> supported.supported) } .map { supportByProvider => val withWrongVettingState = supportByProvider.collect { case (provider, false) => provider } From 30bc04eaae5b75fe10ca1121713688d6550ab8fc Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Thu, 16 Jul 2026 09:10:25 +0200 Subject: [PATCH 056/329] Add cantonbft backup/restore support (#6423) * Add cantonbft backup/restore support [static] Signed-off-by: Nicu Reut --- build-tools/cncluster | 2 +- cluster/scripts/find-recent-backup.sh | 53 +++++++++++++++++++++++---- cluster/scripts/node-backup.sh | 7 ++++ cluster/scripts/node-restore.sh | 30 +++++++++------ cluster/scripts/utils.source | 27 ++++++++++++-- 5 files changed, 96 insertions(+), 23 deletions(-) diff --git a/build-tools/cncluster b/build-tools/cncluster index 706f585319..1a25cb4e80 100755 --- a/build-tools/cncluster +++ b/build-tools/cncluster @@ -1948,7 +1948,7 @@ function subcmd_restore_node() { _info "Silencing SV report creation alerts for $sv_name for 1 hour" subcmd_silence_grafana_alerts "1 hour" "alertname=Report Creation Time Lag" "report_publisher=$sv_name" _info "Restoring sv node $node" - SPLICE_SV=$node SPLICE_MIGRATION_ID=$migration_id "$SPLICE_ROOT"/cluster/scripts/node-restore.sh $force "$node" "$migration_id" "$backup_run_id" cometbft sequencer participant mediator cn-apps + SPLICE_SV=$node SPLICE_MIGRATION_ID=$migration_id "$SPLICE_ROOT"/cluster/scripts/node-restore.sh $force "$node" "$migration_id" "$backup_run_id" cometbft cantonBft sequencer participant mediator cn-apps ;; validator1|splitwell) _info "Restoring validator node $node" diff --git a/cluster/scripts/find-recent-backup.sh b/cluster/scripts/find-recent-backup.sh index 4bdf540fc1..f646ffd1f2 100755 --- a/cluster/scripts/find-recent-backup.sh +++ b/cluster/scripts/find-recent-backup.sh @@ -17,11 +17,11 @@ function usage() { function is_full_backup_kube() { local component_backup_names=$1 - local expected_components=$2 + local expected_patterns=$2 # Check if all expected components can be found in the component_backup_names - for component in $expected_components; do - count=$(echo "$component_backup_names" | grep -c "$component") + for pattern in $expected_patterns; do + count=$(echo "$component_backup_names" | grep -c -F -- "$pattern") if [ "$count" -ne 1 ]; then return 1 fi @@ -44,8 +44,16 @@ function latest_full_backup_run_id_kube() { local expected_components=$4 local before_timestamp=$5 local include_cometbft=$6 + + local expected_patterns="" + for component in $expected_components; do + local instance + instance="$(create_component_instance "$component" "$migration_id" "$namespace")" + expected_patterns="${expected_patterns:+$expected_patterns }-${instance}-pg-" + done + if [ "$is_sv" == "true" ] && [ "$include_cometbft" == "true" ]; then - expected_components="$expected_components cometbft" + expected_patterns="$expected_patterns cometbft" fi local all_run_ids @@ -54,7 +62,7 @@ function latest_full_backup_run_id_kube() { while read -r run_id; do component_backup_names=$(get_component_backup_names_kube "$migration_id" "$run_id") - if is_full_backup_kube "$component_backup_names" "$expected_components"; then + if is_full_backup_kube "$component_backup_names" "$expected_patterns"; then echo "$run_id" return 0 fi @@ -71,6 +79,7 @@ function latest_full_backup_run_id_gcloud() { local stack declare -A backup_id_dict + local sequencer_end_time="" # participant backup must be newer than cn-apps backup stack=$(get_stack_for_namespace_component "$namespace" "participant") @@ -90,6 +99,7 @@ function latest_full_backup_run_id_gcloud() { for component in $expected_components; do [ "$component" == "participant" ] && continue + [ "$component" == "cantonBft" ] && continue stack=$(get_stack_for_namespace_component "$namespace" "$component") instance="$(create_component_instance "$component" "$migration_id" "$namespace")" @@ -98,21 +108,43 @@ function latest_full_backup_run_id_gcloud() { local cloudsql_id cloudsql_id=$(get_cloudsql_id "$full_component_instance" "$stack") - local backup_id + local entry if [ "$component" == "cn-apps" ]; then # cn-apps backup must be older than participant backup - backup_id=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --arg pt "$participant_end_time" '[.[] | select(.endTime <= $pt)] | first | .id') + entry=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --arg pt "$participant_end_time" '[.[] | select(.endTime <= $pt)] | first | "\(.id) \(.endTime)"') else - backup_id=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --argjson ts "$before_timestamp" '[.[] | select(.endTime <= ($ts | todate))] | first | .id') + entry=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --argjson ts "$before_timestamp" '[.[] | select(.endTime <= ($ts | todate))] | first | "\(.id) \(.endTime)"') fi + local backup_id + backup_id=$(echo "$entry" | awk '{print $1}') + local backup_end_time + backup_end_time=$(echo "$entry" | awk '{print $2}') if [ -z "$backup_id" ] || [ "$backup_id" == "null" ]; then _error "No backup found for component $component (instance $cloudsql_id) before timestamp $before_timestamp" fi backup_id_dict[$component]="$backup_id" + + if [ "$component" == "sequencer" ]; then + sequencer_end_time="$backup_end_time" + fi done + # cantonBft backup must be older than the sequencer backup + if [[ " $expected_components " == *" cantonBft "* ]]; then + stack=$(get_stack_for_namespace_component "$namespace" "cantonBft") + instance="$(create_component_instance "cantonBft" "$migration_id" "$namespace")" + local bft_cloudsql_id + bft_cloudsql_id=$(get_cloudsql_id "$namespace-$instance-pg" "$stack") + local bft_backup_id + bft_backup_id=$(gcloud sql backups list --instance "$bft_cloudsql_id" --format=json | jq -r --arg st "$sequencer_end_time" '[.[] | select(.endTime <= $st)] | first | .id') + if [ -z "$bft_backup_id" ] || [ "$bft_backup_id" == "null" ]; then + _error "No backup found for component cantonBft (instance $bft_cloudsql_id) before sequencer backup time $sequencer_end_time" + fi + backup_id_dict["cantonBft"]="$bft_backup_id" + fi + local result="" for component in "${!backup_id_dict[@]}"; do result="${result:+$result,}$component:${backup_id_dict[$component]}" @@ -158,6 +190,11 @@ function main() { is_sv=true full_instance="$namespace-cn-apps-pg" expected_components="cn-apps sequencer participant mediator" + local bft_db_enabled + bft_db_enabled=$(canton_bft_db_enabled "$migration_id" "$config") + if [ "$bft_db_enabled" == "true" ]; then + expected_components="$expected_components cantonBft" + fi stack=$(get_stack_for_namespace_component "$namespace" "cn-apps") ;; *) diff --git a/cluster/scripts/node-backup.sh b/cluster/scripts/node-backup.sh index 533f2d1bf4..f84bcac39d 100755 --- a/cluster/scripts/node-backup.sh +++ b/cluster/scripts/node-backup.sh @@ -297,6 +297,13 @@ function main() { elif [ "$1" == "sv" ]; then _info "Backing up SV node $namespace" + local bft_enabled + bft_enabled=$(canton_bft_db_enabled "$migration_id" "$config") + if [ "$bft_enabled" == "true" ]; then + backup_component "$namespace" "cantonBft" "$requested_component" "$migration_id" + wait_for_backup "$namespace" "cantonBft" "$requested_component" "$migration_id" + fi + backup_component "$namespace" "cn-apps" "$requested_component" "$migration_id" backup_component "$namespace" "mediator" "$requested_component" "$migration_id" backup_component "$namespace" "sequencer" "$requested_component" "$migration_id" diff --git a/cluster/scripts/node-restore.sh b/cluster/scripts/node-restore.sh index d2000ed4b8..a8237442fb 100755 --- a/cluster/scripts/node-restore.sh +++ b/cluster/scripts/node-restore.sh @@ -16,6 +16,8 @@ function component_to_deployments() { local -r namespace=$3 if [[ "$component" == "sequencer" ]]; then echo "global-domain-$migration_id-sequencer" + elif [[ "$component" == "cantonBft" ]]; then + echo "global-domain-$migration_id-sequencer" elif [[ "$component" == "mediator" ]]; then echo "global-domain-$migration_id-mediator" elif [[ "$component" == "participant" && "$namespace" == sv* ]]; then @@ -434,27 +436,33 @@ function main() { | map(select(.id == $migration_id)) | .[0].sequencer.enableBftSequencer // false") - if [[ "$run_id" == *","* ]]; then - _info " ** Validate backup ids ** " - local map_keys - map_keys=$(echo "$run_id" | tr ',' '\n' | cut -d: -f1 | sort) - local req_components - req_components=$(printf '%s\n' "${@:4}" | sort) - if [ "$map_keys" != "$req_components" ]; then - _error "Backup map keys ($map_keys) do not match requested components (${*:4})" - fi - fi + local bft_db_enabled + bft_db_enabled=$(canton_bft_db_enabled "$migration_id" "$config") - # Build the list of components to restore, dropping CometBFT when the BFT sequencer is enabled. local -a components=() for component in "${@:4}"; do if [ "$component" == "cometbft" ] && [ "$bft_sequencer_enabled" == "true" ]; then _info "BFT sequencer is enabled for migration $migration_id, skipping CometBFT restore" continue fi + if [ "$component" == "cantonBft" ] && [ "$bft_db_enabled" != "true" ]; then + _info "Dedicated CantonBFT sequencer DB is not enabled for migration $migration_id, skipping CantonBFT restore" + continue + fi components+=("$component") done + if [[ "$run_id" == *","* ]]; then + _info " ** Validate backup ids ** " + local map_keys + map_keys=$(echo "$run_id" | tr ',' '\n' | cut -d: -f1 | sort) + local req_components + req_components=$(printf '%s\n' "${components[@]}" | sort) + if [ "$map_keys" != "$req_components" ]; then + _error "Backup map keys ($map_keys) do not match requested components (${components[*]})" + fi + fi + for component in "${components[@]}"; do component_to_deployments "$component" "$migration_id" "$namespace" done diff --git a/cluster/scripts/utils.source b/cluster/scripts/utils.source index 0018dc9f2d..5f857452cb 100755 --- a/cluster/scripts/utils.source +++ b/cluster/scripts/utils.source @@ -43,6 +43,8 @@ function get_stack_for_namespace_component() { stack="sv" elif [[ "${component}" == "sequencer" ]]; then stack="sv-canton" + elif [[ "${component}" == "cantonBft" ]]; then + stack="sv-canton" elif [[ "${component}" == "mediator" ]]; then stack="sv-canton" elif [[ "${split_sv_deployment}" == "true" ]]; then @@ -62,16 +64,35 @@ create_component_instance() { local migration_id="$2" local namespace="$3" - if [[ ("$component" == "sequencer" || "$component" == "mediator") + local instance_base="$component" + if [[ "$component" == "cantonBft" ]]; then + instance_base="sequencer-bft" + fi + + if [[ ("$component" == "sequencer" || "$component" == "cantonBft" || "$component" == "mediator") && ("$namespace" != "splitwell" && "$namespace" != "validator1" && "$namespace" != "sv") ]]; then - component_instance="${component}-${migration_id}" + component_instance="${instance_base}-${migration_id}" else - component_instance="${component}" + component_instance="${instance_base}" fi echo "$component_instance" } +function canton_bft_db_enabled() { + local migration_id=$1 + local config=$2 + + echo "$config" | yq " + ([.synchronizerMigration.active, .synchronizerMigration.upgrade, .synchronizerMigration.legacy] + + (.synchronizerMigration.additionalLegacy // []) + + (.synchronizerMigration.archived // [])) + | map(select(. != null and .id == ${migration_id})) + | .[0] + | ((.sequencer.enableBftSequencer // true) and (.sequencer.dedicatedBftSequencerDb // true)) + " +} + function get_resolved_config() { "${SPLICE_ROOT}/cluster/scripts/get-resolved-config.sh" } From 2d33a74f5929b861eee8aa7961b8163cc637369e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Thu, 16 Jul 2026 10:39:05 +0200 Subject: [PATCH 057/329] Silence sync connection shutdown logs (#6437) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- project/ignore-patterns/canton_log.ignore.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/project/ignore-patterns/canton_log.ignore.txt b/project/ignore-patterns/canton_log.ignore.txt index a4acd0c5ca..be2e9e154a 100644 --- a/project/ignore-patterns/canton_log.ignore.txt +++ b/project/ignore-patterns/canton_log.ignore.txt @@ -94,6 +94,9 @@ ACS_COMMITMENT_MISMATCH.*aliceValidatorLocalNewForValidatorReonboardingIT # In principle the confirmations from three of the mediators and counter-participants should be sufficient, but who knows what we do in all tests; ignoring unless it causes problems Response message for request.*timed out.*c.d.c.p.p.TransactionProcessor:participant= +# Transient WARN triggered by a shutdown of the sync connection while the participant is sending confirmation responses. +Failed to send responses: RequestFailed\(No connection available\).*c.d.c.p.p.TransactionProcessor:participant= + # TODO(#936): remove these ignores if possible The operation 'insert block' has failed with an exception Now retrying operation 'insert block' From 750d8f80639eeacc175845c67b27e112f8ba4d3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Thu, 16 Jul 2026 11:07:22 +0200 Subject: [PATCH 058/329] Fix missing appActivityRecords in TestTokenV2SettlementIntegrationTest (#6435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oriol Muñoz --- .../TestTokenV2SettlementIntegrationTest.scala | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala index df4ed66108..c61fbbeb86 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala @@ -198,9 +198,17 @@ class TestTokenV2SettlementIntegrationTest // make venue and ttadmin featured app parties splitwellWalletClient.selfGrantFeaturedAppRight() aliceValidatorWalletLocalClient.selfGrantFeaturedAppRight() - advanceRoundsByOneTickViaAutomation() - advanceRoundsByOneTickViaAutomation() - advanceRoundsByOneTickViaAutomation() + // We need to make sure that after this block, the oldest active OpenMiningRound + // has an `openAt` time that is after the time the first OpenMiningRound was archived, + // otherwise the ingestion start approximation in + // `DbScanRewardsReferenceStore.lookupActiveOpenMiningRounds` might filter out the + // OpenMiningRound contract and AppActivityComputation won't ingest activity records. + clue("Advance rounds") { + advanceRoundsByOneTickViaAutomation() // archives round 0, open rounds are 1-3 + advanceRoundsByOneTickViaAutomation() // archives round 1, open rounds are 2-4 + advanceRoundsByOneTickViaAutomation() // archives round 2, open rounds are 3-5 + advanceRoundsByOneTickViaAutomation() // archives round 3, open rounds are 4-6 + } // Create BatchingUtilityV2 contracts for Alice and Bob val batchingUtilityIds: Map[PartyId, BatchingUtility.ContractId] = From 26a02b2745103046f4ab34d856df00f59060778c Mon Sep 17 00:00:00 2001 From: Timothy Emiola Date: Thu, 16 Jul 2026 18:41:48 +0900 Subject: [PATCH 059/329] Address flakiness and latency in the Traffic-Based App Rewards integration test (#6440) Signed-off-by: Tim Emiola --- ...BasedRewardsTimeBasedIntegrationTest.scala | 74 +++++++++---------- 1 file changed, 35 insertions(+), 39 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsTimeBasedIntegrationTest.scala index 78309a9912..a283f2599a 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsTimeBasedIntegrationTest.scala @@ -84,7 +84,7 @@ abstract class TrafficBasedRewardsTimeBasedIntegrationTestBase override def environmentDefinition: SpliceEnvironmentDefinition = EnvironmentDefinition - .simpleTopology4SvsWithSimTime(this.getClass.getSimpleName) + .simpleTopology1SvWithSimTime(this.getClass.getSimpleName) .withAdditionalSetup(implicit env => { Seq( sv1ValidatorBackend, @@ -118,6 +118,9 @@ abstract class TrafficBasedRewardsTimeBasedIntegrationTestBase .withPausedTrigger[CollectRewardsAndMergeAmuletsTrigger] )(config) ) + // Pause SV reward collection so that it does not race against + // advanceTimeAndWaitForRoundOpening in the activity block, + // which would cause "Skipped N SV rewards" warnings .addConfigTransform((_, config) => updateAutomationConfig(ConfigurableApp.Sv)( _.withPausedTrigger[ReceiveSvRewardCouponTrigger] @@ -165,40 +168,9 @@ abstract class TrafficBasedRewardsTimeBasedIntegrationTestBase val calculateRewardsDryRunTriggers = activeSvs.map(_.dsoAutomation.trigger[CalculateRewardsDryRunTrigger]) - // 3 initial advances with CalculateRewardsTrigger paused but - // verdict ingestion active, so that the meta row is created and - // bootstrap rounds have activity data available. - setTriggersWithin(triggersToPauseAtStart = - calculateRewardsTriggers ++ calculateRewardsDryRunTriggers - ) { - for (round <- 1 to 3) { - advanceTimeAndWaitForRoundOpening - assertOldestOpenRound(round.toLong) - } - - clue("Bootstrap rounds have zero activity on firstSV (no featured apps yet)") { - assertZeroTotals(sv1ScanBackend, 0L to 2L) - } - - clue("All SVs report zero totals for rounds after bootstrap") { - Seq(sv1ScanBackend, sv2ScanBackend, sv3ScanBackend, sv4ScanBackend).foreach { scan => - assertZeroTotals(scan, 1L to 2L, timeout = 40.seconds) - } - } - } - - // Sequence of actions - // Open rounds | Action - // ------------+-------------------------------------- - // 3, 4 | settle id0, grant venue FAP - // 4, 5 | settle id1, grant alice FAP - // 5, 6 | settle id2, cancel venue FAP - // 6, 7 | settle id3, (total 2 DvP trades) - // 7, 8 | settle id4, (total 3 DvP trades) - // 8, 9 | no-activity - // 9, 10 | settle id5, 1 DvP + 3 direct trades - // 10, 11 | settle id6, (total 5 DvP trades) - // 11, 12 | settle id7, (round not closed) + // CalculateRewardsTrigger is paused for the entire test body so + // that we can confirm CalculateRewardsV2 contracts were created + // for each round before the triggers consume them. val ( updateId0, updateId1, @@ -210,10 +182,34 @@ abstract class TrafficBasedRewardsTimeBasedIntegrationTestBase aliceCreateId, svExpireId, ) = - pauseScanVerdictIngestionWithin(sv1ScanBackend) { - setTriggersWithin(triggersToPauseAtStart = - calculateRewardsTriggers ++ calculateRewardsDryRunTriggers - ) { + setTriggersWithin(triggersToPauseAtStart = + calculateRewardsTriggers ++ calculateRewardsDryRunTriggers + ) { + // 3 initial advances with verdict ingestion active, so that the + // meta row is created and bootstrap rounds have activity data + // available. + for (round <- 1 to 3) { + advanceRoundsToNextRoundOpening + assertOldestOpenRound(round.toLong) + } + + clue("Bootstrap rounds have zero activity on firstSV (no featured apps yet)") { + assertZeroTotals(sv1ScanBackend, 0L to 2L) + } + + // Sequence of actions + // Open rounds | Action + // ------------+-------------------------------------- + // 3, 4 | settle id0, grant venue FAP + // 4, 5 | settle id1, grant alice FAP + // 5, 6 | settle id2, cancel venue FAP + // 6, 7 | settle id3, (total 2 DvP trades) + // 7, 8 | settle id4, (total 3 DvP trades) + // 8, 9 | no-activity + // 9, 10 | settle id5, 1 DvP + 3 direct trades + // 10, 11 | settle id6, (total 5 DvP trades) + // 11, 12 | settle id7, (round not closed) + pauseScanVerdictIngestionWithin(sv1ScanBackend) { val id0 = settleTrade(aliceParty, bobParty, venueParty) grantFeaturedAppRight(splitwellWalletClient) From fb12fba1cc5160ba57dff66c28fa0cda54f74503 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Thu, 16 Jul 2026 11:42:55 +0200 Subject: [PATCH 060/329] explain location policy ANY in the autoscaling configuration of node pools (#6443) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Mateusz Błażejewski --- cluster/pulumi/cluster/src/nodePools.ts | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/cluster/pulumi/cluster/src/nodePools.ts b/cluster/pulumi/cluster/src/nodePools.ts index a7392a9463..6ea9b32dc0 100644 --- a/cluster/pulumi/cluster/src/nodePools.ts +++ b/cluster/pulumi/cluster/src/nodePools.ts @@ -82,11 +82,7 @@ function installAppsNodePools( ? allZones : (config.zones ?? (defaultZone !== undefined ? [defaultZone] : undefined)), initialNodeCount: 0, - autoscaling: { - locationPolicy: 'ANY', - minNodeCount: config.minNodes, - maxNodeCount: config.maxNodes, - }, + autoscaling: autoscalingConfigOf(config), }); }); } @@ -125,11 +121,7 @@ function installInfraNodePools( ? allZones : (config.zones ?? (defaultZone !== undefined ? [defaultZone] : undefined)), initialNodeCount: 1, - autoscaling: { - locationPolicy: 'ANY', - minNodeCount: config.minNodes, - maxNodeCount: config.maxNodes, - }, + autoscaling: autoscalingConfigOf(config), }, { replaceOnChanges: ['nodeConfig.machineType'], @@ -137,3 +129,15 @@ function installInfraNodePools( ); }); } + +function autoscalingConfigOf(config: GkeNodePoolConfig): gcp.container.NodePoolArgs['autoscaling'] { + return { + // Location policy decides how nodes are allocated across zones when more then one zone is configured. + // By default it is set to BALANCED, which is useful in HA scenarios. We configure multiple zones on + // scratchnets and in CI to get better availability of compute resources so ANY is more suitable. + // For single-zone clusters, which includes prod clusters, this doesn't matter. + locationPolicy: 'ANY', + minNodeCount: config.minNodes, + maxNodeCount: config.maxNodes, + }; +} From 8dfa60d84c06015bd7fbfe8cf5774e2d2a5c4cef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Thu, 16 Jul 2026 12:14:36 +0200 Subject: [PATCH 061/329] Bump the Dev Fund tests timeouts (#6442) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../tests/DevelopmentFundCouponIntegrationTest.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DevelopmentFundCouponIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DevelopmentFundCouponIntegrationTest.scala index 6bb2557909..a8a1bae694 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DevelopmentFundCouponIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DevelopmentFundCouponIntegrationTest.scala @@ -31,6 +31,7 @@ import org.lfdecentralizedtrust.splice.wallet.store.{ } import java.time.Duration +import scala.concurrent.duration.DurationInt @org.lfdecentralizedtrust.splice.util.scalatesttags.SpliceDsoGovernance_0_1_21 class DevelopmentFundCouponIntegrationTest @@ -507,7 +508,11 @@ class DevelopmentFundCouponIntegrationTest clue( "The coupon is expired" ) { - eventually() { + // The expiry trigger cannot act before expiresAt (5s after allocation) plus the + // clockSkewAutomationDelay grace period (5s), so 10s of this budget are always + // consumed before the DsoRules_ExpireDevelopmentFundCoupon submission can even + // start; leave enough headroom for slow sequencing on loaded CI runners. + eventually(30.seconds) { aliceValidatorWalletClient .listActiveDevelopmentFundCoupons() shouldBe empty withClue "alice coupons" } From 884803e90da441b43e05306adf91333042de3476 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Thu, 16 Jul 2026 12:17:32 +0200 Subject: [PATCH 062/329] abort catchup test if no progress for > 1h (#6445) Signed-off-by: Julien Tinguely --- cluster/scripts/monitor-sv-catchup.sh | 28 +++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/cluster/scripts/monitor-sv-catchup.sh b/cluster/scripts/monitor-sv-catchup.sh index 550b1bcfa3..fb928988f3 100755 --- a/cluster/scripts/monitor-sv-catchup.sh +++ b/cluster/scripts/monitor-sv-catchup.sh @@ -40,6 +40,8 @@ start=$(date +%s) start_time=$(date -u -d @"$start" '+%Y-%m-%dT%H:%M:%S.%3NZ') timeout_hours="8" timeout_secs=$(( timeout_hours * 3600 )) +stall_start=$(date +%s) +stall_timeout_secs=3600 # Helper to query Prometheus for a single value # Fetch data from 2 minutes ago @@ -47,11 +49,11 @@ timeout_secs=$(( timeout_hours * 3600 )) function query_prom() { local default=${2:-"180"} local ts - ts=$(date -d '2 minutes ago' +%s) + ts=$(date -d '2 minutes ago' +%s) curl -ksf "${PROM}/api/v1/query" \ --data-urlencode "query=${1}" \ --data-urlencode "time=${ts}" \ - | jq -r ".data.result[0].value[1] // \"${default}\"" + | jq -r ".data.result[0].value[1] // \"${default}\"" } function query_seq_delay() { @@ -83,7 +85,7 @@ rate_sample_count=0 _info "Monitoring catchup for ${namespace}" _info "Thresholds: seq>=${seq_min_eps} eps, participant>=${part_min_eps} eps, mediator>=${med_min_eps} eps" _info "Caught-up when: seq<=${seq_delay_ok}s, participant<=${part_delay_ok}s, mediator<=${med_delay_ok}s" -_info "Test timeout: ${timeout_hours}h" +_info "Test timeout: ${timeout_hours}h | Stall timeout: 1h of zero progress" while true; do elapsed=$(( $(date +%s) - start )) @@ -120,6 +122,21 @@ while true; do rate_sample_count=$((rate_sample_count + 1)) _info "Mean rates so far> seq: $(echo "scale=1; $seq_rate_sum / $rate_sample_count" | bc) eps, participant: $(echo "scale=1; $part_rate_sum / $rate_sample_count" | bc) eps, mediator: $(echo "scale=1; $med_rate_sum / $rate_sample_count" | bc) eps" + + # Check that the catchup is making progress, otherwise abort after 1h of zero progress + all_zero=$(echo "$seq_rate == 0 && $part_rate == 0 && $med_rate == 0" | bc -l) + if [ "$all_zero" = "1" ]; then + stall_elapsed=$(( $(date +%s) - stall_start )) + _info "Zero progress for ${stall_elapsed}s / ${stall_timeout_secs}s before giving up" + if [ "$stall_elapsed" -ge "$stall_timeout_secs" ]; then + _error_msg "No progress for over 1 hour, aborting catchup test" + outcome="stalled" + break + fi + else + stall_start=$(date +%s) + fi + sleep "$poll_interval" done @@ -144,6 +161,9 @@ med_ok=$(echo "$med_rate_mean >= $med_min_eps" | bc -l) if [ "$outcome" = "success" ]; then icon="✅" exit_code=0 +elif [ "$outcome" = "stalled" ]; then + icon="🚨" + exit_code=1 else icon="❌" exit_code=1 @@ -154,7 +174,7 @@ grafana_domain_link="${grafana_base}/d/ca9df344-c699-4efe-83c2-5fb2639d96d9/glob grafana_participant_link="${grafana_base}/d/edkzo5ukgeqyoc/participant?orgId=1&timezone=UTC&var-namespace=${namespace}&var-job=All&var-participant=All&viewPanel=panel-13&from=${start_time}&to=${end_time}" message="${icon} *SV Catchup Test — \`${namespace}\` on \`${GCP_CLUSTER_BASENAME}\`* -Outcome: ${outcome} | Duration: ${elapsed_mins}m | Started: ${start_time} | Ended: ${end_time} +Outcome: ${outcome}$([ "$outcome" = "stalled" ] && echo " — no events processed for 1h, aborting test, node may be stuck") | Duration: ${elapsed_mins}m | Started: ${start_time} | Ended: ${end_time} *Per-component mean rates over catchup window (${rate_sample_count} samples):* • Sequencer: \`$(printf "%.1f" "$seq_rate_mean")\` events/s (expected ≥ ${seq_min_eps}) $([ "$seq_ok" = "1" ] && echo "✅" || echo "❌") From 45075f4b6797325826b1226460c1d8b043436620 Mon Sep 17 00:00:00 2001 From: Stanislav German-Evtushenko Date: Thu, 16 Jul 2026 19:25:04 +0900 Subject: [PATCH 063/329] helm, info, status: Check and report reachability sequencer + refactor (#6073) * helm, info, status: Check and report reachability for scan and sequencer Possible status values: - Scan: 0 (reachable and not lagging), 1 (lagging), 2 (unreachable). - Sequencer: 0 (reachable and not lagging), 1 (lagging), 2 (unreachable), 3 (unreachable and lagging). Also in this change: - Sort keys in the output - Refactor for readability and robustness Signed-off-by: Stanislav German-Evtushenko * Fix alignment of existing release notes Signed-off-by: Stanislav German-Evtushenko --------- Signed-off-by: Stanislav German-Evtushenko --- .../helm/splice-info/scripts/get-status.sh | 505 +++++++++++++----- docs/src/release_notes_upcoming.rst | 11 +- 2 files changed, 377 insertions(+), 139 deletions(-) diff --git a/cluster/helm/splice-info/scripts/get-status.sh b/cluster/helm/splice-info/scripts/get-status.sh index c726dd613d..f0554dc330 100755 --- a/cluster/helm/splice-info/scripts/get-status.sh +++ b/cluster/helm/splice-info/scripts/get-status.sh @@ -11,14 +11,24 @@ SCAN_URL="${SCAN_URL:-http://scan-app:5012}" SV_THRESHOLD="${SV_THRESHOLD:-600}" MEDIATOR_THRESHOLD="${MEDIATOR_THRESHOLD:-900}" SCAN_THRESHOLD_ROUNDS="${SCAN_THRESHOLD_ROUNDS:-900}" -SCAN_THRESHOLD_EVENT="${SCAN_THRESHOLD_EVENT:-300}" +SCAN_THRESHOLD_EVENTS="${SCAN_THRESHOLD_EVENT:-300}" SEQUENCER_THRESHOLD="${SEQUENCER_THRESHOLD:-2520}" # 42 minutes, Sequencer acknowledgments are irregular, so we use a higher threshold here -CURL_TIMEOUT="${CURL_TIMEOUT:-15}" +PARALLELISM="${PARALLELISM:-8}" +CURL_TIMEOUT="${CURL_TIMEOUT:-15}" TLS_SKIP_VERIFY="${TLS_SKIP_VERIFY:-false}" + CURL_CMD=(curl -fs -m "$CURL_TIMEOUT") -[[ $TLS_SKIP_VERIFY == true ]] && CURL_CMD+=(-k) +GRPC_HEALTH_CMD=(grpc_health_code --max-time "$CURL_TIMEOUT") + +if [[ $TLS_SKIP_VERIFY == true ]]; then + CURL_CMD+=(-k) + GRPC_HEALTH_CMD+=(--insecure) +fi + +CURL_CMD_JSON=$(jq -nc --args '$ARGS.positional' -- "${CURL_CMD[@]}") +GRPC_HEALTH_CMD_JSON=$(jq -nc --args '$ARGS.positional' -- "${GRPC_HEALTH_CMD[@]}") prom2json() { P2J_VERSION="1.5.0" @@ -43,92 +53,180 @@ prom2json() { "$P2J_BIN" "$@" } -sv_get_status() { - SV_METRIC=splice_sv_status_report_creation_time_us +# Converts a JSON object with string values to a JSON object with parsed JSON +# values. If a value is not valid JSON, it is replaced with null. +json_object_values_fromjson() { + jq -e '.[] |= try(fromjson) catch null | values' +} + +grpc_health() { + local insecure=false max_time + + local args=() + while [[ $# -gt 0 ]]; do + case "$1" in + -m|--max-time) max_time=$2; shift 2 ;; + -k|--insecure) insecure=true; shift ;; + *) args+=("$1"); shift ;; + esac + done + + [[ ${#args[@]} -eq 1 ]] || + { echo "Usage: grpc_health [-m|--max-time SECONDS] [-k|--insecure] [http://|https://]HOST:PORT" >&2; return 1; } + + local url=${args[0]} + local curl_opts=() + + [[ -n ${max_time-} ]] && curl_opts+=(--max-time "$max_time") + "$insecure" && curl_opts+=(-k) - local exit_code + if [[ $url == "https://"* ]]; then + curl_opts+=(--http2) + else + curl_opts+=(--http2-prior-knowledge) + fi + + local out; out=$( + set -o pipefail + printf '\0\0\0\0\0' | + curl -fs "${curl_opts[@]}" \ + -X POST -H 'Content-Type: application/grpc' \ + --data-binary @- "$url/grpc.health.v1.Health/Check" | + xxd -p + ) || { echo "error: request failed" >&2; return 1; } + + [[ "$out" == 00000000020801 ]] && { echo SERVING; return 0; } + echo "error: not serving" >&2; return 1 +} + +grpc_health_code() { + grpc_health "$@" &> /dev/null && echo 0 || echo 2 +} + +sv_get_status() { + local sv_metric=splice_sv_status_report_creation_time_us local response; response=$( - "${CURL_CMD[@]}" "$SV_METRICS_URL?name[]=$SV_METRIC" | - prom2json | + get_metric_data "$SV_METRICS_URL" "$sv_metric" | jq -e \ - --arg threshold "$SV_THRESHOLD" \ - --arg metric "$SV_METRIC" \ + --argjson threshold "$SV_THRESHOLD" \ ' - ($threshold | tonumber) as $threshold - | .[] - | select(.name == $metric).metrics + . | map( + .labels.report_publisher as $name | + now - (.value | tonumber) / pow(10;6) as $delay | + { - (.labels.report_publisher): (if (now - (.value | tonumber)/pow (10;6)) < $threshold then 0 else 1 end) + ($name): (if $delay < $threshold then 0 else 1 end) } ) | add + | values ' - ) && exit_code=$? || exit_code=$? - - [[ $exit_code -eq 0 ]] && echo "$response" || echo '{}' + ) && echo "$response" || echo '{}' } -get_sequencer_metric_data() { - local metric_name=$1 +get_metric_data() { + local metrics_url=$1 + local metric_name=$2 + + local response; response=$( + "${CURL_CMD[@]}" "$metrics_url?name[]=$metric_name" | # filtering by name makes the response smaller and much faster + prom2json + ) || response='[]' - "${CURL_CMD[@]}" "$SEQUENCER_METRICS_URL?name[]=$metric_name" | - prom2json || - echo '[]' + local result; result=$( + echo "$response" | + jq -e \ + --arg metric "$metric_name" \ + '.[] | select(.name == $metric).metrics | values' # we have to filter by name again because prom2json returns all metrics prefixed with the given name + ) && echo "$result" || echo '[]' } +# Extracts status from sequencer metric data. Returns a JSON object with +# svNames as keys and 0 (acknowledgment within threshold) or 1 (otherwise) as +# values. get_status_from_sequencer_metric_data() { local metric_json=$1 - local metric_name=$2 - local category_name=$3 - local threshold=$4 - - local exit_code + local category_name=$2 + local threshold=$3 local result; result=$( echo "$metric_json" | jq -e \ - --arg metric "$metric_name" \ --arg category_name "$category_name" \ - --arg threshold "$threshold" \ + --argjson threshold "$threshold" \ ' - ($threshold | tonumber) as $threshold - | .[] - | select(.name == $metric).metrics + . | map( (.labels.member | split("::")) as [$category, $name, $fingerprint] | select($category == $category_name) | + now - (.value | tonumber) / pow(10;6) as $delay | + { - ($name): (if (now - (.value | tonumber)/pow (10;6)) < $threshold then 0 else 1 end) + ($name): (if $delay < $threshold then 0 else 1 end) } ) | add + | values ' - ) && exit_code=$? || exit_code=$? - - [[ $exit_code -eq 0 ]] && echo "$result" || echo '{}' + ) && echo "$result" || echo '{}' } -scan_get_status() { - local scan_url=$SCAN_URL - local scans_info_url="$scan_url/api/scan/v0/scans" +# Usage: json_array_to_bash_array somearr <<< '["val1", "val2"]' +# +# Takes a JSON array of strings from stdin and populates a bash array with the +# same values. The first argument is the name of the bash array to populate. +json_array_to_bash_array() { + local bash_array_name=$1 + local -n bash_array_ref=$bash_array_name + + { + # shellcheck disable=SC2034 + # https://github.com/koalaman/shellcheck/issues/817 + readarray -td '' bash_array_ref + wait "$!" # catch jq exit code + } < <( + jq -j \ + ' + if (type == "array" and all(.[]; type == "string")) then + .[] | (., "\u0000") + else + error("Input must be a JSON array of strings.") + end + ' + ) +} - local scan_info; scan_info=$("${CURL_CMD[@]}" "$scans_info_url" || echo '{}') +# Usage: run_parallel '{"label1": ["command1", "arg1"], "label2": ["command2", "arg2"]}' +# +# Takes a JSON object with labels as keys and command with args as values, runs +# the commands in parallel and returns a JSON object with the same labels as +# keys and the command outputs as values. +run_parallel() { + local label_command_map_json=$1 + + if ! jq -e ' + type == "object" and all(.[]; type == "array" and all(.[]; type == "string")) + ' <<< "$label_command_map_json" > /dev/null + then + echo "Error: JSON must be an object with arrays of strings." >&2 + return 1 + fi - local scan_svnames_and_urls; IFS=$'\n' read -r -d '' -a scan_svnames_and_urls < <( - echo "$scan_info" | - jq -r '.scans?[].scans[] | [.svName, .publicUrl] | join(" ")' && printf '\0' + local labels; json_array_to_bash_array labels < <( + printf "%s" "$label_command_map_json" | jq 'keys' ) - local scan_data; scan_data=$( + local result; result=$( local -i proc_count=0 - local proc_max=8 + local proc_max=$PARALLELISM local lockfile; lockfile=$(mktemp) - for svname_and_url in "${scan_svnames_and_urls[@]}"; do - local svname url - read -r svname url <<< "$svname_and_url"; + for label in "${labels[@]}"; do + local command_with_args; json_array_to_bash_array command_with_args < <( + printf "%s" "$label_command_map_json" | jq --arg label "$label" '.[$label]' + ) # Limit the number of concurrent processes if (( proc_count >= proc_max )); then @@ -137,84 +235,15 @@ scan_get_status() { fi ( - scan_response_rounds=$( - "${CURL_CMD[@]}" \ - --compressed \ - --json '{"cached_open_mining_round_contract_ids":[],"cached_issuing_round_contract_ids":[]}' \ - "$url/api/scan/v0/open-and-issuing-mining-rounds" | jq -e . - ) && exit_code=$? || exit_code=$? - - [[ $exit_code -ne 0 ]] && scan_response_rounds='{}' - - scan_event_is_fetched_successfully=$( - if - migration_id=$( - "${CURL_CMD[@]}" "$url/api/scan/v0/migrations/last" | - jq -er '.migration_id' - ) && - - after=$(TZ=UTC0 printf '%(%FT%TZ)T' "$((EPOCHSECONDS - SCAN_THRESHOLD_EVENT))") && - - events=$( - "${CURL_CMD[@]}" \ - --compressed \ - --json '{"page_size": 1, "after": {"after_migration_id": '"$migration_id"', "after_record_time": "'"$after"'"}}' \ - "$url/api/scan/v0/events" - ) && - - echo "$events" | jq -e '.events | length > 0' > /dev/null - then - echo true - else - echo false - fi - ) - - scan_status=$( - echo "$scan_response_rounds" | - jq \ - --arg svname "$svname" \ - --argjson threshold_rounds "$SCAN_THRESHOLD_ROUNDS" \ - --argjson threshold_event "$SCAN_THRESHOLD_EVENT" \ - --argjson event_is_fetched "$scan_event_is_fetched_successfully" \ - ' - def get_delay($timestamp; $now): - (try($timestamp[0:19] + "Z" | ($now - fromdate) | round) // null) - ; - - def get_round_delay(field; $now): - [ field[]?.contract.created_at ] - | sort[-1] - | get_delay(.; $now) - ; - - now as $now | - get_round_delay(.open_mining_rounds; $now) as $open_delay | - get_round_delay(.issuing_mining_rounds; $now) as $issuing_delay | - [$open_delay, $issuing_delay] as $round_delays | - - { - ($svname): if - ($round_delays | all | not) - then - 2 # unreachable - elif - ($event_is_fetched | not) or - ($round_delays | max > $threshold_rounds) - then - 1 # lagging - else - 0 - end - } - ' - ) + local output; output=$( + "${command_with_args[@]}" 2>/dev/null + ) || true # Use an exclusive lock to make sure we don't mix up the outputs exec {LOCK_FD}<>"$lockfile" flock "$LOCK_FD" - echo "$scan_status" + printf "%s" "$output" | jq -sR --arg label "$label" '{($label): .}' ) & proc_count=$(( proc_count + 1 )) @@ -225,13 +254,172 @@ scan_get_status() { rm "$lockfile" ) - local exit_code + printf "%s" "$result" | jq -es 'add | values' || echo '{}' +} + +# Tries to reach the scan and checks the age of the last open and issuing +# rounds. Returns a JSON object with svNames as keys and 0 (reachable and +# rounds within threshold), 1 (reachable but rounds not within threshold) or 2 +# (not reachable) as values. +scan_get_status_rounds() { + local scan_url=$SCAN_URL + local scans_info_url="$scan_url/api/scan/v0/scans" + + local scan_info; scan_info=$("${CURL_CMD[@]}" "$scans_info_url" || echo '{}') + + local scan_cmds_rounds; scan_cmds_rounds=$( + local result; result=$( + echo "$scan_info" | + jq -e \ + --argjson cmd "$CURL_CMD_JSON" \ + ' + .scans[]?.scans + | map( + { + (.svName): + $cmd + + ["--compressed"] + + ["--json", ({"cached_open_mining_round_contract_ids": [], "cached_issuing_round_contract_ids": []} | tojson)] + + [.publicUrl + "/api/scan/v0/open-and-issuing-mining-rounds"] + } + ) + | add + | values + ' + ) && echo "$result" || echo '{}' + ) + + local scan_data_rounds; scan_data_rounds=$( + run_parallel "$scan_cmds_rounds" | + json_object_values_fromjson || echo '{}' + ) + + local scan_status_rounds; scan_status_rounds=$( + result=$( + printf "%s" "$scan_data_rounds" | + jq -e \ + --argjson threshold "$SCAN_THRESHOLD_ROUNDS" \ + ' + def get_delay(field; $now): + [ field[]?.contract.created_at ] + | sort[-1] + | (try(.[0:19] + "Z" | ($now - fromdate) | round) // null) + ; + + .[] |= ( + now as $now | + get_delay(.open_mining_rounds; $now) as $open_delay | + get_delay(.issuing_mining_rounds; $now) as $issuing_delay | + [$open_delay, $issuing_delay] as $delays | + + if ($delays | all | not) then + 2 + elif ($delays | max > $threshold) then + 1 + else + 0 + end + ) | values + ' + ) && echo "$result" || echo '{}' + ) - local scan_status; scan_status=$( - echo "$scan_data" | jq -es 'sort | add' - ) && exit_code=$? || exit_code=$? + echo "$scan_status_rounds" +} - [[ $exit_code -eq 0 ]] && echo "$scan_status" || echo '{}' +# Tries to fetch a recent event from scan. Returns 0 if successful, 1 +# otherwise. +scan_try_fetch_event() { + local url=$1 + + if + local migration_id; migration_id=$( + "${CURL_CMD[@]}" "$url/api/scan/v0/migrations/last" | + jq -er '.migration_id' + ) && + + local after; after=$(TZ=UTC0 printf '%(%FT%TZ)T' "$((EPOCHSECONDS - SCAN_THRESHOLD_EVENTS))") && + + local events; events=$( + "${CURL_CMD[@]}" \ + --compressed \ + --json '{"page_size": 1, "after": {"after_migration_id": '"$migration_id"', "after_record_time": "'"$after"'"}}' \ + "$url/api/scan/v0/events" + ) && + + echo "$events" | jq -e '.events | length > 0' > /dev/null + then + echo 0 + else + echo 1 + fi +} + +scan_get_status_events() { + local scan_url=$SCAN_URL + local scans_info_url="$scan_url/api/scan/v0/scans" + + local scan_info; scan_info=$("${CURL_CMD[@]}" "$scans_info_url" || echo '{}') + + local scan_urls; scan_urls=$( + local result; result=$( + echo "$scan_info" | + jq -e '.scans[]?.scans | map({ (.svName): .publicUrl }) | add | values' + ) && echo "$result" || echo '{}' + ) + + local scan_cmds_events; scan_cmds_events=$( + local result; result=$( + jq -ne \ + --argjson scan_urls "$scan_urls" \ + ' + $scan_urls | .[] + |= ["scan_try_fetch_event", .] + | values + ' + ) && echo "$result" || echo '{}' + ) + + run_parallel "$scan_cmds_events" | + json_object_values_fromjson || echo '{}' +} + +# Tries to reach the sequencers and checks their health status. Returns a JSON +# object with svNames as keys and 0 (reachable and serving) or 2 (otherwise) as +# values. +sequencer_get_status_reachability() { + local scan_url=$SCAN_URL + local sequencers_info_url="$scan_url/api/scan/v0/dso-sequencers" + + local sequencers_info; sequencers_info=$( + "${CURL_CMD[@]}" "$sequencers_info_url" || echo '{}' + ) + + local sequencers_info_for_serial; sequencers_info_for_serial=$( + echo "$sequencers_info" | + jq --argjson serial "$SERIAL_ID" '[.domainSequencers[]?.sequencers[] | select(.synchronizerSerial == $serial)]' + ) + + local sequencers_cmds; sequencers_cmds=$( + local result; result=$( + echo "$sequencers_info_for_serial" | + jq -e \ + --argjson cmd "$GRPC_HEALTH_CMD_JSON" \ + ' + . + | map({ (.svName): $cmd + [.url] }) + | add + | values + ' + ) && echo "$result" || echo '{}' + ) + + local sequencer_status; sequencer_status=$( + run_parallel "$sequencers_cmds" | + json_object_values_fromjson || echo '{}' + ) + + echo "$sequencer_status" } update_serial_id() { @@ -246,34 +434,77 @@ generate_sequencer_metrics_url() { echo "http://global-domain-$SERIAL_ID-sequencer:10013/metrics" } +# Bitwise combination of status maps. Each map is a JSON object with string keys and +# integer values. The values are combined using a bitwise OR operation. +# combine_status '{k1: v1, k2: v2}' '{k1: v3, k3: v4}' -> '{k1: v1 | v3, k2: v2, k3: v4}' +# +# Examples: +# echo '{"a": 1, "b": 1}' '{"a": 2, "c": 2}' | combine_status -> '{"a": 3, "b": 1, "c": 2}' +# echo '{"a": null}' '{"a": 1}' | combine_status -> '{"a": 1}' +# echo 'null' '{"a": 1}' | combine_status -> '{"a": 1}' +# echo '{"a": 1}' 'null' | combine_status -> '{"a": 1}' +# echo '{"a": null}' 'null' | combine_status -> '{"a": null}' +# echo 'null' 'null' | combine_status -> 'null' +# echo '{}' '{}' | combine_status -> '{}' +combine_status() { + jq -s \ + ' + def bitor: + map(select(. != null)) | unique | + if length == 0 then null + elif length == 1 then first + elif any(. == -1) then -1 + else (map(. % 2 | abs) | max) + 2 * (map(. / 2 | floor) | bitor) + end + ; + + map(select(. != null)) as $inputs | + reduce $inputs[] as $i (null; + reduce ($i | to_entries[]) as $e (. // {}; + .[$e.key] = ([.[$e.key], $e.value] | bitor) + ) + ) + ' +} + main() { if ! prom2json --version &>/dev/null; then echo "ERROR: prom2json is not installed. Exiting." >&2 return 1 fi + update_serial_id + if [[ -z "${SEQUENCER_METRICS_URL:-}" ]]; then - update_serial_id SEQUENCER_METRICS_URL=$(generate_sequencer_metrics_url) fi - sv_status=$(sv_get_status) + # Get SV and Scan status + local sv_status; sv_status=$(sv_get_status) + local scan_status_rounds; scan_status_rounds=$(scan_get_status_rounds) + local scan_status_events; scan_status_events=$(scan_get_status_events) + local scan_status; scan_status=$(echo "$scan_status_rounds" "$scan_status_events" | combine_status) + + # Get acknowledgment metrics from Sequencer + local sequencer_metric_name; sequencer_metric_name=daml_sequencer_block_acknowledgments_micros + local sequencer_metric_data; sequencer_metric_data=$(get_metric_data "$SEQUENCER_METRICS_URL" "$sequencer_metric_name") - sequencer_metric_name=daml_sequencer_block_acknowledgments_micros - sequencer_metric_data=$(get_sequencer_metric_data "$sequencer_metric_name") + # Get Mediator status + local mediator_status; mediator_status=$(get_status_from_sequencer_metric_data "$sequencer_metric_data" MED "$MEDIATOR_THRESHOLD") - mediator_status=$(get_status_from_sequencer_metric_data "$sequencer_metric_data" "$sequencer_metric_name" MED "$MEDIATOR_THRESHOLD") - scan_status=$(scan_get_status) - sequencer_status=$(get_status_from_sequencer_metric_data "$sequencer_metric_data" "$sequencer_metric_name" SEQ "$SEQUENCER_THRESHOLD") + # Get Sequencer status + local sequencer_status_lag; sequencer_status_lag=$(get_status_from_sequencer_metric_data "$sequencer_metric_data" SEQ "$SEQUENCER_THRESHOLD") + local sequencer_status_reachability; sequencer_status_reachability=$(sequencer_get_status_reachability) + local sequencer_status; sequencer_status=$(echo "$sequencer_status_lag" "$sequencer_status_reachability" | combine_status) - jq -n \ + jq -Sn \ --argjson sv "$sv_status" \ --argjson sv_threshold "$SV_THRESHOLD" \ --argjson mediator "$mediator_status" \ --argjson mediator_threshold "$MEDIATOR_THRESHOLD" \ --argjson scan "$scan_status" \ --argjson scan_threshold_rounds "$SCAN_THRESHOLD_ROUNDS" \ - --argjson scan_threshold_event "$SCAN_THRESHOLD_EVENT" \ + --argjson scan_threshold_events "$SCAN_THRESHOLD_EVENTS" \ --argjson sequencer "$sequencer_status" \ --argjson sequencer_threshold "$SEQUENCER_THRESHOLD" \ ' @@ -281,8 +512,8 @@ main() { status: { sv: {nodes: $sv, description: "Last status report within \($sv_threshold) seconds"}, mediator: {nodes: $mediator, description: "Last acknowledgment within \($mediator_threshold) seconds"}, - scan: {nodes: $scan, description: "Reachable, last open and issuing rounds are within \($scan_threshold_rounds) seconds and recent event is within \($scan_threshold_event) seconds"}, - sequencer: {nodes: $sequencer, description: "Last acknowledgment within \($sequencer_threshold) seconds"}, + scan: {nodes: $scan, description: "Reachable, last open and issuing rounds are within \($scan_threshold_rounds) seconds and recent events are within \($scan_threshold_events) seconds"}, + sequencer: {nodes: $sequencer, description: "Reachable, last acknowledgment within \($sequencer_threshold) seconds"}, }, generatedAt: (now | todate), } diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 04fa075f12..fe39157d6d 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -32,6 +32,13 @@ recommended. Follow the `migration guide `__ to move the data of an existing node before that date. - - SV app + - SV app - - Add support for specifying weight in ``GrantFeaturedAppRight`` governance voting UI. + - Add support for specifying weight in ``GrantFeaturedAppRight`` governance voting UI. + + - Deployment + + - splice-info + + - ``/runtime/status.json`` now includes reachability for scan and sequencer (0 is good, 1 is lagging + behind, 2 is unreachable, 3 is lagging behind and unreachable). From 42b6faa83b47a32f87f20d602a42878a3d98d13f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Thu, 16 Jul 2026 12:46:11 +0200 Subject: [PATCH 064/329] Revert "Add cantonbft backup/restore support" (#6446) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- build-tools/cncluster | 2 +- cluster/scripts/find-recent-backup.sh | 53 ++++----------------------- cluster/scripts/node-backup.sh | 7 ---- cluster/scripts/node-restore.sh | 30 ++++++--------- cluster/scripts/utils.source | 27 ++------------ 5 files changed, 23 insertions(+), 96 deletions(-) diff --git a/build-tools/cncluster b/build-tools/cncluster index 1a25cb4e80..706f585319 100755 --- a/build-tools/cncluster +++ b/build-tools/cncluster @@ -1948,7 +1948,7 @@ function subcmd_restore_node() { _info "Silencing SV report creation alerts for $sv_name for 1 hour" subcmd_silence_grafana_alerts "1 hour" "alertname=Report Creation Time Lag" "report_publisher=$sv_name" _info "Restoring sv node $node" - SPLICE_SV=$node SPLICE_MIGRATION_ID=$migration_id "$SPLICE_ROOT"/cluster/scripts/node-restore.sh $force "$node" "$migration_id" "$backup_run_id" cometbft cantonBft sequencer participant mediator cn-apps + SPLICE_SV=$node SPLICE_MIGRATION_ID=$migration_id "$SPLICE_ROOT"/cluster/scripts/node-restore.sh $force "$node" "$migration_id" "$backup_run_id" cometbft sequencer participant mediator cn-apps ;; validator1|splitwell) _info "Restoring validator node $node" diff --git a/cluster/scripts/find-recent-backup.sh b/cluster/scripts/find-recent-backup.sh index f646ffd1f2..4bdf540fc1 100755 --- a/cluster/scripts/find-recent-backup.sh +++ b/cluster/scripts/find-recent-backup.sh @@ -17,11 +17,11 @@ function usage() { function is_full_backup_kube() { local component_backup_names=$1 - local expected_patterns=$2 + local expected_components=$2 # Check if all expected components can be found in the component_backup_names - for pattern in $expected_patterns; do - count=$(echo "$component_backup_names" | grep -c -F -- "$pattern") + for component in $expected_components; do + count=$(echo "$component_backup_names" | grep -c "$component") if [ "$count" -ne 1 ]; then return 1 fi @@ -44,16 +44,8 @@ function latest_full_backup_run_id_kube() { local expected_components=$4 local before_timestamp=$5 local include_cometbft=$6 - - local expected_patterns="" - for component in $expected_components; do - local instance - instance="$(create_component_instance "$component" "$migration_id" "$namespace")" - expected_patterns="${expected_patterns:+$expected_patterns }-${instance}-pg-" - done - if [ "$is_sv" == "true" ] && [ "$include_cometbft" == "true" ]; then - expected_patterns="$expected_patterns cometbft" + expected_components="$expected_components cometbft" fi local all_run_ids @@ -62,7 +54,7 @@ function latest_full_backup_run_id_kube() { while read -r run_id; do component_backup_names=$(get_component_backup_names_kube "$migration_id" "$run_id") - if is_full_backup_kube "$component_backup_names" "$expected_patterns"; then + if is_full_backup_kube "$component_backup_names" "$expected_components"; then echo "$run_id" return 0 fi @@ -79,7 +71,6 @@ function latest_full_backup_run_id_gcloud() { local stack declare -A backup_id_dict - local sequencer_end_time="" # participant backup must be newer than cn-apps backup stack=$(get_stack_for_namespace_component "$namespace" "participant") @@ -99,7 +90,6 @@ function latest_full_backup_run_id_gcloud() { for component in $expected_components; do [ "$component" == "participant" ] && continue - [ "$component" == "cantonBft" ] && continue stack=$(get_stack_for_namespace_component "$namespace" "$component") instance="$(create_component_instance "$component" "$migration_id" "$namespace")" @@ -108,43 +98,21 @@ function latest_full_backup_run_id_gcloud() { local cloudsql_id cloudsql_id=$(get_cloudsql_id "$full_component_instance" "$stack") - local entry + local backup_id if [ "$component" == "cn-apps" ]; then # cn-apps backup must be older than participant backup - entry=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --arg pt "$participant_end_time" '[.[] | select(.endTime <= $pt)] | first | "\(.id) \(.endTime)"') + backup_id=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --arg pt "$participant_end_time" '[.[] | select(.endTime <= $pt)] | first | .id') else - entry=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --argjson ts "$before_timestamp" '[.[] | select(.endTime <= ($ts | todate))] | first | "\(.id) \(.endTime)"') + backup_id=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --argjson ts "$before_timestamp" '[.[] | select(.endTime <= ($ts | todate))] | first | .id') fi - local backup_id - backup_id=$(echo "$entry" | awk '{print $1}') - local backup_end_time - backup_end_time=$(echo "$entry" | awk '{print $2}') if [ -z "$backup_id" ] || [ "$backup_id" == "null" ]; then _error "No backup found for component $component (instance $cloudsql_id) before timestamp $before_timestamp" fi backup_id_dict[$component]="$backup_id" - - if [ "$component" == "sequencer" ]; then - sequencer_end_time="$backup_end_time" - fi done - # cantonBft backup must be older than the sequencer backup - if [[ " $expected_components " == *" cantonBft "* ]]; then - stack=$(get_stack_for_namespace_component "$namespace" "cantonBft") - instance="$(create_component_instance "cantonBft" "$migration_id" "$namespace")" - local bft_cloudsql_id - bft_cloudsql_id=$(get_cloudsql_id "$namespace-$instance-pg" "$stack") - local bft_backup_id - bft_backup_id=$(gcloud sql backups list --instance "$bft_cloudsql_id" --format=json | jq -r --arg st "$sequencer_end_time" '[.[] | select(.endTime <= $st)] | first | .id') - if [ -z "$bft_backup_id" ] || [ "$bft_backup_id" == "null" ]; then - _error "No backup found for component cantonBft (instance $bft_cloudsql_id) before sequencer backup time $sequencer_end_time" - fi - backup_id_dict["cantonBft"]="$bft_backup_id" - fi - local result="" for component in "${!backup_id_dict[@]}"; do result="${result:+$result,}$component:${backup_id_dict[$component]}" @@ -190,11 +158,6 @@ function main() { is_sv=true full_instance="$namespace-cn-apps-pg" expected_components="cn-apps sequencer participant mediator" - local bft_db_enabled - bft_db_enabled=$(canton_bft_db_enabled "$migration_id" "$config") - if [ "$bft_db_enabled" == "true" ]; then - expected_components="$expected_components cantonBft" - fi stack=$(get_stack_for_namespace_component "$namespace" "cn-apps") ;; *) diff --git a/cluster/scripts/node-backup.sh b/cluster/scripts/node-backup.sh index f84bcac39d..533f2d1bf4 100755 --- a/cluster/scripts/node-backup.sh +++ b/cluster/scripts/node-backup.sh @@ -297,13 +297,6 @@ function main() { elif [ "$1" == "sv" ]; then _info "Backing up SV node $namespace" - local bft_enabled - bft_enabled=$(canton_bft_db_enabled "$migration_id" "$config") - if [ "$bft_enabled" == "true" ]; then - backup_component "$namespace" "cantonBft" "$requested_component" "$migration_id" - wait_for_backup "$namespace" "cantonBft" "$requested_component" "$migration_id" - fi - backup_component "$namespace" "cn-apps" "$requested_component" "$migration_id" backup_component "$namespace" "mediator" "$requested_component" "$migration_id" backup_component "$namespace" "sequencer" "$requested_component" "$migration_id" diff --git a/cluster/scripts/node-restore.sh b/cluster/scripts/node-restore.sh index a8237442fb..d2000ed4b8 100755 --- a/cluster/scripts/node-restore.sh +++ b/cluster/scripts/node-restore.sh @@ -16,8 +16,6 @@ function component_to_deployments() { local -r namespace=$3 if [[ "$component" == "sequencer" ]]; then echo "global-domain-$migration_id-sequencer" - elif [[ "$component" == "cantonBft" ]]; then - echo "global-domain-$migration_id-sequencer" elif [[ "$component" == "mediator" ]]; then echo "global-domain-$migration_id-mediator" elif [[ "$component" == "participant" && "$namespace" == sv* ]]; then @@ -436,33 +434,27 @@ function main() { | map(select(.id == $migration_id)) | .[0].sequencer.enableBftSequencer // false") - local bft_db_enabled - bft_db_enabled=$(canton_bft_db_enabled "$migration_id" "$config") + if [[ "$run_id" == *","* ]]; then + _info " ** Validate backup ids ** " + local map_keys + map_keys=$(echo "$run_id" | tr ',' '\n' | cut -d: -f1 | sort) + local req_components + req_components=$(printf '%s\n' "${@:4}" | sort) + if [ "$map_keys" != "$req_components" ]; then + _error "Backup map keys ($map_keys) do not match requested components (${*:4})" + fi + fi + # Build the list of components to restore, dropping CometBFT when the BFT sequencer is enabled. local -a components=() for component in "${@:4}"; do if [ "$component" == "cometbft" ] && [ "$bft_sequencer_enabled" == "true" ]; then _info "BFT sequencer is enabled for migration $migration_id, skipping CometBFT restore" continue fi - if [ "$component" == "cantonBft" ] && [ "$bft_db_enabled" != "true" ]; then - _info "Dedicated CantonBFT sequencer DB is not enabled for migration $migration_id, skipping CantonBFT restore" - continue - fi components+=("$component") done - if [[ "$run_id" == *","* ]]; then - _info " ** Validate backup ids ** " - local map_keys - map_keys=$(echo "$run_id" | tr ',' '\n' | cut -d: -f1 | sort) - local req_components - req_components=$(printf '%s\n' "${components[@]}" | sort) - if [ "$map_keys" != "$req_components" ]; then - _error "Backup map keys ($map_keys) do not match requested components (${components[*]})" - fi - fi - for component in "${components[@]}"; do component_to_deployments "$component" "$migration_id" "$namespace" done diff --git a/cluster/scripts/utils.source b/cluster/scripts/utils.source index 5f857452cb..0018dc9f2d 100755 --- a/cluster/scripts/utils.source +++ b/cluster/scripts/utils.source @@ -43,8 +43,6 @@ function get_stack_for_namespace_component() { stack="sv" elif [[ "${component}" == "sequencer" ]]; then stack="sv-canton" - elif [[ "${component}" == "cantonBft" ]]; then - stack="sv-canton" elif [[ "${component}" == "mediator" ]]; then stack="sv-canton" elif [[ "${split_sv_deployment}" == "true" ]]; then @@ -64,35 +62,16 @@ create_component_instance() { local migration_id="$2" local namespace="$3" - local instance_base="$component" - if [[ "$component" == "cantonBft" ]]; then - instance_base="sequencer-bft" - fi - - if [[ ("$component" == "sequencer" || "$component" == "cantonBft" || "$component" == "mediator") + if [[ ("$component" == "sequencer" || "$component" == "mediator") && ("$namespace" != "splitwell" && "$namespace" != "validator1" && "$namespace" != "sv") ]]; then - component_instance="${instance_base}-${migration_id}" + component_instance="${component}-${migration_id}" else - component_instance="${instance_base}" + component_instance="${component}" fi echo "$component_instance" } -function canton_bft_db_enabled() { - local migration_id=$1 - local config=$2 - - echo "$config" | yq " - ([.synchronizerMigration.active, .synchronizerMigration.upgrade, .synchronizerMigration.legacy] - + (.synchronizerMigration.additionalLegacy // []) - + (.synchronizerMigration.archived // [])) - | map(select(. != null and .id == ${migration_id})) - | .[0] - | ((.sequencer.enableBftSequencer // true) and (.sequencer.dedicatedBftSequencerDb // true)) - " -} - function get_resolved_config() { "${SPLICE_ROOT}/cluster/scripts/get-resolved-config.sh" } From daa3dc258ad6c9893790d295780232cfd255b248 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Thu, 16 Jul 2026 14:47:26 +0200 Subject: [PATCH 065/329] Bump canton to 3.5.9 (#6449) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oriol Muñoz --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index 04a1ec9877..038a77e612 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.9-snapshot.20260714.19082.0.vd76d1db5", - "oss_sha256": "sha256:07q7a0p1b3r1ihvsfl10y77ssad8hds7zddfs2r14g4z8jwn3x4z", - "canton_base_image_sha256": "sha256:1688d1886eabc3e44e8dee5971dca330df5b3b2e0a03ddbadca02859878d438e", - "canton_participant_image_sha256": "sha256:0ccc22e60f570d9050f058420cc0652318a9abadfba5b666b1fe8880aebb9cf5", - "canton_mediator_image_sha256": "sha256:19edb1fd3b4d7ca5a07d0cfe534080d7abcbe1e0e28bd6eb85a11159501decc3", - "canton_sequencer_image_sha256": "sha256:8b34ed10fba56699fc744a62ba62e13737ce9cd696defd69e2d26fbdb642b795" + "version": "3.5.9", + "oss_sha256": "sha256:0b3ks8b3wfm7fbc8wqaiixylpi2mb73zfwmlaksybxwnyblr9zav", + "canton_base_image_sha256": "sha256:7ea01437e4a135aa2d5dcbd27f21e6b3e6ce11334c805f17924eb7f5f089ac52", + "canton_participant_image_sha256": "sha256:2e3be4f8fb62f2f07b1875b64aa16f2ac8c39da9aa087a6e4a05943537e76bcb", + "canton_mediator_image_sha256": "sha256:e9312f2927bfb9f99c328b16def3e1b98bb42897689c4c1ff861a648c5338817", + "canton_sequencer_image_sha256": "sha256:4cc76bc98c30ce9e764f1d37aa628f289cfa4a162b783b1c487b9cfa3bbef801" } From 6443c35985b93e0d16105fb065edf6935955f4a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Thu, 16 Jul 2026 15:03:33 +0200 Subject: [PATCH 066/329] Bump the onboarding timeout in FE integration tests (#6450) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../splice/util/FrontendLoginUtil.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/FrontendLoginUtil.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/FrontendLoginUtil.scala index 2ba07f75c9..e0fef3e7ea 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/FrontendLoginUtil.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/FrontendLoginUtil.scala @@ -124,7 +124,12 @@ trait FrontendLoginUtil extends WithAuth0Support { self: FrontendTestCommon => ) } val userPartyId = if (onboardThroughWalletUI) { - actAndCheck("onboard user", eventuallyClickOn(id("onboard-button")))( + // Onboarding creates the WalletAppInstall contract + // under CI load sequencing can take longer than the default 20s + actAndCheck(timeUntilSuccess = 40.seconds)( + "onboard user", + eventuallyClickOn(id("onboard-button")), + )( "user is onboarded", _ => { val userId = seleniumText(find(id("logged-in-user"))) From 580be31cdbcf43f350c0f9cd835be70c4e177790 Mon Sep 17 00:00:00 2001 From: Itai Segall Date: Thu, 16 Jul 2026 09:19:19 -0400 Subject: [PATCH 067/329] nuke all leftover references to artifactory (#6417) Signed-off-by: krzysztofczyz-da Signed-off-by: Itai Segall Co-authored-by: krzysztofczyz-da --- .envrc | 26 +- .envrc.validate | 9 +- .github/actions/nix/setup_nix/action.yml | 33 +- .../sbt/execute_sbt_command/action.yml | 10 +- .../tests/common_test_setup/action.yml | 16 +- .github/actions/tests/scala_test/action.yml | 21 +- .github/workflows/build.daml_test.yml | 1 - .github/workflows/build.deployment_test.yml | 1 - .github/workflows/build.docs.yml | 3 - .github/workflows/build.scala_test.yml | 8 - .../build.scala_test_for_compose.yml | 2 - .../build.scala_test_with_cometbft.yml | 2 - .github/workflows/build.static_tests.yml | 6 - .github/workflows/build.ts_cli_tests.yml | 1 - .github/workflows/build.ui_tests.yml | 1 - .github/workflows/build.yml | 47 +- .github/workflows/bump_gha_runner_version.yml | 2 - .github/workflows/canton_oss_test.yml | 22 - .github/workflows/performance_tests.yml | 6 +- .github/workflows/pr_check_github_scripts.yml | 4 - .github/workflows/pr_static_checks.yml | 1 - DEVELOPMENT.md | 51 +- MAINTENANCE.md | 3 +- TROUBLESHOOTING.md | 1 - ...icipantKmsIdentitiesIntegrationTest.scala} | 2 +- build-tools/artifactory_to_gcs.py | 785 ------------------ .../copy_release_helm_charts_to_ghcr.sh | 2 +- build-tools/copy_release_images_to_ghcr.sh | 4 +- build.sbt | 5 - cluster/pulumi/sv-runbook/src/installNode.ts | 2 +- .../validator-runbook/src/installNode.ts | 2 +- nix/cometbft-driver.nix | 2 +- nix/flake.nix | 29 +- nix/overlays.nix | 1 - nix/shell.nix | 4 +- test-full-class-names-canton-enterprise.log | 1 - test-full-class-names.log | 1 + 37 files changed, 35 insertions(+), 1082 deletions(-) delete mode 100644 .github/workflows/canton_oss_test.yml rename apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/{ParticipantKmsIdentitiesEnterpriseIntegrationTest.scala => ParticipantKmsIdentitiesIntegrationTest.scala} (99%) delete mode 100755 build-tools/artifactory_to_gcs.py delete mode 100644 test-full-class-names-canton-enterprise.log diff --git a/.envrc b/.envrc index 17a303ba34..115b68cb32 100644 --- a/.envrc +++ b/.envrc @@ -7,22 +7,13 @@ use_flake_subdir() { watch_file $file done mkdir -p "$(direnv_layout_dir)" - if [ -z "$ARTIFACTORY_PASSWORD" ] || [ "${OSS_ONLY:-}" == 1 ] || [ "${OSS_ONLY:-}" == "true" ]; then - eval "$(nix print-dev-env --profile "$(direnv_layout_dir)/flake-profile" "path:nix#oss" "$@")" - else - eval "$(nix print-dev-env --profile "$(direnv_layout_dir)/flake-profile" "path:nix" "$@")" - fi + eval "$(nix print-dev-env --profile "$(direnv_layout_dir)/flake-profile" "path:nix" "$@")" } source_envrc_private() { [[ -f .envrc.private ]] && [[ -z "$IGNORE_PRIVATE_ENVRC" ]] && source_env .envrc.private || true } -# Source .envrc.private before use_flake_subdir because it provides ARTIFACTORY_PASSWORD -# which determines whether the enterprise or OSS nix shell is used. -# This workaround can be removed once the enterprise edition is removed. -source_envrc_private - # TODO(DACH-NY/canton-network-node#3876) work around for $TMPDIR is removed. #3876 to investigate more OLD_TMPDIR=${TMPDIR-unset} @@ -42,8 +33,6 @@ source "${TOOLS_LIB}/libcli.source" source_env .envrc.validate -export ENTERPRISE_ARTIFACTORY_DOCKER_REGISTRY=digitalasset-canton-enterprise-docker.jfrog.io - # Configure docker access for cluster and integration tests ## Read current credentials function read_docker_creds() { @@ -58,13 +47,6 @@ function read_docker_creds() { fi } -function check_docker_creds() { - local docker_creds="$1" - [[ -z "$docker_creds" || "$docker_creds" == "null" || "$docker_creds" != "$ARTIFACTORY_USER:$ARTIFACTORY_PASSWORD" ]] && \ - ## And artifactory user and password are set - [[ -n "$ARTIFACTORY_USER" && -n "$ARTIFACTORY_PASSWORD" ]] -} - function check_docker_creds_ghcr() { local docker_creds="$1" [[ -z "$docker_creds" || "$docker_creds" == "null" || "$docker_creds" != "$GH_USER:$GH_TOKEN" ]] && \ @@ -77,12 +59,6 @@ if check_docker_creds_ghcr "$DOCKER_CREDS"; then echo $GH_TOKEN | docker login "$GHCR" -u "$GH_USER" --password-stdin fi -DOCKER_CREDS=$(read_docker_creds "$ENTERPRISE_ARTIFACTORY_DOCKER_REGISTRY") -if check_docker_creds "$DOCKER_CREDS"; then - echo "Logging into $ENTERPRISE_ARTIFACTORY_DOCKER_REGISTRY" - echo $ARTIFACTORY_PASSWORD | docker login "$ENTERPRISE_ARTIFACTORY_DOCKER_REGISTRY" -u "$ARTIFACTORY_USER" --password-stdin -fi - # re-export CIRCLECI_TOKEN (which we use in many places) as CIRCLECI_CLI_TOKEN (which `circleci` cli tool picks up) export CIRCLECI_CLI_TOKEN="${CIRCLECI_TOKEN}" diff --git a/.envrc.validate b/.envrc.validate index 4a73ce2303..f326e2da30 100644 --- a/.envrc.validate +++ b/.envrc.validate @@ -34,12 +34,5 @@ if [[ -z "$IGNORE_PRIVATE_ENVRC" ]]; then AUTH0_VALIDATOR_MANAGEMENT_API_CLIENT_ID \ AUTH0_VALIDATOR_MANAGEMENT_API_CLIENT_SECRET \ AUTH0_TESTS_MANAGEMENT_API_CLIENT_ID \ - AUTH0_TESTS_MANAGEMENT_API_CLIENT_SECRET \ - ARTIFACTORY_USER \ - ARTIFACTORY_PASSWORD -fi - -if [ "$IS_ENTERPRISE" != "true" ]; then - echo "" - _warning "Note: Using Canton community instead of Canton enterprise. Certain tests which rely on Enterprise features will fail locally." + AUTH0_TESTS_MANAGEMENT_API_CLIENT_SECRET fi diff --git a/.github/actions/nix/setup_nix/action.yml b/.github/actions/nix/setup_nix/action.yml index 78920272cf..9ba47061c8 100644 --- a/.github/actions/nix/setup_nix/action.yml +++ b/.github/actions/nix/setup_nix/action.yml @@ -1,19 +1,14 @@ name: "Setup Nix" description: "Setup Nix" inputs: - artifactory_user: - description: "The Artifactory user" - required: true - artifactory_password: - description: "The Artifactory password" - required: true nix_path: description: "The path to nix flake directory" required: false default: ${{ format('{0}/nix', github.repository == 'DACH-NY/canton-network-internal' && 'splice' || '.') }} target: - description: "Choose nix target: oss - restrict upstream dependencies (e.g. Canton) to OSS versions (the equivalent of OSS_ONLY=1 in local checkouts), static_tests - for static tests, default - for enterprise dependencies" - required: true + description: "Choose nix target: static_tests - for static tests, default - for full dependencies" + required: false + default: "default" cache_version: description: "Cache version" required: true @@ -37,12 +32,12 @@ runs: using: "composite" steps: - name: Validate input - if: "${{!( inputs.target == 'default' || inputs.target == 'oss' || inputs.target == 'static_tests')}}" + if: "${{!( inputs.target == 'default' || inputs.target == 'static_tests')}}" shell: bash id: validate_input run: | echo "Target invalid: ${{ inputs.target }}" - echo "Target needs to be one of: 'default', 'oss', 'static_tests'" + echo "Target needs to be one of: 'default', 'static_tests'" exit 1 - name: Compute cache Key id: cache_key @@ -60,8 +55,8 @@ runs: echo "home: $HOME" >> /tmp/nix-cache-key # important when restoring simlinks from cache, apparently echo "nix binary version: $NIX_BINARY_VERSION" >> /tmp/nix-cache-key # different nix versions might behave differently and corrupt the caches echo "target: ${{ inputs.target }}" >> /tmp/nix-cache-key - if [ "${{ inputs.target }}" != 'default' ]; then - echo "Using OSS only dependencies" + if [ "${{ inputs.target }}" == 'static_tests' ]; then + echo "Using minimal nix dependencies for static tests" fi cat /tmp/nix-cache-key cache_key=($(md5sum "/tmp/nix-cache-key")) @@ -76,11 +71,6 @@ runs: run: | set -euxo pipefail - if [[ ${{ inputs.target }} == 'default' ]]; then - echo "Must use OSS only dependencies in GitHub-hosted runners" - exit 1 - fi - echo "Latest nix cache:" wget -q "https://storage.googleapis.com/splice-nix-cache-public/${cache_key}.tar.gz" -O cache.tar.gz || true @@ -143,15 +133,6 @@ runs: sh <(curl -fsSL --retry 8 "https://releases.nixos.org/nix/nix-$NIX_BINARY_VERSION/install") --no-daemon sudo mkdir -p /etc/nix sudo chmod a+rw /etc/nix - if [[ "${{ inputs.target }}" != 'default' ]]; then - echo "Using OSS only dependencies, not setting up Artifactory credentials" - else - cat < /etc/nix/netrc - machine digitalasset.jfrog.io - login ${{ inputs.artifactory_user }} - password ${{ inputs.artifactory_password }} - EOF - fi export USER=$(whoami) echo "Running nix.sh" . ~/.nix-profile/etc/profile.d/nix.sh diff --git a/.github/actions/sbt/execute_sbt_command/action.yml b/.github/actions/sbt/execute_sbt_command/action.yml index 1bb02c8083..295450f584 100644 --- a/.github/actions/sbt/execute_sbt_command/action.yml +++ b/.github/actions/sbt/execute_sbt_command/action.yml @@ -7,12 +7,6 @@ inputs: # The caller needs to quote commands that contain spaces, e.g. "\"testOnly myTest\"". description: "The SBT command to run" required: true - artifactory_user: - description: "Artifactory user" - required: false - artifactory_password: - description: "Artifactory password" - required: false extra_env_vars: description: "Extra environment variables to set before running the SBT command" required: false @@ -51,7 +45,7 @@ runs: - name: Execute SBT command" uses: ./.github/actions/nix/run_bash_command_in_nix with: - additional_nix_args: "--keep GITHUB_ACTION_PATH --keep ARTIFACTORY_USER --keep ARTIFACTORY_PASSWORD ${{ inputs.additional_nix_args }}" + additional_nix_args: "--keep GITHUB_ACTION_PATH ${{ inputs.additional_nix_args }}" cmd: | # This might help resolve https://github.com/DACH-NY/canton-network-node/issues/8146 export PROTOCBRIDGE_NO_CLEANUP="1" @@ -91,8 +85,6 @@ runs: $GITHUB_ACTION_PATH/../../scripts/check-sbt-output.sh "sbt_output" fi } - export ARTIFACTORY_USER="${{ inputs.artifactory_user }}" - export ARTIFACTORY_PASSWORD="${{ inputs.artifactory_password }}" # Ensure that we're in the root of splice before execution pushd ${{ inputs.splice_root }} &> /dev/null diff --git a/.github/actions/tests/common_test_setup/action.yml b/.github/actions/tests/common_test_setup/action.yml index 7f38144eca..c405f8d369 100644 --- a/.github/actions/tests/common_test_setup/action.yml +++ b/.github/actions/tests/common_test_setup/action.yml @@ -16,14 +16,8 @@ inputs: description: "Whether to save the Nix cache to GCP" required: false default: "false" - artifactory_user: - description: "The Artifactory user" - required: false - artifactory_password: - description: "The Artifactory password" - required: false target: - description: "Choose nix target: oss - restrict upstream dependencies (e.g. Canton) to OSS versions (the equivalent of OSS_ONLY=1 in local checkouts), static_tests - for static tests, default - for enterprise dependencies" + description: "Choose nix target: static_tests - for static tests, default - for full dependencies" default: 'default' # type: choice # options: @@ -51,12 +45,6 @@ outputs: runs: using: "composite" steps: - - name: Validate input - if: inputs.target == 'default' && (inputs.artifactory_password == '' || inputs.artifactory_user == '') - shell: bash - run: | - echo "artifactory_user and artifactory_password must be provided if not using OSS only dependencies." - exit 1 - name: Publish test name metric uses: miguelteixeiraa/action-run-in-background@e28f036c202e9066287e6a50ce8b80749627cc7d # v1.0.0 @@ -70,8 +58,6 @@ runs: - name: Set up Nix (Self hosted) uses: ./.github/actions/nix/setup_nix with: - artifactory_user: ${{ inputs.artifactory_user }} - artifactory_password: ${{ inputs.artifactory_password }} cache_version: ${{ inputs.cache_version }} should_save: ${{ inputs.save_nix_cache }} should_save_gcp: ${{ inputs.save_nix_cache_to_gcp }} diff --git a/.github/actions/tests/scala_test/action.yml b/.github/actions/tests/scala_test/action.yml index 728593c162..ac022f3b5b 100644 --- a/.github/actions/tests/scala_test/action.yml +++ b/.github/actions/tests/scala_test/action.yml @@ -8,12 +8,6 @@ inputs: start_canton_options: description: "Options for start-canton.sh" required: true - artifactory_user: - description: "The Artifactory user" - required: true - artifactory_password: - description: "The Artifactory password" - required: true test_suite_name: description: "Name of the test suite" required: true @@ -93,10 +87,6 @@ inputs: protocol_version: description: "Synchronizer Protocol Version" required: true - oss_only: - description: "Restrict upstream dependencies (e.g. Canton) to OSS versions (the equivalent of OSS_ONLY=1 in local checkouts)" - required: false - default: "false" cache_version: description: "Cache version" required: true @@ -109,12 +99,7 @@ runs: with: test_name: ${{ inputs.test_suite_name }} with_sbt: false # we setup SBT later while canton is starting up - artifactory_user: ${{ inputs.artifactory_user }} - artifactory_password: ${{ inputs.artifactory_password }} - target: ${{ inputs.oss_only == true && 'oss' || 'default' }} - # The docs job saves the oss nix cache, here we save the non-oss one, but only in one runner to reduce contention - # TODO(#1296): When this runner stops using non-oss, move this to one that does - save_nix_cache: ${{ inputs.runner_index == 0 && inputs.test_suite_name == 'canton-enterprise' }} + target: 'default' - name: Wait for postgres uses: ./.github/actions/nix/run_bash_command_in_nix @@ -230,8 +215,6 @@ runs: uses: ./.github/actions/nix/run_bash_command_in_nix with: cmd: | - export ARTIFACTORY_USER="${{ inputs.artifactory_user }}" - export ARTIFACTORY_PASSWORD="${{ inputs.artifactory_password }}" export CIRCLE_REPOSITORY_URL="${{ github.repositoryUrl }}" export CIRCLE_SHA1="${{ github.sha }}" /usr/bin/sudo mkdir -p ~/.docker/buildx @@ -258,8 +241,6 @@ runs: uses: ./.github/actions/sbt/execute_sbt_command with: extra_env_vars: "POSTGRES_DB=postgres POSTGRES_HOST=localhost POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres INITIAL_PACKAGE_VERSIONS=${{ steps.daml_package_versions.outputs.initial_package_versions }} PROTOCOL_VERSION=${{ inputs.protocol_version }}" - artifactory_user: ${{ inputs.artifactory_user }} - artifactory_password: ${{ inputs.artifactory_password }} cmd: ${{ steps.list_tests.outputs.RUN_SPLITTED_TESTS_CMD }} additional_nix_args: "--keep GITHUB_ACTION" extra_parameters: -DAUTH0_MANAGEMENT_API_CLIENT_ID=${{ inputs.auth0_management_api_client_id }} -DAUTH0_MANAGEMENT_API_CLIENT_SECRET=${{ inputs.auth0_management_api_client_secret }} diff --git a/.github/workflows/build.daml_test.yml b/.github/workflows/build.daml_test.yml index 0ef5f6828c..016ba8c4d5 100644 --- a/.github/workflows/build.daml_test.yml +++ b/.github/workflows/build.daml_test.yml @@ -34,7 +34,6 @@ jobs: with: cache_version: 8 test_name: daml_test - target: 'oss' - name: Run Daml tests if: steps.skip.outputs.skip != 'true' diff --git a/.github/workflows/build.deployment_test.yml b/.github/workflows/build.deployment_test.yml index 80f4e14e0b..2652c6b445 100644 --- a/.github/workflows/build.deployment_test.yml +++ b/.github/workflows/build.deployment_test.yml @@ -28,7 +28,6 @@ jobs: cache_version: 8 test_name: deployment_test with_sbt: false - target: 'oss' - name: Helm tests uses: ./.github/actions/nix/run_bash_command_in_nix diff --git a/.github/workflows/build.docs.yml b/.github/workflows/build.docs.yml index 49c5e6c996..11311ee26e 100644 --- a/.github/workflows/build.docs.yml +++ b/.github/workflows/build.docs.yml @@ -29,15 +29,12 @@ jobs: test_name: docs save_nix_cache: true save_nix_cache_to_gcp: ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }} - target: 'oss' upload_workload_identity_provider: ${{ secrets.GOOGLE_WORKLOAD_IDENTITY_PROVIDER_SPLICE }} upload_service_account: ${{ secrets.CACHE_UPLOADER_SA }} - name: Build docs uses: ./.github/actions/sbt/execute_sbt_command with: - artifactory_user: ${{ vars.ARTIFACTORY_USER }} - artifactory_password: ${{ secrets.ARTIFACTORY_PASSWORD }} # We Test/compile here as the docs job is the one that pushes to the SBT cache # as it finishes fastest and we want to ensure that it includes test sources. cmd: "Test/compile docs/bundle" diff --git a/.github/workflows/build.scala_test.yml b/.github/workflows/build.scala_test.yml index 8ec6727935..855939556c 100644 --- a/.github/workflows/build.scala_test.yml +++ b/.github/workflows/build.scala_test.yml @@ -57,11 +57,6 @@ on: description: "Canton protocol version" type: string required: true - oss_only: - description: "Restrict upstream dependencies (e.g. Canton) to OSS versions (the equivalent of OSS_ONLY=1 in local checkouts)" - required: false - type: boolean - default: false postgres_image: description: "The image to use for PostgreSQL" required: false @@ -162,6 +157,3 @@ jobs: failure_notifications_slack_channel: ${{ secrets.FAILURE_NOTIFICATIONS_SLACK_CHANNEL }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} - artifactory_user: ${{ inputs.oss_only && '' || vars.ARTIFACTORY_USER }} - artifactory_password: ${{ inputs.oss_only && '' || secrets.ARTIFACTORY_PASSWORD }} diff --git a/.github/workflows/build.scala_test_for_compose.yml b/.github/workflows/build.scala_test_for_compose.yml index 45b45b90df..1c8d0d21ae 100644 --- a/.github/workflows/build.scala_test_for_compose.yml +++ b/.github/workflows/build.scala_test_for_compose.yml @@ -106,8 +106,6 @@ jobs: with: cache_version: 8 start_canton_options: ${{ inputs.start_canton_options }} - artifactory_user: ${{ vars.ARTIFACTORY_USER }} - artifactory_password: ${{ secrets.ARTIFACTORY_PASSWORD }} test_suite_name: ${{ inputs.test_name }} test_names: ${{ needs.split_tests.outputs.test_names }} runner_index: ${{ matrix.runner-index }} diff --git a/.github/workflows/build.scala_test_with_cometbft.yml b/.github/workflows/build.scala_test_with_cometbft.yml index b8f738c6a1..76a5512c1e 100644 --- a/.github/workflows/build.scala_test_with_cometbft.yml +++ b/.github/workflows/build.scala_test_with_cometbft.yml @@ -122,8 +122,6 @@ jobs: with: cache_version: 8 start_canton_options: -F -w - artifactory_user: ${{ vars.ARTIFACTORY_USER }} - artifactory_password: ${{ secrets.ARTIFACTORY_PASSWORD }} test_suite_name: ${{ inputs.test_name }} test_names: ${{ needs.split_tests.outputs.test_names }} runner_index: ${{ matrix.runner-index }} diff --git a/.github/workflows/build.static_tests.yml b/.github/workflows/build.static_tests.yml index 5690f886c8..62aef4bb8c 100644 --- a/.github/workflows/build.static_tests.yml +++ b/.github/workflows/build.static_tests.yml @@ -13,15 +13,9 @@ on: type: boolean required: false default: false - oss_only: - type: boolean - required: false - default: false jobs: check_canton_consistency: - # Skipped for external contributors (fork PRs) due to container registry auth constraints - if: ${{ !inputs.oss_only }} runs-on: ${{ inputs.self_hosted && 'self-hosted-k8s-x-small' || 'ubuntu-24.04' }} timeout-minutes: 15 container: diff --git a/.github/workflows/build.ts_cli_tests.yml b/.github/workflows/build.ts_cli_tests.yml index 6b0904cffd..c746a821ff 100644 --- a/.github/workflows/build.ts_cli_tests.yml +++ b/.github/workflows/build.ts_cli_tests.yml @@ -43,7 +43,6 @@ jobs: with: cache_version: 8 test_name: ts_cli - target: 'oss' - name: Run Token Standard CLI tests if: steps.skip.outputs.skip != 'true' diff --git a/.github/workflows/build.ui_tests.yml b/.github/workflows/build.ui_tests.yml index 33ebfda8f5..27742c3134 100644 --- a/.github/workflows/build.ui_tests.yml +++ b/.github/workflows/build.ui_tests.yml @@ -43,7 +43,6 @@ jobs: with: cache_version: 8 test_name: ui_tests - target: 'oss' - name: Run UI tests if: steps.skip.outputs.skip != 'true' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4676256eed..4c230aacda 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,11 +17,6 @@ on: type: string required: false default: "" - oss_only: - description: "Restrict upstream dependencies (e.g. Canton) to OSS versions (skips tests that do not support running in OSS)" - required: false - type: boolean - default: false workflow_dispatch: inputs: commit_sha: @@ -39,11 +34,6 @@ on: type: string required: false default: "35" - oss_only: - description: "Restrict upstream dependencies (e.g. Canton) to OSS versions (skips tests that do not support running in OSS)" - required: false - type: boolean - default: false permissions: id-token: write # Required for GCP Workload Identity for failure notifications @@ -93,7 +83,6 @@ jobs: with: commit_sha: ${{ inputs.commit_sha }} self_hosted: true - oss_only: ${{ inputs.oss_only }} deployment_test: uses: ./.github/workflows/build.deployment_test.yml @@ -118,7 +107,6 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} secrets: inherit scala_test_resource_intensive: @@ -132,7 +120,6 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} secrets: inherit scala_test_record_time_tolerance: @@ -146,7 +133,6 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} secrets: inherit scala_test_logical_sync_upgrade: @@ -160,7 +146,6 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} secrets: inherit scala_test_roll_forward_lsu: @@ -174,12 +159,12 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} secrets: inherit scala_test_with_cometbft: uses: ./.github/workflows/build.scala_test_with_cometbft.yml - if: ${{ ! inputs.oss_only }} + # skip for external contributors (fork PRs) as we pull the cometbft image from artifactory. + if: github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name with: runs_on: self-hosted-k8s-medium test_names_file: "test-cometbft-full-class-names.log" @@ -202,7 +187,6 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} secrets: inherit scala_test_wall_clock_time: @@ -219,7 +203,6 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} secrets: inherit scala_test_frontend_wall_clock_time: @@ -234,7 +217,6 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} secrets: inherit scala_test_frontend_simtime: @@ -249,12 +231,10 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} secrets: inherit scala_test_docker_compose: uses: ./.github/workflows/build.scala_test_for_compose.yml - if: ${{ ! inputs.oss_only }} with: runs_on: self-hosted-docker-large test_names_file: 'test-full-class-names-docker-based.log' @@ -268,7 +248,6 @@ jobs: scala_test_with_docker_no_canton: uses: ./.github/workflows/build.scala_test_for_compose.yml - if: ${{ ! inputs.oss_only }} with: runs_on: self-hosted-docker-large test_names_file: 'test-full-class-names-docker-no-canton.log' @@ -283,7 +262,6 @@ jobs: scala_test_with_docker_and_canton_simtime: uses: ./.github/workflows/build.scala_test_for_compose.yml - if: ${{ ! inputs.oss_only }} with: runs_on: self-hosted-docker-large test_names_file: 'test-full-class-names-sim-time-docker.log' @@ -312,22 +290,6 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: ${{ inputs.oss_only }} - secrets: inherit - - scala_test_canton_enterprise: - uses: ./.github/workflows/build.scala_test.yml - if: ${{ ! inputs.oss_only }} - with: - runs_on: self-hosted-k8s-large - test_names_file: 'test-full-class-names-canton-enterprise.log' - start_canton_options: -w - parallelism: 1 - test_name: canton-enterprise - with_gcp_creds: true - commit_sha: ${{ inputs.commit_sha }} - daml_base_version: ${{ inputs.daml_base_version }} - protocol_version: ${{ inputs.protocol_version }} secrets: inherit ui_tests: @@ -360,7 +322,6 @@ jobs: commit_sha: ${{ inputs.commit_sha }} daml_base_version: ${{ inputs.daml_base_version }} protocol_version: ${{ inputs.protocol_version }} - oss_only: true secrets: inherit final_result: @@ -383,7 +344,6 @@ jobs: - scala_test_with_docker_no_canton - scala_test_with_docker_and_canton_simtime - scala_test_app_upgrade - - scala_test_canton_enterprise - scala_test_without_canton - ui_tests - ts_cli_tests @@ -430,5 +390,4 @@ jobs: uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ env.JOBS }} - # Allow skipping the enterprise-only jobs - allowed-skips: scala_test_with_cometbft, scala_test_docker_compose, scala_test_local_net, scala_test_canton_enterprise + allowed-skips: scala_test_with_cometbft, scala_test_docker_compose, scala_test_local_net diff --git a/.github/workflows/bump_gha_runner_version.yml b/.github/workflows/bump_gha_runner_version.yml index abaf4fb34e..8556944a43 100644 --- a/.github/workflows/bump_gha_runner_version.yml +++ b/.github/workflows/bump_gha_runner_version.yml @@ -28,8 +28,6 @@ jobs: uses: ./.github/actions/nix/setup_nix with: cache_version: 8 - artifactory_user: dummy - artifactory_password: dummy target: default - name: Check for the latest version and create a PR to splice diff --git a/.github/workflows/canton_oss_test.yml b/.github/workflows/canton_oss_test.yml deleted file mode 100644 index ee5503e713..0000000000 --- a/.github/workflows/canton_oss_test.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Integration tests against canton oss -on: - schedule: - - cron: '0 4 * * *' - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -permissions: - id-token: write # Required for GCP Workload Identity for failure notifications - contents: read - pull-requests: read # Required for the static tests - issues: read # Required for the static tests - actions: write # To cancel itself if not opted in - -jobs: - build: - uses: ./.github/workflows/build.yml - with: - oss_only: true - secrets: inherit diff --git a/.github/workflows/performance_tests.yml b/.github/workflows/performance_tests.yml index 1e02b76c8b..12895fe0b8 100644 --- a/.github/workflows/performance_tests.yml +++ b/.github/workflows/performance_tests.yml @@ -35,8 +35,7 @@ jobs: uses: ./.github/actions/tests/common_test_setup with: cache_version: 8 - test_name: oss - target: 'oss' + test_name: ingestion_performance_tests # Authenticate to GCP for read access to GCS - name: Authenticate to GCP (mainnet-history-dumps) @@ -139,8 +138,7 @@ jobs: uses: ./.github/actions/tests/common_test_setup with: cache_version: 8 - test_name: oss - target: 'oss' + test_name: read_performance_tests # Authenticate to GCP for read access to GCS - name: Authenticate to GCP (mainnet-history-dumps) diff --git a/.github/workflows/pr_check_github_scripts.yml b/.github/workflows/pr_check_github_scripts.yml index 997641a4a2..491bbacd5c 100644 --- a/.github/workflows/pr_check_github_scripts.yml +++ b/.github/workflows/pr_check_github_scripts.yml @@ -16,11 +16,7 @@ jobs: uses: ./.github/actions/nix/setup_nix with: cache_version: 8 - artifactory_user: dummy - artifactory_password: dummy - target: oss - name: Check github scripts uses: ./.github/actions/nix/run_bash_command_in_nix with: cmd: bash gha-scripts/scripts/check-build.sh - \ No newline at end of file diff --git a/.github/workflows/pr_static_checks.yml b/.github/workflows/pr_static_checks.yml index ca5b57a4b1..2a53c7ee55 100644 --- a/.github/workflows/pr_static_checks.yml +++ b/.github/workflows/pr_static_checks.yml @@ -21,4 +21,3 @@ jobs: self_hosted: false commit_sha: ${{ github.event.pull_request.head.sha }} skip_todo_check: true # runs from forks with on: pull_request run in context of the fork, so issue references will be broken - oss_only: true diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 504b28161b..a07d94875a 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -36,49 +36,6 @@ direnv: error /home/moritz/daml-projects/canton-amulet/.envrc is blocked. Run `direnv allow` to approve its content ``` 1. Run `direnv allow`. You should see a bunch of output including `direnv: using nix`. -1. (Optional) Configure artifactory credentials - A few tests rely on Enterprise canton features. To be able to run those locally, you will require access to - Digital Asset's enterprise artifactory. If you need to run those, please contact the Maintainers of this repo, - per MAINTAINERS.md. - Once you have access to artifactory, you can generate an artifactory Identity Token [here](https://digitalasset.jfrog.io/ui/admin/artifactory/user_profile). - Your username is shown at the top of the page (under "User profile: XX"). - 1. Add the following to `/etc/nix/netrc` (you might need to create that directory as root): - ``` - machine digitalasset.jfrog.io - login yourartifactoryusername - password yourartifactoryidentitytoken - ``` - 1. In addition, add your artifactory user and password to `.envrc.private` file like so: - ``` - export ARTIFACTORY_USER="yourartifactoryusername" - export ARTIFACTORY_PASSWORD="yourartifactoryidentitytoken" - ``` - Once added, reload direnv by typing `direnv reload` in your terminal. -1. (Optional) Configure artifactory credentials - troubleshooting - If you defined Artifactory access, and are getting an authorization exception, like the following: - ``` - direnv: using nix - error: unable to download 'https://digitalasset.jfrog.io/artifactory/canton-enterprise/canton-enterprise-2.7.0-snapshot.20230614.10547.0.v03419b62.tar.gz': HTTP error 401 ('Unauthorized') - ``` - 1. Check that your access token is valid by running the following sample command: - ``` - curl -vvv -L -u: "https://digitalasset.jfrog.io/artifactory/canton-enterprise/canton-enterprise-2.7.0-snapshot.20230614.10547.0.v03419b62.tar.gz" -o canton-enterprise-2.7.0-snapshot.20230614.10547.0.v03419b62.tar.gz - ``` - If the download fails, check that your access token matches what is set in [Artifactory](https://digitalasset.jfrog.io/ui/admin/artifactory/user_profile). - Also, check you have visability via the UI [here](https://digitalasset.jfrog.io/ui/repos/tree/General/canton-enterprise). - If you don't have visibility via the UI then check with the repo Maintainers. - 1. If the artifact successfully downloaded, check the access rights of the file `/etc/nix/netrc`. - If the access rights are more restrictive than `-rw-rw-r--`, update them: - ``` - chmod 664 /etc/nix/netrc - ``` - Note - `sudo` may be required to run the above command. - 1. Switch to the Splice repo directory. - 1. If the authorization exception isn't resolved, investigate further with additional logging - by running the following command at the root of the repo: - ``` - nix develop --debug --verbose path:nix - **Important:** start your IDE and other development tools from a console that has this `direnv` loaded; and thus has the proper version of all the project dependencies on its `PATH`. @@ -144,7 +101,7 @@ and upload them to the dev GHCR registry. In order to do so, you will need to fo There are a number of environment variables managed with `direnv` that are used to contain private information. This includes credentials to -a range of external services, including Auth0 and Artifactory. To keep +a range of external services, such as Auth0. To keep this private information private, they are stored in a specific file in the root of the project repository: `.envrc.private`. This file is listed in `.gitignore` to prevent accidental commit to the repository. @@ -155,12 +112,6 @@ be present in `.envrc.private` are in fact present. Missing definitions will cause a warning to be reported when `.envrc` is executed. -A list of expected environment definitions is as follows: - -* Artifactory credentials - * `ARTIFACTORY_USER`: your username at digitalasset.jfrog.io (can be seen in the top-right corner after logging in with Google SSO) - * `ARTIFACTORY_PASSWORD`: Your identity token at digitalasset.jfrog.io (can be obtained by generating an identity token in your user profile) - If you are a Splice Contributor (see CONTRIBUTOR.md) and wish to push Docker images and deploy to test clusters from your local machine, you will need also the following: diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 3f0d560cbe..4fee1dfda5 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -13,8 +13,7 @@ ## Bumping Canton -1. Choose the Canton version you wish to upgrade to. The currently published versions on - Artifactory can be found [here](https://digitalasset.jfrog.io/ui/repos/tree/General/canton-enterprise). +1. Choose the Canton version you wish to upgrade to. 2. Update the hashes in `nix/canton-sources.json` by running: `build-tools/bump-canton.sh ` 3. In case you have also made configuration changes to Canton in `simple-topology-canton.conf`, remember to also make the corresponding changes for our cluster deployments. It is recommended to test any configuration diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 65307ac462..092381848d 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -5,7 +5,6 @@ This guide outlines steps to resolve common issues that a contributor might enco ## Prerequisites - Install **`nix`**, **`direnv`**, and **`sbt`** as per [DEVELOPMENT.md](./DEVELOPMENT.md) -- Ensure access to JFrog Artifactory (`splice-developers` team). Contact your team lead if access is denied. ## Steps diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ParticipantKmsIdentitiesEnterpriseIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ParticipantKmsIdentitiesIntegrationTest.scala similarity index 99% rename from apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ParticipantKmsIdentitiesEnterpriseIntegrationTest.scala rename to apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ParticipantKmsIdentitiesIntegrationTest.scala index fd73c1fbcc..fdb541a237 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ParticipantKmsIdentitiesEnterpriseIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ParticipantKmsIdentitiesIntegrationTest.scala @@ -16,7 +16,7 @@ import org.lfdecentralizedtrust.splice.util.StandaloneCanton import java.nio.file.{Path, Paths} -class ParticipantKmsIdentitiesEnterpriseIntegrationTest +class ParticipantKmsIdentitiesIntegrationTest extends IntegrationTestWithIsolatedEnvironment with StandaloneCanton { diff --git a/build-tools/artifactory_to_gcs.py b/build-tools/artifactory_to_gcs.py deleted file mode 100755 index 086baff5ba..0000000000 --- a/build-tools/artifactory_to_gcs.py +++ /dev/null @@ -1,785 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -""" -Script to copy non-snapshot versions of Docker images or Helm charts -from JFrog Artifactory to Google Cloud Storage. - -Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -SPDX-License-Identifier: Apache-2.0 -""" - -import argparse -import json -import os -import re -import subprocess -import sys -import tempfile -import time -import urllib.parse -from dataclasses import dataclass -from typing import Dict, List, Optional, Set, Tuple, Union -import logging -import requests -from google.cloud import storage -from google.api_core import exceptions as gcp_exceptions - -# Configure logging -logging.basicConfig( - level=logging.INFO, - format='%(asctime)s - %(levelname)s - %(message)s' -) -logger = logging.getLogger(__name__) - -# Define version regex pattern (matches semantic versioning) -VERSION_PATTERN = re.compile(r'^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$') -# Regex to match git hash versions (git- followed by a hash) -GIT_HASH_PATTERN = re.compile(r'^git-[0-9a-f]{5,40}$') - -@dataclass -class ArtifactInfo: - """Information about an artifact (Docker image or Helm chart)""" - name: str - version: str - path: str - repository: str - type: str # 'docker' or 'helm' - is_snapshot: bool = False - - def __str__(self) -> str: - return f"{self.name}:{self.version} ({self.type})" - - -def parse_args(): - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Copy non-snapshot Docker images or Helm charts from JFrog Artifactory to Google Cloud Storage" - ) - parser.add_argument( - "--artifactory-url", required=True, - help="JFrog Artifactory URL (e.g., https://artifactory.example.com)" - ) - parser.add_argument( - "--repository", required=True, - help="Artifactory repository name containing Docker images or Helm charts" - ) - parser.add_argument( - "--bucket", required=True, - help="Google Cloud Storage bucket name" - ) - parser.add_argument( - "--type", choices=["docker", "helm"], required=True, - help="Artifact type (docker or helm)" - ) - parser.add_argument( - "--api-key", - help="JFrog Artifactory API key (can also be set via ARTIFACTORY_API_KEY environment variable)" - ) - parser.add_argument( - "--username", - help="JFrog Artifactory username (can also be set via ARTIFACTORY_USERNAME environment variable)" - ) - parser.add_argument( - "--dry-run", action="store_true", - help="Show what would be copied without actually copying" - ) - parser.add_argument( - "--force", action="store_true", - help="Skip confirmation prompts" - ) - parser.add_argument( - "--allow-overwrite", action="store_true", - help="Allow overwriting existing files in the bucket" - ) - parser.add_argument( - "--prefix", default="", - help="Prefix to add to the destination path in the bucket" - ) - parser.add_argument( - "--max-retries", type=int, default=5, - help="Maximum number of retries for failed operations" - ) - parser.add_argument( - "--verbose", action="store_true", - help="Enable verbose logging" - ) - parser.add_argument( - "--include-path-pattern", - help="Regular expression to filter artifacts by path" - ) - parser.add_argument( - "--exclude-path-pattern", - help="Regular expression to exclude artifacts by path" - ) - parser.add_argument( - "--policy-file", default="skopeo_policy.json", - help="Path to the skopeo policy JSON file" - ) - parser.add_argument( - "--save-artifacts-file", - help="Save fetched artifacts to a file for later reuse" - ) - parser.add_argument( - "--load-artifacts-file", - help="Load artifacts from a file instead of fetching from Artifactory" - ) - parser.add_argument( - "--docker-registry-url", required=True, - help="External URL of the Docker registry (e.g., myregistry.jfrog.io), required for skopeo copy command" - ) - return parser.parse_args() - - -def check_credentials(args): - """Check and retrieve credentials for Artifactory and Google Cloud.""" - # Check Artifactory credentials - artifactory_api_key = args.api_key or os.environ.get('ARTIFACTORY_API_KEY') - artifactory_username = args.username or os.environ.get('ARTIFACTORY_USERNAME') - - if not artifactory_api_key and not artifactory_username: - logger.error("Artifactory credentials not provided. Set --api-key/--username or ARTIFACTORY_API_KEY/ARTIFACTORY_USERNAME environment variables.") - sys.exit(1) - - # Verify Google Cloud credentials are available - try: - # Attempt to initialize a client to check if credentials are available - storage.Client() - except Exception as e: - logger.error(f"Google Cloud credentials not found or invalid: {e}") - logger.error("Make sure you have set up Google Cloud credentials (run 'gcloud auth application-default login')") - sys.exit(1) - - return { - "artifactory_api_key": artifactory_api_key, - "artifactory_username": artifactory_username - } - - -def fetch_artifacts(artifactory_url: str, repository: str, artifact_type: str, credentials: Dict, - include_pattern: Optional[str] = None, exclude_pattern: Optional[str] = None) -> List[ArtifactInfo]: - """ - Fetch non-snapshot artifacts from Artifactory. - - Args: - artifactory_url: Base URL of the Artifactory instance - repository: Repository name in Artifactory - artifact_type: 'docker' or 'helm' - credentials: Dictionary containing authentication credentials - include_pattern: Optional regex pattern to include artifacts - exclude_pattern: Optional regex pattern to exclude artifacts - - Returns: - List of ArtifactInfo objects representing non-snapshot artifacts - """ - - logger.info(f"Fetching {artifact_type} artifacts from {artifactory_url}/artifactory/{repository}") - - api_url = f"{artifactory_url}/artifactory/api/storage/{repository}" - headers = {} - - # Set up authentication - if credentials.get("artifactory_api_key"): - headers["X-JFrog-Art-Api"] = credentials["artifactory_api_key"] - elif credentials.get("artifactory_username"): - auth = requests.auth.HTTPBasicAuth( - credentials["artifactory_username"], - os.environ.get("ARTIFACTORY_PASSWORD", "") - ) - else: - auth = None - - # Compile regex patterns if provided - include_regex = re.compile(include_pattern) if include_pattern else None - exclude_regex = re.compile(exclude_pattern) if exclude_pattern else None - - # For recursive traversal of the repository - artifacts = [] - - def traverse_directory(path: str, current_depth: int = 0, current_name: str = None): - """ - Traverse directory recursively with optimized strategy: - - For Docker: Only go into version-named directories, stop once manifest.json is found - - For Helm: Regular traversal looking for .tgz files - - Args: - path: Current path in Artifactory repository - current_depth: Track depth in traversal for Docker optimization - current_name: Keep track of the container/chart name while traversing - """ - url = f"{api_url}{path}" - try: - if credentials.get("artifactory_api_key"): - response = requests.get(url, headers=headers) - else: - response = requests.get(url, auth=auth) - - response.raise_for_status() - data = response.json() - - for child in data.get("children", []): - child_path = f"{path}/{child['uri']}" if path else child["uri"] - - if logger.level <= logging.DEBUG: - logger.debug(f"Examining path: {child_path}") - - # Skip if path doesn't match include pattern - if include_regex and not include_regex.search(child_path): - continue - - # Skip if path matches exclude pattern - if exclude_regex and exclude_regex.search(child_path): - continue - - # For Docker repositories, use optimized traversal strategy - if artifact_type == "docker": - if child["folder"]: - # Skip any sha256 directories - if "/sha256:" in child_path or "sha256:" in child_path or "sha256__" in child_path or GIT_HASH_PATTERN.match(child_path.split("/")[-1]): - logger.debug(f"Skipping directory: {child_path}") - continue - - # Get the folder name (the last part of the path) - folder_name = child['uri'].strip("/") - - # Check if this folder looks like a semantic version - is_version = VERSION_PATTERN.match(folder_name) is not None - is_version = is_version and GIT_HASH_PATTERN.match(folder_name) is None - - # If it's a version directory, traverse into it but remember we're in a version dir - # Otherwise, continue regular traversal without changing depth - next_depth = current_depth + 1 if is_version else current_depth - - # If we're at depth 0, this is potentially a container name folder - next_name = folder_name if current_depth == 0 else current_name - - traverse_directory(child_path, next_depth, next_name) - # For files, only consider manifest files in version directories (depth = 1) - elif current_depth == 1 and ('manifest.json' in child['uri'] or child['uri'] == 'manifest.json'): - # We found a manifest in a version directory - this is what we're looking for - logger.debug(f"Found manifest in version directory: {child_path}") - - # Extract version from path (should be the directory containing manifest.json) - # Handle paths that might have double slashes by normalizing the path first - normalized_path = child_path.replace("//", "/").strip("/") - logger.debug(f"Found normalized path: {normalized_path}") - path_parts = [part for part in normalized_path.split("/") if part] - if len(path_parts) < 2: - continue - logger.debug(f"Found normalized path: {path_parts}") - version = path_parts[-2] # Version is the directory containing manifest.json - - name = "/".join(path_parts[:-2]) - if not name: - logger.debug(f"Could not determine artifact name from path: {normalized_path}") - continue - - # Create artifact if it's a valid semantic version - if VERSION_PATTERN.match(version): - is_snapshot = "SNAPSHOT" in version or "-snapshot" in version.lower() - if not is_snapshot: - logger.debug(f"Found Docker artifact - Name: {name}, Version: {version}") - artifact = ArtifactInfo( - name=name, - version=version, - path=child_path, - repository=repository, - type=artifact_type, - is_snapshot=False - ) - logger.debug(f"Adding artifact: {artifact}") - artifacts.append(artifact) - - # For Helm repositories, use regular traversal looking for .tgz files - else: - if child["folder"]: - traverse_directory(child_path, current_depth + 1, current_name) - elif is_version_artifact(child_path, artifact_type): - logger.debug(f"Found potential helm artifact: {child_path}") - artifact = get_artifact_details(child_path, artifactory_url, repository, - artifact_type, credentials) - if artifact and not artifact.is_snapshot: - logger.debug(f"Adding helm artifact: {artifact}") - artifacts.append(artifact) - - except requests.RequestException as e: - logger.error(f"Error fetching artifacts from {url}: {e}") - - # Start traversal from the repository root - traverse_directory("") - - logger.info(f"Found {len(artifacts)} non-snapshot {artifact_type} artifacts") - return artifacts - - -def is_version_artifact(path: str, artifact_type: str) -> bool: - """ - Check if the path appears to point to a versioned artifact. - - This is a preliminary check before fetching detailed metadata. - """ - if artifact_type == "docker": - # Docker repositories typically have a manifest.json file for each tag - return path.endswith("manifest.json") - elif artifact_type == "helm": - # Helm charts typically have a .tgz extension - return path.endswith(".tgz") - - return False - - -def get_artifact_details(path: str, artifactory_url: str, repository: str, - artifact_type: str, credentials: Dict) -> Optional[ArtifactInfo]: - """ - Get detailed information about an artifact. - - Args: - path: Path to the artifact within the repository - artifactory_url: Base URL of the Artifactory instance - repository: Repository name in Artifactory - artifact_type: 'docker' or 'helm' - credentials: Dictionary containing authentication credentials - - Returns: - ArtifactInfo object if the artifact is a valid release version, None otherwise - """ - # Extract version from the path - if artifact_type == "docker": - # For Docker, extract from path like "myimage/1.2.3/manifest.json" - path_parts = path.strip("/").split("/") - if len(path_parts) < 2: - return None - - # The version should be the part before the manifest.json - potential_version = path_parts[-2] - # The name is everything except the version and manifest.json - name = "/".join(path_parts[:-2]) - - elif artifact_type == "helm": - # For Helm, extract from path like "charts/mychart-1.2.3.tgz" - if not path.endswith(".tgz"): - return None - - # Extract the filename without extension - filename = os.path.basename(path)[:-4] # Remove .tgz - - # Try to split name and version (usually separated by a hyphen) - name_version = filename.rsplit("-", 1) - if len(name_version) != 2: - return None - - name = name_version[0] - potential_version = name_version[1] - else: - return None - - # Verify this is a semantic version and not a snapshot - if not VERSION_PATTERN.match(potential_version): - return None - - is_snapshot = "SNAPSHOT" in potential_version or "-snapshot" in potential_version.lower() - - return ArtifactInfo( - name=name, - version=potential_version, - path=path, - repository=repository, - type=artifact_type, - is_snapshot=is_snapshot - ) - - -def check_destination_exists(bucket_name: str, prefix: str, repository: str) -> bool: - """ - Check if the destination folder already exists in the bucket. - - Args: - bucket_name: Name of the GCS bucket - prefix: Optional prefix to add to the destination path - repository: Repository name which will be part of the destination path - - Returns: - True if the destination folder exists and contains files, False otherwise - """ - client = storage.Client() - - try: - bucket = client.get_bucket(bucket_name) - except gcp_exceptions.NotFound: - logger.warning(f"Bucket {bucket_name} not found. Will be created if not in dry-run mode.") - return False - - # Construct the destination prefix - destination = f"{prefix}/{repository}".strip("/") - if not destination: - # If we're copying to the root of the bucket, we need to check if there are any files - blobs = list(bucket.list_blobs(max_results=1)) - return len(blobs) > 0 - - # Check if there are any objects with this prefix - blobs = list(bucket.list_blobs(prefix=destination, max_results=1)) - return len(blobs) > 0 - - -def download_artifact(artifact: ArtifactInfo, artifactory_url: str, credentials: Dict, policy_file: str, docker_registry_url: str) -> str: - """ - Download an artifact from Artifactory to a temporary file. - - Args: - artifact: ArtifactInfo object for the artifact to download - artifactory_url: Base URL of the Artifactory instance - credentials: Dictionary containing authentication credentials - policy_file: Path to the skopeo policy JSON file - docker_registry_url: External Docker registry URL (required for Docker artifacts) - - Returns: - Path to the downloaded temporary file - - Raises: - Exception: If download fails - """ - # For docker, we need to construct the download URL differently - if artifact.type == "docker": - # We need to download the Docker image using skopeo - # First create a temp directory - temp_dir = tempfile.mkdtemp() - # Construct the Docker source URL - docker_url = f"{docker_registry_url}/{artifact.name}:{artifact.version}" - - # Prepare credentials for skopeo - creds_args = [] - if credentials.get("artifactory_username"): - creds_args = ["--src-creds", f"{credentials['artifactory_username']}:{os.environ.get('ARTIFACTORY_PASSWORD', '')}"] - - # Run skopeo to download the image - cmd = [ - "skopeo", "copy", "--policy" , policy_file, "--all", - f"docker://{docker_url}", - f"dir:{temp_dir}", - *creds_args - ] - - logger.debug(f"Running command: {' '.join(cmd)}") - result = subprocess.run(cmd, capture_output=True, text=True) - - if result.returncode != 0: - logger.error(f"Failed to download Docker image: {result.stderr}") - raise Exception(f"Failed to download {artifact}: {result.stderr}") - - return temp_dir - - else: # Helm chart - # Construct the download URL - download_url = f"{artifactory_url}/artifactory/{artifact.repository}/{artifact.path}" - - # Prepare authentication - if credentials.get("artifactory_api_key"): - headers = {"X-JFrog-Art-Api": credentials["artifactory_api_key"]} - auth = None - else: - headers = {} - auth = requests.auth.HTTPBasicAuth( - credentials["artifactory_username"], - os.environ.get("ARTIFACTORY_PASSWORD", "") - ) - - # Download the file - response = requests.get(download_url, headers=headers, auth=auth, stream=True) - response.raise_for_status() - - # Create a temporary file and write the content - fd, temp_path = tempfile.mkstemp() - with os.fdopen(fd, 'wb') as temp_file: - for chunk in response.iter_content(chunk_size=8192): - temp_file.write(chunk) - - return temp_path - - -def upload_to_gcs(local_path: str, artifact: ArtifactInfo, bucket_name: str, - prefix: str, allow_overwrite: bool) -> bool: - """ - Upload an artifact to Google Cloud Storage. - - Args: - local_path: Path to the local file or directory to upload - artifact: ArtifactInfo object for the artifact being uploaded - bucket_name: Name of the GCS bucket - prefix: Optional prefix to add to the destination path - allow_overwrite: Whether to allow overwriting existing files - - Returns: - True if upload was successful, False otherwise - """ - client = storage.Client() - - try: - bucket = client.get_bucket(bucket_name) - except gcp_exceptions.NotFound: - logger.warning(f"Bucket {bucket_name} not found, creating it...") - bucket = client.create_bucket(bucket_name) - - # Construct the destination path - destination_base = f"{prefix}/{artifact.repository}/{artifact.name}/{artifact.version}".strip("/") - - if artifact.type == "docker": - # For Docker, we need to upload all files in the directory - success = True - for root, _, files in os.walk(local_path): - for file in files: - local_file_path = os.path.join(root, file) - rel_path = os.path.relpath(local_file_path, local_path) - destination_path = f"{destination_base}/{rel_path}" - - blob = bucket.blob(destination_path) - - # Check if the blob already exists - if blob.exists() and not allow_overwrite: - logger.warning(f"File {destination_path} already exists in bucket and overwrite not allowed") - success = False - continue - - # Upload the file - blob.upload_from_filename(local_file_path) - logger.info(f"Uploaded {rel_path} to gs://{bucket_name}/{destination_path}") - - return success - else: # Helm chart - # For Helm charts, just upload the single file - chart_name = os.path.basename(artifact.path) - destination_path = f"{destination_base}/{chart_name}" - - blob = bucket.blob(destination_path) - - # Check if the blob already exists - if blob.exists() and not allow_overwrite: - logger.warning(f"File {destination_path} already exists in bucket and overwrite not allowed") - return False - - # Upload the file - blob.upload_from_filename(local_path) - logger.info(f"Uploaded {chart_name} to gs://{bucket_name}/{destination_path}") - - return True - - -def process_artifact(artifact: ArtifactInfo, artifactory_url: str, docker_registry_url: str, bucket_name: str, - prefix: str, credentials: Dict, policy_file: str, allow_overwrite: bool, - dry_run: bool, max_retries: int) -> bool: - """ - Process a single artifact - download and upload to GCS. - - Args: - artifact: ArtifactInfo object for the artifact to process - artifactory_url: Base URL of the Artifactory instance - bucket_name: Name of the GCS bucket - prefix: Optional prefix to add to the destination path - credentials: Dictionary containing authentication credentials - policy_file: Path to the skopeo policy JSON file - allow_overwrite: Whether to allow overwriting existing files - dry_run: If True, don't actually download or upload - max_retries: Maximum number of retry attempts - - Returns: - True if successful, False otherwise - """ - if dry_run: - logger.info(f"Would copy {artifact} to gs://{bucket_name}/{prefix}/{artifact.repository}/{artifact.name}/{artifact.version}") - return True - - temp_path = None - success = False - - for attempt in range(max_retries): - try: - # Download the artifact - logger.info(f"Downloading {artifact}") - temp_path = download_artifact(artifact, artifactory_url, credentials, policy_file, docker_registry_url) - - # Upload to GCS - logger.info(f"Uploading {artifact} to gs://{bucket_name}") - success = upload_to_gcs(temp_path, artifact, bucket_name, prefix, allow_overwrite) - - if success: - break - - except Exception as e: - logger.warning(f"Attempt {attempt + 1}/{max_retries} failed: {e}") - if attempt == max_retries - 1: - logger.error(f"Failed to process {artifact} after {max_retries} attempts") - return False - - # Wait before retrying - time.sleep(2 ** attempt) # Exponential backoff - - finally: - # Clean up temporary files - if temp_path: - if artifact.type == "docker": - # Remove the directory - subprocess.run(["rm", "-rf", temp_path], check=False) - else: - # Remove the file - os.remove(temp_path) - - return success -def save_artifacts_to_file(artifacts: List[ArtifactInfo], filename: str): - """Save artifacts to a JSON file for later reuse.""" - artifacts_data = [ - { - "name": a.name, - "version": a.version, - "path": a.path, - "repository": a.repository, - "type": a.type, - "is_snapshot": a.is_snapshot - } - for a in artifacts - ] - - with open(filename, 'w') as f: - json.dump(artifacts_data, f, indent=2) - - logger.info(f"Saved {len(artifacts)} artifacts to {filename}") - - -def load_artifacts_from_file(filename: str) -> List[ArtifactInfo]: - """Load artifacts from a previously saved JSON file.""" - with open(filename, 'r') as f: - artifacts_data = json.load(f) - - artifacts = [ - ArtifactInfo( - name=item["name"], - version=item["version"], - path=item["path"], - repository=item["repository"], - type=item["type"], - is_snapshot=item.get("is_snapshot", False) - ) - for item in artifacts_data - ] - - logger.info(f"Loaded {len(artifacts)} artifacts from {filename}") - return artifacts - - -def main(): - args = parse_args() - - if args.verbose: - logger.setLevel(logging.DEBUG) - - logger.info("Starting artifact copy from JFrog Artifactory to Google Cloud Storage") - - # Check credentials - credentials = check_credentials(args) - artifacts = [] - if args.load_artifacts_file: - artifacts = load_artifacts_from_file(args.load_artifacts_file) - else: - # Fetch artifacts from Artifactory - artifacts = fetch_artifacts( - args.artifactory_url, - args.repository, - args.type, - credentials, - args.include_path_pattern, - args.exclude_path_pattern - ) - if args.save_artifacts_file: - save_artifacts_to_file(artifacts, args.save_artifacts_file) - - if not artifacts: - logger.warning(f"No non-snapshot {args.type} artifacts found in {args.repository}") - return - - # Check if destination exists - destination_exists = check_destination_exists(args.bucket, args.prefix, args.repository) - - # In dry-run mode, just print what would be copied - if args.dry_run: - print("\nThe following artifacts would be copied:") - for artifact in artifacts: - print(f" {artifact} -> gs://{args.bucket}/{args.prefix}/{args.repository}/{artifact.name}/{artifact.version}") - print(f"\nTotal: {len(artifacts)} artifacts") - - if destination_exists: - print("\nWARNING: Destination folder already exists and may contain files that would be overwritten.") - - return - - # Ask for confirmation - if not args.force: - if destination_exists and not args.allow_overwrite: - print("\nWARNING: Destination folder already exists and may contain files.") - print("Use --allow-overwrite to allow overwriting existing files.") - print("Use --force to skip this confirmation.") - response = input("\nDo you want to continue without overwriting? (yes/no): ").lower() - else: - print(f"\nAbout to copy {len(artifacts)} artifacts from {args.artifactory_url}/artifactory/{args.repository}") - print(f"to gs://{args.bucket}/{args.prefix}/{args.repository}/") - - if destination_exists and args.allow_overwrite: - print("\nWARNING: Destination folder exists and files may be overwritten.") - - print("\nArtifacts to be copied:") - for i, artifact in enumerate(artifacts[:10]): # Show first 10 - print(f" {artifact}") - - if len(artifacts) > 10: - print(f" ... and {len(artifacts) - 10} more") - - response = input("\nDo you want to continue? (yes/no): ").lower() - - if response != "yes": - print("Operation cancelled.") - return - - # Process all artifacts - success_count = 0 - failed_artifacts = [] - - for i, artifact in enumerate(artifacts, 1): - logger.info(f"Processing artifact {i}/{len(artifacts)}: {artifact}") - - success = process_artifact( - artifact, - args.artifactory_url, - args.docker_registry_url, - args.bucket, - args.prefix, - credentials, - args.policy_file, - args.allow_overwrite, - False, # Not dry-run - args.max_retries - ) - - if success: - success_count += 1 - else: - failed_artifacts.append(artifact) - - # Print summary - print("\nCopy operation completed:") - print(f" Successfully copied: {success_count}/{len(artifacts)} artifacts") - - if failed_artifacts: - print(f" Failed to copy: {len(failed_artifacts)} artifacts") - print("\nFailed artifacts:") - for artifact in failed_artifacts: - print(f" {artifact}") - - if success_count == len(artifacts): - print("\nAll artifacts copied successfully!") - else: - print("\nNot all artifacts were copied successfully. See log for details.") - sys.exit(1) - - -if __name__ == "__main__": - main() - diff --git a/build-tools/copy_release_helm_charts_to_ghcr.sh b/build-tools/copy_release_helm_charts_to_ghcr.sh index db84c501dd..1012bd2521 100755 --- a/build-tools/copy_release_helm_charts_to_ghcr.sh +++ b/build-tools/copy_release_helm_charts_to_ghcr.sh @@ -3,7 +3,7 @@ # Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# Copies release helm charts for app charts defined in app_charts_file from artifactory to ghcr.io +# Copies release helm charts for app charts defined in app_charts_file from dev to release in ghcr set -eou pipefail VERSION="" diff --git a/build-tools/copy_release_images_to_ghcr.sh b/build-tools/copy_release_images_to_ghcr.sh index a41574a64e..867315d822 100755 --- a/build-tools/copy_release_images_to_ghcr.sh +++ b/build-tools/copy_release_images_to_ghcr.sh @@ -3,7 +3,7 @@ # Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -# Copies release docker images from artifactory to ghcr.io +# Copies release docker images from dev to release to ghcr.io # skopeo is used to copy multi-arch images correctly # Note: skopeo in nix does not work, complains about policy.json, version from brew install (skopeo version 1.17.0) works fine. # There could be a fix here, though it is a super old link ¯\_(ツ)_/¯: https://github.com/NixOS/nixpkgs/commit/365d07cea0446cbdc3d2c89502ce62c1f283989b @@ -85,8 +85,6 @@ for VERSION in $VERSIONS; do TARGET_IMAGE="$DEST_REGISTRY/$IMAGE_NAME:$TAG" for i in {1..10}; do - # Some images have been copied before from Artifactory, which is not used anymore. - # Artifactory has unknown/unknown attestation manifests, which show up as unknown/unknown os/architecture manifests. There is nothing inherently wrong with this. # skopeo on nix does not bundle the policy.json file, so we need to provide it. if skopeo copy --policy "${SPLICE_ROOT}"/build-tools/skopeo_policy.json --all docker://"$SOURCE_IMAGE" docker://"$TARGET_IMAGE"; then echo "Successfully copied $SOURCE_IMAGE to $TARGET_IMAGE" diff --git a/build.sbt b/build.sbt index bc7e72cc19..cfb3f09da6 100644 --- a/build.sbt +++ b/build.sbt @@ -2615,11 +2615,6 @@ updateTestConfigForParallelRuns := { "test-cometbft-full-class-names.log", (t: String) => !isTimeBasedTest(t) && !isFrontEndTest(t) && isCometBftTest(t), ), - ( - "tests requiring Canton Enterprise", - "test-full-class-names-canton-enterprise.log", - (t: String) => isEnterpriseIntegrationTest(t), - ), ( "tests to check logical sync roll-forward upgrade", "test-full-class-names-roll-forward-lsu.log", diff --git a/cluster/pulumi/sv-runbook/src/installNode.ts b/cluster/pulumi/sv-runbook/src/installNode.ts index 4540e0c7aa..7968944586 100644 --- a/cluster/pulumi/sv-runbook/src/installNode.ts +++ b/cluster/pulumi/sv-runbook/src/installNode.ts @@ -102,7 +102,7 @@ export async function installNode( console.error( activeVersion.type === 'local' ? 'Using locally built charts by default' - : `Using charts from the artifactory by default, version ${activeVersion.version}` + : `Using charts from the ghcr by default, version ${activeVersion.version}` ); console.error(`CLUSTER_BASENAME: ${CLUSTER_BASENAME}`); console.error(`Installing SV node in namespace: ${svNamespaceStr}`); diff --git a/cluster/pulumi/validator-runbook/src/installNode.ts b/cluster/pulumi/validator-runbook/src/installNode.ts index ad83a65b39..c8e075f36a 100644 --- a/cluster/pulumi/validator-runbook/src/installNode.ts +++ b/cluster/pulumi/validator-runbook/src/installNode.ts @@ -69,7 +69,7 @@ export async function installNode(auth0Client: Auth0Client): Promise { console.error( validatorVersion.type === 'local' ? 'Using locally built charts by default' - : `Using charts from the artifactory by default, version ${validatorVersion.version}` + : `Using charts from the ghcr by default, version ${validatorVersion.version}` ); const xns = exactNamespace(validatorConfig.namespace, true); diff --git a/nix/cometbft-driver.nix b/nix/cometbft-driver.nix index 6ad4dfeec8..19fb5587ec 100644 --- a/nix/cometbft-driver.nix +++ b/nix/cometbft-driver.nix @@ -6,7 +6,7 @@ stdenv.mkDerivation rec { name = "cometbft-driver"; version = sources.version; src = builtins.fetchurl { - url = "https://digitalasset.jfrog.io/artifactory/canton-drivers/com/digitalasset/canton/drivers/canton-drivers/${sources.version}/canton-drivers-${sources.version}.tar.gz"; + url = "https://storage.googleapis.com/da-images-public/canton-drivers/canton-drivers-${version}.tar.gz"; sha256 = sources.sha256; }; dontUnpack = true; diff --git a/nix/flake.nix b/nix/flake.nix index e9c18e68dc..5d3b04fd92 100644 --- a/nix/flake.nix +++ b/nix/flake.nix @@ -9,37 +9,24 @@ outputs = { self, nixpkgs, flake-utils }: flake-utils.lib.eachDefaultSystem (system: - let enterprise_pkgs = import nixpkgs { inherit system; overlays = import ./overlays.nix { use_enterprise = true; }; }; - oss_pkgs = import nixpkgs { inherit system; overlays = import ./overlays.nix { use_enterprise = false; }; }; - enterprise_x86Pkgs = + let pkgs = import nixpkgs { inherit system; overlays = import ./overlays.nix; }; + x86Pkgs = if system == "aarch64-darwin" - then import nixpkgs { system = "x86_64-darwin"; overlays = import ./overlays.nix { use_enterprise = true; }; } - else enterprise_pkgs; - oss_x86Pkgs = - if system == "aarch64-darwin" - then import nixpkgs { system = "x86_64-darwin"; overlays = import ./overlays.nix { use_enterprise = false; }; } - else oss_pkgs; + then import nixpkgs { system = "x86_64-darwin"; overlays = import ./overlays.nix; } + else pkgs; in { packages = { # Forwarded so we can get the path from sbt. - reredirects = oss_pkgs.python3.pkgs.sphinx-reredirects; + reredirects = pkgs.python3.pkgs.sphinx-reredirects; }; - # For now, the default is enterprise. Use `nix develop path:nix#oss` to use the OSS version. devShells.default = import ./shell.nix { - x86Pkgs = enterprise_x86Pkgs; - pkgs = enterprise_pkgs; - variant = "enterprise"; - }; - devShells.oss = import ./shell.nix { - x86Pkgs = oss_x86Pkgs; - pkgs = oss_pkgs; - variant = "oss"; + inherit pkgs x86Pkgs; + variant = "full"; }; devShells.static_tests = import ./shell.nix { - x86Pkgs = oss_x86Pkgs; - pkgs = oss_pkgs; + inherit pkgs x86Pkgs; variant = "static_tests"; }; } diff --git a/nix/overlays.nix b/nix/overlays.nix index 4edd72d58a..45e4752416 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -1,4 +1,3 @@ -{ use_enterprise }: [(self: super: { # We need the old version as our code is not compatible with the new one. # Just overwriting the version does not work as they changed the build code to be diff --git a/nix/shell.nix b/nix/shell.nix index 7fc98ff6aa..30733ea1a3 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -1,6 +1,5 @@ { pkgs, x86Pkgs, variant }: let - use_enterprise = if variant == "enterprise" then true else false; inherit (pkgs) stdenv fetchzip; sources = builtins.fromJSON (builtins.readFile ./canton-sources.json); cometbftDriverSources = builtins.fromJSON (builtins.readFile ./cometbft-driver-sources.json); @@ -152,9 +151,8 @@ in pkgs.mkShell { DAML_COMPILER_VERSION = "${dpmSdkSources.version}"; COMETBFT_RELEASE_VERSION = "${cometbftDriverSources.version}"; COMETBFT_IMAGE_SHA256 = "${cometbftDriverSources.image_sha256}"; - COMETBFT_DRIVER = if use_enterprise then "${pkgs.cometbft_driver}" else ""; + COMETBFT_DRIVER = "${pkgs.cometbft_driver}"; PULUMI_HOME = "${pkgs.pulumi-bin}"; - IS_ENTERPRISE = if use_enterprise then "true" else "false"; # Avoid sbt-assembly falling over. See https://github.com/sbt/sbt-assembly/issues/496 LC_ALL = if stdenv.isDarwin then "" else "C.UTF-8"; # Avoid "warning: setlocale: LC_ALL: cannot change locale (C.UTF-8)" diff --git a/test-full-class-names-canton-enterprise.log b/test-full-class-names-canton-enterprise.log deleted file mode 100644 index 29ac689ce0..0000000000 --- a/test-full-class-names-canton-enterprise.log +++ /dev/null @@ -1 +0,0 @@ -org.lfdecentralizedtrust.splice.integration.tests.ParticipantKmsIdentitiesEnterpriseIntegrationTest diff --git a/test-full-class-names.log b/test-full-class-names.log index 759f3556ed..46807bd46b 100644 --- a/test-full-class-names.log +++ b/test-full-class-names.log @@ -27,6 +27,7 @@ org.lfdecentralizedtrust.splice.integration.tests.GcpBucketPeriodicBackupIntegra org.lfdecentralizedtrust.splice.integration.tests.MemberTrafficIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.MultiHostValidatorOperatorIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.PackageWithDependencyIntegrationTest +org.lfdecentralizedtrust.splice.integration.tests.ParticipantKmsIdentitiesIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.ParticipantPlaintextIdentitiesIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.PeriodicTopologySnapshotIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.RecoverExternalPartyIntegrationTest From 2b05da68f84ba4e318dc4750134605ba07b6eaa0 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Thu, 16 Jul 2026 17:15:59 +0200 Subject: [PATCH 068/329] Deflake unsupportedPackageVettingIntegrationTest (#6452) Signed-off-by: Julien Tinguely --- .../tests/UnsupportedPackageVettingIntegrationTest.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala index 6699fec30d..1fe4e66187 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/UnsupportedPackageVettingIntegrationTest.scala @@ -269,6 +269,7 @@ class UnsupportedPackageVettingIntegrationTest _.message should include regex "Success: dars .*48cac5ba4b6bf78df6c3a952ce05409a1d2ef39c05351074679adc0cf9cd1351.* are removed .*" ) }, + timeUntilSuccess = 40.seconds, ) } From 29bb83a38fa17ddebba0742b48a42c741129e51a Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 16 Jul 2026 22:03:38 +0200 Subject: [PATCH 069/329] Replace acs size metric by acs diff metric (#6453) fixes #6124 [ci] --------- Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- .../splice/store/IngestionSummary.scala | 16 ++- .../splice/store/StoreMetrics.scala | 28 ++-- .../store/db/DbMultiDomainAcsStore.scala | 102 ++++++--------- cluster/expected/observability/expected.json | 4 +- .../grafana-alerting/acs-stores_alerts.yaml | 4 +- .../splice-stores/acs-size.json | 121 ++---------------- docs/src/release_notes_upcoming.rst | 11 ++ 7 files changed, 95 insertions(+), 191 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IngestionSummary.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IngestionSummary.scala index 8e818d04ac..7ff7b168fe 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IngestionSummary.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IngestionSummary.scala @@ -21,7 +21,7 @@ import scala.collection.mutable final case class IngestionSummary( offset: Option[Long], synchronizerIdToRecordTime: Map[SynchronizerId, CantonTimestamp], - newAcsSize: Int, + acsSizeDiff: Int, ingestedCreatedEvents: Vector[CreatedEvent], numFilteredCreatedEvents: Int, ingestedArchivedEvents: Vector[ExercisedEvent], @@ -43,7 +43,7 @@ private[store] object IngestionSummary { private val Empty: IngestionSummary = IngestionSummary( offset = None, synchronizerIdToRecordTime = Map.empty, - newAcsSize = 0, + acsSizeDiff = 0, ingestedCreatedEvents = Vector.empty, updatedContractStates = Vector.empty, numFilteredCreatedEvents = 0, @@ -69,7 +69,7 @@ private[store] object IngestionSummary { prettyNode( "", // intentionally left empty, as that worked better in the log messages above paramIfDefined("offset", _.offset), - param("newAcsSize", _.newAcsSize), + param("acsSizeDiff", _.acsSizeDiff), param("synchronizerIdToRecordTime", _.synchronizerIdToRecordTime), paramIfNonEmpty("ingestedCreatedEvents", _.ingestedCreatedEvents), paramIfNonZero("numFilteredCreatedEvents", _.numFilteredCreatedEvents), @@ -122,12 +122,16 @@ case class MutableIngestionSummary( def toIngestionSummary( synchronizerIdToRecordTime: Map[SynchronizerId, CantonTimestamp], offset: Long, - newAcsSize: Int, + acsSizeDiff: Int, metrics: StoreMetrics, ): IngestionSummary = { // We update the metrics in here as it's the easiest way // to not miss any place that might need updating. - metrics.acsSize.updateValue(newAcsSize.toLong) + if (acsSizeDiff > 0) { + metrics.acsSizeIncrease.mark(acsSizeDiff.toLong)(MetricsContext.Empty) + } else if (acsSizeDiff < 0) { + metrics.acsSizeDecrease.mark(-acsSizeDiff.toLong)(MetricsContext.Empty) + } metrics.ingestedTxLogEntries.mark(ingestedTxLogEntries.size.toLong)(MetricsContext.Empty) metrics.eventCount.inc(this.ingestedCreatedEvents.length.toLong)( MetricsContext("event_type" -> "created") @@ -144,7 +148,7 @@ case class MutableIngestionSummary( IngestionSummary( offset = Some(offset), synchronizerIdToRecordTime = synchronizerIdToRecordTime, - newAcsSize = newAcsSize, + acsSizeDiff = acsSizeDiff, ingestedCreatedEvents = this.ingestedCreatedEvents.toVector, numFilteredCreatedEvents = this.numFilteredCreatedEvents, ingestedArchivedEvents = this.ingestedArchivedEvents.toVector, diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/StoreMetrics.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/StoreMetrics.scala index bba1e1a35f..a1a15a539d 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/StoreMetrics.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/StoreMetrics.scala @@ -59,15 +59,28 @@ class StoreMetrics(metricsFactory: LabeledMetricsFactory)(metricsContext: Metric ) ) - val acsSize: Gauge[Long] = - metricsFactory.gauge( + // we track increase and decrease separately as promql's increase doesn't work for counters that go up and down + // and delta will not handle resets to 0 after a restart properly. + val acsSizeIncrease: Meter = + metricsFactory.meter( MetricInfo( - name = prefix :+ "acs-size", - summary = "The number of active contracts in this store", + name = prefix :+ "acs-size-increase", + summary = "Counter for the number of active contracts added to the store", Traffic, - "The number of active contracts in this store. Note that this is only in the given store. The participant might have contracts we do not ingest.", - ), - 0L, + "Counter for the number of active contracts added to the store. This is _not_ an absolute value of the size, it can only be used to track changes. Note that for an individual transaction this is netted against acsSizeDecrease so ony one of the two will increase. Note that this is only in the given store. The participant might have contracts we do not ingest.", + ) + )(metricsContext) + + // we track increase and decrease separately as promql's increase doesn't work for counters that go up and down + // and delta will not handle resets to 0 after a restart properly. + val acsSizeDecrease: Meter = + metricsFactory.meter( + MetricInfo( + name = prefix :+ "acs-size-decrease", + summary = "Counter for the number of active contracts removed to the store", + Traffic, + "Counter for the number of active contracts removed from the store. This is _not_ an absolute value of the size, it can only be used to track changes. Note that for an individual transaction this is netted against acsSizeIncrease so ony one of the two will increase. Note that this is only in the given store. The participant might have contracts we do not ingest.", + ) )(metricsContext) val ingestedTxLogEntries: Meter = metricsFactory.meter( @@ -158,7 +171,6 @@ class StoreMetrics(metricsFactory: LabeledMetricsFactory)(metricsContext: Metric } override def close(): Unit = { - acsSize.close() perSynchronizerLastIngestedRecordTimeMs.values.foreach(_.close()) perSynchronizerLastSeenRecordTimeMs.values.foreach(_.close()) } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala index c5e8825705..a935ddacd5 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala @@ -977,21 +977,6 @@ final class DbMultiDomainAcsStore[TXE]( case Some(descriptor) => initializeDescriptor(descriptor).map(TxLogStoreId.subst) case None => Future.successful(StoreNotUsed[TxLogStoreId]()) } - - acsSizeInDb <- acsInitResult match { - case StoreHasData(acsStoreId, _) => - storage - .querySingle( - sql""" - select count(*) - from #$acsTableName - where store_id = ${acsStoreId} and migration_id = $domainMigrationId - """.as[Int].headOption, - "initialize.getAcsCount", - ) - .getOrElse(0) - case _ => FutureUnlessShutdown.pure(0) - } } yield { def initState( acsStoreId: AcsStoreId, @@ -1004,7 +989,6 @@ final class DbMultiDomainAcsStore[TXE]( _.withInitialState( acsStoreId = acsStoreId, txLogStoreId = txLogStoreId, - acsSizeInDb = acsSizeInDb, lastIngestedOffset = lastIngestedOffset, ) ) @@ -1166,46 +1150,43 @@ final class DbMultiDomainAcsStore[TXE]( // This is fine because all clients are expected to use [[waitUntilAcsIngested()]] to avoid // reading ACS data before it has finished ingesting. _ <- clearDataForCurrentMigrationId() - acsSize <- source.runWith( - Sink.foldAsync[Int, Seq[BaseLedgerConnection.ActiveContractsItem]](0) { - case (acsSizeSoFar, batch) => - val summaryState = MutableIngestionSummary.empty - logger.debug( - s"Ingesting ACS batch with size: ${batch.size}, total ingested size so far: $acsSizeSoFar" - ) - metrics.ingestionTimePerACSBatch - .timeFuture { - ingestAcsBatch( - offset, - batch.collect { case ActiveContractsItem.ActiveContract(contract) => - contract - }, - batch.collect { case ActiveContractsItem.IncompleteUnassign(unassign) => - unassign - }, - batch.collect { case ActiveContractsItem.IncompleteAssign(assign) => assign }, - summaryState, + _ <- source.runWith( + Sink.foreachAsync[Seq[BaseLedgerConnection.ActiveContractsItem]](1) { batch => + val summaryState = MutableIngestionSummary.empty + logger.debug( + s"Ingesting ACS batch with size: ${batch.size}" + ) + metrics.ingestionTimePerACSBatch + .timeFuture { + ingestAcsBatch( + offset, + batch.collect { case ActiveContractsItem.ActiveContract(contract) => + contract + }, + batch.collect { case ActiveContractsItem.IncompleteUnassign(unassign) => + unassign + }, + batch.collect { case ActiveContractsItem.IncompleteAssign(assign) => assign }, + summaryState, + ) + } + .map { _ => + val summary = summaryState + .toIngestionSummary( + synchronizerIdToRecordTime = Map.empty, + offset = offset, + acsSizeDiff = summaryState.acsSizeDiff, + metrics = metrics, ) - } - .map { _ => - val newAcsSize = summaryState.acsSizeDiff + acsSizeSoFar - val summary = summaryState - .toIngestionSummary( - synchronizerIdToRecordTime = Map.empty, - offset = offset, - newAcsSize = newAcsSize, - metrics = metrics, - ) - handleIngestionSummary(summary) - logger.debug(show"Ingested ACS batch $summary") - newAcsSize - } + handleIngestionSummary(summary) + logger.debug(show"Ingested ACS batch $summary") + } } ) // A store is considered initialized if the last ingested offset is set // Therefore, we must do that after the ACS is ingested, // so that in case of failure the whole ACS ingestion will be retried. - _ <- markAcsIngestedAsOf(offset, acsSize) + _ <- markAcsIngestedAsOf(offset) } yield () } } @@ -1367,13 +1348,13 @@ final class DbMultiDomainAcsStore[TXE]( } } - private def markAcsIngestedAsOf(offset: Long, acsSize: Int)(implicit + private def markAcsIngestedAsOf(offset: Long)(implicit traceContext: TraceContext ): Future[Unit] = { storage.update(updateOffset(offset), "markAcsIngestedAsOf").map { _ => state .getAndUpdate( - _.withUpdate(acsSize, offset) + _.withUpdate(offset) ) .signalOffsetChanged(offset) @@ -1413,7 +1394,6 @@ final class DbMultiDomainAcsStore[TXE]( state .getAndUpdate(s => s.withUpdate( - s.acsSize + summaryState.acsSizeDiff, lastTree.getOffset, synchronizerIdToRecordTime.toMap, ) @@ -1423,7 +1403,7 @@ final class DbMultiDomainAcsStore[TXE]( summaryState.toIngestionSummary( offset = lastTree.getOffset, synchronizerIdToRecordTime = synchronizerIdToRecordTime.toMap, - newAcsSize = state.get().acsSize, + acsSizeDiff = summaryState.acsSizeDiff, metrics = metrics, ) logger.debug( @@ -1442,7 +1422,6 @@ final class DbMultiDomainAcsStore[TXE]( state .getAndUpdate(s => s.withUpdate( - s.acsSize + summaryState.acsSizeDiff, reassignment.offset, reassignmentRecordTimes, ) @@ -1452,7 +1431,7 @@ final class DbMultiDomainAcsStore[TXE]( summaryState.toIngestionSummary( synchronizerIdToRecordTime = reassignmentRecordTimes, offset = reassignment.offset, - newAcsSize = state.get().acsSize, + acsSizeDiff = summaryState.acsSizeDiff, metrics = metrics, ) logger.debug(show"Ingested reassignment $summary") @@ -1472,13 +1451,13 @@ final class DbMultiDomainAcsStore[TXE]( ) .map { _ => state - .getAndUpdate(s => s.withUpdate(s.acsSize, offset, synchronizerIdToRecordTime)) + .getAndUpdate(s => s.withUpdate(offset, synchronizerIdToRecordTime)) .signalWaiters(offset, synchronizerIdToRecordTime) val summary = MutableIngestionSummary.empty.toIngestionSummary( synchronizerIdToRecordTime = synchronizerIdToRecordTime, offset = offset, - newAcsSize = state.get().acsSize, + acsSizeDiff = 0, metrics = metrics, ) logger.debug(show"Ingested offset checkpoint $offset") @@ -2234,7 +2213,6 @@ object DbMultiDomainAcsStore { /** @param acsStoreId The primary key of this stores ACS entry in the store_descriptors table * @param txLogStoreId The primary key of this stores TxLog entry in the store_descriptors table * @param offset The last ingested offset, if any - * @param acsSize The number of active contracts in the store * @param offsetChanged A promise that is not yet completed, and will be completed the next time the offset changes * @param offsetIngestionsToSignal A map from offsets to promises. The keys are offsets that are not ingested yet. * The values are promises that are not completed, and will be completed when @@ -2246,7 +2224,6 @@ object DbMultiDomainAcsStore { acsStoreId: Option[AcsStoreId], txLogStoreId: Option[TxLogStoreId], offset: Option[Long], - acsSize: Int, offsetChanged: Promise[Unit], offsetIngestionsToSignal: SortedMap[Long, Promise[Unit]], lastIngestedRecordTimes: Map[SynchronizerId, CantonTimestamp], @@ -2255,7 +2232,6 @@ object DbMultiDomainAcsStore { def withInitialState( acsStoreId: AcsStoreId, txLogStoreId: Option[TxLogStoreId], - acsSizeInDb: Int, lastIngestedOffset: Option[Long], ): State = { assert( @@ -2268,14 +2244,12 @@ object DbMultiDomainAcsStore { this.copy( acsStoreId = Some(acsStoreId), txLogStoreId = txLogStoreId, - acsSize = acsSizeInDb, offset = lastIngestedOffset, offsetChanged = nextOffsetChanged, ) } def withUpdate( - newAcsSize: Int, newOffset: Long, recordTimes: Map[SynchronizerId, CantonTimestamp] = Map.empty, ): State = { @@ -2295,7 +2269,6 @@ object DbMultiDomainAcsStore { } } this.copy( - acsSize = newAcsSize, offset = Some(newOffset), offsetChanged = nextOffsetChanged, offsetIngestionsToSignal = offsetIngestionsToSignal.filter { case (offsetToSignal, _) => @@ -2388,7 +2361,6 @@ object DbMultiDomainAcsStore { acsStoreId = None, txLogStoreId = None, offset = None, - acsSize = 0, offsetChanged = Promise(), offsetIngestionsToSignal = SortedMap.empty, lastIngestedRecordTimes = Map.empty, diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 264a98fed5..4b92aee658 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -73,7 +73,7 @@ "apiVersion": "v1", "data": { "acknowledgement_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: acknowledgements\n folder: canton\n interval: 1m\n rules:\n - uid: aeg75a4mu72tcc\n title: Mediator Acknowledgement Lag\n condition: No recent report\n data:\n - refId: Mediator Acknowledgement Lag\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: time() - (max by (member_prefix) (label_replace(daml_sequencer_block_acknowledgments_micros{member=~\"MED::.*\"}, \"member_prefix\", \"$1\", \"member\", \"(MED::[^:]+::)[^:]+.*\")) / 1e6)\n instant: false\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{report_publisher}}'\n maxDataPoints: 43200\n range: true\n refId: Mediator Acknowledgement Lag\n - refId: Latest report time lag\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params: []\n type: gt\n operator:\n type: and\n query:\n params:\n - B\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Mediator Acknowledgement Lag\n intervalMs: 1000\n maxDataPoints: 43200\n reducer: last\n refId: Latest report time lag\n settings:\n mode: dropNN\n type: reduce\n - refId: No recent report\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 900\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Latest report time lag\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No recent report\n type: threshold\n dashboardUid: 3ccfda97-fb9c-4413-bc05-5ed6f2c888f7\n panelId: 18\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: 3ccfda97-fb9c-4413-bc05-5ed6f2c888f7\n __panelId__: \"13\"\n description: The mediator {{ $labels.member_prefix }} has not submitted a recent acknowledgement\n severity: critical\n summary: Mediator Acknowledgement lag\n labels: {}\n isPaused: false\n", - "acs-stores_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: acs-size\n folder: splice-stores\n interval: 5m\n rules:\n - uid: df5e4501wuj28a\n title: ACS growth\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: avg_over_time(sum by (node_type) (splice_store_acs_size{namespace=\"sv-1\"})[1d:1h])\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: B\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: avg_over_time(sum by (node_type) (splice_store_acs_size{namespace=\"sv-1\"})[7d:1h] offset 1d) or avg_over_time(sum by (node_type) (splice_store_acs_size{namespace=\"sv-1\"})[1d:1h])\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: $A/$B\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1.2\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n unloadEvaluator:\n params:\n - 1.2\n - 0\n type: lt\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 15m\n annotations:\n description: sv-1's {{ $labels.node_type }} reports a 20% increase in the last 24h compared to the 7 days average.\n severity: critical\n summary: ACS is growing too fast.\n labels: {}\n isPaused: false\n", + "acs-stores_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: acs-size\n folder: splice-stores\n interval: 5m\n rules:\n - uid: df5e4501wuj28a\n title: ACS growth\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: avg_over_time(sum by (node_type) (splice_history_acs_snapshots_snapshot_size{namespace=\"sv-1\"})[1d:1h])\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: B\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: avg_over_time(sum by (node_type) (splice_history_acs_snapshots_snapshot_size{namespace=\"sv-1\"})[7d:1h] offset 1d) or avg_over_time(sum by (node_type) (splice_history_acs_snapshots_snapshot_size{namespace=\"sv-1\"})[1d:1h])\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: $A/$B\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1.2\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n unloadEvaluator:\n params:\n - 1.2\n - 0\n type: lt\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 15m\n annotations:\n description: sv-1's {{ $labels.node_type }} reports a 20% increase in the last 24h compared to the 7 days average.\n severity: critical\n summary: ACS is growing too fast.\n labels: {}\n isPaused: false\n", "acs_commitment_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: acs commitment\n folder: canton\n interval: 1m\n rules:\n - uid: ffghab4ms1ds0b\n title: ACS Commitment Checkpoint Delay\n condition: acs commitment checkpoint delay threshold exceeded\n data:\n - refId: acs commitment checkpoint delay\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by (namespace, job) (timestamp(daml_participant_sync_commitments_last_locally_checkpointed) - (daml_participant_sync_commitments_last_locally_checkpointed > 0) / 1e6)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: acs commitment checkpoint delay\n - refId: acs commitment checkpoint delay threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1200\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: acs commitment checkpoint delay\n intervalMs: 1000\n maxDataPoints: 43200\n refId: acs commitment checkpoint delay threshold exceeded\n type: threshold\n dashboardUid: eesa90lstfk00b\n panelId: 11\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eesa90lstfk00b\n __panelId__: \"11\"\n severity: critical\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting an ACS commitment checkpoint delay\n labels: {}\n isPaused: false\n - uid: ffghab4ms1ds0c\n title: ACS Commitment Delay\n condition: acs commitment delay threshold exceeded\n data:\n - refId: acs commitment delay\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by (namespace, job) (timestamp(daml_participant_sync_commitments_last_locally_completed) - (daml_participant_sync_commitments_last_locally_completed > 0) / 1e6)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: acs commitment delay\n - refId: acs commitment delay threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 3600\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: acs commitment delay\n intervalMs: 1000\n maxDataPoints: 43200\n refId: acs commitment delay threshold exceeded\n type: threshold\n dashboardUid: eesa90lstfk00b\n panelId: 10\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eesa90lstfk00b\n __panelId__: \"10\"\n severity: critical\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting an ACS commitment delay\n labels: {}\n isPaused: false\n - uid: ffghab4ms1ds0d\n title: ACS Commitment Compute Duration\n condition: acs commitment compute duration threshold exceeded\n data:\n - refId: acs commitment compute duration\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: histogram_quantile(0.99, sum(rate(daml_participant_sync_commitments_compute_duration_seconds[5m])) by (namespace, job, le))\n instant: false\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: true\n refId: acs commitment compute duration\n - refId: acs commitment compute duration max\n datasourceUid: __expr__\n model:\n datasource:\n type: __expr__\n uid: __expr__\n expression: acs commitment compute duration\n intervalMs: 1000\n maxDataPoints: 43200\n reducer: max\n refId: acs commitment compute duration max\n type: reduce\n - refId: acs commitment compute duration threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1200\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: acs commitment compute duration max\n intervalMs: 1000\n maxDataPoints: 43200\n refId: acs commitment compute duration threshold exceeded\n type: threshold\n dashboardUid: eesa90lstfk00b\n panelId: 2\n noDataState: NoData\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eesa90lstfk00b\n __panelId__: \"2\"\n severity: warning\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting a high ACS commitment compute duration (p99)\n labels: {}\n isPaused: false\n", "acs_snapshots_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: acs-snapshots\n folder: canton-network\n interval: 5m\n rules:\n - uid: adwt1yr5xuscge\n title: Saving ACS snapshot taking too long\n condition: too_long\n data:\n - refId: latency\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n # The metric only has one sample every ~3h (more if catching up), the 10m range in the expression\n # has to be longer than the alert evaluation+pending interval.\n expr: histogram_quantile(0.99, rate(splice_history_acs_snapshots_latency_save_duration_seconds[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: latency\n - refId: too_long\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 5400\n - 0\n type: gt\n operator:\n type: and\n query:\n params: [ ]\n reducer:\n params: [ ]\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: latency\n intervalMs: 1000\n maxDataPoints: 43200\n refId: too_long\n type: threshold\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: The time to save an incremental snapshot was too high. This step scales with the size of the ACS.\n runbook_url: \"\"\n severity: warning\n summary: Saving an ACS snapshot took longer than {{ humanizeDuration 5400 }} in {{ index $labels \"namespace\" }}'s Scan\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A\n isPaused: false\n - uid: t2ln0sn2bdfmcd\n title: Updating ACS snapshot taking too long\n condition: too_long\n data:\n - refId: latency\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n # The metric has one sample every ~30sec (more if catching up).\n # Here we alert on the average update latency, as the 99th percentile is overly noisy.\n expr: histogram_avg(rate(splice_history_acs_snapshots_latency_update_duration_seconds[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: latency\n - refId: too_long\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 3\n - 0\n type: gt\n operator:\n type: and\n query:\n params: [ ]\n reducer:\n params: [ ]\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: latency\n intervalMs: 1000\n maxDataPoints: 43200\n refId: too_long\n type: threshold\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: The time to update an incremental snapshot was too high. This step scales with the TPS.\n runbook_url: \"\"\n severity: warning\n summary: Updating an ACS snapshot took longer than {{ humanizeDuration 3 }} on average over the past 10min in {{ index $labels \"namespace\" }}'s Scan\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A\n isPaused: false\n", "automation_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: automation\n folder: canton-network\n interval: 5m\n rules:\n - uid: fe73c0e7-dcb3-4975-a7d1-04ed8da087be\n title: Automation Failures\n condition: threshold\n data:\n - refId: total\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by(namespace, node_type, trigger_name, migration) (delta(splice_trigger_completed_total{trigger_name=~\".+\", outcome=~\".+\"}[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total\n - refId: failures\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum by(namespace, node_type, trigger_name, migration) (delta(splice_trigger_completed_total{trigger_name=~\".+\", outcome=~\"failure\"}[10m])) or on() vector(0)\n hide: false\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: failures\n - refId: failure_pct\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: ${failures} / ${total} * 100\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: failure_pct\n type: math\n - refId: threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: failure_pct\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: threshold\n dashboardUid: a3e1385f-6f03-46d9-908c-34aca0f507a6\n panelId: 14\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: a3e1385f-6f03-46d9-908c-34aca0f507a6\n __panelId__: \"14\"\n description: The {{ index $labels \"trigger_name\" }} for the {{ index $labels \"node_type\" }} app in the {{ index $labels \"namespace\" }} namespace on migration id {{ index $labels \"migration\" }} experienced {{ index $values \"failure_pct\" }}% failures in the last 10 minutes.\n severity: |-\n {{- if (gt $values.failure_pct.Value 50.0) -}}\n critical\n {{- else -}}\n warning\n {{- end -}}\n summary: '{{ index $values \"failure_pct\" }}% fatal errors occurred in {{ index $labels \"namespace\" }} - {{ index $labels \"trigger_name\" }} automation trigger'\n labels:\n gcloud_filter: 'resource.labels.namespace_name=%22{{ index $labels \"namespace\" }}%22%0A%22{{ index $labels \"trigger_name\" }}%22'\n isPaused: false\n - uid: ady2ks9ehbw1sb\n title: Busy task-based automation\n condition: threshold\n data:\n - refId: runs\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by(namespace, node_type, node_name, job, trigger_name, migration, party) (rate(splice_trigger_completed_total{trigger_name!~\"ScanVerdictStoreIngestion|ScanHistoryBackfillingTrigger|AcsSnapshotTrigger|ScanBackfillAggregatesTrigger|TxLogBackfillingTrigger|ReportValidatorLicenseMetricsExportTrigger\"}[5m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: runs\n - refId: threshold\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: runs\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: threshold\n dashboardUid: a3e1385f-6f03-46d9-908c-34aca0f507a6\n panelId: 14\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: a3e1385f-6f03-46d9-908c-34aca0f507a6\n __panelId__: \"14\"\n description: The {{ index $labels \"trigger_name\" }} for the {{ index $labels \"node_type\" }} app in the {{ index $labels \"namespace\" }} namespace on migration id {{ index $labels \"migration\" }} experienced {{ index $values \"runs\" }} runs per second in the last 5 minutes.\n runbook_url: \"\"\n severity: |-\n {{- if and $values.runs (gt $values.runs.Value 2.0) -}}\n critical\n {{- else -}}\n warning\n {{- end -}}\n summary: '{{ index $values \"runs\" }} trigger runs per second occurred in {{ index $labels \"namespace\" }} - {{ index $labels \"trigger_name\" }} automation trigger'\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A%22{{ index \"trigger_name\" }}%22\n isPaused: false\n - uid: edz6eq1kc543ke\n title: Busy polling-based automation\n condition: threshold\n data:\n - refId: runs\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by(namespace, node_type, node_name, job, trigger_name, migration, party) (rate(splice_trigger_iterations_total{trigger_name!~\"ScanVerdictStoreIngestion|ScanHistoryBackfillingTrigger|AcsSnapshotTrigger|ScanBackfillAggregatesTrigger|TxLogBackfillingTrigger\"}[5m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: runs\n - refId: threshold\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: runs\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: threshold\n dashboardUid: a3e1385f-6f03-46d9-908c-34aca0f507a6\n panelId: 14\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: a3e1385f-6f03-46d9-908c-34aca0f507a6\n __panelId__: \"14\"\n description: The {{ index $labels \"trigger_name\" }} for the {{ index $labels \"node_type\" }} app in the {{ index $labels \"namespace\" }} namespace on migration id {{ index $labels \"migration\" }} experienced {{ index $values \"runs\" }} runs per second in the last 5 minutes.\n runbook_url: \"\"\n severity: |-\n {{- if and $values.runs (gt $values.runs.Value 2.0) -}}\n critical\n {{- else -}}\n warning\n {{- end -}}\n summary: '{{ index $values \"runs\" }} trigger runs per second occurred in {{ index $labels \"namespace\" }} - {{ index $labels \"trigger_name\" }} automation trigger'\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A%22{{ index \"trigger_name\" }}%22\n isPaused: false\n - uid: fe12i7xur3eo0d\n title: Backfilling not progressing\n condition: C\n data:\n - refId: Max (rate vs completed)\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by(namespace)(rate(splice_history_backfilling_transaction_count[5m]) > 0 or splice_history_backfilling_completed)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Max (rate vs completed)\n - refId: C\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1e-11\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: [ ]\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Max (rate vs completed)\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n description: \"\"\n runbook_url: \"\"\n summary: History backfilling is not making any progress in {{ index $labels \"namespace\" }}\n labels:\n \"\": \"\"\n isPaused: false\n - uid: bel66uf182ha8e\n title: TxLog backfilling not progressing\n condition: C\n data:\n - refId: Max (rate vs completed)\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: |-\n max by(namespace)(\n rate(splice_history_txlog_backfilling_transaction_count[5m]) > 0 or\n splice_history_txlog_backfilling_completed or\n # TxLog backfilling can't start until history backfilling is completed\n absent(splice_history_backfilling_completed) or\n # Note: \"== bool\" is a boolean operator, \"==\" is a filter\n (splice_history_backfilling_completed == bool 0)\n )\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Max (rate vs completed)\n - refId: C\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1e-11\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: [ ]\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Max (rate vs completed)\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n description: \"\"\n runbook_url: \"\"\n summary: TxLog backfilling is not making any progress in {{ index $labels \"namespace\" }}\n labels:\n \"\": \"\"\n isPaused: false\n - uid: fentlrcbcrsaoa\n title: Delegateless trigger contention\n condition: threshold\n data:\n - refId: total_attempts\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum(rate(splice_trigger_attempted_total{isDsoDelegateTrigger=\"true\", node_type=\"sv\"}[30m])) by (namespace)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total_attempts\n - refId: total_contention\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(splice_trigger_attempted_total{statusCode!~\"OK\", isDsoDelegateTrigger=\"true\", node_type=\"sv\", contentionFailure=\"true\"}[30m])) by (namespace)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total_contention\n - refId: threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: [ ]\n reducer:\n params: [ ]\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: (${total_contention} > 0.2 * ${total_attempts}) && (${total_contention} > 0)\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: math\n # Alert also reports noData when there is no contention.\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: '{{ index $labels \"namespace\" }} had more than 20% contention in the last 30 minutes.'\n isPaused: false\n - uid: fentlrcbcrsaob\n title: Delegateless trigger non-local contention errors\n condition: threshold\n data:\n - refId: total_attempts\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum(rate(splice_trigger_attempted_total{isDsoDelegateTrigger=\"true\", node_type=\"sv\"}[30m])) by (namespace)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total_attempts\n - refId: total_non_local_contention_errors\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(splice_trigger_attempted_total{statusCode!~\"OK\", isDsoDelegateTrigger=\"true\", node_type=\"sv\", contentionFailure=\"true\", errorCodeId!~\"UNKNOWN_CONTRACT_SYNCHRONIZERS|LOCAL_.*\"}[30m])) by (namespace)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: total_non_local_contention_errors\n - refId: threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: [ ]\n reducer:\n params: [ ]\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: (${total_non_local_contention_errors} > 0.05 * ${total_attempts}) && (${total_non_local_contention_errors} > 0)\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: math\n # Alert also reports noData when there is no contention.\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: '{{ index $labels \"namespace\" }} had more than 5% non-local contention errors in the last 30 minutes.'\n isPaused: false\n - uid: df3t10f23cm68c\n title: 'Ingestion saturation'\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: histogram_avg(sum by (le, namespace,job,store_name,store_party,synchronizer_id) (rate(splice_store_ingestion_batch_size{namespace=~\"sv\"}[20m])))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 80\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: [ ]\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: OK\n execErrState: OK\n for: 30m\n annotations:\n description: |\n The ingestion batch size has exceeded 80 entries/batch for a prolonged time in {{ index $labels \"namespace\" }}'s {{ index $labels \"node_type\" }}.\n This might mean that ingestion is close to not being able to keep up with ledger activity.\n This can also happen when an SV was just onboarded and is catching up with the ACS.\n summary: The ingestion batch size has exceeded the expected size for a prolonged time.\n isPaused: false\n - uid: bgh7m2a8cy3x4a\n title: Automation service unhealthy\n condition: threshold\n data:\n - refId: health\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by(namespace, node_type, automation_service) (splice_automation_background_service_health)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: health\n - refId: threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - threshold\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: health\n intervalMs: 1000\n maxDataPoints: 43200\n refId: threshold\n type: threshold\n dashboardUid: a3e1385f-6f03-46d9-908c-34aca0f507a6\n panelId: 32\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: a3e1385f-6f03-46d9-908c-34aca0f507a6\n __panelId__: \"32\"\n description: The {{ index $labels \"automation_service\" }} background service of the {{ index $labels \"node_type\" }} app in the {{ index $labels \"namespace\" }} namespace has been continuously reporting unhealthy for more than 5 minutes.\n summary: '{{ index $labels \"namespace\" }} - {{ index $labels \"node_type\" }} - {{ index $labels \"automation_service\" }} automation service is unhealthy'\n labels:\n gcloud_filter: 'resource.labels.namespace_name=%22{{ index $labels \"namespace\" }}%22'\n isPaused: false\n", @@ -346,7 +346,7 @@ "inputs": { "apiVersion": "v1", "data": { - "acs-size.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 3573,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Trend #A\"\n },\n \"properties\": [\n {\n \"id\": \"displayName\",\n \"value\": \"ACS size\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 26,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"footer\": {\n \"countRows\": false,\n \"fields\": \"\",\n \"reducer\": [\n \"sum\"\n ],\n \"show\": false\n },\n \"showHeader\": true,\n \"sortBy\": [\n {\n \"desc\": true,\n \"displayName\": \"ACS size\"\n }\n ]\n },\n \"pluginVersion\": \"12.0.2\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, store_name, store_party) (splice_store_acs_size{namespace=~\\\"$namespace\\\",store_name=~\\\"$store_name\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"ACS Size\",\n \"transformations\": [\n {\n \"id\": \"timeSeriesTable\",\n \"options\": {\n \"A\": {\n \"timeField\": \"Time\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 20,\n \"w\": 24,\n \"x\": 0,\n \"y\": 26\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.0.2\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without (__name__,endpoint,instance,job,migration,otel_scope) (splice_store_acs_size{namespace=~\\\"$namespace\\\",store_name=~\\\"$store_name\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Splice Store ACS Size\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"\",\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_store_acs_size,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_store_acs_size,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_store_acs_size,store_name)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"store_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_store_acs_size,store_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Splice Store ACS Size\",\n \"uid\": \"dduss3xr5or28c\",\n \"version\": 1\n}\n", + "acs-size.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 20,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"(increase(splice_store_acs_size_increase_total{namespace=~\\\"$namespace\\\",store_name=~\\\"$store_name\\\"}[24h]))-(increase(splice_store_acs_size_decrease_total{namespace=~\\\"$namespace\\\",store_name=~\\\"$store_name\\\"}[24h]))\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{store_name}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Splice Store ACS Change in last 24h\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"\",\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_store_acs_size,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_store_acs_size,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_store_acs_size,store_name)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"store_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_store_acs_size,store_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Splice Store ACS Size\",\n \"uid\": \"dduss3xr5or28c\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", "acs-snapshots.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 5,\n \"panels\": [],\n \"title\": \"Incremental ACS snapshot generation\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"splice_history_acs_snapshots_latest_record_time_update{namespace=~\\\"$namespace\\\"} / 1000\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Record time of last update\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 1\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"splice_history_acs_snapshots_latest_record_time_save{namespace=~\\\"$namespace\\\"} / 1000\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Record time of last save\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to update an incremental snapshot, i.e., the time to update an incremental snapshot by processing ~30sec worth of update history.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"builder\",\n \"expr\": \"histogram_avg(rate(splice_history_acs_snapshots_latency_update_duration_seconds{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Update latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to save an incremental snapshot, i.e., the time to copy an incremental snapshot to the the big table containing all historical snapshots.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 9\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"builder\",\n \"expr\": \"histogram_avg(rate(splice_history_acs_snapshots_latency_save_duration_seconds{namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Save latency\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_history_acs_snapshots_latest_record_time_update,namespace)\",\n \"includeAll\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_acs_snapshots_latest_record_time_update,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"ACS snapshots\",\n \"uid\": \"cnqnrp4\",\n \"version\": 3,\n \"weekStart\": \"\"\n}\n", "bulk-storage.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_bulk_storage_latest_acs_snapshot{namespace=~\\\"$namespace\\\"} >-62135596800000) / 1000.0)\",\n \"legendFormat\": \"{{namespace}} ACS Snapshots\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_bulk_storage_latest_updates_segment{namespace=~\\\"$namespace\\\"} >-62135596800000) / 1000.0)\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} Updates\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Last record time stored\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"splice_history_bulk_storage_object_count{namespace=~\\\"$namespace\\\"}\",\n \"legendFormat\": \"{{namespace}} {{object_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Num S3 objects stored\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_history_bulk_storage_updates_count,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_bulk_storage_updates_count,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-2d\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Bulk Storage\",\n \"uid\": \"cnxl9bn\",\n \"version\": 3,\n \"weekStart\": \"\"\n}\n", "history-backfilling.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 1387,\n \"links\": [],\n \"panels\": [\n {\n \"fieldConfig\": {\n \"defaults\": {},\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 21,\n \"options\": {\n \"code\": {\n \"language\": \"plaintext\",\n \"showLineNumbers\": false,\n \"showMiniMap\": false\n },\n \"content\": \"There are three backfilling background tasks:\\n\\n1. `ScanHistoryBackfillingTrigger` backfills regular updates\\n - Data is loaded from peer scan apps using BFT network calls\\n1. `ScanHistoryBackfillingTrigger` backfills import updates\\n - Data is loaded from peer scan apps using BFT network calls\\n1. `TxLogBackfillingTrigger` backfills txlog entries\\n - Data is loaded from the local UpdateHistory\\n\\nThe tasks are completed sequentially in the above order.\\nE.g., txlog backfilling won't start until update history backfilling is complete.\",\n \"mode\": \"markdown\"\n },\n \"pluginVersion\": \"12.1.1\",\n \"title\": \"Overview\",\n \"type\": \"text\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 7\n },\n \"id\": 7,\n \"panels\": [],\n \"title\": \"Updates (scan)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 8\n },\n \"id\": 17,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_backfilling_transaction_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Backfilled transactions count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 8\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace)(rate(splice_history_backfilling_transaction_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate backfilled transactions\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 16\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_backfilling_event_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Backfilled events count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 16\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(rate(splice_history_backfilling_event_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate backfilled events\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"axisPlacement\": \"auto\",\n \"fillOpacity\": 70,\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"mappings\": [],\n \"max\": 1,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"green\",\n \"value\": 1\n }\n ]\n },\n \"unit\": \"bool_yes_no\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 24\n },\n \"id\": 15,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"auto\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_backfilling_completed{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Backfilling completed\",\n \"type\": \"state-timeline\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"unit\",\n \"value\": \"dateTimeAsIso\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 24\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_backfilling_latest_record_time{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}>-62135596800000)/1000)\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"Value\"\n }\n ],\n \"title\": \"Last record time backfilled\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 16,\n \"panels\": [],\n \"title\": \"Import Updates (scan)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 33\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_import_updates_backfilling_contract_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Backfilled import contracts count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 33\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace)(rate(splice_history_import_updates_backfilling_contract_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate backfilled contracts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"axisPlacement\": \"auto\",\n \"fillOpacity\": 70,\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"mappings\": [],\n \"max\": 1,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"green\",\n \"value\": 1\n }\n ]\n },\n \"unit\": \"bool_yes_no\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 41\n },\n \"id\": 19,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"auto\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_import_updates_backfilling_completed{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"})\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Import update backfilling completed\",\n \"type\": \"state-timeline\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"unit\",\n \"value\": \"\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 41\n },\n \"id\": 18,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_import_updates_backfilling_latest_migration_id{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\"}>-1))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"Value\"\n }\n ],\n \"title\": \"Last migration id backfilled\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 49\n },\n \"id\": 8,\n \"panels\": [],\n \"title\": \"TxLog entries (all apps)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 50\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_txlog_backfilling_transaction_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Processed transactions count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 50\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace)(rate(splice_history_txlog_backfilling_transaction_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate processed transactions\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 58\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(splice_history_txlog_backfilling_event_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Processed events count\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 58\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace)(rate(splice_history_txlog_backfilling_event_count{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"}[5m]))\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}{{service}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate processed events\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"axisPlacement\": \"auto\",\n \"fillOpacity\": 70,\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"mappings\": [],\n \"max\": 1,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"green\",\n \"value\": 1\n }\n ]\n },\n \"unit\": \"bool_yes_no\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 12,\n \"x\": 0,\n \"y\": 66\n },\n \"id\": 13,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"auto\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by(namespace, service) (splice_history_txlog_backfilling_completed{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"})\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"TxLog backfilling completed\",\n \"type\": \"state-timeline\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"unit\",\n \"value\": \"dateTimeAsIso\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 12,\n \"x\": 12,\n \"y\": 66\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by(namespace)((splice_history_txlog_backfilling_latest_record_time{namespace=~\\\"$namespace\\\", migration=~\\\"$migration\\\", service=~\\\"$service\\\"}>-62135596800000)/1000)\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{service}}\",\n \"range\": true,\n \"refId\": \"Value\"\n }\n ],\n \"title\": \"Last record time backfilled\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"\",\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_history_txlog_backfilling_latest_record_time,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_txlog_backfilling_latest_record_time,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_history_txlog_backfilling_latest_record_time,migration)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"migration\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_txlog_backfilling_latest_record_time,migration)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"definition\": \"label_values(splice_history_txlog_backfilling_completed,service)\",\n \"includeAll\": true,\n \"name\": \"service\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_history_txlog_backfilling_completed,service)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"History backfiling\",\n \"uid\": \"fe12ejqgwdlvke\",\n \"version\": 4\n}\n", diff --git a/cluster/pulumi/observability/grafana-alerting/acs-stores_alerts.yaml b/cluster/pulumi/observability/grafana-alerting/acs-stores_alerts.yaml index 7a2de5e0c9..c52bc27fd9 100644 --- a/cluster/pulumi/observability/grafana-alerting/acs-stores_alerts.yaml +++ b/cluster/pulumi/observability/grafana-alerting/acs-stores_alerts.yaml @@ -16,7 +16,7 @@ groups: datasourceUid: prometheus model: editorMode: code - expr: avg_over_time(sum by (node_type) (splice_store_acs_size{namespace="sv-1"})[1d:1h]) + expr: avg_over_time(sum by (node_type) (splice_history_acs_snapshots_snapshot_size{namespace="sv-1"})[1d:1h]) instant: true intervalMs: 1000 legendFormat: __auto @@ -33,7 +33,7 @@ groups: type: prometheus uid: prometheus editorMode: code - expr: avg_over_time(sum by (node_type) (splice_store_acs_size{namespace="sv-1"})[7d:1h] offset 1d) or avg_over_time(sum by (node_type) (splice_store_acs_size{namespace="sv-1"})[1d:1h]) + expr: avg_over_time(sum by (node_type) (splice_history_acs_snapshots_snapshot_size{namespace="sv-1"})[7d:1h] offset 1d) or avg_over_time(sum by (node_type) (splice_history_acs_snapshots_snapshot_size{namespace="sv-1"})[1d:1h]) instant: true intervalMs: 1000 legendFormat: __auto diff --git a/cluster/pulumi/observability/grafana-dashboards/splice-stores/acs-size.json b/cluster/pulumi/observability/grafana-dashboards/splice-stores/acs-size.json index 247d158258..9b2276632b 100644 --- a/cluster/pulumi/observability/grafana-dashboards/splice-stores/acs-size.json +++ b/cluster/pulumi/observability/grafana-dashboards/splice-stores/acs-size.json @@ -18,108 +18,8 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 3573, "links": [], "panels": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "thresholds" - }, - "custom": { - "align": "auto", - "cellOptions": { - "type": "auto" - }, - "inspect": false - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green" - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [ - { - "matcher": { - "id": "byName", - "options": "Trend #A" - }, - "properties": [ - { - "id": "displayName", - "value": "ACS size" - } - ] - } - ] - }, - "gridPos": { - "h": 26, - "w": 24, - "x": 0, - "y": 0 - }, - "id": 2, - "options": { - "cellHeight": "sm", - "footer": { - "countRows": false, - "fields": "", - "reducer": [ - "sum" - ], - "show": false - }, - "showHeader": true, - "sortBy": [ - { - "desc": true, - "displayName": "ACS size" - } - ] - }, - "pluginVersion": "12.0.2", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "prometheus" - }, - "editorMode": "code", - "expr": "sum by (namespace, store_name, store_party) (splice_store_acs_size{namespace=~\"$namespace\",store_name=~\"$store_name\"})", - "instant": false, - "legendFormat": "__auto", - "range": true, - "refId": "A" - } - ], - "title": "ACS Size", - "transformations": [ - { - "id": "timeSeriesTable", - "options": { - "A": { - "timeField": "Time" - } - } - } - ], - "type": "table" - }, { "datasource": { "type": "prometheus", @@ -154,6 +54,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -168,7 +69,8 @@ "mode": "absolute", "steps": [ { - "color": "green" + "color": "green", + "value": 0 }, { "color": "red", @@ -184,7 +86,7 @@ "h": 20, "w": 24, "x": 0, - "y": 26 + "y": 0 }, "id": 1, "options": { @@ -200,7 +102,7 @@ "sort": "none" } }, - "pluginVersion": "12.0.2", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -208,20 +110,20 @@ "uid": "prometheus" }, "editorMode": "code", - "expr": "sum without (__name__,endpoint,instance,job,migration,otel_scope) (splice_store_acs_size{namespace=~\"$namespace\",store_name=~\"$store_name\"})", + "expr": "(increase(splice_store_acs_size_increase_total{namespace=~\"$namespace\",store_name=~\"$store_name\"}[24h]))-(increase(splice_store_acs_size_decrease_total{namespace=~\"$namespace\",store_name=~\"$store_name\"}[24h]))", "instant": false, - "legendFormat": "__auto", + "legendFormat": "{{namespace}} {{store_name}}", "range": true, "refId": "A" } ], - "title": "Splice Store ACS Size", + "title": "Splice Store ACS Change in last 24h", "type": "timeseries" } ], "preload": false, "refresh": "", - "schemaVersion": 41, + "schemaVersion": 42, "tags": [], "templating": { "list": [ @@ -247,6 +149,7 @@ }, "refresh": 1, "regex": "", + "regexApplyTo": "value", "type": "query" }, { @@ -271,6 +174,7 @@ }, "refresh": 1, "regex": "", + "regexApplyTo": "value", "type": "query" } ] @@ -283,5 +187,6 @@ "timezone": "", "title": "Splice Store ACS Size", "uid": "dduss3xr5or28c", - "version": 1 + "version": 1, + "weekStart": "" } diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index fe39157d6d..655ed47895 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -36,6 +36,17 @@ - Add support for specifying weight in ``GrantFeaturedAppRight`` governance voting UI. + - Observability + + - Remove the ``splice_store_acs_size`` gauge metric by + ``splice_store_acs_size_increase`` and + ``splice_store_acs_size_decrease`` counters to fix a performance + issue in initializing the metric on startup. Note that these + metrics can only be used to track changes but not absolute + sizes. ``splice_history_acs_snapshots_snapshot_size`` provides + an absolute size for SVs, however it counts rows not contracts so it + counts contracts with multiple stakeholders multiple times. + - Deployment - splice-info From e6cdebb47b380b30bb63029a86261119a8de4769 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Fri, 17 Jul 2026 10:00:07 +0200 Subject: [PATCH 070/329] Nicu/cantonbft/add backup restore back (#6447) * Reapply "Add cantonbft backup/restore support" (#6446) This reverts commit 42b6faa83b47a32f87f20d602a42878a3d98d13f. * Disable cantonbft by default [static] Signed-off-by: Nicu Reut --- build-tools/cncluster | 2 +- cluster/scripts/find-recent-backup.sh | 53 +++++++++++++++++++++++---- cluster/scripts/node-backup.sh | 7 ++++ cluster/scripts/node-restore.sh | 30 +++++++++------ cluster/scripts/utils.source | 29 +++++++++++++-- 5 files changed, 97 insertions(+), 24 deletions(-) diff --git a/build-tools/cncluster b/build-tools/cncluster index 706f585319..1a25cb4e80 100755 --- a/build-tools/cncluster +++ b/build-tools/cncluster @@ -1948,7 +1948,7 @@ function subcmd_restore_node() { _info "Silencing SV report creation alerts for $sv_name for 1 hour" subcmd_silence_grafana_alerts "1 hour" "alertname=Report Creation Time Lag" "report_publisher=$sv_name" _info "Restoring sv node $node" - SPLICE_SV=$node SPLICE_MIGRATION_ID=$migration_id "$SPLICE_ROOT"/cluster/scripts/node-restore.sh $force "$node" "$migration_id" "$backup_run_id" cometbft sequencer participant mediator cn-apps + SPLICE_SV=$node SPLICE_MIGRATION_ID=$migration_id "$SPLICE_ROOT"/cluster/scripts/node-restore.sh $force "$node" "$migration_id" "$backup_run_id" cometbft cantonBft sequencer participant mediator cn-apps ;; validator1|splitwell) _info "Restoring validator node $node" diff --git a/cluster/scripts/find-recent-backup.sh b/cluster/scripts/find-recent-backup.sh index 4bdf540fc1..f646ffd1f2 100755 --- a/cluster/scripts/find-recent-backup.sh +++ b/cluster/scripts/find-recent-backup.sh @@ -17,11 +17,11 @@ function usage() { function is_full_backup_kube() { local component_backup_names=$1 - local expected_components=$2 + local expected_patterns=$2 # Check if all expected components can be found in the component_backup_names - for component in $expected_components; do - count=$(echo "$component_backup_names" | grep -c "$component") + for pattern in $expected_patterns; do + count=$(echo "$component_backup_names" | grep -c -F -- "$pattern") if [ "$count" -ne 1 ]; then return 1 fi @@ -44,8 +44,16 @@ function latest_full_backup_run_id_kube() { local expected_components=$4 local before_timestamp=$5 local include_cometbft=$6 + + local expected_patterns="" + for component in $expected_components; do + local instance + instance="$(create_component_instance "$component" "$migration_id" "$namespace")" + expected_patterns="${expected_patterns:+$expected_patterns }-${instance}-pg-" + done + if [ "$is_sv" == "true" ] && [ "$include_cometbft" == "true" ]; then - expected_components="$expected_components cometbft" + expected_patterns="$expected_patterns cometbft" fi local all_run_ids @@ -54,7 +62,7 @@ function latest_full_backup_run_id_kube() { while read -r run_id; do component_backup_names=$(get_component_backup_names_kube "$migration_id" "$run_id") - if is_full_backup_kube "$component_backup_names" "$expected_components"; then + if is_full_backup_kube "$component_backup_names" "$expected_patterns"; then echo "$run_id" return 0 fi @@ -71,6 +79,7 @@ function latest_full_backup_run_id_gcloud() { local stack declare -A backup_id_dict + local sequencer_end_time="" # participant backup must be newer than cn-apps backup stack=$(get_stack_for_namespace_component "$namespace" "participant") @@ -90,6 +99,7 @@ function latest_full_backup_run_id_gcloud() { for component in $expected_components; do [ "$component" == "participant" ] && continue + [ "$component" == "cantonBft" ] && continue stack=$(get_stack_for_namespace_component "$namespace" "$component") instance="$(create_component_instance "$component" "$migration_id" "$namespace")" @@ -98,21 +108,43 @@ function latest_full_backup_run_id_gcloud() { local cloudsql_id cloudsql_id=$(get_cloudsql_id "$full_component_instance" "$stack") - local backup_id + local entry if [ "$component" == "cn-apps" ]; then # cn-apps backup must be older than participant backup - backup_id=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --arg pt "$participant_end_time" '[.[] | select(.endTime <= $pt)] | first | .id') + entry=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --arg pt "$participant_end_time" '[.[] | select(.endTime <= $pt)] | first | "\(.id) \(.endTime)"') else - backup_id=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --argjson ts "$before_timestamp" '[.[] | select(.endTime <= ($ts | todate))] | first | .id') + entry=$(gcloud sql backups list --instance "$cloudsql_id" --format=json | jq -r --argjson ts "$before_timestamp" '[.[] | select(.endTime <= ($ts | todate))] | first | "\(.id) \(.endTime)"') fi + local backup_id + backup_id=$(echo "$entry" | awk '{print $1}') + local backup_end_time + backup_end_time=$(echo "$entry" | awk '{print $2}') if [ -z "$backup_id" ] || [ "$backup_id" == "null" ]; then _error "No backup found for component $component (instance $cloudsql_id) before timestamp $before_timestamp" fi backup_id_dict[$component]="$backup_id" + + if [ "$component" == "sequencer" ]; then + sequencer_end_time="$backup_end_time" + fi done + # cantonBft backup must be older than the sequencer backup + if [[ " $expected_components " == *" cantonBft "* ]]; then + stack=$(get_stack_for_namespace_component "$namespace" "cantonBft") + instance="$(create_component_instance "cantonBft" "$migration_id" "$namespace")" + local bft_cloudsql_id + bft_cloudsql_id=$(get_cloudsql_id "$namespace-$instance-pg" "$stack") + local bft_backup_id + bft_backup_id=$(gcloud sql backups list --instance "$bft_cloudsql_id" --format=json | jq -r --arg st "$sequencer_end_time" '[.[] | select(.endTime <= $st)] | first | .id') + if [ -z "$bft_backup_id" ] || [ "$bft_backup_id" == "null" ]; then + _error "No backup found for component cantonBft (instance $bft_cloudsql_id) before sequencer backup time $sequencer_end_time" + fi + backup_id_dict["cantonBft"]="$bft_backup_id" + fi + local result="" for component in "${!backup_id_dict[@]}"; do result="${result:+$result,}$component:${backup_id_dict[$component]}" @@ -158,6 +190,11 @@ function main() { is_sv=true full_instance="$namespace-cn-apps-pg" expected_components="cn-apps sequencer participant mediator" + local bft_db_enabled + bft_db_enabled=$(canton_bft_db_enabled "$migration_id" "$config") + if [ "$bft_db_enabled" == "true" ]; then + expected_components="$expected_components cantonBft" + fi stack=$(get_stack_for_namespace_component "$namespace" "cn-apps") ;; *) diff --git a/cluster/scripts/node-backup.sh b/cluster/scripts/node-backup.sh index 533f2d1bf4..f84bcac39d 100755 --- a/cluster/scripts/node-backup.sh +++ b/cluster/scripts/node-backup.sh @@ -297,6 +297,13 @@ function main() { elif [ "$1" == "sv" ]; then _info "Backing up SV node $namespace" + local bft_enabled + bft_enabled=$(canton_bft_db_enabled "$migration_id" "$config") + if [ "$bft_enabled" == "true" ]; then + backup_component "$namespace" "cantonBft" "$requested_component" "$migration_id" + wait_for_backup "$namespace" "cantonBft" "$requested_component" "$migration_id" + fi + backup_component "$namespace" "cn-apps" "$requested_component" "$migration_id" backup_component "$namespace" "mediator" "$requested_component" "$migration_id" backup_component "$namespace" "sequencer" "$requested_component" "$migration_id" diff --git a/cluster/scripts/node-restore.sh b/cluster/scripts/node-restore.sh index d2000ed4b8..a8237442fb 100755 --- a/cluster/scripts/node-restore.sh +++ b/cluster/scripts/node-restore.sh @@ -16,6 +16,8 @@ function component_to_deployments() { local -r namespace=$3 if [[ "$component" == "sequencer" ]]; then echo "global-domain-$migration_id-sequencer" + elif [[ "$component" == "cantonBft" ]]; then + echo "global-domain-$migration_id-sequencer" elif [[ "$component" == "mediator" ]]; then echo "global-domain-$migration_id-mediator" elif [[ "$component" == "participant" && "$namespace" == sv* ]]; then @@ -434,27 +436,33 @@ function main() { | map(select(.id == $migration_id)) | .[0].sequencer.enableBftSequencer // false") - if [[ "$run_id" == *","* ]]; then - _info " ** Validate backup ids ** " - local map_keys - map_keys=$(echo "$run_id" | tr ',' '\n' | cut -d: -f1 | sort) - local req_components - req_components=$(printf '%s\n' "${@:4}" | sort) - if [ "$map_keys" != "$req_components" ]; then - _error "Backup map keys ($map_keys) do not match requested components (${*:4})" - fi - fi + local bft_db_enabled + bft_db_enabled=$(canton_bft_db_enabled "$migration_id" "$config") - # Build the list of components to restore, dropping CometBFT when the BFT sequencer is enabled. local -a components=() for component in "${@:4}"; do if [ "$component" == "cometbft" ] && [ "$bft_sequencer_enabled" == "true" ]; then _info "BFT sequencer is enabled for migration $migration_id, skipping CometBFT restore" continue fi + if [ "$component" == "cantonBft" ] && [ "$bft_db_enabled" != "true" ]; then + _info "Dedicated CantonBFT sequencer DB is not enabled for migration $migration_id, skipping CantonBFT restore" + continue + fi components+=("$component") done + if [[ "$run_id" == *","* ]]; then + _info " ** Validate backup ids ** " + local map_keys + map_keys=$(echo "$run_id" | tr ',' '\n' | cut -d: -f1 | sort) + local req_components + req_components=$(printf '%s\n' "${components[@]}" | sort) + if [ "$map_keys" != "$req_components" ]; then + _error "Backup map keys ($map_keys) do not match requested components (${components[*]})" + fi + fi + for component in "${components[@]}"; do component_to_deployments "$component" "$migration_id" "$namespace" done diff --git a/cluster/scripts/utils.source b/cluster/scripts/utils.source index 0018dc9f2d..df02c20b72 100755 --- a/cluster/scripts/utils.source +++ b/cluster/scripts/utils.source @@ -33,7 +33,7 @@ function get_cloudsql_id() { function get_stack_for_namespace_component() { local namespace=$1 local component=$2 - + local split_sv_deployment split_sv_deployment=$(get_resolved_config | yq '.synchronizerMigration.splitSvDeploymentEnabled // false') @@ -43,6 +43,8 @@ function get_stack_for_namespace_component() { stack="sv" elif [[ "${component}" == "sequencer" ]]; then stack="sv-canton" + elif [[ "${component}" == "cantonBft" ]]; then + stack="sv-canton" elif [[ "${component}" == "mediator" ]]; then stack="sv-canton" elif [[ "${split_sv_deployment}" == "true" ]]; then @@ -62,16 +64,35 @@ create_component_instance() { local migration_id="$2" local namespace="$3" - if [[ ("$component" == "sequencer" || "$component" == "mediator") + local instance_base="$component" + if [[ "$component" == "cantonBft" ]]; then + instance_base="sequencer-bft" + fi + + if [[ ("$component" == "sequencer" || "$component" == "cantonBft" || "$component" == "mediator") && ("$namespace" != "splitwell" && "$namespace" != "validator1" && "$namespace" != "sv") ]]; then - component_instance="${component}-${migration_id}" + component_instance="${instance_base}-${migration_id}" else - component_instance="${component}" + component_instance="${instance_base}" fi echo "$component_instance" } +function canton_bft_db_enabled() { + local migration_id=$1 + local config=$2 + + echo "$config" | yq " + ([.synchronizerMigration.active, .synchronizerMigration.upgrade, .synchronizerMigration.legacy] + + (.synchronizerMigration.additionalLegacy // []) + + (.synchronizerMigration.archived // [])) + | map(select(. != null and .id == ${migration_id})) + | .[0] + | ((.sequencer.enableBftSequencer // false) and (.sequencer.dedicatedBftSequencerDb // true)) + " +} + function get_resolved_config() { "${SPLICE_ROOT}/cluster/scripts/get-resolved-config.sh" } From 8023dadca26da3633c174c25d62999d6b8fda24e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Fri, 17 Jul 2026 11:49:45 +0200 Subject: [PATCH 071/329] Clear release notes for 0.6.13 (#6456) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oriol Muñoz --- docs/src/release_notes_upcoming.rst | 46 ----------------------------- 1 file changed, 46 deletions(-) diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 655ed47895..7488c245e7 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -7,49 +7,3 @@ .. release-notes:: Upcoming - .. note:: - - Next-release notes - - - Validator - - - Unsupported package versions are now automatically unvetted by the validator package vetting trigger, - aligning validator behavior with SVs. - - You can disable validator unvetting by setting: - - .. code-block:: yaml - - - name: ADDITIONAL_CONFIG_UNSUPPORTED_DARS_UNVETTING - value: | - canton.validator-apps.validator_backend.parameters.enabled-features.enable-validator-dars-unvetting = false - - - The ``splice-postgres`` Helm chart is deprecated and will not be supported after - 2026-11-12, the PostgreSQL 14 end-of-life date. Published chart versions remain - available, but receive no further updates after that date, and no new chart versions - will be published after 2026-10-12. Run Splice against a PostgreSQL instance you - provision yourself; a managed service such as Amazon RDS or Google Cloud SQL is - recommended. Follow the `migration guide `__ to move the data - of an existing node before that date. - - - SV app - - - Add support for specifying weight in ``GrantFeaturedAppRight`` governance voting UI. - - - Observability - - - Remove the ``splice_store_acs_size`` gauge metric by - ``splice_store_acs_size_increase`` and - ``splice_store_acs_size_decrease`` counters to fix a performance - issue in initializing the metric on startup. Note that these - metrics can only be used to track changes but not absolute - sizes. ``splice_history_acs_snapshots_snapshot_size`` provides - an absolute size for SVs, however it counts rows not contracts so it - counts contracts with multiple stakeholders multiple times. - - - Deployment - - - splice-info - - - ``/runtime/status.json`` now includes reachability for scan and sequencer (0 is good, 1 is lagging - behind, 2 is unreachable, 3 is lagging behind and unreachable). From f18437bbac67fe678425978321c65a6821a91e8c Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Fri, 17 Jul 2026 12:23:11 +0200 Subject: [PATCH 072/329] Compute informees uniformly (#6412) Signed-off-by: Julien Tinguely --- ...hedMultiDomainExpiredContractTrigger.scala | 12 +++- .../ExpireRewardCouponV2Trigger.scala | 26 ++++--- .../ExpireRewardCouponsTrigger.scala | 72 +++++++++++++------ .../ExpiredAmuletAllocationTrigger.scala | 37 +++++----- .../ExpiredAmuletAllocationV2Trigger.scala | 34 +++++---- ...iredAmuletTransferInstructionTrigger.scala | 39 +++++----- .../delegatebased/ExpiredAmuletTrigger.scala | 24 ++++--- .../ExpiredLockedAmuletTrigger.scala | 32 ++++----- .../FeaturedAppActivityMarkerTrigger.scala | 49 +++++++------ .../IgnoredAmuletVersionGuard.scala | 5 +- .../splice/sv/util/ContractStakeholders.scala | 30 ++++++++ 11 files changed, 218 insertions(+), 142 deletions(-) create mode 100644 apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/util/ContractStakeholders.scala diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/BatchedMultiDomainExpiredContractTrigger.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/BatchedMultiDomainExpiredContractTrigger.scala index ca8402976a..26da174abc 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/BatchedMultiDomainExpiredContractTrigger.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/BatchedMultiDomainExpiredContractTrigger.scala @@ -34,7 +34,7 @@ abstract class BatchedMultiDomainExpiredContractTrigger[ companion: C, vettingLookupService: PackageVettingLookupService, pkg: PackageIdResolver.Package, - stakeholders: T => Seq[PartyId], + getStakeholders: T => Seq[PartyId], )(implicit ec: ExecutionContext, mat: Materializer, @@ -58,10 +58,14 @@ abstract class BatchedMultiDomainExpiredContractTrigger[ PackageIdResolver.Package.SpliceAmulet, expiredContracts, batchSize, - )(c => stakeholders(c.payload)) + )(c => getStakeholders(c.payload)) .map { _.toSeq.flatMap { - case (Some(version), contractBatches) => contractBatches.map(Batch(pkg, version, _)) + case (Some(version), contractBatches) => + contractBatches.map { contracts => + val stakeholders = contracts.flatMap(c => getStakeholders(c.payload)).toSet + Batch(pkg, version, contracts, stakeholders) + } case (None, contracts) => logger.warn( show"No vetted $pkg version for ${contracts.flatten.map { _.contractId.contractId }}" @@ -92,6 +96,7 @@ object BatchedMultiDomainExpiredContractTrigger { expiredContracts: Seq[ AssignedContract[TCid, T] ], + stakeholders: Set[PartyId], ) extends PrettyPrinting { override def pretty: Pretty[this.type] = prettyOfClass( @@ -99,6 +104,7 @@ object BatchedMultiDomainExpiredContractTrigger { param("vettedVersion", _.vettedVersion), param("numExpiredContracts", _.expiredContracts.size), param("expiredContractCids", _.expiredContracts.map(_.contractId.contractId.unquoted)), + param("stakeholders", _.stakeholders), ) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponV2Trigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponV2Trigger.scala index 533bdab3fe..9c1ec54266 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponV2Trigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponV2Trigger.scala @@ -6,16 +6,16 @@ package org.lfdecentralizedtrust.splice.sv.automation.delegatebased import org.lfdecentralizedtrust.splice.automation.* import org.lfdecentralizedtrust.splice.codegen.java.splice import org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.AmuletRules_ClaimExpiredRewardsV2 -import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.tracing.TraceContext import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import scala.concurrent.{ExecutionContext, Future} -import ExpireRewardCouponV2Trigger.* +import ExpireRewardCouponV2Trigger.{Task, Coupon, CouponCid, getStakeholders} import org.lfdecentralizedtrust.splice.environment.{DarResources, PackageIdResolver} import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.* @@ -35,7 +35,7 @@ class ExpireRewardCouponV2Trigger( splice.amulet.RewardCouponV2.COMPANION, svTaskContext.vettingLookupService, PackageIdResolver.Package.SpliceAmulet, - payload => (payload.dso +: observerParties(payload)).map(PartyId.tryFromProtoPrimitive(_)), + getStakeholders, ) with SvTaskBasedTrigger[Task] { private val store = svTaskContext.dsoStore @@ -54,8 +54,11 @@ class ExpireRewardCouponV2Trigger( ) } else { val cids = expiredCoupons.map(_.contractId).asJava - val expiryObservers = - expiredCoupons.flatMap(c => observerParties(c.payload)).distinct.sorted + val expiryInformees = (task.work.stakeholders - store.key.dsoParty) + .map(_.toProtoPrimitive) + .toSeq + .distinct + .sorted for { dsoRules <- store.getDsoRules() amuletRules <- store.getAmuletRules() @@ -64,7 +67,7 @@ class ExpireRewardCouponV2Trigger( amuletRules.contractId, new AmuletRules_ClaimExpiredRewardsV2( cids, - expiryObservers.asJava, + expiryInformees.asJava, ), controller, ) @@ -84,7 +87,7 @@ class ExpireRewardCouponV2Trigger( } } -object ExpireRewardCouponV2Trigger { +object ExpireRewardCouponV2Trigger extends ContractStakeholders[splice.amulet.RewardCouponV2] { private type CouponCid = splice.amulet.RewardCouponV2.ContractId private type Coupon = splice.amulet.RewardCouponV2 @@ -93,7 +96,10 @@ object ExpireRewardCouponV2Trigger { BatchedMultiDomainExpiredContractTrigger.Batch[CouponCid, Coupon] ] - private def observerParties(coupon: Coupon): Seq[String] = - if (coupon.providerIsObserver) coupon.provider +: coupon.beneficiary.toScala.toList - else Seq.empty + override def informees(payload: splice.amulet.RewardCouponV2): Seq[String] = if ( + payload.providerIsObserver + ) payload.provider +: payload.beneficiary.toScala.toList + else Seq.empty + + override def dso(payload: splice.amulet.RewardCouponV2): String = payload.dso } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala index 26d51e6196..93c303d6f9 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala @@ -20,7 +20,6 @@ import org.lfdecentralizedtrust.splice.sv.store.{ExpiredRewardCouponsBatch, Igno import org.lfdecentralizedtrust.splice.util.{AssignedContract, Contract} import org.lfdecentralizedtrust.splice.util.PrettyInstances.* import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} -import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.util.ShowUtil.* import com.digitalasset.canton.util.MonadUtil @@ -28,6 +27,7 @@ import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.store.PageLimit +import org.lfdecentralizedtrust.splice.codegen.java.splice import java.util.Optional import scala.concurrent.{ExecutionContext, Future} @@ -35,7 +35,12 @@ import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.* import scala.util.Random import ExpireRewardCouponsTrigger.Task +import org.lfdecentralizedtrust.splice.codegen.java.splice.validatorlicense.{ + ValidatorFaucetCoupon, + ValidatorLivenessActivityRecord, +} import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders class ExpireRewardCouponsTrigger( override protected val context: TriggerContext, @@ -89,33 +94,27 @@ class ExpireRewardCouponsTrigger( SpliceAmulet, batch.validatorCoupons, svTaskContext.delegatelessAutomationExpiredRewardCouponBatchSize, - )(c => Seq(c.payload.dso, c.payload.user).map(PartyId.tryFromProtoPrimitive(_))) + )(c => ValidatorCoupons.getStakeholders(c.payload)) appCoupons <- svTaskContext.vettingLookupService.splitBatch( SpliceAmulet, batch.appCoupons, svTaskContext.delegatelessAutomationExpiredRewardCouponBatchSize, - )(c => - (Seq(c.payload.dso, c.payload.provider) ++ c.payload.beneficiary.toScala.toList) - .map(PartyId.tryFromProtoPrimitive(_)) - ) + )(c => AppRewardCoupons.getStakeholders(c.payload)) svRewardCoupons <- svTaskContext.vettingLookupService.splitBatch( SpliceAmulet, batch.svRewardCoupons, svTaskContext.delegatelessAutomationExpiredRewardCouponBatchSize, - )(c => - Seq(c.payload.dso, c.payload.sv, c.payload.beneficiary) - .map(PartyId.tryFromProtoPrimitive(_)) - ) + )(c => SvRewardCoupons.getStakeholders(c.payload)) validatorFaucets <- svTaskContext.vettingLookupService.splitBatch( SpliceAmulet, batch.validatorFaucets, svTaskContext.delegatelessAutomationExpiredRewardCouponBatchSize, - )(c => Seq(c.payload.dso, c.payload.validator).map(PartyId.tryFromProtoPrimitive(_))) + )(c => ValidatorFaucetCoupons.getStakeholders(c.payload)) validatorLivenessActivityRecords <- svTaskContext.vettingLookupService.splitBatch( SpliceAmulet, batch.validatorLivenessActivityRecords, svTaskContext.delegatelessAutomationExpiredRewardCouponBatchSize, - )(c => Seq(c.payload.dso, c.payload.validator).map(PartyId.tryFromProtoPrimitive(_))) + )(c => ValidatorLivenessActivityRecords.getStakeholders(c.payload)) } yield { val emptyBatch = ExpiredRewardCouponsBatch( closedRoundCid = batch.closedRoundCid, @@ -157,18 +156,17 @@ class ExpireRewardCouponsTrigger( override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - val informees = - (task.batch.validatorCoupons.map(_.payload.user) ++ task.batch.appCoupons.flatMap(c => - Seq(c.payload.provider) ++ c.payload.beneficiary.toScala - ) ++ - task.batch.svRewardCoupons.map(_.payload.beneficiary) ++ task.batch.validatorFaucets.map( - _.payload.validator - ) ++ task.batch.validatorLivenessActivityRecords.map(_.payload.validator)) - .map(PartyId.tryFromProtoPrimitive(_)) - .toSet + val informees = ValidatorCoupons.getInformeesFromContracts(task.batch.validatorCoupons) ++ + AppRewardCoupons.getInformeesFromContracts(task.batch.appCoupons) ++ + SvRewardCoupons.getInformeesFromContracts(task.batch.svRewardCoupons) ++ + ValidatorFaucetCoupons.getInformeesFromContracts(task.batch.validatorFaucets) ++ + ValidatorLivenessActivityRecords.getInformeesFromContracts( + task.batch.validatorLivenessActivityRecords + ) completeWithIgnoredAmuletVersionCheck( task.vettedAmuletVersion.toString, informees, + store.dsoPartyId, enableUnresponsivePartiesAutoIgnore = true, )(completeExpiryTaskAsDsoDelegate(task, controller)) } @@ -318,3 +316,35 @@ object ExpireRewardCouponsTrigger { ) } } + +object ValidatorCoupons extends ContractStakeholders[splice.amulet.ValidatorRewardCoupon] { + override def informees(payload: splice.amulet.ValidatorRewardCoupon): Seq[String] = Seq( + payload.user + ) + override def dso(payload: splice.amulet.ValidatorRewardCoupon): String = payload.dso +} + +object AppRewardCoupons extends ContractStakeholders[splice.amulet.AppRewardCoupon] { + override def informees(payload: splice.amulet.AppRewardCoupon): Seq[String] = + Seq(payload.provider) ++ payload.beneficiary.toScala.toList + override def dso(payload: splice.amulet.AppRewardCoupon): String = payload.dso +} + +object SvRewardCoupons extends ContractStakeholders[splice.amulet.SvRewardCoupon] { + override def informees(payload: splice.amulet.SvRewardCoupon): Seq[String] = + Seq(payload.sv, payload.beneficiary) + override def dso(payload: splice.amulet.SvRewardCoupon): String = payload.dso +} + +object ValidatorFaucetCoupons extends ContractStakeholders[ValidatorFaucetCoupon] { + override def informees(payload: ValidatorFaucetCoupon): Seq[String] = Seq(payload.validator) + override def dso(payload: ValidatorFaucetCoupon): String = payload.dso +} + +object ValidatorLivenessActivityRecords + extends ContractStakeholders[ValidatorLivenessActivityRecord] { + override def informees(payload: ValidatorLivenessActivityRecord): Seq[String] = Seq( + payload.validator + ) + override def dso(payload: ValidatorLivenessActivityRecord): String = payload.dso +} diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala index 88f8009773..c5bba7e680 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala @@ -12,12 +12,13 @@ import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import scala.concurrent.{ExecutionContext, Future} -import ExpiredAmuletAllocationTrigger.* +import ExpiredAmuletAllocationTrigger.{Task, getStakeholders} import com.digitalasset.canton.util.MonadUtil import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import scala.jdk.CollectionConverters.* @@ -41,12 +42,7 @@ class ExpiredAmuletAllocationTrigger( splice.amuletallocation.AmuletAllocation.COMPANION, svTaskContext.vettingLookupService, PackageIdResolver.Package.SpliceAmulet, - allocation => - Seq( - allocation.allocation.transferLeg.sender, - allocation.allocation.settlement.executor, - svTaskContext.dsoStore.key.dsoParty.partyId.toProtoPrimitive, - ).map(PartyId.tryFromProtoPrimitive), + getStakeholders, ) with SvTaskBasedTrigger[Task] with IgnoredAmuletVersionGuard { @@ -56,35 +52,29 @@ class ExpiredAmuletAllocationTrigger( override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - val informees = task.work.expiredContracts.flatMap { contract => - val sender = PartyId.tryFromProtoPrimitive(contract.payload.allocation.transferLeg.sender) - val executor = PartyId.tryFromProtoPrimitive(contract.payload.allocation.settlement.executor) - Seq(sender, executor) - }.toSet completeWithIgnoredAmuletVersionCheck( task.work.vettedVersion.toString, - informees, + task.work.stakeholders, + store.key.dsoParty, enableUnresponsivePartiesAutoIgnore = true, - )(completeExpiryTaskAsDsoDelegate(task, controller, informees)) + )(completeExpiryTaskAsDsoDelegate(task, controller)) } private def completeExpiryTaskAsDsoDelegate( task: Task, controller: String, - informees: Set[PartyId], )(implicit tc: TraceContext): Future[TaskOutcome] = { - val allParties = informees + store.key.dsoParty - + val stakeholders = task.work.stakeholders for { packageSupport <- svTaskContext.packageVersionSupport.supportsExpireAmuletAllocations( - allParties.toSeq, + stakeholders.toSeq, Seq(store.key.dsoParty), clock.now, ) res <- if (!packageSupport.supported) { logger.info( - s"Skipping expiry of ${task.work.expiredContracts.size} allocations because not all parties have vetted the required Amulet package version. Parties: ${allParties + s"Skipping expiry of ${task.work.expiredContracts.size} allocations because not all parties have vetted the required Amulet package version. Parties: ${stakeholders .mkString(", ")}" ) Future.successful( @@ -180,7 +170,8 @@ class ExpiredAmuletAllocationTrigger( } } -object ExpiredAmuletAllocationTrigger { +object ExpiredAmuletAllocationTrigger + extends ContractStakeholders[splice.amuletallocation.AmuletAllocation] { type Task = ScheduledTaskTrigger.ReadyTask[ BatchedMultiDomainExpiredContractTrigger.Batch[ @@ -188,4 +179,10 @@ object ExpiredAmuletAllocationTrigger { splice.amuletallocation.AmuletAllocation, ] ] + + override def informees(payload: splice.amuletallocation.AmuletAllocation): Seq[String] = + Seq(payload.allocation.transferLeg.sender, payload.allocation.settlement.executor) + + override def dso(payload: splice.amuletallocation.AmuletAllocation): String = + payload.allocation.transferLeg.instrumentId.admin } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala index 43f68ffe8a..af5142be8b 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala @@ -4,7 +4,6 @@ package org.lfdecentralizedtrust.splice.sv.automation.delegatebased import com.digitalasset.canton.time.Clock -import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.tracing.TraceContext import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer @@ -15,6 +14,7 @@ import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerC import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.util.{ChoiceContextWithDisclosures, TokenStandardMetadata} +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import scala.concurrent.{ExecutionContext, Future} import scala.jdk.CollectionConverters.* @@ -42,7 +42,7 @@ class ExpiredAmuletAllocationV2Trigger( splice.amuletallocationv2.AmuletAllocationV2.COMPANION, svTaskContext.vettingLookupService, PackageIdResolver.Package.SpliceAmulet, - ExpiredAmuletAllocationV2Trigger.allocationV2Stakeholders, + ExpiredAmuletAllocationV2Trigger.getStakeholders, ) with SvTaskBasedTrigger[ExpiredAmuletAllocationV2Trigger.Task] with IgnoredAmuletVersionGuard { @@ -55,32 +55,29 @@ class ExpiredAmuletAllocationV2Trigger( )(implicit tc: TraceContext ): Future[TaskOutcome] = { - val expiredStakeholders = task.work.expiredContracts.flatMap { contract => - ExpiredAmuletAllocationV2Trigger.allocationV2Stakeholders(contract.payload) - }.toSet completeWithIgnoredAmuletVersionCheck( task.work.vettedVersion.toString, - expiredStakeholders, + task.work.stakeholders, + store.key.dsoParty, enableUnresponsivePartiesAutoIgnore = true, - )(completeExpiryTaskAsDsoDelegate(task, controller, expiredStakeholders)) + )(completeExpiryTaskAsDsoDelegate(task, controller)) } private def completeExpiryTaskAsDsoDelegate( task: ExpiredAmuletAllocationV2Trigger.Task, controller: String, - informees: Set[PartyId], )(implicit tc: TraceContext): Future[TaskOutcome] = { - val allParties = informees + store.key.dsoParty - + val stakeholders = task.work.stakeholders + val informees = stakeholders - store.key.dsoParty for { packageSupport <- svTaskContext.packageVersionSupport.supportsAmuletAllocationV2( - allParties.toSeq, + stakeholders.toSeq, clock.now, ) res <- if (!packageSupport.supported) { logger.info( - s"Skipping expiry of ${task.work.expiredContracts.size} allocations because not all parties have vetted the required Amulet package version. Parties: ${allParties + s"Skipping expiry of ${task.work.expiredContracts.size} allocations because not all parties have vetted the required Amulet package version. Parties: ${stakeholders .mkString(", ")}" ) Future.successful( @@ -158,7 +155,8 @@ class ExpiredAmuletAllocationV2Trigger( } -object ExpiredAmuletAllocationV2Trigger { +object ExpiredAmuletAllocationV2Trigger + extends ContractStakeholders[splice.amuletallocationv2.AmuletAllocationV2] { type Task = ScheduledTaskTrigger.ReadyTask[ BatchedMultiDomainExpiredContractTrigger.Batch[ @@ -167,9 +165,9 @@ object ExpiredAmuletAllocationV2Trigger { ] ] - private def allocationV2Stakeholders(allocation: splice.amuletallocationv2.AmuletAllocationV2) = - (Seq( - allocation.allocation.admin - ) ++ allocation.allocation.authorizer.owner.toScala.toList ++ allocation.settlement.executors.asScala) - .map(PartyId.tryFromProtoPrimitive) + override def informees(payload: splice.amuletallocationv2.AmuletAllocationV2): Seq[String] = + payload.allocation.authorizer.owner.toScala.toList ++ payload.settlement.executors.asScala + + override def dso(payload: splice.amuletallocationv2.AmuletAllocationV2): String = + payload.allocation.admin } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala index c90afbdcd0..c86f6991f1 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala @@ -12,12 +12,13 @@ import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import scala.concurrent.{ExecutionContext, Future} -import ExpiredAmuletTransferInstructionTrigger.* +import ExpiredAmuletTransferInstructionTrigger.{Task, getStakeholders} import com.digitalasset.canton.util.MonadUtil import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import scala.jdk.CollectionConverters.* @@ -41,12 +42,7 @@ class ExpiredAmuletTransferInstructionTrigger( splice.amulettransferinstruction.AmuletTransferInstruction.COMPANION, svTaskContext.vettingLookupService, PackageIdResolver.Package.SpliceAmulet, - instruction => - Seq( - instruction.transfer.sender, - instruction.transfer.receiver, - svTaskContext.dsoStore.key.dsoParty.partyId.toProtoPrimitive, - ).map(PartyId.tryFromProtoPrimitive), + getStakeholders, ) with SvTaskBasedTrigger[Task] with IgnoredAmuletVersionGuard { @@ -56,35 +52,29 @@ class ExpiredAmuletTransferInstructionTrigger( override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - val informees = task.work.expiredContracts - .map(c => PartyId.tryFromProtoPrimitive(c.payload.transfer.sender)) - .toSet ++ task.work.expiredContracts - .map(c => PartyId.tryFromProtoPrimitive(c.payload.transfer.receiver)) - .toSet completeWithIgnoredAmuletVersionCheck( task.work.vettedVersion.toString, - informees, + task.work.stakeholders, + store.key.dsoParty, enableUnresponsivePartiesAutoIgnore = true, - )(completeExpiryTaskAsDsoDelegate(task, controller, informees)) + )(completeExpiryTaskAsDsoDelegate(task, controller)) } private def completeExpiryTaskAsDsoDelegate( task: Task, controller: String, - informees: Set[PartyId], )(implicit tc: TraceContext): Future[TaskOutcome] = { - val allParties = informees + store.key.dsoParty - + val stakeholders = task.work.stakeholders for { packageSupport <- svTaskContext.packageVersionSupport.supportsExpireTransferInstructions( - allParties.toSeq, + stakeholders.toSeq, Seq(store.key.dsoParty), clock.now, ) res <- if (!packageSupport.supported) { logger.info( - s"Skipping expiry of ${task.work.expiredContracts.size} transfer instructions because not all parties have vetted the required Amulet package version. Parties: ${allParties + s"Skipping expiry of ${task.work.expiredContracts.size} transfer instructions because not all parties have vetted the required Amulet package version. Parties: ${stakeholders .mkString(", ")}" ) Future.successful( @@ -165,7 +155,8 @@ class ExpiredAmuletTransferInstructionTrigger( } } -object ExpiredAmuletTransferInstructionTrigger { +object ExpiredAmuletTransferInstructionTrigger + extends ContractStakeholders[splice.amulettransferinstruction.AmuletTransferInstruction] { type Task = ScheduledTaskTrigger.ReadyTask[ BatchedMultiDomainExpiredContractTrigger.Batch[ @@ -173,4 +164,12 @@ object ExpiredAmuletTransferInstructionTrigger { splice.amulettransferinstruction.AmuletTransferInstruction, ] ] + + override def informees( + payload: splice.amulettransferinstruction.AmuletTransferInstruction + ): Seq[String] = Seq(payload.transfer.sender, payload.transfer.receiver) + + override def dso( + payload: splice.amulettransferinstruction.AmuletTransferInstruction + ): String = payload.transfer.instrumentId.admin } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala index a62847bd17..816d82826d 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala @@ -5,17 +5,17 @@ package org.lfdecentralizedtrust.splice.sv.automation.delegatebased import org.lfdecentralizedtrust.splice.automation.* import org.lfdecentralizedtrust.splice.codegen.java.splice -import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.tracing.TraceContext import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import scala.concurrent.{ExecutionContext, Future} -import ExpiredAmuletTrigger.* import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders +import ExpiredAmuletTrigger.{Task, getStakeholders} import java.util.Optional import scala.jdk.CollectionConverters.* @@ -40,7 +40,7 @@ class ExpiredAmuletTrigger( splice.amulet.Amulet.COMPANION, svTaskContext.vettingLookupService, PackageIdResolver.Package.SpliceAmulet, - c => Seq(c.dso, c.owner).map(PartyId.tryFromProtoPrimitive(_)), + getStakeholders, ) with SvTaskBasedTrigger[Task] with IgnoredAmuletVersionGuard { @@ -49,27 +49,25 @@ class ExpiredAmuletTrigger( override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - val informees = - task.work.expiredContracts.map(c => PartyId.tryFromProtoPrimitive(c.payload.owner)).toSet completeWithIgnoredAmuletVersionCheck( task.work.vettedVersion.toString, - informees, + task.work.stakeholders, + store.key.dsoParty, enableUnresponsivePartiesAutoIgnore = true, - )(completeExpiryTaskAsDsoDelegate(task, controller, informees)) + )(completeExpiryTaskAsDsoDelegate(task, controller)) } private def completeExpiryTaskAsDsoDelegate( task: Task, controller: String, - informees: Set[PartyId], )(implicit tc: TraceContext ): Future[TaskOutcome] = { - val allParties = informees + store.key.dsoParty + val stakeholders = task.work.stakeholders for { dsoRules <- store.getDsoRules() supports24hSubmissionDelay <- svTaskContext.packageVersionSupport.supports24hSubmissionDelay( - allParties.toSeq, + stakeholders.toSeq, Seq(store.key.dsoParty), context.clock.now, ) @@ -131,7 +129,7 @@ class ExpiredAmuletTrigger( } } -object ExpiredAmuletTrigger { +object ExpiredAmuletTrigger extends ContractStakeholders[splice.amulet.Amulet] { type Task = ScheduledTaskTrigger.ReadyTask[ BatchedMultiDomainExpiredContractTrigger.Batch[ @@ -139,4 +137,8 @@ object ExpiredAmuletTrigger { splice.amulet.Amulet, ] ] + + override def informees(payload: splice.amulet.Amulet): Seq[String] = Seq(payload.owner) + + override def dso(payload: splice.amulet.Amulet): String = payload.dso } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala index 5affd08b9a..9df0611977 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala @@ -6,16 +6,16 @@ package org.lfdecentralizedtrust.splice.sv.automation.delegatebased import org.lfdecentralizedtrust.splice.automation.* import org.lfdecentralizedtrust.splice.codegen.java.splice import org.lfdecentralizedtrust.splice.environment.PackageIdResolver -import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.tracing.TraceContext import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import scala.concurrent.{ExecutionContext, Future} -import ExpiredLockedAmuletTrigger.* +import ExpiredLockedAmuletTrigger.{Task, getStakeholders} import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import java.util.Optional import scala.jdk.CollectionConverters.* @@ -40,9 +40,7 @@ class ExpiredLockedAmuletTrigger( splice.amulet.LockedAmulet.COMPANION, svTaskContext.vettingLookupService, PackageIdResolver.Package.SpliceAmulet, - c => - (Seq(c.amulet.dso, c.amulet.owner) ++ c.lock.holders.asScala) - .map(PartyId.tryFromProtoPrimitive(_)), + getStakeholders, ) with SvTaskBasedTrigger[Task] with IgnoredAmuletVersionGuard { @@ -51,30 +49,23 @@ class ExpiredLockedAmuletTrigger( override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - val informees = task.work.expiredContracts - .flatMap(c => - PartyId.tryFromProtoPrimitive( - c.payload.amulet.owner - ) +: c.payload.lock.holders.asScala.toSeq.map(PartyId.tryFromProtoPrimitive(_)) - ) - .toSet completeWithIgnoredAmuletVersionCheck( task.work.vettedVersion.toString, - informees, + task.work.stakeholders, + store.key.dsoParty, enableUnresponsivePartiesAutoIgnore = true, - )(completeExpiryTaskAsDsoDelegate(task, controller, informees)) + )(completeExpiryTaskAsDsoDelegate(task, controller)) } private def completeExpiryTaskAsDsoDelegate( task: Task, controller: String, - informees: Set[PartyId], )(implicit tc: TraceContext): Future[TaskOutcome] = { - val allParties = informees + store.key.dsoParty + val stakeholders = task.work.stakeholders for { dsoRules <- store.getDsoRules() supports24hSubmissionDelay <- svTaskContext.packageVersionSupport.supports24hSubmissionDelay( - allParties.toSeq, + stakeholders.toSeq, Seq(store.key.dsoParty), context.clock.now, ) @@ -136,7 +127,7 @@ class ExpiredLockedAmuletTrigger( } } -object ExpiredLockedAmuletTrigger { +object ExpiredLockedAmuletTrigger extends ContractStakeholders[splice.amulet.LockedAmulet] { type Task = ScheduledTaskTrigger.ReadyTask[ BatchedMultiDomainExpiredContractTrigger.Batch[ @@ -144,4 +135,9 @@ object ExpiredLockedAmuletTrigger { splice.amulet.LockedAmulet, ] ] + + override def informees(payload: splice.amulet.LockedAmulet): Seq[String] = + Seq(payload.amulet.owner) ++ payload.lock.holders.asScala + + override def dso(payload: splice.amulet.LockedAmulet): String = payload.amulet.dso } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala index 12f7940b6f..dc404c48c8 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala @@ -25,10 +25,16 @@ import com.digitalasset.canton.util.ShowUtil.* import scala.concurrent.{ExecutionContext, Future} import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.* -import FeaturedAppActivityMarkerTrigger.{CrossVersionBatch, Task} +import FeaturedAppActivityMarkerTrigger.{ + CrossVersionBatch, + Task, + getStakeholders, + getInformeesFromContracts, +} import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import java.util.Optional import scala.util.Random @@ -83,32 +89,31 @@ class FeaturedAppActivityMarkerTrigger( def splitBatchByVettingState( batch: CrossVersionBatch - )(implicit tc: TraceContext): Future[Seq[Task]] = + )(implicit tc: TraceContext): Future[Seq[Task]] = { svTaskContext.vettingLookupService .splitBatch( PackageIdResolver.Package.SpliceAmulet, batch.markers, batchSize, - )(c => - Seq(c.payload.provider, c.payload.beneficiary, c.payload.dso) - .map(PartyId.tryFromProtoPrimitive(_)) - ) + )(c => getStakeholders(c.payload)) .map { _.toSeq.flatMap { case (Some(version), markerBatches) => - markerBatches.map( + markerBatches.map { markers => Task( batch.retrievalKind, - _, + markers, version, + getInformeesFromContracts(markers), ) - ) + } case (None, markers) => logger.warn(show"No vetted amulet version for $markers") Seq.empty } } + } private def retrieveBatchesBySvIndex( dsoRules: dsorules.DsoRules @@ -195,34 +200,30 @@ class FeaturedAppActivityMarkerTrigger( override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - val informees = task.markers - .flatMap(m => Seq(m.payload.provider, m.payload.beneficiary)) - .map(PartyId.tryFromProtoPrimitive) - .toSet completeWithIgnoredAmuletVersionCheck( task.vettedAmuletVersion.toString, - informees, + task.informees, + store.key.dsoParty, // ignoring a party would mean their featured app activity markers do not get converted into rewards enableUnresponsivePartiesAutoIgnore = false, - )(completeExpiryTaskAsDsoDelegate(task, controller, informees)) + )(completeExpiryTaskAsDsoDelegate(task, controller)) } private def completeExpiryTaskAsDsoDelegate( task: Task, controller: String, - informees: Set[PartyId], )(implicit tc: TraceContext): Future[TaskOutcome] = { for { dsoRules <- store.getDsoRules() amuletRules <- store.getAmuletRules() now = context.clock.now openMiningRound <- store.getLatestUsableOpenMiningRound(now) - allParties = informees + PartyId.tryFromProtoPrimitive(dsoRules.payload.dso) + stakeholders = task.informees + store.key.dsoParty supportsConvertFeaturedAppActivityMarkerObservers <- if (svConfig.convertFeaturedAppActivityMarkerObservers) { svTaskContext.packageVersionSupport .supportsConvertFeaturedAppActivityMarkerObservers( - allParties.toSeq, + stakeholders.toSeq, context.clock.now, ) .map(_.supported) @@ -240,7 +241,7 @@ class FeaturedAppActivityMarkerTrigger( Option .when( supportsConvertFeaturedAppActivityMarkerObservers - )(allParties.toSeq.map(_.toProtoPrimitive).asJava) + )(stakeholders.toSeq.map(_.toProtoPrimitive).asJava) .toJava, ), Optional.of(controller), @@ -270,7 +271,8 @@ class FeaturedAppActivityMarkerTrigger( } yield markers.exists(_.isEmpty) } -object FeaturedAppActivityMarkerTrigger { +object FeaturedAppActivityMarkerTrigger + extends ContractStakeholders[amulet.FeaturedAppActivityMarker] { final case class CrossVersionBatch( retrievalKind: String, markers: Seq[ @@ -291,6 +293,7 @@ object FeaturedAppActivityMarkerTrigger { Contract[amulet.FeaturedAppActivityMarker.ContractId, amulet.FeaturedAppActivityMarker] ], vettedAmuletVersion: PackageVersion, + informees: Set[PartyId], ) extends PrettyPrinting { override def pretty: Pretty[this.type] = prettyOfClass( @@ -298,6 +301,12 @@ object FeaturedAppActivityMarkerTrigger { param("numMarkers", _.markers.size), param("vettedAmuletVersion", _.vettedAmuletVersion), param("markerCids", _.markers.map(_.contractId.contractId.unquoted)), + param("informees", _.informees), ) } + + override def informees(payload: amulet.FeaturedAppActivityMarker): Seq[String] = + Seq(payload.provider, payload.beneficiary) + + override def dso(payload: amulet.FeaturedAppActivityMarker): String = payload.dso } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala index 3e7c3ede52..9d15092d0a 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala @@ -21,11 +21,14 @@ trait IgnoredAmuletVersionGuard { protected def completeWithIgnoredAmuletVersionCheck( vettedVersion: String, - expiredOwners: Set[PartyId], + stakeholders: Set[PartyId], + dsoParty: PartyId, enableUnresponsivePartiesAutoIgnore: Boolean, )( fallback: => Future[TaskOutcome] )(implicit ec: ExecutionContext): Future[TaskOutcome] = { + // ensure we do not ignore the DSO party itself, even if it is unresponsive + val expiredOwners = stakeholders - dsoParty if ( svConfig.allIgnoredAmuletVersions.contains(vettedVersion) && svConfig.parameters.enabledFeatures.ignorePartyIdWithIgnoredAmulet diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/util/ContractStakeholders.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/util/ContractStakeholders.scala new file mode 100644 index 0000000000..b7782d80ff --- /dev/null +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/util/ContractStakeholders.scala @@ -0,0 +1,30 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.sv.util + +import com.digitalasset.canton.topology.PartyId +import org.lfdecentralizedtrust.splice.util.Contract + +trait ContractStakeholders[T] { + + def informees(payload: T): Seq[String] + + def dso(payload: T): String + + final def getStakeholders(payload: T): Seq[PartyId] = + getInformees(payload) :+ getDsoParty(payload) + + private final def getInformees(payload: T): Seq[PartyId] = + informees(payload).map(PartyId.tryFromProtoPrimitive) + + private final def getDsoParty(payload: T): PartyId = + PartyId.tryFromProtoPrimitive(dso(payload)) + + // ExpireRewardCouponTrigger and FeaturedAppActivityMarkerTrigger do not use BatchedMultiDomainExpiredContractTrigger + final def getInformeesFromContracts[TCid]( + contracts: Seq[Contract[TCid, T]] + ): Set[PartyId] = + contracts.flatMap(c => getInformees(c.payload)).toSet + +} From 0b834bf4f5bf3b325986f488e5436209f73b4b56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Fri, 17 Jul 2026 13:50:45 +0200 Subject: [PATCH 073/329] Bump VERSION and LATEST_RELEASE (#6459) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Oriol Muñoz --- LATEST_RELEASE | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LATEST_RELEASE b/LATEST_RELEASE index 592e815ea9..e196726d2b 100644 --- a/LATEST_RELEASE +++ b/LATEST_RELEASE @@ -1 +1 @@ -0.6.12 +0.6.13 diff --git a/VERSION b/VERSION index e196726d2b..fcbaa84781 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.6.13 +0.6.14 From ab53e3b6100ac30b89894b411d8b92858d78ca1c Mon Sep 17 00:00:00 2001 From: Tim Date: Fri, 17 Jul 2026 14:39:42 +0200 Subject: [PATCH 074/329] Improved Scrollable Party- and Contract Ids (#6430) Signed-off-by: Tim Pelzer --- .../components/copyable-identifier.test.tsx | 85 +++++++++++ apps/sv/frontend/src/components/Layout.tsx | 7 +- .../src/components/PartyIdScrollTracks.tsx | 107 +++++++++++++ .../components/beta/CopyableIdentifier.tsx | 85 +++++++---- .../src/components/beta/MemberIdentifier.tsx | 13 +- .../src/components/beta/identifierStyles.ts | 144 ++++++++++++++++++ .../form-components/SelectField.tsx | 5 +- .../components/form-components/TextField.tsx | 13 +- ...UnallocatedUnclaimedActivityRecordForm.tsx | 1 + .../forms/GrantRevokeFeaturedAppForm.tsx | 3 + .../governance/ProposalDetailsContent.tsx | 4 +- .../governance/ProposalListingSection.tsx | 5 +- .../components/governance/ProposalSummary.tsx | 48 +++++- .../src/hooks/useHorizontalScrollMetrics.ts | 57 +++++++ 14 files changed, 525 insertions(+), 52 deletions(-) create mode 100644 apps/sv/frontend/src/__tests__/components/copyable-identifier.test.tsx create mode 100644 apps/sv/frontend/src/components/PartyIdScrollTracks.tsx create mode 100644 apps/sv/frontend/src/components/beta/identifierStyles.ts create mode 100644 apps/sv/frontend/src/hooks/useHorizontalScrollMetrics.ts diff --git a/apps/sv/frontend/src/__tests__/components/copyable-identifier.test.tsx b/apps/sv/frontend/src/__tests__/components/copyable-identifier.test.tsx new file mode 100644 index 0000000000..0f2d6ec304 --- /dev/null +++ b/apps/sv/frontend/src/__tests__/components/copyable-identifier.test.tsx @@ -0,0 +1,85 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { GlobalStyles, ThemeProvider } from '@mui/material'; +import { PartyId, theme } from '@canton-network/splice-common-frontend'; +import { describe, expect, test } from 'vitest'; + +import CopyableIdentifier from '../../components/beta/CopyableIdentifier'; +import MemberIdentifier from '../../components/beta/MemberIdentifier'; +import { partyIdScrollGlobalStyles } from '../../components/beta/identifierStyles'; +import PartyIdScrollTracks from '../../components/PartyIdScrollTracks'; + +const LONG_CONTRACT_ID = + '00deadbeefcafebabe0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'; + +const LONG_PARTY_ID = `digital-asset-2::12200eab17c2b87a3da9f7b3b81d371ff794a4515fa3a0b258422a251d6148b031d`; + +const NarrowContainer: React.FC<{ children: React.ReactNode }> = ({ children }) => ( +
{children}
+); + +describe('CopyableIdentifier', () => { + test('renders the full contract ID for horizontal scrolling', () => { + render(); + + expect(screen.getByTestId('contract-id-value')).toHaveTextContent(LONG_CONTRACT_ID); + expect(screen.getByTestId('contract-id-scroll')).toHaveStyle({ overflowX: 'auto' }); + }); + + test('shows a scroll track below the identifier when content overflows', async () => { + render( + + + + ); + + const scroll = screen.getByTestId('contract-id-scroll'); + Object.defineProperty(scroll, 'scrollWidth', { configurable: true, value: 400 }); + Object.defineProperty(scroll, 'clientWidth', { configurable: true, value: 100 }); + fireEvent.scroll(scroll); + + await waitFor(() => { + expect(screen.getByTestId('contract-id-scroll-track')).toBeInTheDocument(); + }); + + expect(screen.getByTestId('contract-id-scroll-track')).toHaveStyle({ + opacity: '0', + height: '0px', + }); + }); +}); + +describe('MemberIdentifier', () => { + test('renders the full party ID instead of an abbreviated preview', () => { + render( + + ); + + expect(screen.getByTestId('member-value')).toHaveTextContent(LONG_PARTY_ID); + expect(screen.getByTestId('member-value')).not.toHaveTextContent('...'); + expect(screen.getByTestId('member-scroll')).toHaveStyle({ overflowX: 'auto' }); + }); +}); + +describe('common PartyId', () => { + test('does not ellipsize the party ID when SV scroll styles are applied', async () => { + render( + + + + + + + + ); + + const input = await screen.findByTestId('sv-party-id-input'); + const partyIdRoot = input.closest('.party-id'); + + expect(input).toHaveDisplayValue(LONG_PARTY_ID); + expect(input).toHaveStyle({ textOverflow: 'clip' }); + expect(partyIdRoot).toHaveClass('identifier-scroll-area'); + expect(partyIdRoot?.querySelector('.party-id-scroll-track')).not.toBeNull(); + }); +}); diff --git a/apps/sv/frontend/src/components/Layout.tsx b/apps/sv/frontend/src/components/Layout.tsx index 8c69b6b673..80c3facaea 100644 --- a/apps/sv/frontend/src/components/Layout.tsx +++ b/apps/sv/frontend/src/components/Layout.tsx @@ -9,10 +9,13 @@ import { } from '@canton-network/splice-common-frontend'; import { Logout } from '@mui/icons-material'; -import { Box, Button, Divider, Stack, Typography } from '@mui/material'; +import { Box, Button, Divider, GlobalStyles, Stack, Typography } from '@mui/material'; import Container from '@mui/material/Container'; import Link from '@mui/material/Link'; +import { partyIdScrollGlobalStyles } from './beta/identifierStyles'; +import PartyIdScrollTracks from './PartyIdScrollTracks'; + import { useFeatureSupport } from '../contexts/SvContext'; import { useNetworkInstanceName } from '../hooks/index'; import { useSvConfig } from '../utils'; @@ -49,6 +52,8 @@ const Layout: React.FC = (props: LayoutProps) => { return ( + + {networkInstanceName === undefined ? ( <> ) : ( diff --git a/apps/sv/frontend/src/components/PartyIdScrollTracks.tsx b/apps/sv/frontend/src/components/PartyIdScrollTracks.tsx new file mode 100644 index 0000000000..0a074f93c1 --- /dev/null +++ b/apps/sv/frontend/src/components/PartyIdScrollTracks.tsx @@ -0,0 +1,107 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { useLayoutEffect } from 'react'; + +import { computeScrollMetrics } from '../hooks/useHorizontalScrollMetrics'; + +const nodeContainsPartyId = (node: Node): boolean => + node instanceof HTMLElement && + (node.classList.contains('party-id') || node.querySelector('.party-id') !== null); + +const usePartyIdScrollTracks = (): void => { + useLayoutEffect(() => { + const cleanups = new Map void>(); + let scanScheduled = false; + + const detachTrack = (partyIdRoot: HTMLElement) => { + const cleanup = cleanups.get(partyIdRoot); + if (!cleanup) return; + cleanup(); + cleanups.delete(partyIdRoot); + }; + + const attachTrack = (partyIdRoot: HTMLElement) => { + if (cleanups.has(partyIdRoot)) return; + + const input = partyIdRoot.querySelector('.MuiInputBase-input.Mui-disabled'); + if (!input) return; + + partyIdRoot.classList.add('identifier-scroll-area'); + + const track = document.createElement('div'); + track.className = 'party-id-scroll-track'; + track.setAttribute('aria-hidden', 'true'); + + const thumb = document.createElement('div'); + thumb.className = 'party-id-scroll-thumb'; + track.appendChild(thumb); + partyIdRoot.appendChild(track); + + const update = () => { + const metrics = computeScrollMetrics(input); + track.style.display = metrics.canScroll ? 'block' : 'none'; + if (!metrics.canScroll) return; + + thumb.style.transform = `translateX(${metrics.thumbLeftPercent}%) scaleX(${metrics.thumbWidthPercent / 100})`; + }; + + update(); + + input.addEventListener('scroll', update, { passive: true }); + const resizeObserver = new ResizeObserver(update); + resizeObserver.observe(input); + resizeObserver.observe(partyIdRoot); + + cleanups.set(partyIdRoot, () => { + input.removeEventListener('scroll', update); + resizeObserver.disconnect(); + track.remove(); + partyIdRoot.classList.remove('identifier-scroll-area'); + }); + }; + + const scan = () => { + for (const partyIdRoot of cleanups.keys()) { + if (!document.body.contains(partyIdRoot)) { + detachTrack(partyIdRoot); + } + } + document.querySelectorAll('.party-id').forEach(attachTrack); + }; + + const scheduleScan = () => { + if (scanScheduled) return; + scanScheduled = true; + requestAnimationFrame(() => { + scanScheduled = false; + scan(); + }); + }; + + scan(); + + const mutationObserver = new MutationObserver(mutations => { + const shouldScan = mutations.some( + mutation => + mutation.removedNodes.length > 0 || + Array.from(mutation.addedNodes).some(nodeContainsPartyId) + ); + if (shouldScan) scheduleScan(); + }); + + mutationObserver.observe(document.body, { childList: true, subtree: true }); + + return () => { + mutationObserver.disconnect(); + [...cleanups.keys()].forEach(detachTrack); + }; + }, []); +}; + +const PartyIdScrollTracks: React.FC = () => { + usePartyIdScrollTracks(); + return null; +}; + +export default PartyIdScrollTracks; diff --git a/apps/sv/frontend/src/components/beta/CopyableIdentifier.tsx b/apps/sv/frontend/src/components/beta/CopyableIdentifier.tsx index c4a7cff7b1..1896cf0d47 100644 --- a/apps/sv/frontend/src/components/beta/CopyableIdentifier.tsx +++ b/apps/sv/frontend/src/components/beta/CopyableIdentifier.tsx @@ -2,6 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 import { ContentCopy } from '@mui/icons-material'; import { Box, Chip, IconButton, Typography } from '@mui/material'; +import { useRef } from 'react'; + +import { useHorizontalScrollMetrics } from '../../hooks/useHorizontalScrollMetrics'; +import { scrollContainerSx, scrollTextSx, scrollThumbSx, scrollTrackSx } from './identifierStyles'; export type CopyableIdentifierSize = 'small' | 'large'; @@ -19,34 +23,61 @@ const CopyableIdentifier: React.FC = ({ badge, size, 'data-testid': testId, -}) => ( - - - {value} - - { - e.stopPropagation(); - e.preventDefault(); - navigator.clipboard.writeText(copyValue ?? value); +}) => { + const scrollRef = useRef(null); + const metrics = useHorizontalScrollMetrics(scrollRef, [value]); + const fontSize = size === 'small' ? '14px' : '18px'; + + return ( + - - - {badge !== undefined && } - -); + + + + {value} + + + {metrics.canScroll && ( + + + + )} + + { + e.stopPropagation(); + e.preventDefault(); + navigator.clipboard.writeText(copyValue ?? value); + }} + > + + + {badge !== undefined && ( + + )} + + ); +}; export default CopyableIdentifier; diff --git a/apps/sv/frontend/src/components/beta/MemberIdentifier.tsx b/apps/sv/frontend/src/components/beta/MemberIdentifier.tsx index bda90aa44c..80a50ac7ac 100644 --- a/apps/sv/frontend/src/components/beta/MemberIdentifier.tsx +++ b/apps/sv/frontend/src/components/beta/MemberIdentifier.tsx @@ -11,17 +11,6 @@ interface MemberIdentifierProps { 'data-testid': string; } -function abbreviatePartyId(partyId: string, length = 10): string { - const [partyHint, hash] = partyId.split('::'); - if (hash === undefined) { - return partyHint; - } - - const partOfHash = hash.slice(0, length); - - return `${partyHint}::${partOfHash}...`; -} - const MemberIdentifier: React.FC = ({ partyId, isYou, @@ -29,7 +18,7 @@ const MemberIdentifier: React.FC = ({ 'data-testid': testId, }) => ( = { + minWidth: 0, + overflowX: 'auto', + overflowY: 'hidden', + ...hiddenScrollbarSx, +}; + +export const scrollTextSx: SxProps = { + display: 'inline-block', + width: 'max-content', + minWidth: '100%', + whiteSpace: 'nowrap', + textOverflow: 'clip', +}; + +export const scrollableIdentifierFieldSx: SxProps = { + fontFamily: 'Source Code Pro, monospace', + ...scrollTextSx, +}; + +const scrollableInputTextSx = { + fontFamily: 'Source Code Pro, monospace', + overflowX: 'auto', + textOverflow: 'clip', + whiteSpace: 'nowrap', + ...hiddenScrollbarSx, +} as const; + +export const scrollableTextFieldSx: SxProps = { + '& .MuiOutlinedInput-input': scrollableInputTextSx, +}; + +export const scrollableSelectFieldSx: SxProps = { + '& .MuiSelect-select': { + ...scrollableInputTextSx, + display: 'block', + width: '100%', + maxWidth: '100%', + }, +}; + +export const scrollTrackSx: SxProps = { + height: 0, + opacity: 0, + overflow: 'hidden', + mt: 0, + borderRadius: 1, + bgcolor: 'rgba(255, 255, 255, 0.12)', + position: 'relative', + flexShrink: 0, + transition: 'opacity 0.15s ease, height 0.15s ease, margin-top 0.15s ease', + '.identifier-scroll-area:hover &': { + height: 4, + opacity: 1, + mt: 0.5, + bgcolor: 'rgba(255, 255, 255, 0.18)', + }, +}; + +export const scrollThumbSx = ( + thumbLeftPercent: number, + thumbWidthPercent: number +): SxProps => ({ + position: 'absolute', + top: 0, + bottom: 0, + left: `${thumbLeftPercent}%`, + width: `${thumbWidthPercent}%`, + borderRadius: 1, + bgcolor: 'rgba(255, 255, 255, 0.35)', + transition: 'background-color 0.15s ease', + '.identifier-scroll-area:hover &': { + bgcolor: 'rgba(255, 255, 255, 0.72)', + }, +}); + +export const partyIdScrollGlobalStyles = { + '.party-id': { + position: 'relative', + minWidth: 0, + maxWidth: '100%', + }, + '.party-id .MuiInputBase-root': { + overflow: 'visible !important', + minWidth: 0, + maxWidth: '100%', + width: '100%', + }, + '.party-id .MuiInputBase-input.Mui-disabled': { + overflowX: 'auto', + textOverflow: 'clip !important', + whiteSpace: 'nowrap', + scrollbarWidth: 'none', + msOverflowStyle: 'none', + }, + '.party-id .MuiInputBase-input.Mui-disabled::-webkit-scrollbar': { + display: 'none', + }, + '.party-id-scroll-track': { + position: 'absolute', + left: 0, + right: '40px', + bottom: 0, + height: 0, + opacity: 0, + overflow: 'hidden', + borderRadius: '4px', + backgroundColor: 'rgba(255, 255, 255, 0.12)', + pointerEvents: 'none', + transition: 'opacity 0.15s ease, height 0.15s ease', + }, + '.party-id.identifier-scroll-area:hover .party-id-scroll-track': { + height: '4px', + opacity: 1, + backgroundColor: 'rgba(255, 255, 255, 0.18)', + }, + '.party-id-scroll-thumb': { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + width: '100%', + transformOrigin: 'left center', + borderRadius: '4px', + backgroundColor: 'rgba(255, 255, 255, 0.35)', + transition: 'background-color 0.15s ease', + }, + '.party-id.identifier-scroll-area:hover .party-id-scroll-thumb': { + backgroundColor: 'rgba(255, 255, 255, 0.72)', + }, +} as const; diff --git a/apps/sv/frontend/src/components/form-components/SelectField.tsx b/apps/sv/frontend/src/components/form-components/SelectField.tsx index a6513bc4d4..95bb6d8fb5 100644 --- a/apps/sv/frontend/src/components/form-components/SelectField.tsx +++ b/apps/sv/frontend/src/components/form-components/SelectField.tsx @@ -12,6 +12,7 @@ import { } from '@mui/material'; import type { FormEvent } from 'react'; import { useFieldContext } from '../../hooks/formContext'; +import { scrollableSelectFieldSx } from '../beta/identifierStyles'; export type Option = { key: string; value: string }; export interface SelectFieldProps { @@ -21,10 +22,11 @@ export interface SelectFieldProps { onChange?: () => void; disabled?: boolean; placeholder?: string; + scrollableIdentifier?: boolean; } export const SelectField: React.FC = props => { - const { title, options, id, disabled = false, placeholder } = props; + const { title, options, id, disabled = false, placeholder, scrollableIdentifier = false } = props; const externalOnChange = props.onChange ?? (() => {}); const field = useFieldContext(); const handleSelectValueChange = (value: string) => { @@ -45,6 +47,7 @@ export const SelectField: React.FC = props => { { if (!selected) { return showPlaceholder ? ( @@ -68,6 +73,14 @@ export const SelectField: React.FC = props => { disabled={disabled} id={`${id}-dropdown`} data-testid={id} + sx={ + scrollableIdentifier + ? theme => ({ + ...(typeof selectFieldSx === 'function' ? selectFieldSx(theme) : selectFieldSx), + ...scrollableSelectFieldSx, + }) + : selectFieldSx + } inputProps={{ 'data-testid': `${id}-dropdown`, onChange: (e: FormEvent) => { diff --git a/apps/sv/frontend/src/components/form-components/TextField.tsx b/apps/sv/frontend/src/components/form-components/TextField.tsx index be820eb58f..fd1cfa7931 100644 --- a/apps/sv/frontend/src/components/form-components/TextField.tsx +++ b/apps/sv/frontend/src/components/form-components/TextField.tsx @@ -9,6 +9,12 @@ import { } from '@mui/material'; import { useFieldContext } from '../../hooks/formContext'; import { scrollableTextFieldSx } from '../beta/identifierStyles'; +import { + fieldDescriptionSx, + fieldSectionSx, + fieldSectionTitleSx, + singleLineFieldSx, +} from '../../themes/fieldStyles'; export interface TextFieldProps { id: string; @@ -32,8 +38,8 @@ export const TextField: React.FC = props => { } = props; const field = useFieldContext(); return ( - - + + {title} @@ -48,7 +54,12 @@ export const TextField: React.FC = props => { }} error={!field.state.meta.isValid} helperText={ - + {field.state.meta.errors?.[0]} } @@ -58,11 +69,20 @@ export const TextField: React.FC = props => { }} inputProps={{ 'data-testid': id }} id={id} - sx={scrollableIdentifier ? scrollableTextFieldSx : undefined} + sx={ + scrollableIdentifier + ? theme => ({ + ...(typeof singleLineFieldSx === 'function' + ? singleLineFieldSx(theme) + : singleLineFieldSx), + ...scrollableTextFieldSx, + }) + : singleLineFieldSx + } {...muiTextFieldProps} /> {subtitle && ( - + {subtitle} )} diff --git a/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx b/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx index 6a303b2b50..81f722e465 100644 --- a/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx +++ b/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx @@ -227,7 +227,7 @@ export const CreateUnallocatedUnclaimedActivityRecordForm: React.FC = _ => { > {field => ( )} diff --git a/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx b/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx index 61fa337a34..98c501f330 100644 --- a/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx +++ b/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx @@ -344,7 +344,7 @@ export const GrantRevokeFeaturedAppForm: React.FC validateUrl(value), }} > - {field => } + {field => }
)} diff --git a/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx b/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx index 3f93cdfcbd..722fe87d9b 100644 --- a/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx +++ b/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx @@ -175,7 +175,7 @@ export const OffboardSvForm: React.FC = _ => { onChange: ({ value }) => validateUrl(value), }} > - {field => } + {field => } )} diff --git a/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx b/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx index 0af20b6170..56a7a311b9 100644 --- a/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx +++ b/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx @@ -288,7 +288,7 @@ export const SetAmuletConfigRulesForm: () => JSX.Element = () => { onChange: ({ value }) => validateUrl(value), }} > - {field => } + {field => } )} diff --git a/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx b/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx index 624e9c4daf..3d9b2be85d 100644 --- a/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx +++ b/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx @@ -304,7 +304,7 @@ export const SetDsoConfigRulesForm: () => JSX.Element = () => { onChange: ({ value }) => validateUrl(value), }} > - {field => } + {field => } )} diff --git a/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx b/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx index 8a535d255a..94838d058d 100644 --- a/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx +++ b/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx @@ -220,7 +220,7 @@ export const UpdateSvRewardWeightForm: React.FC = _ => { onChange: ({ value }) => validateUrl(value), }} > - {field => } + {field => } )} diff --git a/apps/sv/frontend/src/themes/fieldStyles.ts b/apps/sv/frontend/src/themes/fieldStyles.ts new file mode 100644 index 0000000000..53c308d5b1 --- /dev/null +++ b/apps/sv/frontend/src/themes/fieldStyles.ts @@ -0,0 +1,319 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import type { SxProps, Theme } from '@mui/material/styles'; + +const fieldSurfaceBackground = '#363636'; + +export const fieldSectionTitleSx: SxProps = { + fontSize: 12, + lineHeight: '22px', + fontWeight: 600, + textTransform: 'uppercase', + color: 'common.white', +}; + +export const fieldDescriptionSx: SxProps = { + fontSize: 12, + lineHeight: '22px', + color: 'text.light', +}; + +export const fieldSectionSx: SxProps = { + display: 'flex', + flexDirection: 'column', + gap: 1, +}; + +const fieldHelperSx: SxProps = { + '& .MuiFormHelperText-root': { + mx: 0, + mt: 1, + }, +}; + +const inputTypographySx = (theme: Theme) => ({ + fontSize: theme.typography.body2.fontSize, + fontWeight: theme.typography.body2.fontWeight, + fontFamily: theme.typography.body2.fontFamily, + lineHeight: '22px', + letterSpacing: 0, + color: theme.palette.text.light, + backgroundColor: 'transparent', + boxSizing: 'border-box' as const, + WebkitBoxShadow: 'none', +}); + +const fieldOutlineSx = (theme: Theme) => ({ + '& .MuiOutlinedInput-notchedOutline, & fieldset': { + border: 'none', + borderRadius: '4px', + }, + '&.Mui-error .MuiOutlinedInput-notchedOutline, &.Mui-error fieldset': { + border: `1px solid ${theme.palette.error.main}`, + }, +}); + +const fieldAdornmentSx = { + '& .MuiInputAdornment-root': { + margin: 0, + maxHeight: '22px', + height: '22px', + alignSelf: 'center', + }, + '& .MuiInputAdornment-positionEnd': { + marginLeft: 'auto', + }, + '& .MuiInputAdornment-root .MuiIconButton-root': { + color: 'common.white', + padding: 0, + width: 16, + height: 16, + '& svg': { + fontSize: 16, + }, + }, + '& .MuiSelect-icon': { + color: 'common.white', + fontSize: 16, + top: 'calc(50% - 0.5em)', + }, +}; + +const multilineSurfaceSx = (theme: Theme) => ({ + display: 'flex', + padding: '13px 16px', + justifyContent: 'space-between', + alignItems: 'flex-start', + alignContent: 'flex-start', + flexWrap: 'wrap', + rowGap: '10px', + alignSelf: 'stretch', + backgroundColor: fieldSurfaceBackground, + borderRadius: '4px', + overflow: 'hidden', + ...fieldOutlineSx(theme), +}); + +const multilineInputSx = (theme: Theme) => ({ + ...inputTypographySx(theme), + flex: 1, + width: '100%', + minWidth: '100%', + padding: 0, +}); + +export const singleLineInputRootSx: SxProps = theme => ({ + ...inputTypographySx(theme), + display: 'flex', + padding: '13px 16px', + justifyContent: 'space-between', + alignItems: 'center', + alignContent: 'center', + flexWrap: 'wrap', + rowGap: '10px', + alignSelf: 'stretch', + minHeight: 0, + height: 'auto', + backgroundColor: fieldSurfaceBackground, + borderRadius: '4px', + overflow: 'hidden', + ...fieldOutlineSx(theme), + ...fieldAdornmentSx, + '&.MuiInputBase-root, &.MuiOutlinedInput-root, &.MuiInputBase-adornedEnd': { + minHeight: 0, + height: 'auto', + }, + // Override common MuiInputBase global `.MuiOutlinedInput-input` background (neutral[10]). + '& .MuiOutlinedInput-input, & .MuiInputBase-input, & input, & .MuiSelect-select': { + ...inputTypographySx(theme), + flex: 1, + minWidth: 0, + padding: 0, + minHeight: 0, + height: 'auto', + }, + '&.MuiInputBase-sizeSmall': { + minHeight: 0, + }, + '& .MuiOutlinedInput-inputSizeSmall': { + padding: 0, + }, +}); + +const proposalSummaryInputRootSx: SxProps = theme => ({ + ...multilineSurfaceSx(theme), + height: '130px', + '& textarea, & .MuiOutlinedInput-input': { + ...multilineInputSx(theme), + alignSelf: 'stretch', + minHeight: 0, + height: '100%', + resize: 'none', + overflow: 'auto', + }, +}); + +const configFieldInputRootSx: SxProps = theme => ({ + ...inputTypographySx(theme), + display: 'flex', + width: '238px', + maxWidth: '100%', + height: '48px', + padding: '14px 24px', + justifyContent: 'flex-end', + alignItems: 'center', + gap: '10px', + boxSizing: 'border-box', + minHeight: 0, + backgroundColor: fieldSurfaceBackground, + borderRadius: '4px', + overflow: 'hidden', + ...fieldOutlineSx(theme), + // Edited state: ConfigField sets focused when value differs from default (Figma: #F3FF97 border). + '&.Mui-focused .MuiOutlinedInput-notchedOutline, &.Mui-focused fieldset': { + border: `1px solid ${theme.palette.secondary.main}`, + }, + '&.MuiInputBase-root, &.MuiOutlinedInput-root': { + minHeight: 0, + height: '48px', + }, + '& .MuiOutlinedInput-input, & .MuiInputBase-input, & input': { + ...inputTypographySx(theme), + flex: 1, + minWidth: 0, + padding: 0, + minHeight: 0, + height: 'auto', + textAlign: 'right', + }, +}); + +/** Single-line TextField wrapper — overrides common MuiInputBase global styles. */ +export const singleLineFieldSx: SxProps = theme => ({ + ...fieldHelperSx, + '& .MuiOutlinedInput-root': + typeof singleLineInputRootSx === 'function' + ? singleLineInputRootSx(theme) + : singleLineInputRootSx, +}); + +/** Proposal summary — fixed 130px height. */ +export const proposalSummaryFieldSx: SxProps = theme => ({ + ...fieldHelperSx, + '& .MuiOutlinedInput-root': + typeof proposalSummaryInputRootSx === 'function' + ? proposalSummaryInputRootSx(theme) + : proposalSummaryInputRootSx, +}); + +/** Date picker OutlinedInput root — single row; 16px inset for chevron via root padding. */ +const datePickerInputRootSx: SxProps = theme => ({ + ...inputTypographySx(theme), + display: 'flex', + flexWrap: 'nowrap', + padding: '13px 16px', + justifyContent: 'space-between', + alignItems: 'center', + alignSelf: 'stretch', + width: '100%', + minHeight: 0, + height: 'auto', + backgroundColor: fieldSurfaceBackground, + borderRadius: '4px', + overflow: 'hidden', + ...fieldOutlineSx(theme), + ...fieldAdornmentSx, + '&.MuiInputBase-root, &.MuiOutlinedInput-root, &.MuiInputBase-adornedEnd': { + minHeight: 0, + height: 'auto', + }, + '& .MuiOutlinedInput-input, & .MuiInputBase-input, & input': { + ...inputTypographySx(theme), + flex: 1, + minWidth: 0, + padding: 0, + minHeight: 0, + height: 'auto', + }, + '& .MuiInputAdornment-positionEnd': { + marginLeft: 'auto', + marginRight: 0, + flexShrink: 0, + }, +}); + +/** Date picker TextField wrapper (helper text spacing + OutlinedInput surface). */ +export const datePickerFieldSx: SxProps = theme => ({ + ...fieldHelperSx, + width: '100%', + '& .MuiOutlinedInput-root': + typeof datePickerInputRootSx === 'function' + ? datePickerInputRootSx(theme) + : datePickerInputRootSx, +}); + +/** Date picker OutlinedInput root — apply via `slotProps.input.sx` when needed. */ +export const datePickerInputSx: SxProps = datePickerInputRootSx; + +/** Select OutlinedInput root — single row; chevron inset matches date picker. */ +const selectInputRootSx: SxProps = theme => ({ + ...inputTypographySx(theme), + display: 'flex', + flexWrap: 'nowrap', + padding: '13px 16px', + justifyContent: 'space-between', + alignItems: 'center', + alignContent: 'center', + alignSelf: 'stretch', + minHeight: 0, + height: 'auto', + backgroundColor: fieldSurfaceBackground, + borderRadius: '4px', + overflow: 'hidden', + ...fieldOutlineSx(theme), + ...fieldAdornmentSx, + '&.MuiInputBase-root, &.MuiOutlinedInput-root, &.MuiInputBase-adornedEnd': { + minHeight: 0, + height: 'auto', + }, + '& .MuiOutlinedInput-input, & .MuiInputBase-input, & input, & .MuiSelect-select': { + ...inputTypographySx(theme), + flex: 1, + minWidth: 0, + padding: 0, + minHeight: 0, + height: 'auto', + }, + '&.MuiSelect-outlined .MuiSelect-select': { + paddingRight: 0, + }, + '& .MuiSelect-icon': { + color: 'common.white', + fontSize: 16, + marginLeft: 'auto', + marginRight: 0, + flexShrink: 0, + position: 'static', + top: 'auto', + right: 'auto', + }, + '&.MuiInputBase-sizeSmall': { + minHeight: 0, + }, + '& .MuiOutlinedInput-inputSizeSmall': { + padding: 0, + }, +}); + +export const selectFieldSx: SxProps = selectInputRootSx; + +/** DSO config table TextField wrapper (helper text spacing). */ +export const configFieldFieldSx: SxProps = { + ...fieldHelperSx, + width: '238px', + maxWidth: '100%', +}; + +/** DSO config table OutlinedInput root — apply via `slotProps.input.sx`. */ +export const configFieldInputSx: SxProps = configFieldInputRootSx; From e4763ced74febfd0634d9256f8a9f511f0cf2d4e Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Wed, 29 Jul 2026 14:19:26 +0200 Subject: [PATCH 146/329] Document staging branches (#6598) [static] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- DEVELOPMENT.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index a07d94875a..43c40e7805 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -19,6 +19,7 @@ - [Troubleshooting](#troubleshooting) - [Nix Issues on MacOS](#nix-issues-on-macos) - [NPM Lock file issues](#npm-lock-file-issues) +- [Staging Branches for Minor Releases](#staging-branches-for-minor-releases) # Setting up Your Development Environment @@ -473,3 +474,20 @@ Caused by: java.lang.NullPointerException: Cannot invoke "jdk.internal.platform. at java.base/jdk.internal.platform.cgroupv2.CgroupV2Subsystem.getInstance(CgroupV2Subsystem.java:80) ``` in start-canton.sh, try adding: `export ADDITIONAL_JAVA_TOOLS_OPTIONS="-XX:-UseContainerSupport"` to .envrc.private + +## Staging Branches for Minor Releases + +Daml changes, breaking API changes and new Canton protocol versions +must only be included in new minor releases. To manage changes that +are ready but cannot be merged to main until the next minor version, +create a ``staging-X.Y.0`` branch for the next minor release based off +main and merge changes for that release into that. You likely want to +regularly merge main into that to avoid it going too far out of sync. + +Note: This is intended for changes that are complete and could be +released not for incomplete changes that then may need to get backed +out again before the release in case we do not manage to finish them +completely. + +Once the time is reached where the minor should be the next weekly +release, merge the staging branch back into main. From aeb9263f4ebec7a60b6a30e42400fcd147d95fdc Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Wed, 29 Jul 2026 11:07:52 -0400 Subject: [PATCH 147/329] replace Canton source submodules with binaries where matching (#6571) * remove base-errors and google-common-protos-scala * remove empty pekko-fork * use binary util-external * fix util-external call in HttpScanAppClient * remove adjustable-clock * remove upstream-removed ledger-common * remove kms-driver-api * remove wartremover-annotations * remove scalatest-addon --------- Signed-off-by: Stephen Compall --- .../client/commands/HttpScanAppClient.scala | 2 +- build.sbt | 17 +- .../com/daml/clock/AdjustableClock.scala | 37 -- .../com/digitalasset/base/error/Alarm.scala | 48 -- .../digitalasset/base/error/BaseError.scala | 138 ------ .../base/error/BaseErrorLogger.scala | 37 -- .../digitalasset/base/error/DamlError.scala | 58 --- .../base/error/ErrorCategory.scala | 449 ------------------ .../digitalasset/base/error/ErrorClass.scala | 32 -- .../digitalasset/base/error/ErrorCode.scala | 210 -------- .../digitalasset/base/error/ErrorGroup.scala | 12 - .../base/error/ErrorResource.scala | 80 ---- .../base/error/GrpcStatuses.scala | 27 -- .../base/error/LogOnCreation.scala | 13 - .../digitalasset/base/error/RpcError.scala | 35 -- .../error/SerializableErrorComponents.scala | 315 ------------ .../base/error/samples/Example.scala | 122 ----- .../base/error/utils/DecodedCantonError.scala | 234 --------- .../base/error/utils/ErrorDetails.scala | 118 ----- .../base/error/ErrorCodeSpec.scala | 387 --------------- .../base/error/ErrorGenerator.scala | 83 ---- .../base/error/ErrorGroupSpec.scala | 36 -- .../base/error/ErrorsAssertions.scala | 131 ----- .../base/error/GrpcStatusesSpec.scala | 58 --- .../base/error/RedactedMessageSpec.scala | 27 -- .../SerializableErrorComponentsSpec.scala | 205 -------- .../error/samples/SampleClientSideSpec.scala | 15 - .../base/error/utils/BenignError.scala | 31 -- .../error/utils/DecodedCantonErrorSpec.scala | 199 -------- .../base/error/utils/ErrorDetailsSpec.scala | 70 --- .../base/error/utils/SevereError.scala | 31 -- .../config/ConfidentialConfigWriter.scala | 21 - .../canton/config/KeyStoreConfig.scala | 27 -- .../canton/config/PemFileOrString.scala | 33 -- .../canton/config/RequireTypes.scala | 418 ---------------- .../canton/discard/Implicits.scala | 14 - .../canton/time/TimeProvider.scala | 12 - .../digitalasset/canton/util/BytesUnit.scala | 60 --- .../canton/util/JarResourceUtils.scala | 34 -- .../com/digitalasset/canton/util/Mutex.scala | 92 ---- .../canton/util/VersionUtil.scala | 34 -- .../canton/config/RequireTypesTest.scala | 24 - .../crypto/kms/driver/api/KmsDriver.scala | 6 - .../kms/driver/api/KmsDriverFactory.scala | 10 - .../crypto/kms/driver/api/v1/KmsDriver.scala | 218 --------- .../driver/api/v1/KmsDriverException.scala | 17 - .../kms/driver/api/v1/KmsDriverFactory.scala | 15 - .../kms/driver/api/v1/KmsDriverHealth.scala | 30 -- .../kms/driver/api/v1/KmsDriverSpecs.scala | 74 --- .../canton/driver/api/DriverFactory.scala | 17 - .../canton/driver/api/v1/DriverFactory.scala | 59 --- .../scala/org/scalatest/AssertionsUtil.scala | 19 - .../org/scalatest/AssertionsUtilMacros.scala | 71 --- .../org/scalatest/AssertionsUtilTest.scala | 36 -- .../canton/AllowTraverseSingleContainer.scala | 8 - .../canton/DoNotDiscardLikeFuture.scala | 11 - ...oNotReturnFromSynchronizedLikeFuture.scala | 11 - .../canton/DoNotTraverseLikeFuture.scala | 11 - .../canton/FutureTransformer.scala | 14 - .../canton/GrpcServiceInvocationMethod.scala | 11 - project/BuildCommon.scala | 312 ++---------- project/CantonDependencies.scala | 7 + scripts/copy-canton.sh | 6 + 63 files changed, 62 insertions(+), 4927 deletions(-) delete mode 100644 canton/base/adjustable-clock/src/main/scala/com/daml/clock/AdjustableClock.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/Alarm.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/BaseError.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/BaseErrorLogger.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/DamlError.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorCategory.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorClass.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorCode.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorGroup.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorResource.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/GrpcStatuses.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/LogOnCreation.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/RpcError.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/SerializableErrorComponents.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/samples/Example.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/utils/DecodedCantonError.scala delete mode 100644 canton/base/errors/src/main/scala/com/digitalasset/base/error/utils/ErrorDetails.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorCodeSpec.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorGenerator.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorGroupSpec.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorsAssertions.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/GrpcStatusesSpec.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/RedactedMessageSpec.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/SerializableErrorComponentsSpec.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/samples/SampleClientSideSpec.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/BenignError.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/DecodedCantonErrorSpec.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/ErrorDetailsSpec.scala delete mode 100644 canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/SevereError.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/config/ConfidentialConfigWriter.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/config/KeyStoreConfig.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/config/PemFileOrString.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/config/RequireTypes.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/discard/Implicits.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/time/TimeProvider.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/util/BytesUnit.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/util/JarResourceUtils.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/util/Mutex.scala delete mode 100644 canton/base/util-external/src/main/scala/com/digitalasset/canton/util/VersionUtil.scala delete mode 100644 canton/base/util-external/src/test/scala/com/digitalasset/canton/config/RequireTypesTest.scala delete mode 100644 canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/KmsDriver.scala delete mode 100644 canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/KmsDriverFactory.scala delete mode 100644 canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriver.scala delete mode 100644 canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverException.scala delete mode 100644 canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverFactory.scala delete mode 100644 canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverHealth.scala delete mode 100644 canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverSpecs.scala delete mode 100644 canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/driver/api/DriverFactory.scala delete mode 100644 canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/driver/api/v1/DriverFactory.scala delete mode 100644 canton/community/lib/scalatest/src/main/scala/org/scalatest/AssertionsUtil.scala delete mode 100644 canton/community/lib/scalatest/src/main/scala/org/scalatest/AssertionsUtilMacros.scala delete mode 100644 canton/community/lib/scalatest/src/test/scala/org/scalatest/AssertionsUtilTest.scala delete mode 100644 canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/AllowTraverseSingleContainer.scala delete mode 100644 canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotDiscardLikeFuture.scala delete mode 100644 canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotReturnFromSynchronizedLikeFuture.scala delete mode 100644 canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotTraverseLikeFuture.scala delete mode 100644 canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/FutureTransformer.scala delete mode 100644 canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/GrpcServiceInvocationMethod.scala diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala index e8211bcc08..0aa87796ad 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala @@ -843,7 +843,7 @@ object HttpScanAppClient { P2PEndpoint.fromEndpointConfig( P2PEndpointConfig( uri.authority.host.address(), - RequireTypes.Port(uri.effectivePort), + RequireTypes.Port.tryCreate(uri.effectivePort), Option.when(uri.scheme == "https")( TlsClientConfig( None, diff --git a/build.sbt b/build.sbt index 767af49aa3..3f6794cef1 100644 --- a/build.sbt +++ b/build.sbt @@ -27,19 +27,10 @@ lazy val `canton-community-integration-testing` = BuildCommon.`canton-community- lazy val `canton-community-testing` = BuildCommon.`canton-community-testing` lazy val `canton-slick-fork` = BuildCommon.`canton-slick-fork` lazy val `canton-wartremover-extension` = BuildCommon.`canton-wartremover-extension` -lazy val `canton-wartremover-annotations` = BuildCommon.`canton-wartremover-annotations` -lazy val `canton-util-external` = BuildCommon.`canton-util-external` lazy val `canton-util-observability` = BuildCommon.`canton-util-observability` -lazy val `canton-pekko-fork` = BuildCommon.`canton-pekko-fork` -lazy val `canton-scalatest-addon` = BuildCommon.`canton-scalatest-addon` -lazy val `canton-ledger-common` = BuildCommon.`canton-ledger-common` lazy val `canton-ledger-api-value` = BuildCommon.`canton-ledger-api-value` lazy val `canton-ledger-json-api` = BuildCommon.`canton-ledger-json-api` -lazy val `canton-daml-adjustable-clock` = BuildCommon.`canton-daml-adjustable-clock` -lazy val `canton-base-errors` = BuildCommon.`canton-base-errors` -lazy val `canton-google-common-protos-scala` = BuildCommon.`canton-google-common-protos-scala` lazy val `canton-sequencer-driver-api` = BuildCommon.`canton-sequencer-driver-api` -lazy val `canton-kms-driver-api` = BuildCommon.`canton-kms-driver-api` lazy val `canton-community-reference-driver` = BuildCommon.`canton-community-reference-driver` lazy val `canton-observability-metrics-testing` = BuildCommon.`canton-observability-metrics-testing` lazy val `canton-traffic-enforcement-component` = BuildCommon.`canton-traffic-enforcement-component` @@ -143,9 +134,7 @@ lazy val root: Project = (project in file(".")) `canton-community-app-base`, `canton-community-synchronizer`, `canton-community-participant`, - `canton-ledger-common`, `canton-ledger-api-value`, - `canton-google-common-protos-scala`, `canton-observability-metrics-testing`, pulumi, `load-tester`, @@ -2046,9 +2035,6 @@ def mergeStrategy(oldStrategy: String => MergeStrategy): String => MergeStrategy "Log4j2Plugins.dat", ) => MergeStrategy.first - case (PathList("org", "apache", "pekko", "stream", "scaladsl", broadcasthub, _*)) - if broadcasthub.startsWith("BroadcastHub") => - MergeStrategy.first case "META-INF/versions/9/module-info.class" => MergeStrategy.discard case path if path.contains("module-info.class") => MergeStrategy.discard case PathList("org", "jline", _ @_*) => MergeStrategy.first @@ -2335,7 +2321,6 @@ lazy val `apps-dar-resources-generator` = project .in(file("apps/dar-resources-generator")) .dependsOn( - `canton-util-external`, // We include all DARs here to make sure they are available as resources. `splice-amulet-daml`, `splice-amulet-name-service-daml`, @@ -2370,6 +2355,7 @@ lazy val `apps-dar-resources-generator` = Headers.ApacheDAHeaderSettings, libraryDependencies ++= Seq( Dependencies.better_files, + CantonDependencies.canton_util_external, CantonDependencies.daml_lf_archive_reader, CantonDependencies.cats, ), @@ -2396,6 +2382,7 @@ lazy val `apps-app`: Project = // scalatestplus-selenium is lagging behind, it depends on selenium 4.12, // but that's fine as it's compatible with selenium 4.44 that we end up using libraryDependencies += "org.scalatestplus" %% "selenium-4-12" % "3.2.17.0" % "test", + libraryDependencies += CantonDependencies.scalatest_shouldmatchers, libraryDependencies += "org.seleniumhq.selenium" % "selenium-java" % "4.44.0" % "test", libraryDependencies += "eu.rekawek.toxiproxy" % "toxiproxy-java" % "2.1.4" % "test", libraryDependencies += auth0, diff --git a/canton/base/adjustable-clock/src/main/scala/com/daml/clock/AdjustableClock.scala b/canton/base/adjustable-clock/src/main/scala/com/daml/clock/AdjustableClock.scala deleted file mode 100644 index 82813de3ee..0000000000 --- a/canton/base/adjustable-clock/src/main/scala/com/daml/clock/AdjustableClock.scala +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 -package com.daml.clock - -import java.time.{Clock, Duration, Instant, ZoneId} - -@SuppressWarnings(Array("org.wartremover.warts.Var")) -final case class AdjustableClock(baseClock: Clock, var offset: Duration) extends Clock { - def fastForward(by: Duration): Unit = - offset = offset.plus(by) - - def rewind(by: Duration): Unit = - offset = offset.minus(by) - - def set(to: Instant): Unit = - offset = Duration.between(baseClock.instant(), to) - - override def getZone: ZoneId = baseClock.getZone - - override def withZone(zone: ZoneId): Clock = - if (zone == baseClock.getZone) this - else AdjustableClock(baseClock.withZone(zone), offset) - - override def millis: Long = Math.addExact(baseClock.millis, offset.toMillis) - - override def instant: Instant = baseClock.instant.plus(offset) - - override def equals(obj: Any): Boolean = obj match { - case other: AdjustableClock => baseClock == other.baseClock && offset == other.offset - case _ => false - } - - override def hashCode: Int = baseClock.hashCode ^ offset.hashCode -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/Alarm.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/Alarm.scala deleted file mode 100644 index b669afbaf3..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/Alarm.scala +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.digitalasset.base.error.ErrorCategory.{SecurityAlert, UnredactedSecurityAlert} -import io.grpc.StatusRuntimeException - -/** An alarm indicates that a different node is behaving maliciously. Alarms include situations - * where an attack has been mitigated successfully. Alarms are security relevant events that need - * to be logged in a standardized way for monitoring and auditing. - */ -abstract class AlarmErrorCode(id: String, redactDetails: Boolean = true)(implicit - parent: ErrorClass -) extends ErrorCode(id, if (redactDetails) SecurityAlert else UnredactedSecurityAlert) { - implicit override val code: AlarmErrorCode = this - -} - -trait BaseAlarm extends BaseError { - override def code: AlarmErrorCode - - override def context: Map[String, String] = - super.context ++ BaseError.extractContext(this) - - /** Report the alarm to the logger. */ - def report()(implicit logger: BaseErrorLogger): Unit = logWithContext() - - /** Reports the alarm to the logger. - * - * @return - * this alarm - */ - def reported()(implicit logger: BaseErrorLogger): this.type = { - report() - this - } - - def asGrpcError(implicit logger: BaseErrorLogger): StatusRuntimeException = - ErrorCode.asGrpcError(this)(logger) -} - -abstract class Alarm( - override val cause: String, - override val throwableO: Option[Throwable] = None, -)(implicit - override val code: AlarmErrorCode -) extends BaseAlarm diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/BaseError.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/BaseError.scala deleted file mode 100644 index a4e69bf19d..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/BaseError.scala +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.google.rpc.status.Status as ProtoStatus -import org.slf4j.event.Level - -/** The main error interface for everything that should be logged and notified. - * - * There are two ways to communicate an error to the user: write it into a log or send it as a - * string. In most cases, we'll do both: log the error appropriately locally and communicate it to - * the user by failing the API call with an error string. - */ -trait BaseError extends LocationMixin { - - /** Optional override for the log level used when this error is logged. If defined, this level - * will be used instead of the default level associated with the error code. - */ - def overrideLogLevel: Option[Level] = None - - /** The error code, usually passed in as implicit where the error class is defined */ - def code: ErrorCode - - /** A human readable string indicating the error */ - def cause: String - - /** An optional argument to log exceptions - * - * If you want to log an exception as part of your error, then use the following example: - * - * {{{ - * object MyCode extends ErrorCode(id="SUPER_DUPER_ERROR") { - * case class MyError(someString: String, throwable: Throwable) extends SomeInternalError( - * cause = "Something failed with an exception bla", - * throwableO = Some(throwable) - * ) - * } - * }}} - * - * NOTE: This throwable's details are not included the exception communicated to the gRPC clients - * so if you want them communicated, you need to explicitly add them to the e.g. context map or - * cause string. - */ - def throwableO: Option[Throwable] = None - - /** The context (declared fields) of this error - */ - def context: Map[String, String] = Map() - - /** The resources related to this error - * - * We return the set of resources via com.google.rpc.ResourceInfo. Override this method in order - * to return resource information via com.google.rpc.Status - */ - def resources: Seq[(ErrorResource, String)] = Seq() - - def logWithContext(extra: Map[String, String] = Map())(implicit - errorLoggingContext: BaseErrorLogger - ): Unit = - errorLoggingContext.logError(this, extra, overrideLogLevel) - - /** Returns retryability information of this particular error - * - * In some cases, error instances would like to provide custom retry intervals. This can be - * achieved by locally overriding this method. - * - * Do not use this to change the contract of the error categories. Non-retryable errors shouldn't - * be made retryable. Only use it for adjusting the retry intervals. - */ - def retryable: Option[ErrorCategoryRetry] = code.category.retryable - - /** Controls whether a `definite_answer` error detail is added to the gRPC status code */ - def definiteAnswerO: Option[Boolean] = None - - def rpcStatus()(implicit - loggingContext: BaseErrorLogger - ): com.google.rpc.status.Status = - ProtoStatus.fromJavaProto(ErrorCode.asGrpcStatus(this)) - -} - -trait LocationMixin { - - /** Contains the location where the error has been created. */ - val location: Option[String] = { - val stack = Thread.currentThread().getStackTrace - val thisClassName = this.getClass.getName - val idx = stack.indexWhere(_.getClassName == thisClassName) - if (idx != -1 && (idx + 1) < stack.length) { - val stackTraceElement = stack(idx + 1) - Some(s"${stackTraceElement.getFileName}:${stackTraceElement.getLineNumber}") - } else None - } -} - -object BaseError { - object RedactedMessage { - val Prefix = "An error occurred. Please contact the operator and inquire about the request" - private val regex = s"$Prefix (.+) with tid (.+)".r - - def apply(correlationId: Option[String] = None, traceId: Option[String] = None): String = - s"$Prefix ${correlationId.getOrElse("")} with tid ${traceId - .getOrElse("")}" - - def unapply(msg: String): Option[(Option[String], Option[String])] = - msg match { - case regex(corrIdO, tIdO) => - val checkedCorrelationId = Option(corrIdO).filter(_ != "") - val checkedTraceId = Option(tIdO).filter(_ != "") - Some(checkedCorrelationId -> checkedTraceId) - case _ => None - } - } - - val isRedactedMessage: String => Boolean = - _.startsWith(RedactedMessage.Prefix) - - private val ignoreFields = - Set( - "cause", - "throwable", - "loggingContext", - "definiteAnswer", - "representativeProtocolVersion", - "companionObj", - "orderedErrors", - ) - - def extractContext[D](obj: D): Map[String, String] = - obj.getClass.getDeclaredFields - .filterNot(x => ignoreFields.contains(x.getName) || x.getName.startsWith("_")) - .map { field => - field.setAccessible(true) - (field.getName, field.get(obj).toString) - } - .toMap -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/BaseErrorLogger.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/BaseErrorLogger.scala deleted file mode 100644 index dd570a1e97..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/BaseErrorLogger.scala +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import org.slf4j.event.Level - -trait BaseErrorLogger { - def logError( - err: BaseError, - extra: Map[String, String], - overrideLogLevel: Option[Level] = None, - ): Unit - def correlationId: Option[String] - def traceId: Option[String] - def properties: Map[String, String] - - // Error construction warnings/errors - def warn(message: => String): Unit - def error(message: => String, throwable: Throwable): Unit -} -object NoBaseLogging - extends NoBaseLogging(properties = Map.empty, correlationId = None, traceId = None) {} - -class NoBaseLogging( - val properties: Map[String, String], - val correlationId: Option[String], - val traceId: Option[String] = None, -) extends BaseErrorLogger { - override def logError( - err: BaseError, - extra: Map[String, String], - overrideLogLevel: Option[Level] = None, - ): Unit = () - override def warn(message: => String): Unit = () - override def error(message: => String, throwable: Throwable): Unit = () -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/DamlError.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/DamlError.scala deleted file mode 100644 index 46984becdf..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/DamlError.scala +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.google.rpc.Status -import io.grpc.StatusRuntimeException - -abstract class ContextualizedDamlError( - override val cause: String, - override val throwableO: Option[Throwable] = None, - extraContext: Map[String, Any] = Map(), -)(implicit - override val code: ErrorCode, - val logger: BaseErrorLogger, -) extends BaseError - with RpcError - with LogOnCreation { - - // Automatically log the error on generation - override def logOnCreation: Boolean = true - - def logError(): Unit = logWithContext()(logger) - - def asGrpcStatus: Status = - ErrorCode.asGrpcStatus(this)(logger) - - def asGrpcError: StatusRuntimeException = - ErrorCode.asGrpcError(this)(logger) - - override def context: Map[String, String] = - super.context ++ extraContext.view.mapValues(_.toString) - - def correlationId: Option[String] = logger.correlationId - - def traceId: Option[String] = logger.traceId -} - -/** @param definiteAnswer - * Determines the value of the `definite_answer` key in the error details - */ -class DamlErrorWithDefiniteAnswer( - override val cause: String, - override val throwableO: Option[Throwable] = None, - val definiteAnswer: Boolean = false, - extraContext: Map[String, Any] = Map(), -)(implicit - override val code: ErrorCode, - loggingContext: BaseErrorLogger, -) extends ContextualizedDamlError( - cause = cause, - throwableO = throwableO, - extraContext = extraContext, - ) { - - final override def definiteAnswerO: Option[Boolean] = Some(definiteAnswer) - -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorCategory.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorCategory.scala deleted file mode 100644 index 7509e48628..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorCategory.scala +++ /dev/null @@ -1,449 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import io.grpc.Status.Code -import org.slf4j.event.Level - -import scala.concurrent.duration.* - -/** Standard error categories - * - * Ideally, all products will return errors with appropriate error codes. Every such error code is - * associated with an error category that defines how the error will appear in the log file and on - * the api level. - */ -sealed trait ErrorCategory extends Product with Serializable { - - /** The Grpc code use to signal this error (in case it is signalled via API) */ - def grpcCode: Option[Code] - - /** The log level used to log this error on the server side */ - def logLevel: Level - - /** Default retryability information for this error category */ - def retryable: Option[ErrorCategoryRetry] - - /** If true, error details should not be emitted on the api, typically for security reasons. Must - * be true for authentication errors, permission errors and internal errors by OWASP - * recommendations. - */ - def redactDetails: Boolean - - /** Int representation of this error category */ - def asInt: Int - - /** Rank used to order severity (internal only) */ - def rank: Int -} - -object ErrorCategory { - - val all: Seq[ErrorCategory] = - Seq( - TransientServerFailure, - ContentionOnSharedResources, - DeadlineExceededRequestStateUnknown, - SystemInternalAssumptionViolated, - AuthInterceptorInvalidAuthenticationCredentials, - InsufficientPermission, - // UnredactedSecurityAlert comes before SecurityAlert with the same int representation so that - // find by int returns the unredacted one (redacted security alerts will never be found because they are redacted!) - UnredactedSecurityAlert, - SecurityAlert, - InvalidIndependentOfSystemState, - InvalidGivenCurrentSystemStateOther, - InvalidGivenCurrentSystemStateResourceExists, - InvalidGivenCurrentSystemStateResourceMissing, - InvalidGivenCurrentSystemStateSeekAfterEnd, - BackgroundProcessDegradationWarning, - InternalUnsupportedOperation, - ) - - def fromInt(ii: Int): Option[ErrorCategory] = all.find(_.asInt == ii) - - abstract class ErrorCategoryImpl( - val grpcCode: Option[Code], - val logLevel: Level, - val retryable: Option[ErrorCategoryRetry], - val redactDetails: Boolean, - val asInt: Int, - val rank: Int, - ) - - /** Service is temporarily unavailable - */ - @Description("""One of the services required to process the request was not available. - |The request might or might not have been processed, as the server aborted the request while it was being processed. - |Note that for requests that change the state of the system, this error may be returned - |even if the request has completed successfully.""") - @RetryStrategy("Retry quickly in load balancer.") - @Resolution( - "Expectation: transient failure that should be handled by retrying the request with appropriate backoff." - ) - case object TransientServerFailure - extends ErrorCategoryImpl( - grpcCode = Some(Code.UNAVAILABLE), - logLevel = Level.INFO, - retryable = Some(ErrorCategoryRetry(1.second)), - redactDetails = false, - asInt = 1, - rank = 3, - ) - with ErrorCategory - - /** Failure due to contention on some resources - */ - @Description( - """The request could not be processed due to shared processing resources - |(e.g. locks or rate limits that replenish quickly) being occupied. - |If the resource is known (i.e. locked contract), it will be included as a resource info. (Not known - |resource contentions are e.g. overloaded networks where we just observe timeouts, but can’t pin-point the cause).""" - ) - @RetryStrategy("Retry quickly (indefinitely or limited), but do not retry in load balancer.") - @Resolution("""Expectation: this is processing-flow level contention that should be handled by - |retrying the request with appropriate backoff.""") - case object ContentionOnSharedResources - extends ErrorCategoryImpl( - grpcCode = Some(Code.ABORTED), - logLevel = Level.INFO, - retryable = Some(ErrorCategoryRetry(1.second)), - redactDetails = false, - asInt = 2, - rank = 3, - ) - with ErrorCategory - - /** Request completion not observed within a pre-defined window - */ - @Description("""The request might not have been processed, as its deadline expired before its - |completion was signalled. Note that for requests that change the state of the - |system, this error may be returned even if the request has completed successfully. - |Note that known and well-defined timeouts are signalled as - |[[ContentionOnSharedResources]], while this category indicates that the - |state of the request is unknown.""") - @RetryStrategy("Retry for a limited number of times with deduplication.") - @Resolution( - """Expectation: the deadline might have been exceeded due to transient resource - |congestion or due to a timeout in the request processing pipeline being too low. - |The transient errors might be solved by the application retrying. - |The non-transient errors will require operator intervention to change the timeouts.""" - ) - case object DeadlineExceededRequestStateUnknown - extends ErrorCategoryImpl( - grpcCode = Some(Code.DEADLINE_EXCEEDED), - logLevel = Level.INFO, - retryable = Some(ErrorCategoryRetry(1.second)), - redactDetails = false, - asInt = 3, - rank = 3, - ) - with ErrorCategory - - /** Some internal error - */ - @Description( - "Request processing failed due to a violation of system internal invariants. This error is exposed on the API with grpc-status INTERNAL without any details for security reasons" - ) - @RetryStrategy("Retry after operator intervention.") - @Resolution( - """Expectation: this is due to a bug in the implementation or data corruption in the systems databases. - |Resolution will require operator intervention, and potentially vendor support.""" - ) - case object SystemInternalAssumptionViolated - extends ErrorCategoryImpl( - grpcCode = Some(Code.INTERNAL), - logLevel = Level.ERROR, - retryable = None, - redactDetails = true, - asInt = 4, - rank = 1, - ) - with ErrorCategory - - @Description( - """A potential attack or a faulty peer component has been detected. - |This error is exposed on the API with grpc-status INVALID_ARGUMENT with unredacted details.""" - ) - @RetryStrategy("Errors in this category are non-retryable.") - @Resolution( - """Expectation: this can be a severe issue that requires operator attention or intervention, and - |potentially vendor support. It means that the system has detected invalid information that can be attributed - |to either faulty or malicious manipulation of data coming from a peer source.""" - ) - case object UnredactedSecurityAlert - extends ErrorCategoryImpl( - grpcCode = Some(Code.INVALID_ARGUMENT), - logLevel = Level.WARN, - retryable = None, - redactDetails = false, - asInt = 5, - rank = 2, - ) - with ErrorCategory - - @Description( - """A potential attack or a faulty peer component has been detected. - |This error is exposed on the API with grpc-status INVALID_ARGUMENT without any details for security reasons.""" - ) - @RetryStrategy("Errors in this category are non-retryable.") - @Resolution( - """Expectation: this can be a severe issue that requires operator attention or intervention, and - |potentially vendor support. It means that the system has detected invalid information that can be attributed - |to either faulty or malicious manipulation of data coming from a peer source.""" - ) - case object SecurityAlert - extends ErrorCategoryImpl( - grpcCode = Some(Code.INVALID_ARGUMENT), - logLevel = Level.WARN, - retryable = None, - redactDetails = true, - asInt = 5, - rank = 2, - ) - with ErrorCategory - - /** Client is not authenticated properly - */ - @Description( - """The request does not have valid authentication credentials for the operation. This error is exposed on the API with grpc-status UNAUTHENTICATED without any details for security reasons""" - ) - @RetryStrategy("""Retry after application operator intervention.""") - @Resolution( - """Expectation: this is an application bug, application misconfiguration or ledger-level - |misconfiguration. Resolution requires application and/or ledger operator intervention.""" - ) - case object AuthInterceptorInvalidAuthenticationCredentials - extends ErrorCategoryImpl( - grpcCode = Some(Code.UNAUTHENTICATED), - logLevel = Level.WARN, - retryable = None, - redactDetails = true, - asInt = 6, - rank = 2, - ) - with ErrorCategory - - /** Client does not have appropriate permissions - */ - @Description( - """The caller does not have permission to execute the specified operation. This error is exposed on the API with grpc-status PERMISSION_DENIED without any details for security reasons""" - ) - @RetryStrategy("""Retry after application operator intervention.""") - @Resolution( - """Expectation: this is an application bug or application misconfiguration. Resolution requires - |application operator intervention.""" - ) - case object InsufficientPermission - extends ErrorCategoryImpl( - grpcCode = Some(Code.PERMISSION_DENIED), - logLevel = Level.WARN, - retryable = None, - redactDetails = true, - asInt = 7, - rank = 2, - ) - with ErrorCategory - - /** A request which is never going to be valid - */ - @Description("""The request is invalid independent of the state of the system.""") - @RetryStrategy("""Retry after application operator intervention.""") - @Resolution( - """Expectation: this is an application bug or ledger-level misconfiguration (e.g. request size limits). - |Resolution requires application and/or ledger operator intervention.""" - ) - case object InvalidIndependentOfSystemState - extends ErrorCategoryImpl( - grpcCode = Some(Code.INVALID_ARGUMENT), - logLevel = Level.INFO, - retryable = None, - redactDetails = false, - asInt = 8, - rank = 3, - ) - with ErrorCategory - - /** A failure due to the current system state - */ - @Description( - """The mutable state of the system does not satisfy the preconditions required to execute the request. - |We consider the whole Daml ledger including ledger config, parties, packages, users and command - |deduplication to be mutable system state. Thus all Daml interpretation errors are reported - |as this error or one of its specializations.""" - ) - @RetryStrategy("""Retry after application operator intervention.""") - @Resolution("""ALREADY_EXISTS and NOT_FOUND are special cases for the existence and non-existence of well-defined - |entities within the system state; e.g., a .dalf package, contracts ids, contract keys, or a - |transaction at an offset. OUT_OF_RANGE is a special case for reading past a range. Violations of the - |Daml ledger model always result in these kinds of errors. Expectation: this is due to - |application-level bugs, misconfiguration or contention on application-visible resources; and might be - |resolved by retrying later, or after changing the state of the system. Handling these errors requires - |an application-specific strategy and/or operator intervention.""") - case object InvalidGivenCurrentSystemStateOther - extends ErrorCategoryImpl( - grpcCode = Some(Code.FAILED_PRECONDITION), - logLevel = Level.INFO, - retryable = None, - redactDetails = false, - asInt = 9, - rank = 3, - ) - with ErrorCategory - - /** A failure due to a resource already existing in the current system state - */ - @Description("""Special type of InvalidGivenCurrentSystemState referring to a well-defined - |resource.""") - @RetryStrategy( - """Inspect resource failure and retry after resource failure has been resolved (depends on type of - |resource and application).""" - ) - @Resolution("""Same as [[InvalidGivenCurrentSystemStateOther]].""") - case object InvalidGivenCurrentSystemStateResourceExists - extends ErrorCategoryImpl( - grpcCode = Some(Code.ALREADY_EXISTS), - logLevel = Level.INFO, - retryable = None, - redactDetails = false, - asInt = 10, - rank = 3, - ) - with ErrorCategory - - /** A failure due to a resource not existing in the current system state - */ - @Description("""Special type of InvalidGivenCurrentSystemState referring to a well-defined - |resource.""") - @RetryStrategy( - """Inspect resource failure and retry after resource failure has been resolved (depends on type of - |resource and application).""" - ) - @Resolution("""Same as [[InvalidGivenCurrentSystemStateOther]].""") - case object InvalidGivenCurrentSystemStateResourceMissing - extends ErrorCategoryImpl( - grpcCode = Some(Code.NOT_FOUND), - logLevel = Level.INFO, - retryable = None, - redactDetails = false, - asInt = 11, - rank = 3, - ) - with ErrorCategory - - /** A failure due to requesting a resource using a parameter value that falls beyond the current - * upper bound (or 'end') defined by the system's state. - */ - @Description( - """The request failed because it resulted in an operation beyond the current upper bound (or 'end') - |defined by the system's state. For example, supplying a ledger offset which is larger than the current - |ledger end, or a record time that is in the future.""" - ) - @RetryStrategy( - """Wait and retry. For example, retry a limited number of times with potentially increasing backoff. - |Hint: Inspect the retryable value of the error code to decide on the particular retry duration.""" - ) - @Resolution( - """Resolution can occur naturally as the system progresses. The requested operation may become valid - |eventually once the system's state has advanced further. For example, when new ledger entries are added. - |If however the situation does not resolve as expected, operator intervention may be required.""" - ) - case object InvalidGivenCurrentSystemStateSeekAfterEnd - extends ErrorCategoryImpl( - grpcCode = Some(Code.OUT_OF_RANGE), - logLevel = Level.INFO, - retryable = Some(ErrorCategoryRetry(1.second)), - redactDetails = false, - asInt = 12, - rank = 3, - ) - with ErrorCategory - - /** Background daemon notifying about observed degradation - */ - @Description( - """This error category is used internally to signal to the system operator an internal degradation.""" - ) - @RetryStrategy("""Not an API error, therefore not retryable.""") - @Resolution("""Inspect details of the specific error for more information.""") - case object BackgroundProcessDegradationWarning - extends ErrorCategoryImpl( - grpcCode = None, // should not be used on the API level - logLevel = Level.WARN, - retryable = None, - redactDetails = false, - asInt = 13, - rank = 2, - ) - with ErrorCategory - - @Description( - """This error category is used to signal that an unimplemented code-path has been triggered by a client or participant operator request. This error is exposed on the API with grpc-status UNIMPLEMENTED without any details for security reasons""" - ) - @RetryStrategy("""Errors in this category are non-retryable.""") - @Resolution( - """This error is caused by a ledger-level misconfiguration or by an implementation bug. - |Resolution requires node operator intervention.""" - ) - case object InternalUnsupportedOperation - extends ErrorCategoryImpl( - grpcCode = Some(Code.UNIMPLEMENTED), - logLevel = Level.ERROR, - retryable = None, - redactDetails = true, - asInt = 14, - rank = 1, - ) - with ErrorCategory - - implicit val orderingErrorType: Ordering[ErrorCategory] = Ordering.by[ErrorCategory, Int](_.rank) - - /** Special error category that isn't included in [[ErrorCategory.all]] and is meant purely for - * overriding the error category text in the generated error code documentation. It is expected - * that this category is replaced by an existing category from ErrorCategory.all in the error - * code implementation of [[RpcError.code]] when the actual error is instantiated. as such, all - * methods in this Error Category are errors - */ - final case class OverrideDocStringErrorCategory(overrideText: String) extends ErrorCategory { - private def unimplemented = throw new IllegalArgumentException( - "Attempted to use OverrideDocStringErrorCategory in thrown error. This should be replaced at construction" - ) - override def grpcCode: Option[Code] = unimplemented - override def logLevel: Level = unimplemented - override def retryable: Option[ErrorCategoryRetry] = unimplemented - override def redactDetails: Boolean = unimplemented - override def asInt: Int = unimplemented - override def rank: Int = unimplemented - } - - /** Generic error category class meant to be used as a data container for information deserialized - * from gRPC statuses (see [[com.digitalasset.base.error.utils.DecodedCantonError]]). - * - * Note: Do NOT use this class for adding error category information to error code instances but - * instead re-use existing ones or define new ones (see [[ErrorCategory.all]]) - */ - final case class GenericErrorCategory( - override val grpcCode: Option[Code], - override val logLevel: Level, - override val retryable: Option[ErrorCategoryRetry], - override val redactDetails: Boolean, - override val asInt: Int, - override val rank: Int, - ) extends ErrorCategoryImpl( - grpcCode = grpcCode, - logLevel = logLevel, - retryable = retryable, - redactDetails = redactDetails, - asInt = asInt, - rank = rank, - ) - with ErrorCategory -} - -/** Default retryability information - * - * Every error category has a default retryability classification. An error code may adjust the - * retry duration. - */ -final case class ErrorCategoryRetry(duration: FiniteDuration) diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorClass.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorClass.scala deleted file mode 100644 index b6b0052465..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorClass.scala +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -/** A component of [[ErrorClass]] - * - * @param docName - * The name that will appear in the generated documentation for the grouping. - * @param fullClassName - * Full class name of the corresponding [[ErrorGroup]]. - */ -final case class Grouping( - docName: String, - fullClassName: String, -) { - require( - docName.trim.nonEmpty, - s"Grouping.docName must be non empty and must contain not only whitespace characters, but was: |$docName|!", - ) -} - -/** Used to hierarchically structure error codes in the official documentation. - */ -final case class ErrorClass(groupings: List[Grouping]) { - def extend(grouping: Grouping): ErrorClass = - ErrorClass(groupings :+ grouping) -} - -object ErrorClass { - def root(): ErrorClass = ErrorClass(Nil) -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorCode.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorCode.scala deleted file mode 100644 index a1e25c9d67..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorCode.scala +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.google.rpc.Status -import io.grpc.Status.Code -import io.grpc.StatusRuntimeException -import io.grpc.protobuf.StatusProto -import org.slf4j.event.Level - -import scala.annotation.StaticAnnotation -import scala.util.control.{NoStackTrace, NonFatal} - -import SerializableErrorCodeComponents.validateTraceIdAndCorrelationId - -/** Error Code Definition - * - * We want to support our users and our developers with good error codes. Therefore, every error - * that an API returns should refer to a documented error code and provide some context - * information. - * - * Every error code is uniquely identified using an error-id of max 63 CAPITALIZED_WITH_UNDERSCORES - * characters - * - * Errors are organised according to ErrorGroups. And we separate the error code definition (using - * a singleton by virtue of using objects to express the nested hierarchy) and the actual error. - * - * Please note that there is some implicit argument passing involved in the example below: - * - * {{{ - * object SyncServiceErrors extends ParticipantErrorGroup { - * object ConnectionErrors extends ErrorGroup { - * object SynchronizerUnavailable extends ErrorCode(id="SYNCHRONIZER_UNAVAILABLE", ..) { - * case class ActualError(someContext: Val) extends BaseError with SyncServiceError - * // this error will actually be referring to the same error code! - * case class OtherError(otherContext: Val) extends BaseError with SyncServiceError - * } - * } - * object HandshakeErrors extends ErrorGroup { - * ... - * } - * } - * }}} - */ -abstract class ErrorCode(val id: String, val category: ErrorCategory)(implicit - val parent: ErrorClass -) { - - require(id.nonEmpty, "error-id must be non empty") - require(id.length < 64, s"error-id is too long: $id") - require(id.forall(c => c.isUpper || c == '_' || c.isDigit), s"Invalid characters in error-id $id") - - implicit val code: ErrorCode = this - - /** The machine readable error code string, uniquely identifiable by the error id, error category - * and correlation id. e.g. NOT_CONNECTED_TO_ANY_SYNCHRONIZER(2,ABC234) - */ - def codeStr(correlationId: Option[String]): String = - ErrorCodeMsg.codeStr(code.id, category.asInt, correlationId) - - /** @return - * message including error category id, error code id, correlation id and cause - */ - def toMsg(cause: => String, correlationId: Option[String], limit: Option[Int]): String = { - val truncatedCause = limit match { - case Some(maxLength) if (cause.length > maxLength) => cause.take(maxLength) + "..." - case _ => cause - } - ErrorCodeMsg(id, category.asInt, correlationId, truncatedCause) - } - - /** Log level of the error code - * - * Generally, the log level is defined by the error category. In rare cases, it might be - * overridden by the error code. - */ - def logLevel: Level = category.logLevel - - /** True if this error may appear on the API */ - protected def exposedViaApi: Boolean = category.grpcCode.nonEmpty - - /** The error conveyance doc string provides a statement about the form this error will be - * returned to the user - */ - def errorConveyanceDocString: Option[String] = { - val loggedAs = s"This error is logged with log-level $logLevel on the server side" - val apiLevel = (category.grpcCode, exposedViaApi) match { - case (Some(grpcCode), true) => - if (category.redactDetails) - s". It is exposed on the API with grpc-status $grpcCode without any details for security reasons." - else - s" and exposed on the API with grpc-status $grpcCode including a detailed error message." - case _ => "." - } - Some(loggedAs ++ apiLevel) - } -} - -object ErrorCodeMsg { - private val ErrorCodeMsgRegex = """([A-Z_]+)\((\d+),(.+?)\): (.*)""".r - - def apply( - errorCodeId: String, - errorCategoryInt: Int, - maybeCorrelationId: Option[String], - cause: String, - ): String = - s"${codeStr(errorCodeId, errorCategoryInt, maybeCorrelationId)}: $cause" - - def codeStr( - errorCodeId: String, - errorCategoryInt: Int, - maybeCorrelationId: Option[String], - ): String = s"$errorCodeId($errorCategoryInt,${maybeCorrelationId.getOrElse("0").take(8)})" - - def extract(errorCodeMsg: String): Either[String, (String, Int, String, String)] = - errorCodeMsg match { - case ErrorCodeMsgRegex(errorCodeId, errorCategoryIdIntAsString, corrId, cause) => - Right((errorCodeId, errorCategoryIdIntAsString.toInt, corrId, cause)) - case other => Left(s"Could not extract error code constituents from $other") - } -} - -object ErrorCode { - - /** Maximum size (in bytes) of the [[com.google.rpc.Status]] proto that a self-service error code - * can be serialized into. - * - * We choose this value with the following considerations: - * - The serialized error Status proto is packed into a [[io.grpc.Metadata]] together with the - * error description, which is then enriched with additional gRPC-internal entries and - * converted into HTTP2 headers for transmission. - * - The default maximum gRPC metadata size is 8KB (for both clients and servers). We MUST - * ensure that we don't exceed this value for error-returning gRPC metadata, otherwise - * INTERNAL errors may be reported on both the client and server. - * - The error description is packed twice in the serialized metadata (see - * [[ErrorCode.asGrpcError]] and how it creates a [[io.grpc.StatusRuntimeException]]). - * - * Conservatively we allow a buffer of > 3KB for gRPC and gRPC->HTTP2 internals overhead. - * (considering a [[MaxErrorContentBytes]] maximum Status proto size and limit the cause - * description size). - * - * Note: instead of increasing this value, consider limiting better the error contents. - */ - val MaxErrorContentBytes = 4096 - - def asGrpcError(err: BaseError)(implicit - loggingContext: BaseErrorLogger - ): StatusRuntimeException = { - val status = asGrpcStatus(err)(loggingContext) - // Builder methods for metadata are not exposed, so going route via creating an exception - val e = StatusProto.toStatusRuntimeException(status) - // Stripping stacktrace - err match { - case loc: LogOnCreation if loc.logOnCreation => - new ErrorCode.LoggedApiException(e.getStatus, e.getTrailers) - case _ => - new ErrorCode.ApiException(e.getStatus, e.getTrailers) - } - } - - def asGrpcStatus(err: BaseError)(implicit loggingContext: BaseErrorLogger): Status = - asGrpcStatus(err, MaxErrorContentBytes) - - private[error] def asGrpcStatus(err: BaseError, maxSerializedErrorSize: Int)(implicit - loggingContext: BaseErrorLogger - ): Status = - try - SerializableErrorCodeComponents( - errorCode = err.code, - loggingContext = loggingContext, - rawCorrelationId = loggingContext.correlationId, - rawTraceId = loggingContext.traceId, - cause = err.cause, - definiteAnswer = err.definiteAnswerO, - errorResources = err.resources, - contextMap = err.context ++ loggingContext.properties, - retryableInfo = err.retryable.map(_.duration), - ).toStatusProto(maxSerializedErrorSize) - catch { - case NonFatal(e) => - val (traceId, correlationId) = - validateTraceIdAndCorrelationId(loggingContext.traceId, loggingContext.correlationId) - - loggingContext.error(s"Error building gRPC status for error $err", e) - com.google.rpc.Status - .newBuilder() - .setCode(Code.INTERNAL.value()) - .setMessage( - BaseError.RedactedMessage(correlationId = correlationId, traceId = traceId) - ) - .build() - } - - class ApiException(status: io.grpc.Status, metadata: io.grpc.Metadata) - extends StatusRuntimeException(status, metadata) - with NoStackTrace - - /** Exception that has already been logged. - */ - class LoggedApiException(status: io.grpc.Status, metadata: io.grpc.Metadata) - extends ApiException(status, metadata) -} - -// Use these annotations to add more information to the documentation for an error on the website -final case class Explanation(explanation: String) extends StaticAnnotation -final case class Resolution(resolution: String) extends StaticAnnotation -final case class Description(description: String) extends StaticAnnotation -final case class RetryStrategy(retryStrategy: String) extends StaticAnnotation diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorGroup.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorGroup.scala deleted file mode 100644 index 1f92d91607..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorGroup.scala +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -abstract class ErrorGroup()(implicit parent: ErrorClass) { - private val simpleClassName: String = getClass.getSimpleName.replace("$", "") - val fullClassName: String = getClass.getName - - implicit val errorClass: ErrorClass = - parent.extend(Grouping(docName = simpleClassName, fullClassName = fullClassName)) -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorResource.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorResource.scala deleted file mode 100644 index 3228f3ac05..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/ErrorResource.scala +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -/** Type of error resource - * - * Some errors are linked to a specific resource such as a contract id or a package id. In such - * cases, we include the resource identifier as part of the error message. This enum allows an - * error to provide identifiers of a resource - */ -final case class ErrorResource(asString: String) { - def nullable: ErrorResource = ErrorResource(s"NULLABLE_$asString") -} - -object ErrorResource { - lazy val ContractId: ErrorResource = ErrorResource("CONTRACT_ID") - lazy val ContractIds: ErrorResource = ErrorResource("CONTRACT_IDS") - lazy val ContractKey: ErrorResource = ErrorResource("CONTRACT_KEY") - lazy val ContractArg: ErrorResource = ErrorResource("CONTRACT_ARG") - lazy val CryptoValue: ErrorResource = ErrorResource("CRYPTO_VALUE") - lazy val TransactionId: ErrorResource = ErrorResource("TRANSACTION_ID") - lazy val UpdateId: ErrorResource = ErrorResource("UPDATE_ID") - lazy val DalfPackage: ErrorResource = ErrorResource("PACKAGE") - lazy val TemplateId: ErrorResource = ErrorResource("TEMPLATE_ID") - lazy val InterfaceId: ErrorResource = ErrorResource("INTERFACE_ID") - lazy val PackageName: ErrorResource = ErrorResource("PACKAGE_NAME") - lazy val CommandId: ErrorResource = ErrorResource("COMMAND_ID") - lazy val Party: ErrorResource = ErrorResource("PARTY") - lazy val Parties: ErrorResource = ErrorResource("PARTIES") - lazy val User: ErrorResource = ErrorResource("USER") - lazy val IdentityProviderConfig: ErrorResource = ErrorResource("IDENTITY_PROVIDER_CONFIG") - lazy val ContractKeyHash: ErrorResource = ErrorResource("CONTRACT_KEY_HASH") - lazy val ExceptionValue: ErrorResource = ErrorResource("EXCEPTION_VALUE") - lazy val ExceptionType: ErrorResource = ErrorResource("EXCEPTION_TYPE") - lazy val ExceptionText: ErrorResource = ErrorResource("EXCEPTION_TEXT") - lazy val DevErrorType: ErrorResource = ErrorResource("DEV_ERROR_TYPE") - lazy val SynchronizerId: ErrorResource = ErrorResource("SYNCHRONIZER_ID") - lazy val SynchronizerAlias: ErrorResource = ErrorResource("SYNCHRONIZER_ALIAS") - lazy val Offset: ErrorResource = ErrorResource("OFFSET") - lazy val ExpectedType: ErrorResource = ErrorResource("EXPECTED_TYPE") - lazy val FieldIndex: ErrorResource = ErrorResource("FIELD_INDEX") - - lazy val all: Seq[ErrorResource] = Seq( - CommandId, - ContractArg, - ContractId, - ContractIds, - ContractKey, - ContractKeyHash, - CryptoValue, - DalfPackage, - DevErrorType, - ExceptionText, - ExceptionType, - ExceptionValue, - ExpectedType, - FieldIndex, - IdentityProviderConfig, - InterfaceId, - Offset, - PackageName, - Parties, - Party, - SynchronizerAlias, - SynchronizerId, - TemplateId, - TransactionId, - UpdateId, - User, - ) - - def fromString(str: String): Option[ErrorResource] = str.split("NULLABLE_") match { - case Array("", resource) => - all.find(_.asString == resource).map(_.nullable) - case Array(resource) => - all.find(_.asString == resource) - case _ => None - } -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/GrpcStatuses.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/GrpcStatuses.scala deleted file mode 100644 index 66ec1c88b7..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/GrpcStatuses.scala +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.google.rpc.error_details.ErrorInfo -import com.google.rpc.status.Status as StatusProto - -import scala.util.Try - -object GrpcStatuses { - val DefiniteAnswerKey = "definite_answer" - val CompletionOffsetKey = "completion_offset" - - def isDefiniteAnswer(status: StatusProto): Boolean = - status.details.exists { any => - if (any.is(ErrorInfo.messageCompanion)) { - Try(any.unpack(ErrorInfo.messageCompanion)).toOption - .exists(isDefiniteAnswer) - } else { - false - } - } - - private def isDefiniteAnswer(errorInfo: ErrorInfo): Boolean = - errorInfo.metadata.get(DefiniteAnswerKey).exists(value => java.lang.Boolean.valueOf(value)) -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/LogOnCreation.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/LogOnCreation.scala deleted file mode 100644 index 656a2f97ab..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/LogOnCreation.scala +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -/** Trait to log on creation */ -trait LogOnCreation { - def logOnCreation: Boolean = true - def logError(): Unit - if (logOnCreation) { - logError() - } -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/RpcError.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/RpcError.scala deleted file mode 100644 index a3deb52cd2..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/RpcError.scala +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.google.rpc.Status -import io.grpc.StatusRuntimeException - -trait RpcError { - - /** The error code, usually passed in as implicit where the error class is defined */ - def code: ErrorCode - - /** The context (declared fields) of this error */ - def context: Map[String, String] - - /** A human readable string indicating the error */ - def cause: String - - /** The resources related to this error */ - def resources: Seq[(ErrorResource, String)] - - /** The correlationId (e.g. submissionId) associated with the request that caused the error */ - def correlationId: Option[String] - - /** The traceId associated with the TraceContext at error creation */ - def traceId: Option[String] - - /** The gRPC status */ - def asGrpcStatus: Status - - /** The gRPC status encoded as a StatusRuntimeException */ - def asGrpcError: StatusRuntimeException - -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/SerializableErrorComponents.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/SerializableErrorComponents.scala deleted file mode 100644 index 27e129fd19..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/SerializableErrorComponents.scala +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.digitalasset.base.error.utils.ErrorDetails -import io.grpc.Status.Code - -import scala.concurrent.duration.FiniteDuration -import scala.jdk.CollectionConverters.IterableHasAsJava -import scala.util.matching.Regex - -import NonSecuritySensitiveErrorCodeComponents.{ - MaxCauseLogLength, - stringsPackedSize, - truncateDetails, -} -import SerializableErrorCodeComponents.* - -object SerializableErrorCodeComponents { - private[error] val ValidMetadataKeyRegex: Regex = "[^(a-zA-Z0-9-_)]".r - private[error] val GrpcCodeBytes = 1 - // Each string is encoded additionally with a tag and a size value - // Hence, we add some overhead to account for them - private[error] val MaximumPerTagOverheadBytes = 5 - private[error] val ErrorInfoDetailPackingOverheadBytes = - ErrorDetails.ErrorInfoDetail("", Map.empty).toRpcAny.getSerializedSize - private[error] val ResourceInfoDetailPackingOverheadBytes = - ErrorDetails.ResourceInfoDetail("", "").toRpcAny.getSerializedSize - // By default, hex encoded correlation-ids and trace-ids have 32 characters - // Since we don't have any explicit enforcement, truncate everything above 256 to disallow error serialization issues - private[error] val MaxTraceIdCorrelationIdSize = 256 - - def apply( - errorCode: ErrorCode, - loggingContext: BaseErrorLogger, - rawTraceId: Option[String], - rawCorrelationId: Option[String], - // Next parameters are by-name to avoid unnecessary computation if the error is security sensitive - cause: => String, - definiteAnswer: => Option[Boolean], - errorResources: => Seq[(ErrorResource, String)], - contextMap: => Map[String, String], - retryableInfo: => Option[FiniteDuration], - ): SerializableErrorCodeComponents = { - val (traceId, correlationId) = - validateTraceIdAndCorrelationId(rawTraceId, rawCorrelationId)(loggingContext) - - if (errorCode.category.redactDetails) - SecuritySensitiveErrorCodeComponents( - grpcStatusCode = errorCode.category.grpcCode, - traceId = traceId, - correlationId = correlationId, - )(logger = loggingContext) - else - NonSecuritySensitiveErrorCodeComponents( - traceId = traceId, - correlationId = correlationId, - cause = cause, - errorCode = errorCode, - definiteAnswer = definiteAnswer, - errorResources = errorResources, - contextMap = contextMap ++ loggingContext.properties, - retryableInfo = retryableInfo, - )(loggingContext = loggingContext) - } - - private[error] def validateTraceIdAndCorrelationId( - rawTraceId: Option[String], - rawCorrelationId: Option[String], - )(implicit loggingContext: BaseErrorLogger) = { - val traceId = rawTraceId.map(tId => - truncateString( - tId, - MaxTraceIdCorrelationIdSize, - loggingContext.warn( - s"Trace-id $tId exceeded maximum allowed size of $MaxTraceIdCorrelationIdSize and has been truncated for gRPC error serialization" - ), - ) - ) - val correlationId = rawCorrelationId.map(cId => - truncateString( - cId, - MaxTraceIdCorrelationIdSize, - loggingContext.warn( - s"Correlation-id $cId exceeded maximum allowed size of $MaxTraceIdCorrelationIdSize and has been truncated for gRPC error serialization" - ), - ) - ) - (traceId, correlationId) - } - - private[error] def truncateString(v: String, maxSize: Int, onTruncate: => Unit = ()): String = - if (v.length > maxSize) { - onTruncate - s"${v.take(maxSize - 3)}..." - } else v -} - -sealed trait SerializableErrorCodeComponents { - def toStatusProto(maxSizeBytes: Int): com.google.rpc.Status -} - -private[error] final case class SecuritySensitiveErrorCodeComponents( - grpcStatusCode: Option[Code], - traceId: Option[String], - correlationId: Option[String], -)(logger: BaseErrorLogger) - extends SerializableErrorCodeComponents { - - override def toStatusProto(maxSizeBytes: Int): com.google.rpc.Status = - com.google.rpc.Status - .newBuilder() - .setCode( - grpcStatusCode - .getOrElse { - logger.warn("Missing grpc status code for security sensitive error") - Code.INTERNAL - } - .value() - ) - .setMessage(BaseError.RedactedMessage(correlationId, traceId)) - .addAllDetails( - correlationId - .orElse(traceId) - .map(ErrorDetails.RequestInfoDetail.apply) - .toList - .map(_.toRpcAny) - .asJava - ) - .build() -} - -private[error] final case class NonSecuritySensitiveErrorCodeComponents( - traceId: Option[String], - correlationId: Option[String], - cause: String, - errorCode: ErrorCode, - definiteAnswer: Option[Boolean], - errorResources: Seq[(ErrorResource, String)], - contextMap: Map[String, String], - retryableInfo: Option[FiniteDuration], -)(loggingContext: BaseErrorLogger) - extends SerializableErrorCodeComponents { - - /** Truncates and serializes the self-service error components into a [[com.google.rpc.Status]]. - * - * Truncation happens for both the error message and error details aiming to ensure that the - * maximum message size ([[NonSecuritySensitiveErrorCodeComponents.MaxCauseLogLength]]) and - * maximum total Status serialization size ([[ErrorCode.MaxErrorContentBytes]]) are respected. - */ - def toStatusProto(maxSizeBytes: Int): com.google.rpc.Status = { - val grpcStatusCode = validatedGrpcErrorCode(errorCode.category.grpcCode) - val errorCategoryContext = "category" -> errorCode.category.asInt.toString - val traceIdContext = traceId.map("tid" -> _) - val definiteAnswerContext = - definiteAnswer.map(value => GrpcStatuses.DefiniteAnswerKey -> value.toString) - - val retryInfoRpc = retryableInfo.map(ErrorDetails.RetryInfoDetail(_).toRpcAny) - val requestInfoRpc = - correlationId.orElse(traceId).map(ErrorDetails.RequestInfoDetail(_).toRpcAny) - val errorCodeId = errorCode.id - - val validatedMessage = errorCode.toMsg(cause, correlationId, limit = Some(MaxCauseLogLength)) - val mandatoryDetailsEncodedSize = - GrpcCodeBytes + MaximumPerTagOverheadBytes + // Grpc code size + its byte tag overhead - stringsPackedSize( - Seq(validatedMessage, errorCodeId, errorCategoryContext._1, errorCategoryContext._2) ++ - traceIdContext.map(v => Seq(v._1, v._2)).getOrElse(Seq.empty) ++ - definiteAnswerContext.map(v => Seq(v._1, v._2)).getOrElse(Seq.empty) - ) + - retryInfoRpc.fold(0)(_.getSerializedSize + MaximumPerTagOverheadBytes) + - requestInfoRpc.fold(0)(_.getSerializedSize + MaximumPerTagOverheadBytes) + - // overhead bytes for packing ErrorInfo - ErrorInfoDetailPackingOverheadBytes - - val bytesLeftForTruncateableDetails = maxSizeBytes - mandatoryDetailsEncodedSize - - // Truncate-able error details - val (truncatedContext, truncatedErrorResources) = - truncateDetails(contextMap, errorResources, bytesLeftForTruncateableDetails) - - val errorInfoDetail = ErrorDetails.ErrorInfoDetail( - errorCodeId = errorCodeId, - // For simplicity, all key-values added here have been accounted with tag overheads - // even though they are not tagged when serialized. - metadata = - (truncatedContext ++ definiteAnswerContext.toList ++ traceIdContext.toList :+ errorCategoryContext).toMap, - ) - - val resourceInfos = - truncatedErrorResources.view - .map(_.swap) - .map((ErrorDetails.ResourceInfoDetail.apply _).tupled) - .toList - - val allDetails = - Seq(errorInfoDetail.toRpcAny) ++ retryInfoRpc.toList ++ requestInfoRpc.toList ++ resourceInfos - .map(_.toRpcAny) - - // Build status - com.google.rpc.Status - .newBuilder() - .setCode(grpcStatusCode.value()) - .setMessage(validatedMessage) - .addAllDetails(allDetails.asJava) - .build() - } - - private def validatedGrpcErrorCode(grpcCode: Option[Code]): Code = - grpcCode.getOrElse { - loggingContext.warn(s"Passing non-grpc error via grpc ${errorCode.id} ") - Code.INTERNAL - } -} - -private[error] object NonSecuritySensitiveErrorCodeComponents { - - /** The maximum size (in characters) of the self-service error description, truncated for - * transport as part of a Status - */ - val MaxCauseLogLength = 512 - - private[error] def truncateDetails( - context: Map[String, String], - errResources: Seq[(ErrorResource, String)], - remainingBudgetBytes: Int, - ): (Seq[(String, String)], Seq[(String, String)]) = { - - val numberOfEntries = context.size + errResources.size - if (numberOfEntries == 0) (Seq.empty, Seq.empty) - else { - val remainingBudgetForEntries = remainingBudgetBytes - - // account for two tags per key-value pair - 2 * MaximumPerTagOverheadBytes * numberOfEntries - - val budgetForTruncatedResources = - (errResources.size.toLong * remainingBudgetForEntries.toLong) / numberOfEntries.toLong - - val budgetForTruncatedContext = remainingBudgetForEntries - budgetForTruncatedResources - - val truncatedErrorResources = truncateResources( - errResources.map { case (res, v) => res.asString -> v }, - budgetForTruncatedResources.toInt, - ) - - val truncatedContext = truncateContext(context.toSeq, budgetForTruncatedContext.toInt) - (truncatedContext, truncatedErrorResources) - } - } - - private[error] def truncateContext( - rawContextEntries: Seq[(String, String)], - maxBudgetBytes: Int, - ): Seq[(String, String)] = { - val raw: Seq[(String, String)] = rawContextEntries.view - // Make key gRPC compliant - .map { case (k, v) => ValidMetadataKeyRegex.replaceAllIn(k, "").take(63) -> v } - // Discard empty values - .filter(_._1.nonEmpty) - // Discard empty keys - .filter(_._2.nonEmpty) - .toSeq - .sortBy(v => v._1.length + v._2.length) - - val rawSize = raw.size - - raw.view.zipWithIndex - .foldLeft((Vector.empty[(String, String)], maxBudgetBytes)) { - case ((acc, free), ((k, v), idx)) => - // This value can be regarded as an "inverse" moving average, - // (i.e. computed on the remaining entries instead of the past ones). - // Since the series is sorted by the entries size, this value progresses strictly ascending - // which allows more aggressive truncation of the bigger/later entries of this series. - val maxSize = free / (rawSize - idx) - - val maybeNewEntry = if (k.length + v.length > maxSize) { - // We need at least 8 chars per (k,v) entry to get something meaningful after truncation - // since we might suffix each of the k,v strings with `...` (3 chars) - see truncateString - Option.when(maxSize > 7) { - val truncatedKey = truncateString(k, maxSize / 2) - truncatedKey -> truncateString(v, maxSize - truncatedKey.length) - } - } else Some(k -> v) - - maybeNewEntry - .map { case (k, v) => (k, v, encSize(k) + encSize(v)) } - // Check again that unexpected encoding did not lead to exceeding the budget - // (For simplicity, in truncateString, we assume 1-byte per string encoding) - .filter { case (_, _, encodedSize) => encodedSize <= maxSize } - .map { case (newK, newV, encodedSize) => (acc :+ (newK -> newV), free - encodedSize) } - .getOrElse((acc, free)) - } - ._1 - } - - private def truncateResources( - details: Seq[(String, String)], - remaining: Int, - ): Seq[(String, String)] = - details - .foldLeft(Vector.empty[(String, String)] -> remaining) { case ((acc, free), (k, v)) => - // Account for the resource being packed as a ResourceInfo - val newFree = - free - (encSize(k) + encSize(v) + ResourceInfoDetailPackingOverheadBytes) - if (newFree < 0) (acc, free) - else (acc :+ (k -> v), newFree) - } - ._1 - - private def stringsPackedSize(strings: Seq[String]): Int = - strings.map(encSize(_) + MaximumPerTagOverheadBytes).sum - - private def encSize(s: String): Int = s.getBytes("UTF-8").length -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/samples/Example.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/samples/Example.scala deleted file mode 100644 index 3d9d29d349..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/samples/Example.scala +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error.samples - -import com.digitalasset.base.error.{BaseErrorLogger, ContextualizedDamlError, NoBaseLogging} - -import scala.concurrent.duration.* - -object DummmyServer { - - import com.digitalasset.base.error.{ - ErrorCategory, - ErrorCategoryRetry, - ErrorClass, - ErrorCode, - ErrorResource, - } - - object ErrorCodeFoo - extends ErrorCode(id = "MY_ERROR_CODE_ID", ErrorCategory.ContentionOnSharedResources)( - ErrorClass.root() - ) { - - implicit val errorLogger: BaseErrorLogger = new NoBaseLogging( - correlationId = Some("full-correlation-id-123456790"), - properties = Map.empty, - ) - - final case class Error(message: String) extends ContextualizedDamlError(cause = message) { - - override def resources: Seq[(ErrorResource, String)] = Seq( - ErrorResource.ContractId -> "someContractId" - ) - - override def retryable: Option[ErrorCategoryRetry] = Some( - ErrorCategoryRetry(123.second + 456.milliseconds) - ) - - override def context: Map[String, String] = Map("foo" -> "bar") - } - - } - - def serviceEndpointDummy(): Unit = - throw ErrorCodeFoo.Error("A user oriented message").asGrpcError - -} - -/** This shows how a user can handle error codes. In particular it shows how to extract useful - * information from the signalled exception with minimal library dependencies. - * - * NOTE: This class is given as an example in the official Daml documentation. If you change it - * here, change it also in the docs. - */ -object SampleClientSide { - - import com.google.rpc.ResourceInfo - import com.google.rpc.{ErrorInfo, RequestInfo, RetryInfo} - import io.grpc.StatusRuntimeException - import scala.jdk.CollectionConverters.* - - def example(): Unit = - try { - DummmyServer.serviceEndpointDummy() - } catch { - case e: StatusRuntimeException => - // Converting to a status object. - val status = io.grpc.protobuf.StatusProto.fromThrowable(e) - - // Extracting gRPC status code. - assert(status.getCode == io.grpc.Status.Code.ABORTED.value()) - assert(status.getCode == 10) - - // Extracting error message, both - // machine oriented part: "MY_ERROR_CODE_ID(2,full-cor):", - // and human oriented part: "A user oriented message". - assert(status.getMessage == "MY_ERROR_CODE_ID(2,full-cor): A user oriented message") - - // Getting all the details - val rawDetails: Seq[com.google.protobuf.Any] = status.getDetailsList.asScala.toSeq - - // Extracting error code id, error category id and optionally additional metadata. - assert { - rawDetails.collectFirst { - case any if any.is(classOf[ErrorInfo]) => - val v = any.unpack(classOf[ErrorInfo]) - assert(v.getReason == "MY_ERROR_CODE_ID") - assert(v.getMetadataMap.asScala.toMap == Map("category" -> "2", "foo" -> "bar")) - }.isDefined - } - - // Extracting full correlation id, if present. - assert { - rawDetails.collectFirst { - case any if any.is(classOf[RequestInfo]) => - val v = any.unpack(classOf[RequestInfo]) - assert(v.getRequestId == "full-correlation-id-123456790") - }.isDefined - } - - // Extracting retry information if the error is retryable. - assert { - rawDetails.collectFirst { - case any if any.is(classOf[RetryInfo]) => - val v = any.unpack(classOf[RetryInfo]) - assert(v.getRetryDelay.getSeconds == 123, v.getRetryDelay.getSeconds) - assert(v.getRetryDelay.getNanos == 456 * 1000 * 1000, v.getRetryDelay.getNanos) - }.isDefined - } - - // Extracting resource if the error pertains to some well defined resource. - assert { - rawDetails.collectFirst { - case any if any.is(classOf[ResourceInfo]) => - val v = any.unpack(classOf[ResourceInfo]) - assert(v.getResourceType == "CONTRACT_ID") - assert(v.getResourceName == "someContractId") - }.isDefined - } - } -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/utils/DecodedCantonError.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/utils/DecodedCantonError.scala deleted file mode 100644 index c1b384e690..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/utils/DecodedCantonError.scala +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error.utils - -import cats.implicits.toTraverseOps -import cats.syntax.either.* -import com.digitalasset.base.error.BaseError.RedactedMessage -import com.digitalasset.base.error.ErrorCategory.GenericErrorCategory -import com.digitalasset.base.error.{ - BaseError, - ErrorCategory, - ErrorCategoryRetry, - ErrorClass, - ErrorCode, - ErrorCodeMsg, - ErrorResource, - Grouping, - NoBaseLogging, -} -import com.google.common.annotations.VisibleForTesting -import com.google.protobuf.any -import com.google.rpc.error_details.{ErrorInfo, RequestInfo, ResourceInfo, RetryInfo} -import com.google.rpc.status.Status as RpcStatus -import io.grpc.{Status, StatusRuntimeException} -import org.slf4j.event.Level -import scalapb.{GeneratedMessage, GeneratedMessageCompanion} - -import scala.concurrent.duration.FiniteDuration -import scala.jdk.DurationConverters.* -import scala.util.Try - -/** Generic error class used for creating error instances from deserialized gRPC statuses that - * resulted from serialization of self-service error codes (children of [[BaseError]]). Its aim is - * to be used in client applications and components for simplifying programmatic inspection and/or - * enrichment of Canton errors received over-the-wire or from persistence. - * - * Note: Do NOT use this class for explicitly instantiating errors. Instead, use or create a - * fully-typed error instance. - */ -final case class DecodedCantonError( - code: ErrorCode, - cause: String, - correlationId: Option[String], - traceId: Option[String], - override val context: Map[String, String], - override val resources: Seq[(ErrorResource, String)], - override val definiteAnswerO: Option[Boolean] = None, -) extends BaseError { - def toRpcStatusWithForwardedRequestId: RpcStatus = super.rpcStatus()( - new NoBaseLogging(properties = Map.empty, correlationId = correlationId, traceId = traceId) - ) - - def retryIn: Option[FiniteDuration] = code.category.retryable.map(_.duration) - - def isRetryable: Boolean = retryIn.nonEmpty -} - -object DecodedCantonError { - - /** Deserializes a [[com.google.rpc.status.Status]] to [[DecodedCantonError]]. With the exception - * of throwables, all serialized error information is extracted, making this method an inverse of - * [[BaseError.rpcStatus]]. - */ - def fromGrpcStatus(status: RpcStatus): Either[String, DecodedCantonError] = { - val rawDetails = status.details - - val statusCode = Status.fromCodeValue(status.code).getCode - status.message match { - case RedactedMessage(correlationId, traceId) => - Right( - redactedError( - grpcCode = statusCode, - correlationId = correlationId, - traceId = traceId, - ) - ) - case _ => tryDeserializeStatus(status, rawDetails) - } - } - - def fromStatusRuntimeException( - statusRuntimeException: StatusRuntimeException - ): Either[String, DecodedCantonError] = - Either - .catchOnly[IllegalArgumentException]( - io.grpc.protobuf.StatusProto.fromThrowable(statusRuntimeException) - ) - .leftMap(ex => s"Failed to decode error from exception: ${ex.getMessage}") - .map(RpcStatus.fromJavaProto) - .flatMap(fromGrpcStatus) - - private def tryDeserializeStatus( - status: RpcStatus, - rawDetails: Seq[any.Any], - ): Either[String, DecodedCantonError] = - for { - errorInfoSeq <- extractErrorDetail[ErrorInfo](rawDetails) - errorInfo <- errorInfoSeq.exactlyOne - requestInfoSeq <- extractErrorDetail[RequestInfo](rawDetails) - requestInfoO <- requestInfoSeq.atMostOne - retryInfoSeq <- extractErrorDetail[RetryInfo](rawDetails) - retryInfo <- retryInfoSeq.atMostOne - resourceInfo <- extractErrorDetail[ResourceInfo](rawDetails) - resources = resourceInfo.map { resourceInfo => - ErrorResource(resourceInfo.resourceType) -> resourceInfo.resourceName - } - errorCategory <- extractErrorCategory( - errorInfo = errorInfo, - statusCode = status.code, - retryableDuration = retryInfo.flatMap(_.retryDelay).map(_.asJavaDuration.toScala), - ) - traceId = errorInfo.metadata.get("tid") - cause = extractCause(status) - correlationId = requestInfoO.collect { - case requestInfo if !traceId.contains(requestInfo.requestId) => requestInfo.requestId - } - } yield DecodedCantonError( - code = GenericErrorCode(id = errorInfo.reason, category = errorCategory), - cause = cause, - context = errorInfo.metadata, - resources = resources, - correlationId = correlationId, - traceId = traceId, - ) - - @VisibleForTesting - def unapply(throwable: Throwable): Option[DecodedCantonError] = throwable match { - case statusRuntimeException: StatusRuntimeException => - fromStatusRuntimeException(statusRuntimeException).toOption - case _other => None - } - - private def extractCause(status: RpcStatus) = - ErrorCodeMsg - .extract(status.message) - .map { case (_, _, _, cause) => cause } - // We don't guarantee backwards-compatibility for error message formats - // Hence fallback to the original cause on failure to parse - .getOrElse(status.message) - - private def redactedError( - grpcCode: Status.Code, - correlationId: Option[String], - traceId: Option[String], - ): DecodedCantonError = - DecodedCantonError( - code = GenericErrorCode( - id = "NA", - category = GenericErrorCategory( - grpcCode = Some(grpcCode), - logLevel = Level.ERROR, - retryable = None, - redactDetails = true, - // Security sensitive errors do not carry the category id - asInt = -1, - rank = 1, - ), - ), - cause = "A security-sensitive error has been received", - correlationId = correlationId, - traceId = traceId, - context = Map.empty, - resources = Seq.empty, - ) - - private def extractErrorCategory( - errorInfo: ErrorInfo, - statusCode: Int, - retryableDuration: Option[FiniteDuration], - ): Either[String, ErrorCategory] = { - def unknownCategory(categoryId: Int) = - GenericErrorCategory( - grpcCode = Some(Status.fromCodeValue(statusCode).getCode), - // If we log it, we use INFO since it's received from an - // external component - logLevel = Level.INFO, - retryable = retryableDuration.map(ErrorCategoryRetry.apply), - redactDetails = false, - asInt = categoryId, - rank = -1, - ) - - for { - categoryValue <- errorInfo.metadata - .get("category") - .toRight(s"category key not found in error metadata: ${errorInfo.metadata}") - categoryId <- Try(categoryValue.toInt).toEither.left.map(e => - s"Failed parsing category value: ${e.getMessage}" - ) - } yield ErrorCategory.all.find(_.asInt == categoryId).getOrElse(unknownCategory(categoryId)) - } - - private def extractErrorDetail[T <: GeneratedMessage]( - errorDetails: Seq[com.google.protobuf.any.Any] - )(implicit - expectedTypeCompanion: GeneratedMessageCompanion[T] - ): Either[String, List[T]] = - errorDetails.toList - .filter(_ is expectedTypeCompanion) - .traverse { errDetail => - Try(errDetail.unpack[T]).toEither.left.map(throwable => - s"Could not extract ${expectedTypeCompanion.scalaDescriptor.fullName} from error details: ${throwable.getMessage}" - ) - } - - private implicit class AritySelectors[T <: GeneratedMessage](seq: Seq[T])(implicit - expectedTypeCompanion: GeneratedMessageCompanion[T] - ) { - def atMostOne: Either[String, Option[T]] = - Either.cond(seq.sizeIs <= 1, seq.headOption, invalid("at most one")) - - def exactlyOne: Either[String, T] = seq match { - case Seq(errInfo) => Right(errInfo) - case _ => Left(invalid("exactly one")) - } - - private def invalid(times: String) = - s"Could not extract error detail. Expected $times ${expectedTypeCompanion.scalaDescriptor.fullName} in status details, but got ${seq.size}" - } - - /** Dummy error class for the purpose of creating the [[GenericErrorCode]]. It has no effect on - * documentation as its intended user ([[DecodedCantonError]]) does not appear in documentation. - */ - private implicit val genericErrorClass: ErrorClass = ErrorClass( - List(Grouping("generic", "ErrorClass")) - ) - - /** Generic wrapper for error codes received from deserialized gRPC-statuses */ - private final case class GenericErrorCode( - override val id: String, - override val category: ErrorCategory, - ) extends ErrorCode(id, category) -} diff --git a/canton/base/errors/src/main/scala/com/digitalasset/base/error/utils/ErrorDetails.scala b/canton/base/errors/src/main/scala/com/digitalasset/base/error/utils/ErrorDetails.scala deleted file mode 100644 index 8858ad69a2..0000000000 --- a/canton/base/errors/src/main/scala/com/digitalasset/base/error/utils/ErrorDetails.scala +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error.utils - -import com.digitalasset.base.error.ErrorCode -import com.google.protobuf -import com.google.rpc.{ErrorInfo, RequestInfo, ResourceInfo, RetryInfo} -import io.grpc.StatusRuntimeException -import io.grpc.protobuf.StatusProto - -import scala.concurrent.duration.* -import scala.jdk.CollectionConverters.* - -object ErrorDetails { - - sealed trait ErrorDetail extends Product with Serializable { - type T <: com.google.protobuf.Message - def toRpc: T - def toRpcAny: com.google.protobuf.Any = com.google.protobuf.Any.pack(toRpc) - } - - final case class ResourceInfoDetail(name: String, typ: String) extends ErrorDetail { - type T = ResourceInfo - def toRpc: ResourceInfo = - ResourceInfo.newBuilder().setResourceType(typ).setResourceName(name).build() - } - final case class ErrorInfoDetail(errorCodeId: String, metadata: Map[String, String]) - extends ErrorDetail { - type T = ErrorInfo - def toRpc: ErrorInfo = - ErrorInfo - .newBuilder() - .setReason(errorCodeId) - .putAllMetadata(metadata.asJava) - .build() - } - final case class RetryInfoDetail(duration: Duration) extends ErrorDetail { - type T = RetryInfo - def toRpc: RetryInfo = { - val millis = duration.toMillis - val fullSeconds = millis / 1000 - val remainderMillis = millis % 1000 - // Ensuring that we do not exceed max allowed value of nanos as documented in [[com.google.protobuf.Duration.Builder.setNanos]] - val remainderNanos = Math.min(remainderMillis * 1000 * 1000, 999999999).toInt - val protoDuration = com.google.protobuf.Duration - .newBuilder() - .setNanos(remainderNanos) - .setSeconds(fullSeconds) - .build() - RetryInfo - .newBuilder() - .setRetryDelay(protoDuration) - .build() - } - } - final case class RequestInfoDetail(correlationId: String) extends ErrorDetail { - type T = RequestInfo - def toRpc: RequestInfo = - RequestInfo - .newBuilder() - .setRequestId(correlationId) - .setServingData("") - .build() - } - - def from(status: com.google.rpc.Status): Seq[ErrorDetail] = - from(status.getDetailsList.asScala.toSeq) - def from(e: StatusRuntimeException): Seq[ErrorDetail] = - from(StatusProto.fromThrowable(e)) - - def from(anys: Seq[protobuf.Any]): Seq[ErrorDetail] = anys.toList.map { - case any if any.is(classOf[ResourceInfo]) => - val v = any.unpack(classOf[ResourceInfo]) - ResourceInfoDetail(typ = v.getResourceType, name = v.getResourceName) - - case any if any.is(classOf[ErrorInfo]) => - val v = any.unpack(classOf[ErrorInfo]) - ErrorInfoDetail(errorCodeId = v.getReason, metadata = v.getMetadataMap.asScala.toMap) - - case any if any.is(classOf[RetryInfo]) => - val v = any.unpack(classOf[RetryInfo]) - val delay = v.getRetryDelay - val duration = (delay.getSeconds.seconds + delay.getNanos.nanos).toCoarsest - RetryInfoDetail(duration = duration) - - case any if any.is(classOf[RequestInfo]) => - val v = any.unpack(classOf[RequestInfo]) - RequestInfoDetail(correlationId = v.getRequestId) - - case any => throw new IllegalStateException(s"Could not unpack value of: |$any|") - } - - /** @return - * whether a status runtime exception matches the error code. - * - * NOTE: This method is not suitable for: - * 1. security sensitive error codes (e.g. internal or authentication related) as they are - * stripped from all the details when being converted to instances of - * [[io.grpc.StatusRuntimeException]], - * 1. error codes that do not translate to gRPC level errors (i.e. error codes that don't have - * a corresponding gRPC status) - */ - def matches(e: StatusRuntimeException, errorCode: ErrorCode): Boolean = { - val matchesErrorCodeId = from(e).exists { - case ErrorInfoDetail(errorCodeId, _) => errorCodeId == errorCode.id - case _ => false - } - val matchesMessagePrefix = Option(e.getStatus.getDescription).exists(_.startsWith(errorCode.id)) - val matchesStatusCode = errorCode.category.grpcCode.contains(e.getStatus.getCode) - matchesErrorCodeId && matchesMessagePrefix && matchesStatusCode - } - - def matches(t: Throwable, errorCode: ErrorCode): Boolean = t match { - case e: StatusRuntimeException => matches(e, errorCode) - case _ => false - } -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorCodeSpec.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorCodeSpec.scala deleted file mode 100644 index b385f8ef10..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorCodeSpec.scala +++ /dev/null @@ -1,387 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.digitalasset.base.error.utils.{DecodedCantonError, ErrorDetails} -import com.google.rpc.Status -import io.grpc.Status.Code -import org.scalatest.EitherValues -import org.scalatest.concurrent.{Eventually, IntegrationPatience} -import org.scalatest.freespec.AnyFreeSpec -import org.scalatest.matchers.should.Matchers -import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks - -import scala.concurrent.duration.* -import scala.jdk.CollectionConverters.* - -class ErrorCodeSpec - extends AnyFreeSpec - with Matchers - with Eventually - with IntegrationPatience - with ErrorsAssertions - with ScalaCheckDrivenPropertyChecks - with EitherValues { - - object FooErrorCodeRedacted - extends ErrorCode( - "FOO_ERROR_CODE_SECURITY_SENSITIVE", - ErrorCategory.SystemInternalAssumptionViolated, - )(ErrorClass.root()) - - object FooErrorCode - extends ErrorCode("FOO_ERROR_CODE", ErrorCategory.InvalidIndependentOfSystemState)( - ErrorClass.root() - ) - - classOf[ErrorCode].getSimpleName - { - - "meet test preconditions" in { - FooErrorCodeRedacted.category.redactDetails shouldBe true - FooErrorCode.category.redactDetails shouldBe false - FooErrorCodeRedacted.category.grpcCode shouldBe Some(Code.INTERNAL) - FooErrorCode.category.grpcCode shouldBe Some(Code.INVALID_ARGUMENT) - } - - val maxLength = 512 - - "create correct message" in { - FooErrorCode.toMsg( - cause = "cause123", - correlationId = Some("123correlationId"), - limit = Some(maxLength), - ) shouldBe "FOO_ERROR_CODE(8,123corre): cause123" - FooErrorCode.toMsg( - cause = "cause123", - correlationId = None, - limit = Some(maxLength), - ) shouldBe "FOO_ERROR_CODE(8,0): cause123" - FooErrorCode.toMsg( - cause = "x" * maxLength * 2, - correlationId = Some("123correlationId"), - limit = Some(maxLength), - ) shouldBe s"FOO_ERROR_CODE(8,123corre): ${"x" * maxLength}..." - FooErrorCodeRedacted.toMsg( - cause = "cause123", - correlationId = Some("123correlationId"), - limit = Some(maxLength), - ) shouldBe "FOO_ERROR_CODE_SECURITY_SENSITIVE(4,123corre): cause123" - } - - "create a minimal grpc status and exception" - { - - "when correlation-id and trace-id are not set" in { - testMinimalGrpcStatus(NoBaseLogging) - } - - "when only correlation-id is set" in { - testMinimalGrpcStatus( - new NoBaseLogging( - properties = Map.empty, - correlationId = Some("123correlationId"), - ) - ) - } - - "when only trace-id is set" in { - testMinimalGrpcStatus( - new NoBaseLogging( - properties = Map.empty, - // correlationId should be the traceId when not set - correlationId = Some("123traceId"), - traceId = Some("123traceId"), - ) - ) - } - - "when correlation-id and trace-id are set" in { - testMinimalGrpcStatus( - new NoBaseLogging( - properties = Map.empty, - correlationId = Some("123correlationId"), - traceId = Some("123traceId"), - ) - ) - } - - } - - "create a big grpc status and exception" - { - class FooErrorBig(override val code: ErrorCode) extends BaseError { - override val cause: String = "cause123" - - override def retryable: Option[ErrorCategoryRetry] = Some( - ErrorCategoryRetry(duration = 123.seconds + 456.milliseconds) - ) - - override def resources: Seq[(ErrorResource, String)] = - super.resources ++ - Seq[(ErrorResource, String)]( - ErrorResource.CommandId -> "commandId1", - ErrorResource.CommandId -> "commandId2", - ErrorResource.Party -> "party1", - ) - - override def context: Map[String, String] = - super.context ++ Map( - "contextKey1" -> "contextValue1", - "key????" -> "keyWithInvalidCharacters", - ) - - override def definiteAnswerO: Option[Boolean] = Some(false) - - override def throwableO: Option[Throwable] = - Some(new RuntimeException("runtimeException123")) - } - - val errorLoggerBig: BaseErrorLogger = new NoBaseLogging( - correlationId = Some("123correlationId"), - properties = Map( - "loggingEntryKey" -> "loggingEntryValue" - ), - ) - val requestInfo = ErrorDetails.RequestInfoDetail("123correlationId") - val retryInfo = ErrorDetails.RetryInfoDetail(123.seconds + 456.milliseconds) - - def getDetails(tested: ErrorCode) = Seq( - ErrorDetails - .ErrorInfoDetail( - tested.id, - Map( - "category" -> tested.category.asInt.toString, - "definite_answer" -> "false", - "loggingEntryKey" -> "loggingEntryValue", - "contextKey1" -> "contextValue1", - "key" -> "keyWithInvalidCharacters", - ), - ), - requestInfo, - retryInfo, - ErrorDetails.ResourceInfoDetail(name = "commandId1", typ = "COMMAND_ID"), - ErrorDetails.ResourceInfoDetail(name = "commandId2", typ = "COMMAND_ID"), - ErrorDetails.ResourceInfoDetail(name = "party1", typ = "PARTY"), - ) - - "not security sensitive" in { - val testedErrorCode = FooErrorCode - val details = getDetails(testedErrorCode) - final case class TestedError() extends FooErrorBig(testedErrorCode) - - val expectedStatus = Status - .newBuilder() - .setMessage("FOO_ERROR_CODE(8,123corre): cause123") - .setCode(testedErrorCode.category.grpcCode.value.value()) - .addAllDetails(details.map(_.toRpcAny).asJava) - .build() - val testedError = TestedError() - - assertStatus( - actual = ErrorCode.asGrpcStatus(testedError)(errorLoggerBig), - expected = expectedStatus, - ) - assertError( - actual = ErrorCode.asGrpcError(testedError)(errorLoggerBig), - expectedStatusCode = testedErrorCode.category.grpcCode.value, - expectedMessage = "FOO_ERROR_CODE(8,123corre): cause123", - expectedDetails = details, - ) - } - - "security sensitive" in { - val testedErrorCode = FooErrorCodeRedacted - final case class FooError() extends FooErrorBig(testedErrorCode) - val expectedStatus = Status - .newBuilder() - .setMessage( - BaseError.RedactedMessage(Some("123correlationId")) - ) - .setCode(testedErrorCode.category.grpcCode.value.value()) - .addDetails(requestInfo.toRpcAny) - .build() - val testedError = FooError() - testedError.logWithContext(Map.empty)(errorLoggerBig) - - assertStatus( - actual = ErrorCode.asGrpcStatus(testedError)(errorLoggerBig), - expected = expectedStatus, - ) - assertError( - actual = ErrorCode.asGrpcError(testedError)(errorLoggerBig), - expectedStatusCode = testedErrorCode.category.grpcCode.value, - expectedMessage = BaseError.RedactedMessage(Some("123correlationId")), - expectedDetails = Seq(requestInfo), - ) - } - - } - - "create a grpc status and exception for input exceeding details size limits" in { - class FooErrorBig(override val code: ErrorCode) extends BaseError { - override val cause: String = "cause123" - - override def context: Map[String, String] = - super.context ++ Map( - ("y" * ErrorCode.MaxErrorContentBytes) -> ("y" * ErrorCode.MaxErrorContentBytes) - ) - - override def retryable: Option[ErrorCategoryRetry] = Some( - ErrorCategoryRetry(duration = 123.seconds + 456.milliseconds) - ) - - override def resources: Seq[(ErrorResource, String)] = - super.resources ++ - Seq[(ErrorResource, String)]( - ErrorResource.CommandId -> "commandId1", - ErrorResource.CommandId -> "commandId2", - ErrorResource.Party -> "party1", - ErrorResource.Party -> ("x" * ErrorCode.MaxErrorContentBytes), - ) - - override def definiteAnswerO: Option[Boolean] = Some(false) - } - val errorLoggerOversized: BaseErrorLogger = new NoBaseLogging( - correlationId = Some("123correlationId"), - properties = Map( - "loggingEntryKey" -> "loggingEntryValue", - "loggingEntryValueTooBig" -> ("x" * ErrorCode.MaxErrorContentBytes), - ("x" * ErrorCode.MaxErrorContentBytes) -> "loggingEntryKeyTooBig", - ), - ) - val requestInfo = ErrorDetails.RequestInfoDetail("123correlationId") - val retryInfo = ErrorDetails.RetryInfoDetail(123.seconds + 456.milliseconds) - - val testedErrorCode = FooErrorCode - final case class TestedError() extends FooErrorBig(FooErrorCode) - val testedError = TestedError() - - val expectedDetails = Seq( - ErrorDetails - .ErrorInfoDetail( - testedErrorCode.id, - Map( - "category" -> testedErrorCode.category.asInt.toString, - "definite_answer" -> "false", - "loggingEntryKey" -> "loggingEntryValue", - "loggingEntryValueTooBig" -> ("x" * 849 + "..."), - "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" -> "loggingEntryKeyTooBig", - "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy" -> ("y" * 809 + "..."), - ), - ), - requestInfo, - retryInfo, - ErrorDetails.ResourceInfoDetail(name = "commandId1", typ = "COMMAND_ID"), - ErrorDetails.ResourceInfoDetail(name = "commandId2", typ = "COMMAND_ID"), - ErrorDetails.ResourceInfoDetail(name = "party1", typ = "PARTY"), - ) - val expectedMessage = "FOO_ERROR_CODE(8,123corre): cause123" - val expectedStatus = Status - .newBuilder() - .setMessage(expectedMessage) - .setCode(testedErrorCode.category.grpcCode.value.value()) - .addAllDetails(expectedDetails.map(_.toRpcAny).asJava) - .build() - - assertStatus( - actual = ErrorCode.asGrpcStatus(testedError)(errorLoggerOversized), - expected = expectedStatus, - ) - assertError( - actual = ErrorCode.asGrpcError(testedError)(errorLoggerOversized), - expectedStatusCode = testedErrorCode.category.grpcCode.value, - expectedMessage = expectedMessage, - expectedDetails = expectedDetails, - ) - } - - "do not exceed the safe-to-serialize limit" in { - implicit val generatorDrivenConfig: PropertyCheckConfiguration = - PropertyCheckConfiguration(minSuccessful = 100) - - // The description gets added to the io.grpc.Status and to the StatusProto as well - // so we must leave some space for it - forAll(ErrorGenerator.defaultErrorGen) { err => - whenever( - ErrorCode - // Ensure we evaluate the test only for errors that must be truncated - .asGrpcStatus(err, ErrorCode.MaxErrorContentBytes * 10)(err.logger) - .getSerializedSize > ErrorCode.MaxErrorContentBytes - ) { - val protoResult = err.asGrpcStatus - val serializedSize = protoResult.getSerializedSize - - serializedSize should be <= ErrorCode.MaxErrorContentBytes withClue s"for $err" - } - } - } - - "truncate the trace-id if abnormaly large" in { - val errWithLargeTraceId = - ErrorGenerator.defaultErrorGen.sample.value.copy(traceId = Some("x" * 1000)).asGrpcError - - DecodedCantonError - .fromStatusRuntimeException(errWithLargeTraceId) - .value - .traceId - .value shouldBe ("x" * 253 + "...") - } - - "truncate the correlation-id if abnormaly large" in { - val errWithLargeTraceId = - ErrorGenerator.defaultErrorGen.sample.value - .copy(correlationId = Some("x" * 1000)) - .asGrpcError - - DecodedCantonError - .fromStatusRuntimeException(errWithLargeTraceId) - .value - .correlationId - .value shouldBe ("x" * 253 + "...") - } - } - - def testMinimalGrpcStatus(errorLoggerSmall: BaseErrorLogger): Unit = { - class FooErrorMinimal(override val code: ErrorCode) extends BaseError { - override val cause: String = "cause123" - } - val testedErrorCode = FooErrorCode - val id = errorLoggerSmall.correlationId.orElse(errorLoggerSmall.traceId) - val idTruncated = id.getOrElse("0").take(8) - final case class TestedError() extends FooErrorMinimal(testedErrorCode) - val details = Seq( - ErrorDetails - .ErrorInfoDetail( - testedErrorCode.id, - Map( - "category" -> testedErrorCode.category.asInt.toString - ) ++ errorLoggerSmall.traceId.fold(Map.empty[String, String])(tid => Map("tid" -> tid)), - ) - ) ++ id - .map(correlationId => - ErrorDetails.RequestInfoDetail( - correlationId = correlationId - ) - ) - .toList - - val expected = Status - .newBuilder() - .setMessage(s"FOO_ERROR_CODE(8,$idTruncated): cause123") - .setCode(Code.INVALID_ARGUMENT.value()) - .addAllDetails(details.map(_.toRpcAny).asJava) - .build() - val testedError = TestedError() - - assertStatus( - actual = ErrorCode.asGrpcStatus(testedError)(errorLoggerSmall), - expected = expected, - ) - assertError( - actual = ErrorCode.asGrpcError(testedError)(errorLoggerSmall), - expectedStatusCode = testedErrorCode.category.grpcCode.value, - expectedMessage = s"FOO_ERROR_CODE(8,$idTruncated): cause123", - expectedDetails = details, - ) - } - -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorGenerator.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorGenerator.scala deleted file mode 100644 index 63b4068341..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorGenerator.scala +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import org.scalacheck.{Arbitrary, Gen} - -object ErrorGenerator { - final case class RichTestError( - errorCode: ErrorCode, - override val correlationId: Option[String] = None, - override val traceId: Option[String] = None, - contextMap: Map[String, Any] = Map(), - loggingProperties: Map[String, String] = Map(), - override val cause: String, - override val throwableO: Option[Throwable] = None, - override val definiteAnswerO: Option[Boolean] = None, - override val resources: Seq[(ErrorResource, String)] = Seq(), - ) extends ContextualizedDamlError( - cause, - throwableO, - contextMap, - )(errorCode, new NoBaseLogging(loggingProperties, correlationId, traceId)) - - private final case class TestErrorCode(override val id: String, errorCategory: ErrorCategory) - extends ErrorCode(id, errorCategory)(ErrorClass.root()) {} - - private[error] def asciiPrintableStrOfN(maxSize: Int) = for { - chars <- Gen.listOfN(maxSize, Gen.alphaNumChar) - } yield chars.mkString - - private val errorResourceGen = for { - typ <- asciiPrintableStrOfN(256).map(ErrorResource(_)) - msg <- asciiPrintableStrOfN(1024) - } yield (typ, msg) - - private val contextMapGen = for { - contextMap <- Gen.mapOfN( - 50, - for { - k <- asciiPrintableStrOfN(256) - v <- asciiPrintableStrOfN(256) - } yield (k, v), - ) - } yield contextMap - - val defaultErrorGen: Gen[RichTestError] = errorGenerator(None) - - def errorGenerator( - redactDetails: Option[Boolean], - additionalErrorCategoryFilter: ErrorCategory => Boolean = _ => true, - ): Gen[RichTestError] = - for { - errorCodeId <- Gen.listOfN(63, Gen.alphaUpperChar).map(_.mkString) - category <- Gen.oneOf( - redactDetails - .fold(ErrorCategory.all)(redact => ErrorCategory.all.filter(_.redactDetails == redact)) - .filter(additionalErrorCategoryFilter) - ) - errorCode = TestErrorCode(errorCodeId, category) - correlationId <- asciiPrintableStrOfN( - SerializableErrorCodeComponents.MaxTraceIdCorrelationIdSize - ).map(Option(_).filter(_.nonEmpty)) - traceId <- asciiPrintableStrOfN(SerializableErrorCodeComponents.MaxTraceIdCorrelationIdSize) - .map(Option(_).filter(_.nonEmpty)) - message <- asciiPrintableStrOfN(2000) - definiteAnswerO <- Arbitrary.arbitrary[Option[Boolean]] - errorResources <- Gen.listOfN(50, errorResourceGen) - extraContextMap <- contextMapGen - loggingProperties <- contextMapGen - throwableO <- Gen.option(Gen.asciiPrintableStr.map(new RuntimeException(_))) - } yield RichTestError( - errorCode = errorCode, - correlationId = correlationId, - traceId = traceId, - contextMap = extraContextMap, - loggingProperties = loggingProperties, - cause = message, - definiteAnswerO = definiteAnswerO, - throwableO = throwableO, - resources = errorResources, - ) -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorGroupSpec.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorGroupSpec.scala deleted file mode 100644 index 813215d124..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorGroupSpec.scala +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import org.scalatest.BeforeAndAfter -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class ErrorGroupSpec extends AnyFlatSpec with Matchers with BeforeAndAfter { - - object ErrorGroupBar extends ErrorGroup()(ErrorClass.root()) - - object ErrorGroupFoo1 extends ErrorGroup()(ErrorClass.root()) { - object ErrorGroupFoo2 extends ErrorGroup() { - object ErrorGroupFoo3 extends ErrorGroup() - } - } - - it should "resolve correct error group names" in { - ErrorGroupFoo1.ErrorGroupFoo2.ErrorGroupFoo3.errorClass shouldBe ErrorClass( - List( - Grouping("ErrorGroupFoo1", ErrorGroupFoo1.fullClassName), - Grouping("ErrorGroupFoo2", ErrorGroupFoo1.ErrorGroupFoo2.fullClassName), - Grouping( - "ErrorGroupFoo3", - ErrorGroupFoo1.ErrorGroupFoo2.ErrorGroupFoo3.fullClassName, - ), - ) - ) - ErrorGroupBar.errorClass shouldBe ErrorClass( - List(Grouping("ErrorGroupBar", ErrorGroupBar.fullClassName)) - ) - } - -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorsAssertions.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorsAssertions.scala deleted file mode 100644 index a476ea71ef..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/ErrorsAssertions.scala +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.digitalasset.base.error.utils.ErrorDetails -import com.digitalasset.base.error.utils.ErrorDetails.{ErrorInfoDetail, RequestInfoDetail} -import io.grpc.Status.Code -import io.grpc.StatusRuntimeException -import io.grpc.protobuf.StatusProto -import org.scalatest.Checkpoints.Checkpoint -import org.scalatest.matchers.should.Matchers -import org.scalatest.{AppendedClues, Assertion, OptionValues} - -import scala.jdk.CollectionConverters.* - -trait ErrorsAssertions extends Matchers with OptionValues with AppendedClues { - - /** NOTE: This method is not suitable for: - * 1. security sensitive error codes (e.g. internal or authentication related) as they are - * stripped from all the details when being converted to instances of - * [[StatusRuntimeException]], - * 1. error codes that do not translate to gRPC level errors (i.e. error codes that don't have - * a corresponding gRPC status) - */ - def assertMatchesErrorCode( - actual: StatusRuntimeException, - expectedErrorCode: ErrorCode, - ): Assertion = { - val actualErrorCodeId = ErrorDetails.from(actual).collectFirst { - case ErrorInfoDetail(errorCodeId, _) => errorCodeId - } - val actualDescription = Option(actual.getStatus.getDescription) - val actualStatusCode = actual.getStatus.getCode - val cp = new Checkpoint - cp(actualErrorCodeId.value shouldBe expectedErrorCode.id) - cp(Some(actualStatusCode) shouldBe expectedErrorCode.category.grpcCode) - cp(actualDescription.value should startWith(expectedErrorCode.id)) - cp.reportAll() - succeed - } - - def assertStatus( - actual: com.google.rpc.Status, - expected: com.google.rpc.Status, - ): Assertion = { - val actualDetails = ErrorDetails.from(actual) - val expectedDetails = ErrorDetails.from(expected) - val actualDescription = Option(actual.getMessage) - val expectedDescription = Option(expected.getMessage) - val actualStatusCode = actual.getCode - val expectedStatusCode = expected.getCode - val cp = new Checkpoint - cp(actualDescription shouldBe expectedDescription) - cp { - actualStatusCode shouldBe expectedStatusCode withClue (s", expecting status code: '$expectedStatusCode''") - } - cp(actualDetails should contain theSameElementsAs expectedDetails) - cp.reportAll() - succeed - } - - /** Asserts that the two errors have the same code, message and details. - */ - def assertError( - actual: StatusRuntimeException, - expected: StatusRuntimeException, - ): Unit = { - val expectedStatus = StatusProto.fromThrowable(expected) - val expectedDetails = expectedStatus.getDetailsList.asScala.toSeq - assertError( - actual = actual, - expectedStatusCode = expected.getStatus.getCode, - expectedMessage = expectedStatus.getMessage, - expectedDetails = ErrorDetails.from(expectedDetails), - ) - } - - /** @param verifyEmptyStackTrace - * should be enabled for the server-side testing and disabled for the client side testing - */ - def assertError( - actual: StatusRuntimeException, - expectedStatusCode: Code, - expectedMessage: String, - expectedDetails: Seq[ErrorDetails.ErrorDetail], - verifyEmptyStackTrace: Boolean = true, - ): Unit = { - val actualStatus = StatusProto.fromThrowable(actual) - val actualDetails = actualStatus.getDetailsList.asScala.toSeq - val cp = new Checkpoint - cp(actual.getStatus.getCode shouldBe expectedStatusCode) - cp(actualStatus.getMessage shouldBe expectedMessage) - cp { - ErrorDetails.from(actualDetails) should contain theSameElementsAs expectedDetails - } - if (verifyEmptyStackTrace) { - cp { - actual.getStackTrace.length shouldBe 0 withClue ("it should contain no stacktrace") - } - } - cp(actual.getCause shouldBe null) - cp.reportAll() - } - - /** Asserts that the error has the expected code and matches the form of the message and details. - */ - def assertError( - actual: StatusRuntimeException, - expectedStatusCode: Code, - expectedMessage: String => String, - expectedDetails: String => String => Seq[ErrorDetails.ErrorDetail], - ): Unit = { - val actualStatus = StatusProto.fromThrowable(actual) - val actualDetails = actualStatus.getDetailsList.asScala.toSeq - val errorDetails = ErrorDetails.from(actualDetails) - val tid = errorDetails.collectFirst { case RequestInfoDetail(tid) => tid }.value - val errorInfoDetail = - errorDetails.collectFirst { case detail: ErrorInfoDetail => detail }.value - val submissionId = errorInfoDetail.metadata.get("submissionId").value - assertError( - actual, - expectedStatusCode, - expectedMessage(tid), - expectedDetails(tid)(submissionId), - verifyEmptyStackTrace = false, - ) - - } - -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/GrpcStatusesSpec.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/GrpcStatusesSpec.scala deleted file mode 100644 index 6783693c0b..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/GrpcStatusesSpec.scala +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import com.google.protobuf.any -import com.google.rpc.error_details.ErrorInfo -import com.google.rpc.status.Status -import org.scalatest.matchers.should.Matchers -import org.scalatest.prop.TableDrivenPropertyChecks.* -import org.scalatest.wordspec.AnyWordSpec - -import GrpcStatuses.DefiniteAnswerKey - -class GrpcStatusesSpec extends AnyWordSpec with Matchers { - "isDefiniteAnswer" should { - "return correct value" in { - val anErrorInfo = ErrorInfo.of("reason", "synchronizer", Map.empty) - val testCases = Table( - ("Description", "Error Info", "Expected"), - ( - "ErrorInfo contains definite answer key and its value is true", - Some(anErrorInfo.copy(metadata = Map(DefiniteAnswerKey -> "true"))), - true, - ), - ( - "ErrorInfo contains definite answer key and its value is false", - Some(anErrorInfo.copy(metadata = Map(DefiniteAnswerKey -> "false"))), - false, - ), - ( - "ignore casing of value associated to definite answer key (#1)", - Some(anErrorInfo.copy(metadata = Map(DefiniteAnswerKey -> "TRUE"))), - true, - ), - ( - "ignore casing of value associated to definite answer key (#2)", - Some(anErrorInfo.copy(metadata = Map(DefiniteAnswerKey -> "True"))), - true, - ), - ( - "ErrorInfo does not contain definite answer key", - Some(anErrorInfo.copy(metadata = Map("some" -> "key"))), - false, - ), - ("no ErrorInfo is available", None, false), - ) - - forAll(testCases) { case (_, errorInfoMaybe, expected) => - val details = - errorInfoMaybe.map(errorInfo => any.Any.pack(errorInfo)).map(Seq(_)).getOrElse(Seq.empty) - val inputStatus = Status.of(123, "an error", details) - GrpcStatuses.isDefiniteAnswer(inputStatus) should be(expected) - } - } - } - -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/RedactedMessageSpec.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/RedactedMessageSpec.scala deleted file mode 100644 index 68cd315d0c..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/RedactedMessageSpec.scala +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import org.scalacheck.Gen -import org.scalatest.Inside -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks - -import BaseError.RedactedMessage - -class RedactedMessageSpec extends AnyFlatSpec with ScalaCheckPropertyChecks with Inside { - private val traceIdGen = - Gen.option(Gen.asciiPrintableStr).filterNot(_.exists(v => v.isEmpty || v == "")) - private val correlationIdGen = Gen - .option(Gen.asciiPrintableStr) - .filterNot(_.exists(v => v.isEmpty || v == "")) - - RedactedMessage.getClass.getSimpleName should "correctly construct and extract the security message fields" in { - forAll(correlationIdGen, traceIdGen) { (corrIdO, tIdO) => - inside(RedactedMessage(corrIdO, tIdO)) { case RedactedMessage(`corrIdO`, `tIdO`) => - succeed - } - } - } -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/SerializableErrorComponentsSpec.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/SerializableErrorComponentsSpec.scala deleted file mode 100644 index 67575161ad..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/SerializableErrorComponentsSpec.scala +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error - -import org.scalacheck.Gen -import org.scalatest.Assertions.fail -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -import org.scalatest.{EitherValues, Inside, OptionValues} -import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks - -import ErrorGenerator.asciiPrintableStrOfN -import SerializableErrorComponentsSpec.* - -class SerializableErrorComponentsSpec - extends AnyFlatSpec - with Matchers - with EitherValues - with Inside - with OptionValues - with ScalaCheckDrivenPropertyChecks { - - behavior of "truncateDetails" - - it should "not change details if reasonably-sized and valid" in { - val (truncatedContext, truncatedResources) = - NonSecuritySensitiveErrorCodeComponents.truncateDetails( - context = rawContextMap, - errResources = rawErrorResources, - remainingBudgetBytes = 1000, - ) - - truncatedContext should contain theSameElementsAs rawContextMap - truncatedResources should contain theSameElementsAs comparable(rawErrorResources) - } - - it should "truncate context map and resources when oversize" in { - val cMap = - Map( - "k1".padTo(20, 'a') -> cValue(20), - "k2".padTo(30, 'a') -> cValue(40), - "k3".padTo(20, 'a') -> cValue(100), - "k4".padTo(50, 'a') -> cValue(160), - "k5".padTo(60, 'a') -> cValue(320), - ) - - val errRes: Seq[(ErrorResource, String)] = - (1 to 10).map(i => ErrorResource.CommandId -> s"some-command-id-$i".padTo(50, 'a')) - - val (truncatedContext, truncatedResources) = - NonSecuritySensitiveErrorCodeComponents.truncateDetails( - context = cMap, - errResources = errRes, - remainingBudgetBytes = 1000, - ) - - val expected = Map( - "k1".padTo(20, 'a') -> cValue(20), - "k2".padTo(30, 'a') -> truncatedValue("", 31), - "k3".padTo(20, 'a') -> truncatedValue("", 41), - truncatedValue("k4", 30) -> truncatedValue("", 31), - truncatedValue("k5", 30) -> truncatedValue("", 31), - ) - truncatedContext should contain theSameElementsAs expected - truncatedResources should contain theSameElementsAs comparable(errRes.take(5)) - } - - behavior of "truncateContext" - - it should "validate context map keys" in { - val invalidKeyEntry = "%k4!" -> cValue(50) - val invalidKeyEntryCorrectSize = "%k5!".padTo(65, 'a') -> cValue(50) - val invalidKeyEntryOversize = "%k6!".padTo(100, 'a') -> cValue(50) - val correctKeyOversize = "k7-size".padTo(64, 'a') -> cValue(50) - - val truncatedContext = - NonSecuritySensitiveErrorCodeComponents.truncateContext( - rawContextEntries = rawContextMap.toVector ++ Vector( - invalidKeyEntry, - invalidKeyEntryCorrectSize, - invalidKeyEntryOversize, - correctKeyOversize, - ), - maxBudgetBytes = 1000, - ) - - truncatedContext should contain theSameElementsAs rawContextMap.toVector ++ Vector( - "k4" -> cValue(50), - "k5".padTo(63, 'a') -> cValue(50), - "k6".padTo(63, 'a') -> cValue(50), - "k7-size".padTo(63, 'a') -> cValue(50), - ) - } - - it should "return an empty list on empty input" in { - NonSecuritySensitiveErrorCodeComponents.truncateContext( - rawContextEntries = Vector.empty, - maxBudgetBytes = 4096, - ) shouldBe empty - } - - it should "return an empty list on too small budget" in { - NonSecuritySensitiveErrorCodeComponents.truncateContext( - rawContextEntries = Vector("1" -> "23", "123" -> "45", "12345" -> "678"), - maxBudgetBytes = 3, - ) shouldBe empty - } - - it should "not add context map entries if one from the pair is empty" in { - val truncatedContext = - NonSecuritySensitiveErrorCodeComponents.truncateContext( - rawContextEntries = rawContextMap.toVector ++ Vector( - "" -> cValue(50), - "k" -> "", - // After key is cleaned, it remains empty - "$!@" -> cValue(50), - "" -> "", - ), - maxBudgetBytes = 1000, - ) - - truncatedContext should contain theSameElementsAs rawContextMap - } - - it should "pack everything on exact size" in { - val validContextMapGen = for { - contextMap <- Gen.mapOfN( - 50, - for { - k <- asciiPrintableStrOfN(63) - v <- asciiPrintableStrOfN(63) - } yield (k, v), - ) - } yield contextMap - - forAll(validContextMapGen) { map => - whenever(!map.exists { case (k, v) => k.isEmpty || v.isEmpty }) { - val input = map.toVector - NonSecuritySensitiveErrorCodeComponents.truncateContext( - rawContextEntries = input, - maxBudgetBytes = input.map(v => v._1.length + v._2.length).sum, - ) should contain theSameElementsAs input - } - } - } - - it should "too small entries that need to be truncated are skipped instead" in { - NonSecuritySensitiveErrorCodeComponents.truncateContext( - rawContextEntries = Vector("123" -> "456"), - maxBudgetBytes = 5, - ) shouldBe empty - - NonSecuritySensitiveErrorCodeComponents.truncateContext( - rawContextEntries = Vector("1" -> "23", "123" -> "45", "12345" -> "678"), - maxBudgetBytes = 8, - ) should contain theSameElementsAs Vector("12345" -> "678") - } - - it should "truncate bigger entries more" in { - val inputSize = 20 - // Input with entries sorted by increasing size - val input = (1 to inputSize).map(idx => s"k$idx".padTo(5 + idx * 2, 'a') -> cValue(idx * 2)) - val requestedEntriesSize = input.map(v => v._1.length + v._2.length).sum - val output = - NonSecuritySensitiveErrorCodeComponents.truncateContext( - rawContextEntries = input, - maxBudgetBytes = requestedEntriesSize / 2, - ) - - def sum(s: Seq[(String, String)]) = s.map(v => v._1.length + v._2.length).sum - def truncationPercentage(in: Seq[(String, String)], out: Seq[(String, String)]): Double = - sum(out).toDouble / sum(in).toDouble - def avgSize(s: Seq[(String, String)]) = sum(s).toDouble / s.size - - val (inputFirstHalf, inputSecondHalf) = input.splitAt(inputSize / 2) - val (outputFirstHalf, outputSecondHalf) = output.splitAt(inputSize / 2) - - val truncPCSmaller = truncationPercentage(inputFirstHalf, outputFirstHalf) - val truncPCBigger = truncationPercentage(inputSecondHalf, outputSecondHalf) - - // Check that truncation percentage is lower for the for smaller entries - truncPCSmaller should be > truncPCBigger - - // Check that the average size still remains higher for bigger entries - avgSize(outputFirstHalf) < avgSize(outputSecondHalf) - } -} - -private object SerializableErrorComponentsSpec { - val rawContextMap: Map[String, String] = - Map("key" -> cValue(10), "key-max-size".padTo(63, 'a') -> cValue(50)) - val rawErrorResources: Seq[(ErrorResource, String)] = Seq( - ErrorResource.CommandId -> "some-command-id", - ErrorResource.ContractKey -> "some-contract-key", - ) - - private def cValue(size: Int): String = "a" * size - private def truncatedValue(prefix: String, size: Int): String = - if (size < 3 + prefix.length) fail(s"size: $size") - else s"$prefix${cValue(size - 3 - prefix.length)}..." - - private def comparable(res: Seq[(ErrorResource, String)]): Seq[(String, String)] = - res.map { case (k, v) => k.asString -> v } -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/samples/SampleClientSideSpec.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/samples/SampleClientSideSpec.scala deleted file mode 100644 index 08c1200141..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/samples/SampleClientSideSpec.scala +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error.samples - -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -class SampleClientSideSpec extends AnyFlatSpec with Matchers { - - it should "run successfully" in { - SampleClientSide.example() - } - -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/BenignError.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/BenignError.scala deleted file mode 100644 index 9dd9aa268d..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/BenignError.scala +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error.utils - -import com.digitalasset.base.error.{ - BaseErrorLogger, - DamlErrorWithDefiniteAnswer, - ErrorCategory, - ErrorClass, - ErrorCode, - Explanation, - Resolution, -} - -@Explanation("Not the end of the world.") -@Resolution( - "Retry re-submitting the request. If the error persists, contact the participant operator." -) -case object BenignError - extends ErrorCode("BENIGN_ERROR", ErrorCategory.TransientServerFailure)( - ErrorClass.root() - ) { - - final case class Reject(serviceName: String)(implicit - loggingContext: BaseErrorLogger - ) extends DamlErrorWithDefiniteAnswer( - cause = s"Benign problem in $serviceName.", - extraContext = Map("service_name" -> serviceName), - ) -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/DecodedCantonErrorSpec.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/DecodedCantonErrorSpec.scala deleted file mode 100644 index 3b733b38bb..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/DecodedCantonErrorSpec.scala +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error.utils - -import com.digitalasset.base.error.{ - BaseError, - BaseErrorLogger, - DamlErrorWithDefiniteAnswer, - ErrorCategory, - ErrorClass, - ErrorCode, - ErrorResource, - NoBaseLogging, -} -import com.google.protobuf.any.Any -import com.google.protobuf.any.Any.toJavaProto -import com.google.rpc.error_details.{ErrorInfo, RequestInfo, ResourceInfo, RetryInfo} -import com.google.rpc.status.Status as RpcStatus -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers -import org.scalatest.{Assertion, EitherValues, OptionValues} -import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -import scalapb.{GeneratedMessage, GeneratedMessageCompanion} - -import ErrorDetails.ErrorDetail - -class DecodedCantonErrorSpec - extends AnyFlatSpec - with Matchers - with EitherValues - with OptionValues - with ScalaCheckPropertyChecks { - behavior of DecodedCantonError.getClass.getSimpleName - - it should s"correctly deserialize a gRPC status code to a ${BaseError.getClass.getSimpleName}" in - forAll(errorCategoriesTable) { errorCategory => - forAll(propertyMapTable) { propertyMap => - forAll(correlationIdTable) { correlationId => - forAll(traceIdTable) { traceId => - val fromGrpc: RpcStatus => DecodedCantonError = - DecodedCantonError.fromGrpcStatus(_).value - val toGrpc: DecodedCantonError => RpcStatus = _.toRpcStatusWithForwardedRequestId - - val someGrpcStatus = - createGrpcStatus(errorCategory, propertyMap, correlationId, traceId) - - checkEquivalence(someGrpcStatus, toGrpc(fromGrpc(someGrpcStatus))) - } - } - } - } - - it should "fallback to the original cause if error message format is not recognized" in { - implicit val contextErrorLogger: BaseErrorLogger = NoBaseLogging - - val nonStandardMessage = "!!!Some non-standard message" - val error = BenignError.Reject("nvm").rpcStatus().copy(message = nonStandardMessage) - val deserializedError = DecodedCantonError.fromGrpcStatus(error).value - - deserializedError.cause shouldBe nonStandardMessage - } - - private val errorGrpcStatus = - createGrpcStatus(ErrorCategory.TransientServerFailure, Map("1" -> "2"), Some("c"), Some("t")) - - it should "return a Left on missing category information in error metadata" in { - val errInfo = errorGrpcStatus.details.find(_ is ErrorInfo).value.unpack[ErrorInfo] - val modifiedErrInfo = errInfo.copy(metadata = errInfo.metadata.removed("category")) - val modifiedErrorGrpcStatus = errorGrpcStatus.copy(details = - errorGrpcStatus.details.filterNot(_ is ErrorInfo) :+ Any.pack(modifiedErrInfo) - ) - - DecodedCantonError.fromGrpcStatus(modifiedErrorGrpcStatus).left.value should include( - "category key not found in error metadata" - ) - } - - it should s"return a Left on invalid number of ${classOf[ErrorInfo].getSimpleName} in the gRPC status" in { - testErrorDetails[ErrorInfo]( - "exactly one", - Map(0 -> false, 1 -> true, 2 -> false), - ) - } - - it should s"return a Left on invalid number of ${classOf[RequestInfo].getSimpleName} in the gRPC status" in { - testErrorDetails[RequestInfo]("at most one", Map(0 -> true, 1 -> true, 2 -> false)) - } - - it should s"return a Left on invalid number of ${classOf[RetryInfo].getSimpleName} in the gRPC status" in { - testErrorDetails[RetryInfo]("at most one", Map(0 -> true, 1 -> true, 2 -> false)) - } - - it should s"allow any number of ${classOf[ResourceInfo].getSimpleName} in the gRPC status" in { - testErrorDetails[ResourceInfo]("doesn't matter", Map(0 -> true, 1 -> true, 2 -> true)) - } - - it should s"use ${ErrorCategory.UnredactedSecurityAlert} for an unredacted security alert" in { - val status = - createGrpcStatus(ErrorCategory.UnredactedSecurityAlert, Map.empty, Some("c"), Some("t")) - val decoded = DecodedCantonError.fromGrpcStatus(status).value - decoded.code.code.category shouldBe ErrorCategory.UnredactedSecurityAlert - } - - it should s"handle redacted security alerts" in { - val status = createGrpcStatus(ErrorCategory.SecurityAlert, Map.empty, Some("c"), Some("t")) - val decoded = DecodedCantonError.fromGrpcStatus(status).value - decoded.cause shouldBe "A security-sensitive error has been received" - decoded.correlationId shouldBe Some("c") - decoded.traceId shouldBe Some("t") - } - - private def testErrorDetails[T <: GeneratedMessage]( - arityMsg: String, - allowedNumbers: Map[Int, Boolean], - )(implicit - expectedTypeCompanion: GeneratedMessageCompanion[T] - ): Assertion = { - allowedNumbers.foreach { case (times, expectPass) => - val errorDetails = errorGrpcStatus.details - val errDetail = errorDetails.find(_ is expectedTypeCompanion).value - val modifiedStatus = errorGrpcStatus.copy(details = - errorDetails.filterNot(_ is expectedTypeCompanion) ++ (1 to times).map(_ => errDetail) - ) - - val actual = DecodedCantonError.fromGrpcStatus(modifiedStatus) - - if (expectPass) - actual.isRight shouldBe true - else - actual shouldBe Left( - s"Could not extract error detail. Expected $arityMsg ${expectedTypeCompanion.scalaDescriptor.fullName} in status details, but got $times" - ) - } - succeed - } - - private def createGrpcStatus( - errorCategory: ErrorCategory, - propertyMap: Map[String, String], - correlationId: Option[String], - traceId: Option[String], - ) = { - implicit val contextErrorLogger: NoBaseLogging = - new NoBaseLogging(propertyMap, correlationId, traceId) - implicit val errorCode: ErrorCode = - new ErrorCode("SOME_ERROR_CODE_ID", errorCategory)(ErrorClass(List.empty)) {} - { - new DamlErrorWithDefiniteAnswer( - cause = "Some cause", - throwableO = Some(new RuntimeException("oups")), - definiteAnswer = true, - extraContext = Map("key" -> "val"), - ) { - override def resources: Seq[(ErrorResource, String)] = - super.resources :+ (ErrorResource.CommandId -> "some-cmd-id") - } - }.rpcStatus() - } - - private def propertyMapTable = Table( - "property map", - Map("p1" -> "k1", "p2" -> "k2"), - Map("p1" -> "k1"), - Map.empty[String, String], - ) - - private def correlationIdTable = Table( - "correlation id", - Some("corr-id"), - None, - ) - - private def traceIdTable = Table("trace id", Some("trace-id"), None) - - private def errorCategoriesTable = { - val grpcAwareErrorCats = ErrorCategory.all.filter(_.grpcCode.nonEmpty) - Table( - "error category", - grpcAwareErrorCats* - ) - } - - // Manual equality check for rpc statuses since encoding of maps (error details) - // in ByteString is non-deterministic - private def checkEquivalence( - rpcStatusOriginal: RpcStatus, - rpcStatusFromReconstructedError: RpcStatus, - ): Assertion = { - def refine(details: Seq[com.google.protobuf.any.Any]): Seq[ErrorDetail] = - ErrorDetails.from(details.map(toJavaProto)) - - rpcStatusFromReconstructedError.code shouldBe rpcStatusOriginal.code - rpcStatusFromReconstructedError.message shouldBe rpcStatusOriginal.message - val recontructedErrorDetails = refine(rpcStatusFromReconstructedError.details) - val originalErrorDetails = refine(rpcStatusOriginal.details) - recontructedErrorDetails shouldBe originalErrorDetails - } -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/ErrorDetailsSpec.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/ErrorDetailsSpec.scala deleted file mode 100644 index 0bc5c267c1..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/ErrorDetailsSpec.scala +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error.utils - -import com.digitalasset.base.error.ErrorCategory.BackgroundProcessDegradationWarning -import com.digitalasset.base.error.{ErrorClass, ErrorCode, NoBaseLogging} -import com.google.protobuf -import io.grpc.{Status, StatusRuntimeException} -import org.scalatest.flatspec.AnyFlatSpec -import org.scalatest.matchers.should.Matchers - -import scala.concurrent.duration.* - -class ErrorDetailsSpec extends AnyFlatSpec with Matchers { - - private val errorLogger = NoBaseLogging - - behavior of classOf[ErrorDetails.type].getName - - it should "correctly match exception to error codes " in { - val securitySensitive = - SevereError.Reject("some internal failure")(errorLogger).asGrpcError - val notSecuritySensitive = - BenignError.Reject("some service")(errorLogger).asGrpcError - - ErrorDetails.matches( - securitySensitive, - SevereError, - ) shouldBe false - - ErrorDetails.matches( - notSecuritySensitive, - BenignError, - ) shouldBe true - - ErrorDetails.matches( - new StatusRuntimeException(Status.ABORTED), - BenignError, - ) shouldBe false - - ErrorDetails.matches( - new Exception, - BenignError, - ) shouldBe false - - object NonGrpcErrorCode - extends ErrorCode( - id = "NON_GRPC_ERROR_CODE_123", - BackgroundProcessDegradationWarning, - )(ErrorClass.root()) - NonGrpcErrorCode.category.grpcCode shouldBe empty - ErrorDetails.matches( - new StatusRuntimeException(Status.ABORTED), - NonGrpcErrorCode, - ) shouldBe false - } - - it should "should preserve details when going through grpc Any" in { - val details = Seq( - ErrorDetails - .ErrorInfoDetail(errorCodeId = "errorCodeId1", metadata = Map("a" -> "b", "c" -> "d")), - ErrorDetails.ResourceInfoDetail(name = "name1", typ = "type1"), - ErrorDetails.RequestInfoDetail(correlationId = "correlationId1"), - ErrorDetails.RetryInfoDetail(1.seconds + 2.milliseconds), - ) - val anys: Seq[protobuf.Any] = details.map(_.toRpcAny) - ErrorDetails.from(anys) shouldBe details - } -} diff --git a/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/SevereError.scala b/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/SevereError.scala deleted file mode 100644 index 8f81b700f8..0000000000 --- a/canton/base/errors/src/test/scala/com/digitalasset/base/error/utils/SevereError.scala +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.base.error.utils - -import com.digitalasset.base.error.{ - BaseErrorLogger, - DamlErrorWithDefiniteAnswer, - ErrorCategory, - ErrorClass, - ErrorCode, - Explanation, - Resolution, -} - -@Explanation("Things happen.") -@Resolution("Turn it off and on again.") -case object SevereError - extends ErrorCode("BLUE_SCREEN", ErrorCategory.SystemInternalAssumptionViolated)( - ErrorClass.root() - ) { - final case class Reject( - message: String, - override val throwableO: Option[Throwable] = None, - )(implicit - loggingContext: BaseErrorLogger - ) extends DamlErrorWithDefiniteAnswer( - cause = message, - extraContext = Map("throwableO" -> throwableO.toString), - ) -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/ConfidentialConfigWriter.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/ConfidentialConfigWriter.scala deleted file mode 100644 index d6d9216043..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/ConfidentialConfigWriter.scala +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.config - -import pureconfig.ConfigWriter -import pureconfig.generic.DerivedConfigWriter -import pureconfig.generic.semiauto.deriveWriter -import shapeless.Lazy - -class ConfidentialConfigWriter(confidential: Boolean) { - def apply[Config]( - map: Config => Config - )(implicit writer: Lazy[DerivedConfigWriter[Config]]): ConfigWriter[Config] = { - val parent = deriveWriter[Config] - if (confidential) - (a: Config) => parent.to(map(a)) - else - parent - } -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/KeyStoreConfig.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/KeyStoreConfig.scala deleted file mode 100644 index a288005939..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/KeyStoreConfig.scala +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.config - -import pureconfig.ConfigReader -import pureconfig.generic.semiauto.deriveReader - -/** Password wrapper for keystores to prevent the values being printed in logs. - * @param pw - * password value - public for supporting PureConfig parsing but callers should prefer accessing - * through unwrap - */ -final case class Password(pw: String) extends AnyVal { - def unwrap: String = pw - - def toCharArray: Array[Char] = pw.toCharArray - - // We do not want to print out the password in log files - override def toString: String = s"Password(****)" -} - -object Password { - implicit val passwordReader: ConfigReader[Password] = deriveReader[Password] - - def empty: Password = Password("") -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/PemFileOrString.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/PemFileOrString.scala deleted file mode 100644 index 9ae2c05b92..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/PemFileOrString.scala +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.config - -import better.files.* -import com.digitalasset.canton.config.RequireTypes.ExistingFile -import com.google.protobuf.ByteString - -/** A class representing either an existing PEM file path or an inlined PEM string for configuration - * file fields - */ -sealed trait PemFileOrString { - def pemBytes: ByteString - def pemStream: java.io.InputStream = pemBytes.newInput() -} - -/** A class representing an existing PEM file path for configuration file fields - */ -final case class PemFile(pemFile: ExistingFile) extends PemFileOrString { - override lazy val pemBytes: ByteString = - ByteString.copyFrom(File(pemFile.unwrap.getAbsolutePath).loadBytes) -} - -/** A class representing an inlined PEM string for configuration file fields - */ -final case class PemString(override val pemBytes: ByteString) extends PemFileOrString { - lazy val pemString: String = pemBytes.toStringUtf8 -} - -object PemString { - def apply(pemString: String): PemString = new PemString(ByteString.copyFromUtf8(pemString)) -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/RequireTypes.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/RequireTypes.scala deleted file mode 100644 index 4b50bcba6f..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/config/RequireTypes.scala +++ /dev/null @@ -1,418 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.config - -import cats.Monoid -import cats.syntax.either.* -import com.digitalasset.canton.config.RequireTypes.NonNegativeNumeric.SubtractionResult -import pureconfig.error.{CannotConvert, FailureReason} -import pureconfig.{ConfigReader, ConfigWriter} -import slick.jdbc.{GetResult, SetParameter} - -import java.io.File - -/** Encapsulates those classes and their utility methods which enforce a given invariant via the use - * of require. - */ -object RequireTypes { - final case class InvariantViolation(message: String) - - final case class Port private (private val n: Int) extends Ordered[Port] { - def unwrap: Int = n - - require( - n >= Port.minValidPort && n <= Port.maxValidPort, - s"Unable to create Port as value $n was given, but only values between ${Port.minValidPort} and ${Port.maxValidPort} (inclusive) are allowed.", - ) - - def +(n: Int): Port = Port.tryCreate(this.unwrap + n) - override def compare(that: Port): Int = this.unwrap compare that.unwrap - - override def toString: String = n.toString - } - - object Port { - val (minValidPort, maxValidPort) = (0, scala.math.pow(2, 16).toInt - 1) - - def create(n: Int): Either[InvariantViolation, Port] = - Either.cond( - n >= Port.minValidPort && n <= Port.maxValidPort, - new Port(n), - InvariantViolation( - s"Unable to create Port as value $n was given, but only values between ${Port.minValidPort} and ${Port.maxValidPort} are allowed." - ), - ) - - def tryCreate(n: Int): Port = - new Port(n) - - lazy implicit val portReader: ConfigReader[Port] = - ConfigReader.fromString[Port] { str => - def err(message: String) = - CannotConvert(str, Port.getClass.getName, message) - - Either - .catchOnly[NumberFormatException](str.toInt) - .leftMap[FailureReason](error => err(error.getMessage)) - .flatMap(n => create(n).leftMap(_ => InvalidPort(n))) - } - - implicit val portWriter: ConfigWriter[Port] = ConfigWriter.toString(x => x.unwrap.toString) - - final case class InvalidPort(n: Int) extends FailureReason { - override def description: String = - s"Unable to create Port as value $n was given, but only values between ${Port.minValidPort} and ${Port.maxValidPort} are allowed" - } - - /** This instructs the server to automatically choose a free port. - */ - lazy val Dynamic: Port = Port.tryCreate(0) - } - - sealed trait RefinedNumeric[T] extends Ordered[RefinedNumeric[T]] { - protected def value: T - implicit def num: Numeric[T] - - def unwrap: T = value - - override def compare(that: RefinedNumeric[T]): Int = num.compare(value, that.value) - - override def toString: String = value.toString - } - - final case class NonNegativeNumeric[T] private (value: T)(implicit val num: Numeric[T]) - extends RefinedNumeric[T] { - import num.* - - def map[U](f: T => U)(implicit num: Numeric[U]) = NonNegativeNumeric.tryCreate(f(value)) - def increment: PositiveNumeric[T] = PositiveNumeric.tryCreate(value + num.one) - def toPositiveNumeric: Option[PositiveNumeric[T]] = PositiveNumeric.create(value).toOption - - def +(other: NonNegativeNumeric[T]): NonNegativeNumeric[T] = - NonNegativeNumeric.tryCreate(value + other.value) - def *(other: NonNegativeNumeric[T]): NonNegativeNumeric[T] = - NonNegativeNumeric.tryCreate(value * other.value) - def /(other: NonNegativeNumeric[T])(implicit fractional: Fractional[T]): NonNegativeNumeric[T] = - NonNegativeNumeric.tryCreate(fractional.div(value, other.value)) - def tryAdd(other: T): NonNegativeNumeric[T] = NonNegativeNumeric.tryCreate(value + other) - - /** Subtract other from this. Subtracts as much as possible of "other" from "this" such that - * "this" stays >= 0. Any remaining amount will be the remainder. e.g: - * {{{ - * NonNegativeNumeric(5).subtract(NonNegativeNumeric(3)) == SubtractionResult(NonNegativeNumeric(2), NonNegativeNumeric(0)) - * NonNegativeNumeric(2).subtract(NonNegativeNumeric(3)) == SubtractionResult(NonNegativeNumeric(0), NonNegativeNumeric(1)) - * }}} - * @param other - * value to subtract to this - */ - def subtract(other: NonNegativeNumeric[T]): SubtractionResult[T] = { - val difference = value - other.value - if (difference < num.zero) { - SubtractionResult(NonNegativeNumeric(num.zero), NonNegativeNumeric(-difference)) - } else { - SubtractionResult(NonNegativeNumeric.tryCreate(difference), NonNegativeNumeric(num.zero)) - } - } - } - - object NonNegativeNumeric { - - final case class SubtractionResult[T]( - result: NonNegativeNumeric[T], - remainder: NonNegativeNumeric[T], - ) - def tryCreate[T](t: T)(implicit num: Numeric[T]): NonNegativeNumeric[T] = - create(t).valueOr(err => throw new IllegalArgumentException(err.message)) - - def create[T]( - t: T - )(implicit num: Numeric[T]): Either[InvariantViolation, NonNegativeNumeric[T]] = - Either.cond( - num.compare(t, num.zero) >= 0, - NonNegativeNumeric(t), - InvariantViolation( - s"Received the negative $t as argument, but we require a non-negative value here." - ), - ) - - implicit val readNonNegativeLong: GetResult[NonNegativeLong] = GetResult { r => - NonNegativeLong.tryCreate(r.nextLong()) - } - - implicit val readNonNegativeInt: GetResult[NonNegativeInt] = GetResult { r => - NonNegativeInt.tryCreate(r.nextInt()) - } - - implicit val readNonNegativeIntOption: GetResult[Option[NonNegativeInt]] = GetResult { r => - r.nextIntOption().map(NonNegativeInt.tryCreate) - } - - implicit val readNonNegativeLongOption: GetResult[Option[NonNegativeLong]] = GetResult { r => - r.nextLongOption().map(NonNegativeLong.tryCreate) - } - - implicit def writeNonNegativeNumeric[T](implicit - f: SetParameter[T] - ): SetParameter[NonNegativeNumeric[T]] = - (s, pp) => { - pp >> s.unwrap - } - - implicit def writeNonNegativeNumericOption[T](implicit - f: SetParameter[Option[T]] - ): SetParameter[Option[NonNegativeNumeric[T]]] = (s, pp) => { - pp >> s.map(_.unwrap) - } - - implicit def nonNegativeNumericReader[T](implicit - num: Numeric[T] - ): ConfigReader[NonNegativeNumeric[T]] = - ConfigReader.fromString[NonNegativeNumeric[T]] { str => - def err(message: String) = - CannotConvert(str, NonNegativeNumeric.getClass.getName, message) - - num - .parseString(str) - .toRight[FailureReason](err("Cannot convert `str` to numeric")) - .flatMap(n => Either.cond(num.compare(n, num.zero) >= 0, tryCreate(n), NegativeValue(n))) - } - - implicit def nonNegativeNumericWriter[T]: ConfigWriter[NonNegativeNumeric[T]] = - ConfigWriter.toString(x => x.unwrap.toString) - - final case class NegativeValue[T](t: T) extends FailureReason { - override def description: String = - s"The value you gave for this configuration setting ($t) was negative, but we require a non-negative value for this configuration setting" - } - } - - type NonNegativeInt = NonNegativeNumeric[Int] - - object NonNegativeInt { - lazy val zero: NonNegativeInt = NonNegativeInt.tryCreate(0) - lazy val one: NonNegativeInt = NonNegativeInt.tryCreate(1) - lazy val two: NonNegativeInt = NonNegativeInt.tryCreate(2) - lazy val three: NonNegativeInt = NonNegativeInt.tryCreate(3) - lazy val maxValue: NonNegativeInt = NonNegativeInt.tryCreate(Int.MaxValue) - - def create(n: Int): Either[InvariantViolation, NonNegativeInt] = NonNegativeNumeric.create(n) - def tryCreate(n: Int): NonNegativeInt = NonNegativeNumeric.tryCreate(n) - def size[T](collection: Iterable[T]): NonNegativeInt = tryCreate(collection.size) - } - - type NonNegativeDouble = NonNegativeNumeric[Double] - - object NonNegativeDouble { - lazy val zero: NonNegativeDouble = NonNegativeDouble.tryCreate(0.0) - lazy val one: NonNegativeDouble = NonNegativeDouble.tryCreate(1.0) - lazy val maxValue: NonNegativeDouble = NonNegativeDouble.tryCreate(Double.MaxValue) - - def create(n: Double): Either[InvariantViolation, NonNegativeDouble] = - NonNegativeNumeric.create(n) - def tryCreate(n: Double): NonNegativeDouble = NonNegativeNumeric.tryCreate(n) - } - - final case class NonNegativeProportion(n: NonNegativeDouble) { - require(n <= NonNegativeDouble.one, "proportion may not be larger than 1") - } - - object NonNegativeProportion { - lazy val zero: NonNegativeProportion = NonNegativeProportion(NonNegativeDouble.zero) - } - - type NonNegativeLong = NonNegativeNumeric[Long] - - object NonNegativeLong { - implicit val nonNegativeLongMonoid: Monoid[NonNegativeLong] = new Monoid[NonNegativeLong] { - override def empty: NonNegativeLong = NonNegativeLong.zero - override def combine(x: NonNegativeLong, y: NonNegativeLong): NonNegativeLong = x + y - } - lazy val zero: NonNegativeLong = NonNegativeLong.tryCreate(0) - lazy val one: NonNegativeLong = NonNegativeLong.tryCreate(1) - lazy val maxValue: NonNegativeLong = NonNegativeLong.tryCreate(Long.MaxValue) - - def create(n: Long): Either[InvariantViolation, NonNegativeLong] = NonNegativeNumeric.create(n) - def tryCreate(n: Long): NonNegativeLong = NonNegativeNumeric.tryCreate(n) - def size[T](collection: Iterable[T]): NonNegativeLong = tryCreate(collection.size.toLong) - } - - final case class PositiveNumeric[T] private (value: T)(implicit val num: Numeric[T]) - extends RefinedNumeric[T] { - import num.* - - def +(other: PositiveNumeric[T]): PositiveNumeric[T] = - PositiveNumeric.tryCreate(value + other.value) - - def +(other: NonNegativeNumeric[T]): PositiveNumeric[T] = - PositiveNumeric.tryCreate(value + other.value) - - def *(other: PositiveNumeric[T]): PositiveNumeric[T] = - PositiveNumeric.tryCreate(value * other.value) - - def max(other: PositiveNumeric[T]): PositiveNumeric[T] = - PositiveNumeric.tryCreate(num.max(value, other.value)) - - def increment: PositiveNumeric[T] = PositiveNumeric.tryCreate(value + num.one) - def decrement: NonNegativeNumeric[T] = NonNegativeNumeric.tryCreate(value - num.one) - - def tryAdd(other: T): PositiveNumeric[T] = PositiveNumeric.tryCreate(value + other) - - def toNonNegative: NonNegativeNumeric[T] = - NonNegativeNumeric.tryCreate(value) // always possible to convert positive to non negative num - } - - type PositiveInt = PositiveNumeric[Int] - - object PositiveInt { - def create(n: Int): Either[InvariantViolation, PositiveInt] = PositiveNumeric.create(n) - def tryCreate(n: Int): PositiveInt = PositiveNumeric.tryCreate(n) - - lazy val one: PositiveInt = PositiveInt.tryCreate(1) - lazy val two: PositiveInt = PositiveInt.tryCreate(2) - lazy val three: PositiveInt = PositiveInt.tryCreate(3) - lazy val four: PositiveInt = PositiveInt.tryCreate(4) - lazy val MaxValue: PositiveInt = PositiveInt.tryCreate(Int.MaxValue) - } - - type PositiveLong = PositiveNumeric[Long] - - object PositiveLong { - def create(n: Long): Either[InvariantViolation, PositiveLong] = PositiveNumeric.create(n) - def tryCreate(n: Long): PositiveLong = PositiveNumeric.tryCreate(n) - - lazy val one: PositiveLong = PositiveLong.tryCreate(1) - lazy val MaxValue: PositiveLong = PositiveLong.tryCreate(Long.MaxValue) - } - - type PositiveDouble = PositiveNumeric[Double] - object PositiveDouble { - def create(n: Double): Either[InvariantViolation, PositiveDouble] = PositiveNumeric.create(n) - def tryCreate(n: Double): PositiveDouble = PositiveNumeric.tryCreate(n) - } - - object PositiveNumeric { - def tryCreate[T](t: T)(implicit num: Numeric[T]): PositiveNumeric[T] = - create(t).valueOr(err => throw new IllegalArgumentException(err.message)) - - def create[T]( - t: T - )(implicit num: Numeric[T]): Either[InvariantViolation, PositiveNumeric[T]] = - Either.cond( - num.compare(t, num.zero) > 0, - PositiveNumeric(t), - InvariantViolation( - s"Received the non-positive $t as argument, but we require a positive value here." - ), - ) - - implicit def positiveNumericReader[T](implicit - num: Numeric[T], - tReader: ConfigReader[T], - ): ConfigReader[PositiveNumeric[T]] = - tReader.emap(n => - Either.cond(num.compare(n, num.zero) >= 0, tryCreate(n), NonPositiveValue(n)) - ) - - implicit def readPositiveDouble: GetResult[PositiveDouble] = GetResult { r => - PositiveNumeric.tryCreate(r.nextDouble()) - } - - implicit def writePositiveNumeric[T](implicit - f: SetParameter[T] - ): SetParameter[PositiveNumeric[T]] = - (s, pp) => { - pp >> s.unwrap - } - - implicit def positiveNumericWriter[T](implicit - tWriter: ConfigWriter[T] - ): ConfigWriter[PositiveNumeric[T]] = - tWriter.contramap[PositiveNumeric[T]](pn => pn.value) - - final case class NonPositiveValue[T](t: T) extends FailureReason { - override def description: String = - s"The value you gave for this configuration setting ($t) was non-positive, but we require a positive value for this configuration setting" - } - } - - final case class DoubleGreaterEqual1 private (override val value: Double) - extends RefinedNumeric[Double] { - override implicit def num: Numeric[Double] = Numeric.DoubleIsFractional - } - - object DoubleGreaterEqual1 { - def tryCreate(t: Double): DoubleGreaterEqual1 = - create(t).valueOr(err => throw new IllegalArgumentException(err.message)) - - def create( - value: Double - ): Either[InvariantViolation, DoubleGreaterEqual1] = - Either.cond( - value >= 1.0, - DoubleGreaterEqual1(value), - InvariantViolation( - s"Received $value < 1 as argument, but we require a value greter or equal to 1." - ), - ) - - implicit def doubleGreaterThanEqual1Reader[T]: ConfigReader[DoubleGreaterEqual1] = - ConfigReader.fromString[DoubleGreaterEqual1] { str => - def err(message: String) = - CannotConvert(str, NonNegativeNumeric.getClass.getName, message) - - Numeric[Double] - .parseString(str) - .toRight[FailureReason](err("Cannot convert `str` to numeric")) - .flatMap(n => Either.cond(n >= 1, tryCreate(n), LessThan1Value(n))) - } - - implicit def doubleGreaterThanEqual1Writer[T]: ConfigWriter[DoubleGreaterEqual1] = - ConfigWriter.toString(x => x.unwrap.toString) - - final case class LessThan1Value[T](t: T) extends FailureReason { - override def description: String = - s"The value you gave for this configuration setting ($t) was less than one, but we require a value >= 1 for this configuration setting" - } - } - - final case class ExistingFile private (private val file: File) { - def unwrap: File = file - require(file.exists(), s"Unable to create ExistingFile as non-existing file $file was given.") - } - - object ExistingFile { - - def create(file: File): Either[InvariantViolation, ExistingFile] = - Either.cond( - file.exists, - new ExistingFile(file), - InvariantViolation( - s"The specified file $file does not exist/was not found. Please specify an existing file" - ), - ) - - def tryCreate(file: File): ExistingFile = - new ExistingFile(file) - - def tryCreate(path: String): ExistingFile = - new ExistingFile(new File(path)) - - lazy implicit val existingFileReader: ConfigReader[ExistingFile] = - ConfigReader.fromString[ExistingFile] { str => - def err(message: String) = - CannotConvert(str, ExistingFile.getClass.getName, message) - - Either - .catchOnly[NullPointerException](new File(str)) - .leftMap[FailureReason](error => err(error.getMessage)) - .flatMap(f => create(f).leftMap(_ => NonExistingFile(f))) - } - - final case class NonExistingFile(file: File) extends FailureReason { - override def description: String = - s"The specified file $file does not exist/was not found. Please specify an existing file" - } - } - -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/discard/Implicits.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/discard/Implicits.scala deleted file mode 100644 index 03d2794bf0..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/discard/Implicits.scala +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.discard - -object Implicits { - - /** Evaluate the expression and discard the result. */ - implicit final class DiscardOps[A](private val a: A) extends AnyVal { - @inline - def discard[B](implicit ev: A =:= B): Unit = () - } - -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/time/TimeProvider.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/time/TimeProvider.scala deleted file mode 100644 index dd02c40567..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/time/TimeProvider.scala +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.time - -trait TimeProvider { - - /** Potentially non-monotonic time provider - */ - def nowInMicrosecondsSinceEpoch: Long - -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/BytesUnit.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/BytesUnit.scala deleted file mode 100644 index 4bd17b01b7..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/BytesUnit.scala +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.util - -import scala.math.Numeric.LongIsIntegral - -final case class BytesUnit(bytes: Long) { - - def *(that: Long): BytesUnit = BytesUnit(this.bytes * that) - - override def toString: String = { - val factorL = BytesUnit.factor - val factorD = BytesUnit.factor.toDouble - val (convertedValue: Double, unit) = bytes match { - case v if v < factorL => (v.toDouble, "B") - case v if v >= factorL && v < factorL * factorL => (v / factorD, "KB") - case v if v >= factorL * factorL && v < factorL * factorL * factorL => - (v / (factorD * factorD), "MB") - case v => - (v / (factorD * factorD * factorD), "GB") - } - f"$convertedValue%.2f $unit" - } -} - -object BytesUnit { - private[BytesUnit] val factor = 1024L - - val zero: BytesUnit = BytesUnit(0L) - - def KB(value: Long): BytesUnit = BytesUnit(value) * factor - def MB(value: Long): BytesUnit = KB(value) * factor - - implicit val bytesUnitIsNumeric: Numeric[BytesUnit] = new Numeric[BytesUnit] { - override def plus(x: BytesUnit, y: BytesUnit): BytesUnit = BytesUnit(x.bytes + y.bytes) - - override def minus(x: BytesUnit, y: BytesUnit): BytesUnit = BytesUnit(x.bytes - y.bytes) - - override def times(x: BytesUnit, y: BytesUnit): BytesUnit = BytesUnit(x.bytes * y.bytes) - - override def negate(x: BytesUnit): BytesUnit = BytesUnit(-x.bytes) - - override def fromInt(x: Int): BytesUnit = BytesUnit(x.toLong) - - override def parseString(str: String): Option[BytesUnit] = - LongIsIntegral.parseString(str).map(BytesUnit(_)) - - override def toInt(x: BytesUnit): Int = x.bytes.toInt - - override def toLong(x: BytesUnit): Long = x.bytes - - override def toFloat(x: BytesUnit): Float = x.bytes.toFloat - - override def toDouble(x: BytesUnit): Double = x.bytes.toDouble - - override def compare(x: BytesUnit, y: BytesUnit): Int = java.lang.Long.compare(x.bytes, y.bytes) - } - -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/JarResourceUtils.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/JarResourceUtils.scala deleted file mode 100644 index bd103fca6f..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/JarResourceUtils.scala +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.util - -import java.io.File -import java.nio.file.{Files, Paths, StandardCopyOption} -import scala.util.Using - -/** Utility methods for loading resource test files. - */ -object JarResourceUtils { - // Extract a resource and store it in a temporary file. - // This method works for resources embedded in a JAR. - def extractFileFromJar(path: String): File = - Using(getClass.getClassLoader.getResourceAsStream(path)) { inputStream => - if (inputStream == null) throw new RuntimeException(s"Resource for $path not found") - - // In case of absolute path, get only the file name - // (to be used as temp file name) - val tmpFileName = new File(path).getName - val tmpFilePath = Files.createTempFile(tmpFileName, "tmp") - Files.copy(inputStream, tmpFilePath, StandardCopyOption.REPLACE_EXISTING) - tmpFilePath.toFile - }.getOrElse(throw new RuntimeException(s"Resource for $path not found")) - - // Use resource file directly wrapping it in a File. This method works in unit tests. - // It doesn't work for resources from a JAR - def resourceFile(path: String): File = - Option(getClass.getClassLoader.getResource(path)) - .map(_.toURI) - .map(Paths.get(_).toFile) - .getOrElse(throw new RuntimeException(s"Resource for $path not found")) -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/Mutex.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/Mutex.scala deleted file mode 100644 index 6e47d8f5a5..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/Mutex.scala +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.util - -import com.digitalasset.canton.util.Mutex.MutexBlocker - -import java.lang.Thread.onSpinWait -import java.util.concurrent.ForkJoinPool -import java.util.concurrent.ForkJoinPool.ManagedBlocker -import java.util.concurrent.atomic.AtomicInteger -import java.util.concurrent.locks.ReentrantLock -import scala.annotation.tailrec - -/** Lock to be used instead of blocking synchronized - * - * The fork join pool is leaking threads potentially with every invocation of blocking. Therefore, - * only invoke it if really necessary. - */ -class Mutex { - - private val lock = new ReentrantLock() - - @deprecated("use exclusive, not synchronize", since = "3.4") - def synchronized[T](body: => T): T = sys.error("use exclusive to distinguish") - - def exclusive[T](f: => T): T = - // We perform lock inflation to reduce the chance of having to use expensive locks - // First level: Immediate acquisition - if (lock.tryLock()) { - try { - f - } finally { - lock.unlock() - } - } else { - // Second level: Spinning - Trying to avoid parking the thread - val maxSpins = Mutex.MaxSpins.get() - @tailrec - def retry(spins: Int): T = - if (spins < maxSpins) { - if (lock.tryLock()) { - try { - f - } finally { - lock.unlock() - } - } else { - onSpinWait() - retry(spins + 1) - } - } else { - // 3. "Heavy Lock" (Inflation) - Trigger FJP compensation - // Only at this point do we notify the ForkJoinPool. - val blocker = new MutexBlocker(lock) - ForkJoinPool.managedBlock(blocker) - try { - f - } finally { - lock.unlock() - } - } - - retry(0) - } -} - -object Mutex { - def apply(): Mutex = new Mutex - val MaxSpins = new AtomicInteger(3000) - // Inner class to handle the official FJP integration - @SuppressWarnings(Array("org.wartremover.warts.Var")) - private class MutexBlocker(lock: ReentrantLock) extends ManagedBlocker { - private var acquired = false - - // FJP calls this to see if it even needs to block/compensate - override def isReleasable: Boolean = { - acquired = lock.tryLock() - acquired - } - - // FJP calls this if isReleasable returned false. - // This is where thread compensation (spawning a new thread) happens. - override def block(): Boolean = { - if (!acquired) { - lock.lock() // This is the heavy-weight park - acquired = true - } - true - } - } -} diff --git a/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/VersionUtil.scala b/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/VersionUtil.scala deleted file mode 100644 index d52ce0b64d..0000000000 --- a/canton/base/util-external/src/main/scala/com/digitalasset/canton/util/VersionUtil.scala +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.util - -import cats.syntax.traverse.* - -object VersionUtil { - def create( - rawVersion: String, - baseName: String, - ): Either[String, (Int, Int, Int, Option[String])] = { - // `?:` removes the capturing group, so we get a cleaner pattern-match statement - val regex = raw"([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,4})(?:-(.*))?".r - - rawVersion match { - case regex(rawMajor, rawMinor, rawPatch, suffix) => - val parsedDigits = List(rawMajor, rawMinor, rawPatch).traverse(raw => - raw.toIntOption.toRight(s"Couldn't parse number `$raw`") - ) - parsedDigits.flatMap { - case List(major, minor, patch) => - // `suffix` is `null` if no suffix is given - Right((major, minor, patch, Option(suffix))) - case _ => Left(s"Unexpected error while parsing version `$rawVersion`") - } - - case _ => - Left( - s"Unable to convert string `$rawVersion` to a valid $baseName. A $baseName is similar to a semantic version. For example, '1.2.3' or '1.2.3-SNAPSHOT' are valid ${baseName}s." - ) - } - } -} diff --git a/canton/base/util-external/src/test/scala/com/digitalasset/canton/config/RequireTypesTest.scala b/canton/base/util-external/src/test/scala/com/digitalasset/canton/config/RequireTypesTest.scala deleted file mode 100644 index 44f964db58..0000000000 --- a/canton/base/util-external/src/test/scala/com/digitalasset/canton/config/RequireTypesTest.scala +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.config - -import com.digitalasset.canton.config.RequireTypes.NonNegativeNumeric -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AnyWordSpec - -class RequireTypesTest extends AnyWordSpec with Matchers { - "NonNegativeNumeric" should { - "subtract" in { - val ten = NonNegativeNumeric.tryCreate(10) - val three = NonNegativeNumeric.tryCreate(3) - val tenMinusThree = ten.subtract(three) - tenMinusThree.result.value shouldBe 7 - tenMinusThree.remainder.value shouldBe 0 - - val threeMinusTen = three.subtract(ten) - threeMinusTen.result.value shouldBe 0 - threeMinusTen.remainder.value shouldBe 7 - } - } -} diff --git a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/KmsDriver.scala b/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/KmsDriver.scala deleted file mode 100644 index d1d8a54ad5..0000000000 --- a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/KmsDriver.scala +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.crypto.kms.driver.api - -trait KmsDriver diff --git a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/KmsDriverFactory.scala b/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/KmsDriverFactory.scala deleted file mode 100644 index 70292b2091..0000000000 --- a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/KmsDriverFactory.scala +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.crypto.kms.driver.api - -import com.digitalasset.canton.driver.api.DriverFactory - -trait KmsDriverFactory extends DriverFactory { - override type Driver <: KmsDriver -} diff --git a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriver.scala b/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriver.scala deleted file mode 100644 index 00827300b8..0000000000 --- a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriver.scala +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.crypto.kms.driver.api.v1 - -import com.digitalasset.canton.crypto.kms.driver.api -import io.opentelemetry.context.Context - -import scala.concurrent.Future - -/** The interface for a pluggable KMS implementation, that is, a KMS Driver. - * - * Cryptographic operations are asynchronous, i.e., they return a Future. In case of failure, the - * Future must fail with a [[KmsDriverException]]. Transient failures should still fail the Future, - * but the exception’s `retryable` flag should be set to true. An exception should only be thrown - * for driver operations (e.g., sign), and not, for example, for health checks. - * - * Each KMS operation takes an OpenTelemetry [[io.opentelemetry.context.Context]] as a trace - * context that can optionally be propagated to the external KMS. - */ -trait KmsDriver extends api.KmsDriver with AutoCloseable { - - require( - supportedSigningKeySpecs.nonEmpty, - "Supported signing key specifications must not be empty.", - ) - require( - supportedSigningAlgoSpecs.nonEmpty, - "Supported signing algorithm specifications must not be empty.", - ) - require( - supportedEncryptionKeySpecs.nonEmpty, - "Supported encryption key specifications must not be empty.", - ) - require( - supportedEncryptionAlgoSpecs.nonEmpty, - "Supported encryption algorithm specifications must not be empty.", - ) - - /** Returns the current health of the driver. The driver should not throw an exception; instead, - * it should return a [[com.digitalasset.canton.crypto.kms.driver.api.v1.KmsDriverHealth]] value. - * - * @return - * A future that completes with the driver's health. - */ - def health: Future[KmsDriverHealth] - - /** The supported signing key specifications by the driver. This must not be empty. */ - def supportedSigningKeySpecs: Set[SigningKeySpec] - - /** The supported signing algorithm specifications by the driver. This must not be empty. */ - def supportedSigningAlgoSpecs: Set[SigningAlgoSpec] - - /** The supported encryption key specifications by the driver. This must not be empty. */ - def supportedEncryptionKeySpecs: Set[EncryptionKeySpec] - - /** The supported encryption algorithm specifications by the driver. This must not be empty. */ - def supportedEncryptionAlgoSpecs: Set[EncryptionAlgoSpec] - - /** Generate a new signing key pair. - * - * @param signingKeySpec - * The key specification for the new signing key pair. The caller ensures it is a - * [[supportedSigningKeySpecs]]. - * @param keyName - * An optional descriptive name for the key pair, max 300 characters long. - * - * @return - * A future that completes with the unique KMS key identifier, max 300 characters long. - */ - def generateSigningKeyPair( - signingKeySpec: SigningKeySpec, - keyName: Option[String], - )(traceContext: Context): Future[String] - - /** Generate a new asymmetric encryption key pair. - * - * @param encryptionKeySpec - * The key specification of the new encryption key pair. The caller ensures it is a - * [[supportedEncryptionKeySpecs]]. - * @param keyName - * An optional descriptive name for the key pair, max 300 characters long. - * - * @return - * A future that completes with the unique KMS key identifier, max 300 characters long. - */ - def generateEncryptionKeyPair( - encryptionKeySpec: EncryptionKeySpec, - keyName: Option[String], - )(traceContext: Context): Future[String] - - /** Generate a new symmetric encryption key. The default symmetric key specification of the KMS is - * used. - * - * @param keyName - * An optional descriptive name for the symmetric key, max 300 characters long. - * - * @return - * A future that completes with the unique KMS key identifier, max 300 characters long. - */ - def generateSymmetricKey(keyName: Option[String])(traceContext: Context): Future[String] - - /** Sign the given data using the private key identified by the keyId with the given signing - * algorithm specification. If the `algoSpec` is not compatible with the key spec of `keyId` then - * this method must fail with a non-retryable exception. - * - * @param data - * The data to be signed with the specified signature algorithm. The upper bound of the data - * size is 4kb. - * @param keyId - * The identifier of the private signing key. - * @param algoSpec - * The signature algorithm specification. The caller ensures it is a - * [[supportedSigningAlgoSpecs]]. - * - * @return - * A future that completes with the signature. - */ - def sign(data: Array[Byte], keyId: String, algoSpec: SigningAlgoSpec)( - traceContext: Context - ): Future[Array[Byte]] - - /** Asymmetrically decrypt the given ciphertext using the private key identified by the keyId with - * the given asymmetric encryption algorithm specification. If the `algoSpec` is not compatible - * with the key spec of `keyId` then this method must fail with a non-retryable exception. - * - * @param ciphertext - * The asymmetrically encrypted ciphertext that needs to be decrypted. The length of the - * ciphertext depends on the parameters of the asymmetric encryption algorithm. Implementations - * may assume that the length of the ciphertext is at most 6144 bytes in any case. - * @param keyId - * The identifier of the private encryption key to perform the asymmetric decryption with. - * @param algoSpec - * The asymmetric encryption algorithm specification. The caller ensures it is a - * [[supportedEncryptionAlgoSpecs]]. - * - * @return - * A future that completes with the plaintext. - */ - def decryptAsymmetric( - ciphertext: Array[Byte], - keyId: String, - algoSpec: EncryptionAlgoSpec, - )(traceContext: Context): Future[Array[Byte]] - - /** Symmetrically encrypt the given plaintext using the symmetric encryption key identified by the - * keyId. The same/default symmetric encryption algorithm of the KMS must be used for both - * symmetric encryption and decryption. - * - * @param data - * The plaintext to symmetrically encrypt. The upper bound of the data size is 4kb. - * @param keyId - * The identifier of the symmetric encryption key. - * - * @return - * A future that completes with the ciphertext. - */ - def encryptSymmetric(data: Array[Byte], keyId: String)(traceContext: Context): Future[Array[Byte]] - - /** Symmetrically decrypt the given ciphertext using the symmetric encryption key identified by - * the keyId. The same/default symmetric encryption algorithm of the KMS must be used for both - * symmetric encryption and decryption. - * - * @param ciphertext - * The ciphertext to symmetrically decrypt. The upper bound of the ciphertext size is 6144 - * bytes. - * @param keyId - * The identifier of the symmetric encryption key. - * - * @return - * A future that completes with the plaintext. - */ - def decryptSymmetric(ciphertext: Array[Byte], keyId: String)( - traceContext: Context - ): Future[Array[Byte]] - - /** Exports a public key from the KMS for the given key pair identified by keyId. - * - * @param keyId - * The identifier of the key pair. - * - * @return - * A future that completes with the exported [[PublicKey]] - */ - def getPublicKey(keyId: String)(traceContext: Context): Future[PublicKey] - - /** Asserts that the key given by its identifier exists and is active. - * - * @param keyId - * The identifier of the key to be checked. - * - * @return - * A future that completes successfully if the key exists and is active. Otherwise, the future - * must have been failed. - */ - def keyExistsAndIsActive(keyId: String)(traceContext: Context): Future[Unit] - - /** Deletes a key given by its identifier from the KMS. - * - * @param keyId - * The identifier of the key to be deleted. - * - * @return - * A future that completes when the key has been deleted or the deletion of the key has been - * scheduled. - */ - def deleteKey(keyId: String)(traceContext: Context): Future[Unit] - -} - -/** A public key exported from the KMS. - * - * @param key - * The DER-encoded X.509 public key (SubjectPublicKeyInfo). EC keys must be uncompressed. - * @param spec - * The key specification of the key pair - */ -final case class PublicKey(key: Array[Byte], spec: KeySpec) diff --git a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverException.scala b/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverException.scala deleted file mode 100644 index 5462ad9288..0000000000 --- a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverException.scala +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.crypto.kms.driver.api.v1 - -/** An exception that should be thrown by the KMS driver in case of failure. - * - * @param exception - * The underlying exception of the KMS that lead to the failure. - * @param retryable - * If true the caller can retry the failing operation. - */ -final case class KmsDriverException(exception: Throwable, retryable: Boolean) - extends RuntimeException( - s"KMS Driver exception (retryable=$retryable): ${exception.getMessage}", - exception, - ) diff --git a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverFactory.scala b/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverFactory.scala deleted file mode 100644 index 34493d52fb..0000000000 --- a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverFactory.scala +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.crypto.kms.driver.api.v1 - -import com.digitalasset.canton.crypto.kms.driver.api -import com.digitalasset.canton.driver.api.v1 - -trait KmsDriverFactory extends api.KmsDriverFactory with v1.DriverFactory { - - override val version: Int = 1 - - override type Driver <: KmsDriver - -} diff --git a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverHealth.scala b/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverHealth.scala deleted file mode 100644 index e2fbd24177..0000000000 --- a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverHealth.scala +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.crypto.kms.driver.api.v1 - -sealed trait KmsDriverHealth extends Product with Serializable - -object KmsDriverHealth { - - /** The driver is healthy. */ - case object Ok extends KmsDriverHealth - - /** The driver's state is degraded but still functional (e.g., increased latency). The node's - * crypto component will report as degraded, although the node's APIs will continue to serve - * requests. - */ - final case class Degraded(reason: String) extends KmsDriverHealth - - /** The driver has failed and is currently non-functional, but may recover without a restart. - * While in this failing state, the node's APIs will be marked as not serving, but the liveness - * probe will continue to succeed and will not trigger a restart. - */ - final case class Failed(reason: String) extends KmsDriverHealth - - /** The driver is in a fatal and irrecoverable state, requiring the entire node to be restarted. - * The [[Fatal]] health status will propagate to the node's liveness probe, causing it to fail - * and trigger a restart via Kubernetes. - */ - final case class Fatal(reason: String) extends KmsDriverHealth -} diff --git a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverSpecs.scala b/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverSpecs.scala deleted file mode 100644 index 1ec3a9785d..0000000000 --- a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/crypto/kms/driver/api/v1/KmsDriverSpecs.scala +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.crypto.kms.driver.api.v1 - -sealed trait KeySpec extends Product with Serializable - -sealed trait SigningKeySpec extends KeySpec -object SigningKeySpec { - - /** Elliptic Curve Key from the Curve25519 curve as defined in http://ed25519.cr.yp.to/ - */ - case object EcCurve25519 extends SigningKeySpec - - /** Elliptic Curve Signing Key based on NIST P-256 (aka secp256r1) as defined in - * https://doi.org/10.6028/NIST.FIPS.186-4 - */ - case object EcP256 extends SigningKeySpec - - /** Elliptic Curve Signing Key based on NIST P-384 as defined in - * https://doi.org/10.6028/NIST.FIPS.186-4 - */ - case object EcP384 extends SigningKeySpec - - /** Elliptic Curve Key from SECG P256k1 curve (aka secp256k1) commonly used in bitcoin and - * ethereum as defined in https://www.secg.org/sec2-v2.pdf - */ - case object EcSecp256k1 extends SigningKeySpec -} - -sealed trait SigningAlgoSpec -object SigningAlgoSpec { - - /** EdDSA signature scheme based on Curve25519 and SHA512 as defined in http://ed25519.cr.yp.to/ - */ - case object Ed25519 extends SigningAlgoSpec - - /** Elliptic Curve Digital Signature Algorithm with SHA256 as defined in - * https://doi.org/10.6028/NIST.FIPS.186-4 - */ - case object EcDsaSha256 extends SigningAlgoSpec - - /** Elliptic Curve Digital Signature Algorithm with SHA384 as defined in - * https://doi.org/10.6028/NIST.FIPS.186-4 - */ - case object EcDsaSha384 extends SigningAlgoSpec -} - -sealed trait EncryptionKeySpec extends KeySpec -object EncryptionKeySpec { - - /** Elliptic Curve Key from the P-256 curve (aka Secp256r1) as defined in - * https://doi.org/10.6028/NIST.FIPS.186-4 - */ - case object EcP256 extends EncryptionKeySpec - - /** RSA 2048 bit */ - case object Rsa2048 extends EncryptionKeySpec -} - -sealed trait EncryptionAlgoSpec -object EncryptionAlgoSpec { - - /** ECIES with ECDH, AES128 CBC, and HKDF and authentication (MAC) with HMAC-SHA256. This requires - * a P-256 key because we use SHA256, and we need to align the lengths of the curve and the hash - * function. - */ - case object EciesHkdfHmacSha256Aes128Cbc extends EncryptionAlgoSpec - - /** RSA Encryption Scheme with Optimal Asymmetric Encryption Padding (OAEP) using SHA256 as - * defined in https://datatracker.ietf.org/doc/html/rfc8017#section-7.1 - */ - case object RsaEsOaepSha256 extends EncryptionAlgoSpec -} diff --git a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/driver/api/DriverFactory.scala b/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/driver/api/DriverFactory.scala deleted file mode 100644 index 015cb29b2a..0000000000 --- a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/driver/api/DriverFactory.scala +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.driver.api - -trait DriverFactory { - - /** The type of the driver that is instantiated by an implementation of the driver factory. */ - type Driver - - /** The name of the driver that is instantiated by an implementation of the driver factory. */ - def name: String - - /** The version of the driver API this factory is implemented against. */ - def version: Int - -} diff --git a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/driver/api/v1/DriverFactory.scala b/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/driver/api/v1/DriverFactory.scala deleted file mode 100644 index d004c5decc..0000000000 --- a/canton/community/kms-driver-api/src/main/scala/com/digitalasset/canton/driver/api/v1/DriverFactory.scala +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.driver.api.v1 - -import com.digitalasset.canton.driver.api -import org.slf4j.Logger -import pureconfig.{ConfigReader, ConfigWriter} - -import scala.concurrent.ExecutionContext - -/** The corresponding factory for an implementation of a [[Driver]] that can instantiate a new - * driver. - */ -trait DriverFactory extends api.DriverFactory { - - /** The name of the driver that is instantiated by an implementation of the driver factory. */ - def name: String - - /** The version of the driver API this factory is implemented against. */ - def version: Int - - /** Optional information for the build of the driver factory, e.g., git commit hash. */ - def buildInfo: Option[String] - - /** The driver-specific configuration type. */ - type ConfigType - - /** The parser to load the driver-specific configuration. */ - def configReader: ConfigReader[ConfigType] - - /** The configuration writer for the driver-specific configuration. - * - * @param confidential - * If the flag is true, the config writer should omit any sensitive configuration items, such - * as credentials. - */ - def configWriter(confidential: Boolean): ConfigWriter[ConfigType] - - /** The creation method of a driver by this factory. If the creation of the driver fails this - * method should throw an exception. - * - * @param config - * The driver-specific configuration. - * @param loggerFactory - * A logger factory that should be used by the driver to create a logger for a particular - * class. - * @param executionContext - * The execution context that should be used by the driver. - * - * @return - * A new instance of [[Driver]]. - */ - def create( - config: ConfigType, - loggerFactory: Class[?] => Logger, - executionContext: ExecutionContext, - ): Driver -} diff --git a/canton/community/lib/scalatest/src/main/scala/org/scalatest/AssertionsUtil.scala b/canton/community/lib/scalatest/src/main/scala/org/scalatest/AssertionsUtil.scala deleted file mode 100644 index 0f49ed546c..0000000000 --- a/canton/community/lib/scalatest/src/main/scala/org/scalatest/AssertionsUtil.scala +++ /dev/null @@ -1,19 +0,0 @@ -package org.scalatest - -import org.scalactic.source - -import scala.language.experimental.macros - -trait AssertionsUtil extends Assertions { - - /** Generalizes [[Assertions.assertTypeError]] in that the type error message can be inspected. - * - * [[Assertions.assertTypeError]](code) is equivalent to [[assertOnTypeError]](code)(_ => - * succeed). - */ - def assertOnTypeError(code: String)(assertion: String => Assertion)(implicit - pos: source.Position - ): Assertion = macro AssertionsUtilMacros.assertOnTypeErrorImpl -} - -object AssertionsUtil extends AssertionsUtil diff --git a/canton/community/lib/scalatest/src/main/scala/org/scalatest/AssertionsUtilMacros.scala b/canton/community/lib/scalatest/src/main/scala/org/scalatest/AssertionsUtilMacros.scala deleted file mode 100644 index 930b23758d..0000000000 --- a/canton/community/lib/scalatest/src/main/scala/org/scalatest/AssertionsUtilMacros.scala +++ /dev/null @@ -1,71 +0,0 @@ -package org.scalatest - -import org.scalactic.source -import org.scalatest.CompileMacro.{containsAnyValNullStatement, getCodeStringFromCodeExpression} -import org.scalatest.exceptions.{StackDepthException, TestFailedException} - -import scala.annotation.nowarn -import scala.reflect.macros.{Context, ParseException, TypecheckException} - -object AssertionsUtilMacros { - // Generalized version of org.scalatest.CompileMacro.assertTypeErrorImpl. Modifications are marked with MODIFIED - @nowarn("msg=dead code following this construct") - @nowarn("cat=deprecation") - @nowarn("msg=unused value of type") - def assertOnTypeErrorImpl(c: Context)(code: c.Expr[String])( - // MODIFIED: This parameter is new - assertion: c.Expr[String => Assertion] - )( - pos: c.Expr[source.Position] - ): c.Expr[Assertion] = { - import c.universe.* - - // extract code snippet - val codeStr = getCodeStringFromCodeExpression(c)( - // MODIFIED: Using the appropriate name here - "assertOnTypeError", - code, - ) - - try { - val tree = c.parse("{ " + codeStr + " }") - if (!containsAnyValNullStatement(c)(List(tree))) { - c.typecheck(tree) // parse and type check code snippet - // If reach here, type check passes, let's generate code to throw TestFailedException - val messageExpr = c.literal(Resources.expectedTypeErrorButGotNone(codeStr)) - reify { - throw new TestFailedException( - (_: StackDepthException) => Some(messageExpr.splice), - None, - pos.splice, - ) - } - } else { - - reify { - // statement such as val i: Int = null, compile fails as expected, generate code to return Succeeded - Succeeded - } - } - } catch { - case e: TypecheckException => - // MODIFIED: Evaluate the given assertion on the exception's message instead of always succeeding - val errorMessage = c.literal(e.msg) - reify { - assertion.splice(errorMessage.splice) - } - case e: ParseException => - // parse error, generate code to throw TestFailedException - val messageExpr = - c.literal(Resources.expectedTypeErrorButGotParseError(e.getMessage, codeStr)) - reify { - throw new TestFailedException( - (_: StackDepthException) => Some(messageExpr.splice), - None, - pos.splice, - ) - } - } - } - -} diff --git a/canton/community/lib/scalatest/src/test/scala/org/scalatest/AssertionsUtilTest.scala b/canton/community/lib/scalatest/src/test/scala/org/scalatest/AssertionsUtilTest.scala deleted file mode 100644 index 7a7443e9d2..0000000000 --- a/canton/community/lib/scalatest/src/test/scala/org/scalatest/AssertionsUtilTest.scala +++ /dev/null @@ -1,36 +0,0 @@ -package org.scalatest - -import org.scalatest.exceptions.TestFailedException -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AnyWordSpec - -import scala.util.Try - -class AssertionsUtilTest extends AnyWordSpec with Matchers with EitherValues { - - "AssertionsUtil.assertOnTypeError" should { - "check for type errors" in { - AssertionsUtil.assertOnTypeError("val i: String = 5") { typeError => - typeError should (include("type mismatch") and include("5") and include("java.lang.String")) - } - - AssertionsUtil.assertOnTypeError("implicitly[Int =:= String]") { typeError => - typeError should include("Cannot prove that Int =:= String") - } - - Try( - AssertionsUtil.assertOnTypeError("val i: Int = 5")(_ => succeed) - ).toEither.swap.value shouldBe a[TestFailedException] - - Try(AssertionsUtil.assertOnTypeError("val i: String = 5") { typeError => - typeError should include("foobar") - }).toEither.swap.value shouldBe a[TestFailedException] - } - - "fail on parse errors" in { - val err = - Try(AssertionsUtil.assertOnTypeError("val i = 5)")(_ => succeed)).toEither.swap.value - err.getMessage should include("Expected a type error, but got the following parse error:") - } - } -} diff --git a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/AllowTraverseSingleContainer.scala b/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/AllowTraverseSingleContainer.scala deleted file mode 100644 index ec3d4783b5..0000000000 --- a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/AllowTraverseSingleContainer.scala +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton - -import scala.annotation.StaticAnnotation - -final class AllowTraverseSingleContainer extends StaticAnnotation diff --git a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotDiscardLikeFuture.scala b/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotDiscardLikeFuture.scala deleted file mode 100644 index 5a9e4cf8a8..0000000000 --- a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotDiscardLikeFuture.scala +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton - -import scala.annotation.StaticAnnotation - -/** Annotated type constructors will be treated like a [[scala.concurrent.Future]] when looking for - * discarded futures. - */ -final class DoNotDiscardLikeFuture extends StaticAnnotation diff --git a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotReturnFromSynchronizedLikeFuture.scala b/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotReturnFromSynchronizedLikeFuture.scala deleted file mode 100644 index 7b5e9b0675..0000000000 --- a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotReturnFromSynchronizedLikeFuture.scala +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton - -import scala.annotation.StaticAnnotation - -/** Annotated type constructors will be treated like a [[scala.concurrent.Future]] when looking at - * the return types of synchronized blocks. - */ -final class DoNotReturnFromSynchronizedLikeFuture extends StaticAnnotation diff --git a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotTraverseLikeFuture.scala b/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotTraverseLikeFuture.scala deleted file mode 100644 index dec4239396..0000000000 --- a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/DoNotTraverseLikeFuture.scala +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton - -import scala.annotation.StaticAnnotation - -/** Annotated type constructors will be treated like a [[scala.concurrent.Future]] when looking for - * traverse-like calls with such an applicative instance. - */ -final class DoNotTraverseLikeFuture extends StaticAnnotation diff --git a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/FutureTransformer.scala b/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/FutureTransformer.scala deleted file mode 100644 index 9e13e9aaee..0000000000 --- a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/FutureTransformer.scala +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton - -import scala.annotation.StaticAnnotation - -/** Annotation for computation transformer type constructors (e.g., a monad transformer) so that if - * it will be treated future-like when applied to a future-like computation type. - * - * @param transformedTypeArgumentPosition - * The type argument position for the computation type that is transformed - */ -final case class FutureTransformer(transformedTypeArgumentPosition: Int) extends StaticAnnotation diff --git a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/GrpcServiceInvocationMethod.scala b/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/GrpcServiceInvocationMethod.scala deleted file mode 100644 index 2d6f6803d2..0000000000 --- a/canton/community/lib/wartremover-annotations/src/main/scala/com/digitalasset/canton/GrpcServiceInvocationMethod.scala +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) 2026 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton - -import scala.annotation.StaticAnnotation - -/** Annotation for methods and constructors. Implementations of such method (and any overrides) are - * not checked. Neither are the arguments to calls of such a method. - */ -final class GrpcServiceInvocationMethod extends StaticAnnotation diff --git a/project/BuildCommon.scala b/project/BuildCommon.scala index fac823afb1..baa9138b8c 100644 --- a/project/BuildCommon.scala +++ b/project/BuildCommon.scala @@ -377,96 +377,12 @@ object BuildCommon { headerResources / excludeFilter := "*", ) ++ sharedProtocSettings ++ Headers.NoHeaderSettings - // Project for utilities that are also used outside of the Canton repo - lazy val `canton-util-external` = { - import CantonDependencies._ - sbt.Project - .apply("canton-util-external", file("canton/base/util-external")) - .dependsOn( - `canton-pekko-fork`, - `canton-wartremover-extension` % "compile->compile;test->test", - // Canton depends on the Daml code via a git submodule and the two - // projects below. We instead depend on the artifacts released - // from the Daml repo listed in libraryDependencies below. - // `daml-copy-common`, - // `daml-copy-testing` % "test->test", - ) - .settings( - sharedCantonSettings, - libraryDependencies ++= Seq( - aws_kms, - aws_sts, - better_files, - canton_magnolify_addon, - gcp_kms, - canton_observability_metrics, - daml_tracing, - daml_executors, - daml_lf_data, - daml_nonempty_cats, - logback_classic, - logback_core, - scala_logging, - scala_collection_contrib, - scalatest % Test, - mockito_scala % Test, - scalatestMockito % Test, - cats, - jul_to_slf4j % Test, - log4j_core, - log4j_api, - monocle_macro, // Include it here, even if unused, so that it can be used everywhere - pureconfig, // Only dependencies may be needed, but it is simplest to include it like this - opentelemetry_api, - opentelemetry_sdk, - opentelemetry_sdk_autoconfigure, - opentelemetry_instrumentation_grpc, - opentelemetry_zipkin, - ), - dependencyOverrides ++= Seq(log4j_core, log4j_api), - // commented out from Canton OS repo as settings don't apply to us (yet) - // JvmRulesPlugin.damlRepoHeaderSettings, - ) - } - - lazy val `canton-base-errors` = { - import CantonDependencies._ - sbt.Project - .apply("canton-base-errors", file("canton/base/errors")) - .dependsOn( - `canton-google-common-protos-scala`, - `canton-wartremover-extension` % "compile->compile;test->test", - ) - .settings( - sharedCantonSettings, - libraryDependencies ++= Seq( - slf4j_api, - grpc_api, - reflections, - scalatest % Test, - scalacheck % Test, - scalatestScalacheck % Test, - ), - ) - } - - lazy val `canton-daml-adjustable-clock` = { - import CantonDependencies._ - sbt.Project - .apply("canton-daml-adjustable-clock", file("canton/base/adjustable-clock")) - .settings( - sharedCantonSettings - ) - } - lazy val `canton-util-observability` = { import CantonDependencies._ sbt.Project .apply("canton-util-observability", file("canton/community/util-observability")) .dependsOn( - `canton-base-errors` % "compile->compile;test->test", - `canton-util-external`, - `canton-wartremover-extension` % "compile->compile;test->test", + `canton-wartremover-extension` % "compile->compile;test->test" ) .settings( sharedCantonSettings, @@ -475,8 +391,10 @@ object BuildCommon { libraryDependencies ++= Seq( daml_grpc_utils, better_files, + canton_base_errors, canton_observability_metrics, canton_contextualized_logging, + canton_util_external, daml_lf_data, daml_nonempty_cats, daml_tracing, @@ -594,11 +512,7 @@ object BuildCommon { .enablePlugins(BuildInfoPlugin) .dependsOn( `canton-slick-fork`, - `canton-util-external`, - `canton-ledger-common`, `canton-community-admin-api`, - `canton-kms-driver-api`, - `canton-scalatest-addon` % "compile->test", // Canton depends on the Daml code via a git submodule and the two // projects below. We instead depend on the artifacts released // from the Daml repo listed in libraryDependencies below. @@ -612,9 +526,12 @@ object BuildCommon { // JvmRulesPlugin.damlRepoHeaderSettings, libraryDependencies ++= Seq( apache_commons_compress, + aws_kms, better_files, bouncycastle_bcpkix_jdk15on, bouncycastle_bcprov_jdk15on, + canton_kms_driver_api, + canton_util_external, cats, chimney, circe_core, @@ -624,6 +541,7 @@ object BuildCommon { daml_tls, flyway.excludeAll(ExclusionRule("org.apache.logging.log4j")), flyway_postgresql, + gcp_kms, grpc_services, postgres, pprint, @@ -639,6 +557,7 @@ object BuildCommon { CantonDependencies.opentelemetry_instrumentation_hikari, CantonDependencies.canton_java_bindings, ), + libraryDependencies ++= canton_ledger_common_deps, Compile / PB.targets := Seq( scalapb.gen(flatPackage = true) -> (Compile / sourceManaged).value / "protobuf" ), @@ -779,12 +698,9 @@ object BuildCommon { .apply("canton-community-common", file("canton/community/common")) .enablePlugins(DamlPlugin) .dependsOn( - `canton-pekko-fork` % "compile->compile;test->test", `canton-community-base`, `canton-wartremover-extension` % "compile->compile;test->test", - `canton-util-external` % "compile->compile;test->test", `canton-community-testing` % "test", - `canton-ledger-common` % "compile->compile;test->test", ) .settings( removeTestSources, @@ -805,6 +721,7 @@ object BuildCommon { daml_lf_transaction, // needed for importing java classes daml_nonempty_cats, canton_blake2b, + canton_util_external, canton_magnolify_addon, logback_classic, logback_core, @@ -907,11 +824,13 @@ object BuildCommon { import CantonDependencies._ sbt.Project .apply("canton-community-admin-api", file("canton/community/admin-api")) - .dependsOn(`canton-util-external`) .settings( sharedCantonSettings, libraryDependencies ++= Seq( - scalapb_runtime // not sufficient to include only through the `common` dependency - race conditions ensue + canton_util_external, + grpc_api, + scalapb_runtime, // not sufficient to include only through the `common` dependency - race conditions ensue + scalapb_runtime_grpc, ), Compile / PB.targets := Seq( scalapb.gen(flatPackage = true) -> (Compile / sourceManaged).value / "protobuf" @@ -1021,7 +940,7 @@ object BuildCommon { import CantonDependencies._ sbt.Project .apply("canton-wartremover-extension", file("canton/community/lib/wartremover")) - .dependsOn(`canton-wartremover-annotations`, `canton-slick-fork`) + .dependsOn(`canton-slick-fork`) .settings( Test / scalacOptions ++= Seq( "-Wconf:msg=synchronized not selected from this instance:silent" @@ -1029,6 +948,7 @@ object BuildCommon { disableTests, sharedSettings, libraryDependencies ++= Seq( + canton_wartremover_annotations, cats, grpc_stub, mockito_scala % Test, @@ -1045,159 +965,35 @@ object BuildCommon { ) } - lazy val `canton-wartremover-annotations` = - sbt.Project - .apply("canton-wartremover-annotations", file("canton/community/lib/wartremover-annotations")) - .settings(sharedSettings) - - // https://github.com/DACH-NY/canton/issues/10617: remove when no longer needed - lazy val `canton-pekko-fork` = { + private[this] lazy val canton_ledger_common_deps = { import CantonDependencies._ - sbt.Project - .apply("canton-pekko-fork", file("canton/community/lib/pekko")) - .disablePlugins(ScalafixPlugin, ScalafmtPlugin, WartRemover) - .settings( - sharedCantonSettings, - sharedSettings, - libraryDependencies ++= Seq( - pekko_stream, - pekko_stream_testkit % Test, - pekko_slf4j, - scalatest % Test, - ), - // commented out from Canton OS repo as settings don't apply to us (yet) - // // Exclude to apply our license header to any Scala files - // headerSources / excludeFilter := "*.scala", - // coverageEnabled := false, - ) - } - - lazy val `canton-scalatest-addon` = { - import CantonDependencies._ - sbt.Project - .apply("canton-scalatest-addon", file("canton/community/lib/scalatest")) - .settings( - sharedSettings, - libraryDependencies += scalatest, - // Exclude to apply our license header to any Scala files - headerSources / excludeFilter := "*.scala", - ) - } - - lazy val `canton-ledger-common` = { - import CantonDependencies._ - sbt.Project - .apply("canton-ledger-common", file("canton/community/ledger/ledger-common")) - .disablePlugins(WartRemover, ScalafmtPlugin) - .dependsOn( - `canton-util-external`, - `canton-util-observability`, - ) - .settings( - removeTestSources, - sharedCantonSettings, - disableTests, - sharedSettings, - scalacOptions += "-Wconf:src=src_managed/.*:silent", - Compile / PB.targets := Seq( - PB.gens.java -> (Compile / sourceManaged).value / "protobuf", - scalapb.gen(flatPackage = false) -> (Compile / sourceManaged).value / "protobuf", - ), - // commented out from Canton OS repo as settings don't apply to us (yet) - // addProtobufFilesToHeaderCheck(Compile), - libraryDependencies ++= Seq( - canton_contextualized_logging, - daml_lf_engine, - daml_lf_archive_reader, - CantonDependencies.canton_java_bindings, - CantonDependencies.canton_ledger_api_scala, - daml_jwt, - daml_tracing, - apache_commons_codec, - apache_commons_io, - daml_ledger_resources, - daml_timer_utils, - daml_rs_grpc_pekko, - opentelemetry_api, - pekko_stream, - slf4j_api, - grpc_api, - reflections, - grpc_netty_shaded, - caffeine, - scalapb_runtime, - scalapb_runtime_grpc, - scopt, - awaitility % Test, - logback_classic % Test, - scalatest % Test, - mockito_scala % Test, - scalatestMockito % Test, - pekko_stream_testkit % Test, - scalacheck % Test, - opentelemetry_sdk_testing % Test, - scalatestScalacheck % Test, - daml_lf_data, - daml_lf_transaction, - daml_ports % Test, - ), - Test / fork := true, - Test / testForkedParallel := true, - // commented out from Canton OS repo as settings don't apply to us (yet) - // coverageEnabled := false, - // JvmRulesPlugin.damlRepoHeaderSettings, - ) - } - - // this project builds scala protobuf versions that include - // java conversions of a few google standard items - // the google protobuf files are extracted from the provided jar files - lazy val `canton-google-common-protos-scala` = { - import CantonDependencies._ - sbt.Project - .apply( - "canton-google-common-protos-scala", - file("canton/community/lib/google-common-protos-scala"), - ) - .disablePlugins( - ScalafixPlugin, - ScalafmtPlugin, - WartRemover, - ) - .settings( - sharedCantonSettings, - scalacOptions --= removeCompileFlagsForDaml, - sharedSettings, - // we restrict the compilation to a few files that we actually need, skipping the large majority ... - excludeFilter := HiddenFileFilter || "scalapb.proto", - PB.generate / includeFilter := "status.proto" || "code.proto" || "error_details.proto" || "health.proto", - dependencyOverrides ++= Seq(), - // compile proto files that we've extracted here - Compile / PB.protoSources += (target.value / "protobuf_external"), - Compile / PB.targets := Seq( - // with java conversions but no java classes! - scalapb.gen( - javaConversions = true, - flatPackage = false, // consistent with upstream daml - ) -> (Compile / sourceManaged).value - ), - libraryDependencies ++= Seq( - scalapb_runtime, - scalapb_runtime_grpc, - // the grpc services is necessary so we can build the - // scala version of the health services, without - // building the java protoc (to avoid duplicate symbols - // during assembly) - grpc_services, - // extract the protobuf to target/protobuf_external - // however, we'll only be including the ones in the includeFilter - grpc_services % "protobuf", - google_common_protos % "protobuf", - google_common_protos, - google_protobuf_java, - google_protobuf_java_util, - ), - ) + Seq( + canton_contextualized_logging, + canton_util_external, + daml_lf_engine, + daml_lf_archive_reader, + CantonDependencies.canton_java_bindings, + CantonDependencies.canton_ledger_api_scala, + daml_jwt, + daml_tracing, + apache_commons_codec, + apache_commons_io, + daml_ledger_resources, + daml_timer_utils, + daml_rs_grpc_pekko, + opentelemetry_api, + pekko_stream, + slf4j_api, + grpc_api, + reflections, + grpc_netty_shaded, + caffeine, + scalapb_runtime, + scalapb_runtime_grpc, + scopt, + daml_lf_data, + daml_lf_transaction, + ) } // this project exists solely for the purpose of extracting value.proto @@ -1228,7 +1024,6 @@ object BuildCommon { .apply("canton-ledger-json-api", file("canton/community/ledger/ledger-json-api")) .dependsOn( `canton-util-observability`, - `canton-ledger-common` % "test->test", `canton-community-testing` % Test, ) .disablePlugins( @@ -1297,12 +1092,12 @@ object BuildCommon { sbt.Project .apply("canton-sequencer-driver-api", file("canton/community/sequencer-driver")) .dependsOn( - `canton-util-external`, - `canton-util-observability`, + `canton-util-observability` ) .settings( sharedCantonSettings, libraryDependencies ++= Seq( + canton_util_external, logback_classic, logback_core, scala_logging, @@ -1378,35 +1173,20 @@ object BuildCommon { file("canton/community/reference-sequencer-driver/"), ) .dependsOn( - `canton-util-external`, `canton-community-common` % "compile->compile;test->test", `canton-sequencer-driver-api` % "compile->compile;test->test", `canton-community-testing` % Test, ) - .dependsOn(`canton-util-external`) .settings( sharedCantonSettings, dependencyOverrides ++= Seq(log4j_core, log4j_api), + libraryDependencies ++= Seq(canton_util_external), Compile / PB.targets := Seq( scalapb.gen(flatPackage = true) -> (Compile / sourceManaged).value / "protobuf" ), ) } - lazy val `canton-kms-driver-api` = project - .in(file("canton/community/kms-driver-api")) - .settings( - sharedCantonSettings, - libraryDependencies ++= { - import CantonDependencies.* - Seq( - pureconfig, - slf4j_api, - opentelemetry_api, - ) - }, - ) - import defs._ /** Typescript code generation from daml models. diff --git a/project/CantonDependencies.scala b/project/CantonDependencies.scala index f15bc01e5d..9fea4e15e5 100644 --- a/project/CantonDependencies.scala +++ b/project/CantonDependencies.scala @@ -97,14 +97,19 @@ object CantonDependencies { "com.daml" %% "daml-grpc-utils" % canton_library_version lazy val canton_java_bindings = "com.daml" % "bindings-java" % canton_library_version + lazy val canton_kms_driver_api = "com.daml" %% "kms-driver-api" % canton_library_version lazy val canton_ledger_api_core = "com.daml" %% "ledger-api-core" % canton_library_version lazy val canton_ledger_api_scala = "com.daml" %% "ledger-api-scala" % canton_library_version + lazy val canton_base_errors = "com.daml" %% "base-errors" % canton_library_version lazy val canton_observability_metrics = "com.daml" %% "observability-metrics" % canton_library_version lazy val canton_contextualized_logging = "com.daml" %% "contextualized-logging" % canton_library_version lazy val canton_traffic_enforcement_api = "com.daml" %% "traffic-enforcement-api" % canton_library_version + lazy val canton_util_external = "com.daml" %% "util-external" % canton_library_version + lazy val canton_wartremover_annotations = + "com.daml" %% "wartremover-annotations" % canton_library_version lazy val canton_blake2b = "com.daml" %% "blake2b" % canton_library_version lazy val canton_magnolify_addon = "com.daml" %% "magnolify-addon" % canton_library_version @@ -184,6 +189,8 @@ object CantonDependencies { lazy val scala_logging = "com.typesafe.scala-logging" %% "scala-logging" % "3.9.5" lazy val scalacheck = "org.scalacheck" %% "scalacheck" % scalacheck_version lazy val scalatest = "org.scalatest" %% "scalatest" % scalatest_version + lazy val scalatest_shouldmatchers = + "org.scalatest" %% "scalatest-shouldmatchers" % scalatest_version lazy val scalaz_core = "org.scalaz" %% "scalaz-core" % scalaz_version lazy val scalatestScalacheck = "org.scalatestplus" %% "scalacheck-1-18" % (scalatest_version + ".0") diff --git a/scripts/copy-canton.sh b/scripts/copy-canton.sh index 43b2709cf7..a2ae78176b 100755 --- a/scripts/copy-canton.sh +++ b/scripts/copy-canton.sh @@ -16,11 +16,17 @@ rsync -av --delete --exclude version.sbt --exclude community-build.sbt --exclude --exclude '*/wartremove/test/*' --exclude "*/ledger-api-bench-tool" \ --exclude '.ci' --exclude '.circleci' --exclude '.hooks' --exclude 'contributing' --exclude 'docker' \ --exclude 'docs-open' --exclude 'nix' --exclude 'performance' --exclude 'dashboards' --exclude 'release' \ + --exclude 'base/adjustable-clock' \ --exclude 'base/contextualized-logging' --exclude 'base/crypto' \ --exclude 'base/daml-jwt' --exclude 'base/daml-tls' \ + --exclude 'base/errors' --exclude 'base/util-external' \ --exclude '*/community/lib/Blake2b' \ + --exclude '*/community/lib/google-common-protos-scala' \ --exclude '*/community/lib/magnolify' \ + --exclude '*/community/lib/scalatest' \ + --exclude '*/community/lib/wartremover-annotations' \ --exclude 'community/bindings-java' --exclude "*/community/transcode" \ + --exclude '*/community/kms-driver-api' \ --exclude '*/community/ledger-api-scala' --exclude "*/ledger-api-proto" \ --exclude '*/canton-community-app/test/scala/*/integration/tests' \ --exclude '*/community/ledger/ledger-api-core' \ From bc8848f3abe3afb00f62ff3c4f43e682a7c3a833 Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Wed, 29 Jul 2026 12:10:25 -0400 Subject: [PATCH 148/329] hard-fork ApiCodecCompressed & tests to separate library (#6576) * move ApiCodecCompressed to hard fork library lf-value-json * restore codec tests from DACH-NY/canton#27917 * remove tests that need JsonEncodingTest.dar - dealing with LF Record - decode a JSON array of the right length - fail to decode if missing fields - fail to decode if extra fields - dealing with LF Variant - decode Foo/Baz from JSON - fail decoding Foo/Qux from JSON if 'value' field is missing - decode Foo/Qux (empty value) from JSON - dealing with Contract Key - decode type Key = Party from JSON - decode type Key = (Party, Int) from JSON - decode type Key = (Party, (Int, Foo, BazRecord)) from JSON * proper dependencies for lf-value-json * use lf-value-json in apps-common --------- Signed-off-by: Stephen Compall --- build.sbt | 26 ++ .../lf/value/json/ApiCodecCompressed.scala | 0 .../lf/value/json/ApiValueImplicits.scala | 0 .../daml/lf/value/json/JsonVariant.scala | 0 .../lf/value/json/NavigatorModelAliases.scala | 0 .../value/json/ApiCodecCompressedSpec.scala | 407 ++++++++++++++++++ project/BuildCommon.scala | 47 ++ 7 files changed, 480 insertions(+) rename {canton/community/ledger/ledger-json-api => canton-fork/lf-value-json}/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiCodecCompressed.scala (100%) rename {canton/community/ledger/ledger-json-api => canton-fork/lf-value-json}/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiValueImplicits.scala (100%) rename {canton/community/ledger/ledger-json-api => canton-fork/lf-value-json}/src/main/scala/com/digitalasset/canton/daml/lf/value/json/JsonVariant.scala (100%) rename {canton/community/ledger/ledger-json-api => canton-fork/lf-value-json}/src/main/scala/com/digitalasset/canton/daml/lf/value/json/NavigatorModelAliases.scala (100%) create mode 100644 canton-fork/lf-value-json/src/test/scala/com/digitalasset/canton/daml/lf/value/json/ApiCodecCompressedSpec.scala diff --git a/build.sbt b/build.sbt index 3f6794cef1..246d7e85c3 100644 --- a/build.sbt +++ b/build.sbt @@ -34,6 +34,8 @@ lazy val `canton-sequencer-driver-api` = BuildCommon.`canton-sequencer-driver-ap lazy val `canton-community-reference-driver` = BuildCommon.`canton-community-reference-driver` lazy val `canton-observability-metrics-testing` = BuildCommon.`canton-observability-metrics-testing` lazy val `canton-traffic-enforcement-component` = BuildCommon.`canton-traffic-enforcement-component` +lazy val `daml-lf-transaction-test-lib` = BuildCommon.`daml-lf-transaction-test-lib` +lazy val `daml-lf-data-scalacheck` = BuildCommon.`daml-lf-data-scalacheck` lazy val `splice-wartremover-extension` = Wartremover.`splice-wartremover-extension` @@ -1165,6 +1167,29 @@ lazy val `splitwell-test-daml` = Compile / damlEnableJavaCodegen := false, ) +lazy val `lf-value-json` = + project + .in(file("canton-fork/lf-value-json")) + .dependsOn( + `canton-ledger-json-api`, + `daml-lf-transaction-test-lib`, + ) + .settings( + scalacOptions += "-Xsource-features:infer-override", + libraryDependencies ++= { + import CantonDependencies._ + Seq( + CantonDependencies.canton_ledger_api_core, + daml_lf_api_type_signature, + scalatest % Test, + scalacheck % Test, + scalaz_scalacheck % Test, + scalatestScalacheck % Test, + ) + }, + CantonDependencies.excludeTranscodeConflictingDependencies, + ) + lazy val `apps-common` = project .in(file("apps/common")) @@ -1172,6 +1197,7 @@ lazy val `apps-common` = `canton-community-common`, `canton-community-app` % "compile->compile;test->test", `canton-community-testing` % "test->test", + `lf-value-json`, `splice-wartremover-extension` % "compile->compile;test->test", // We include all DARs here to make sure they are available as resources. `splice-amulet-daml`, diff --git a/canton/community/ledger/ledger-json-api/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiCodecCompressed.scala b/canton-fork/lf-value-json/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiCodecCompressed.scala similarity index 100% rename from canton/community/ledger/ledger-json-api/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiCodecCompressed.scala rename to canton-fork/lf-value-json/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiCodecCompressed.scala diff --git a/canton/community/ledger/ledger-json-api/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiValueImplicits.scala b/canton-fork/lf-value-json/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiValueImplicits.scala similarity index 100% rename from canton/community/ledger/ledger-json-api/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiValueImplicits.scala rename to canton-fork/lf-value-json/src/main/scala/com/digitalasset/canton/daml/lf/value/json/ApiValueImplicits.scala diff --git a/canton/community/ledger/ledger-json-api/src/main/scala/com/digitalasset/canton/daml/lf/value/json/JsonVariant.scala b/canton-fork/lf-value-json/src/main/scala/com/digitalasset/canton/daml/lf/value/json/JsonVariant.scala similarity index 100% rename from canton/community/ledger/ledger-json-api/src/main/scala/com/digitalasset/canton/daml/lf/value/json/JsonVariant.scala rename to canton-fork/lf-value-json/src/main/scala/com/digitalasset/canton/daml/lf/value/json/JsonVariant.scala diff --git a/canton/community/ledger/ledger-json-api/src/main/scala/com/digitalasset/canton/daml/lf/value/json/NavigatorModelAliases.scala b/canton-fork/lf-value-json/src/main/scala/com/digitalasset/canton/daml/lf/value/json/NavigatorModelAliases.scala similarity index 100% rename from canton/community/ledger/ledger-json-api/src/main/scala/com/digitalasset/canton/daml/lf/value/json/NavigatorModelAliases.scala rename to canton-fork/lf-value-json/src/main/scala/com/digitalasset/canton/daml/lf/value/json/NavigatorModelAliases.scala diff --git a/canton-fork/lf-value-json/src/test/scala/com/digitalasset/canton/daml/lf/value/json/ApiCodecCompressedSpec.scala b/canton-fork/lf-value-json/src/test/scala/com/digitalasset/canton/daml/lf/value/json/ApiCodecCompressedSpec.scala new file mode 100644 index 0000000000..5936fa38c5 --- /dev/null +++ b/canton-fork/lf-value-json/src/test/scala/com/digitalasset/canton/daml/lf/value/json/ApiCodecCompressedSpec.scala @@ -0,0 +1,407 @@ +// Copyright (c) 2025 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.digitalasset.canton.daml.lf.value.json + +import com.digitalasset.canton.daml.lf.value.json.NavigatorModelAliases as model +import com.digitalasset.daml.lf.data.{ImmArray, Numeric, Ref, SortedLookupList, Time} +import com.digitalasset.daml.lf.value.Value.ContractId +import com.digitalasset.daml.lf.value.test.TypedValueGenerators.{ + ValueAddend as VA, + genAddend, + genTypeAndValue, +} +import com.digitalasset.daml.lf.value.test.ValueGenerators.coidGen +import org.scalacheck.Arbitrary +import org.scalactic.source +import org.scalatest.Inside +import org.scalatest.matchers.should.Matchers +import org.scalatest.wordspec.AnyWordSpec +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks +import shapeless.record.Record as HRecord +import shapeless.{Coproduct as HSum, HNil} +import spray.json.* + +import java.time.Instant +import scala.annotation.nowarn +import scala.util.{Success, Try} + +import ApiCodecCompressed.{apiValueToJsValue, jsValueToApiValue} + +class ApiCodecCompressedSpec + extends AnyWordSpec + with Matchers + with ScalaCheckPropertyChecks + with Inside { + + import C.typeLookup + + protected implicit val cidArb: Arbitrary[ContractId] = Arbitrary(coidGen) + + /** Serializes the API value to JSON, then parses it back to an API value */ + protected def serializeAndParse( + value: model.ApiValue, + typ: model.DamlLfType, + ): Try[model.ApiValue] = { + import ApiCodecCompressed.JsonImplicits.* + + for { + serialized <- Try(value.toJson.prettyPrint) + json <- Try(serialized.parseJson) + parsed <- Try(jsValueToApiValue(json, typ, typeLookup)) + } yield parsed + } + + protected def roundtrip(va: VA)(v: va.Inj): Option[va.Inj] = + va.prj(jsValueToApiValue(apiValueToJsValue(va.inj(v)), va.t, typeLookup)) + + protected val decimalScale = Numeric.Scale.assertFromInt(10) + + protected object C /* based on navigator DamlConstants */ { + import shapeless.syntax.singleton.* + val packageId0 = Ref.PackageId assertFromString "hash" + val moduleName0 = Ref.ModuleName assertFromString "Module" + def defRef(name: String) = + Ref.Identifier( + packageId0, + Ref.QualifiedName(moduleName0, Ref.DottedName assertFromString name), + ) + val emptyRecordId = defRef("EmptyRecord") + val (emptyRecordDDT, emptyRecordT) = VA.record(emptyRecordId, HNil) + val simpleRecordId = defRef("SimpleRecord") + val simpleRecordVariantSpec = HRecord(fA = VA.text, fB = VA.int64) + val (simpleRecordDDT, simpleRecordT) = + VA.record(simpleRecordId, simpleRecordVariantSpec) + val simpleRecordV: simpleRecordT.Inj = HRecord(fA = "foo", fB = 100L) + + val simpleVariantId = defRef("SimpleVariant") + val (simpleVariantDDT, simpleVariantT) = + VA.variant(simpleVariantId, simpleRecordVariantSpec) + val simpleVariantV = HSum[simpleVariantT.Inj](Symbol("fA") ->> "foo") + + val complexRecordId = defRef("ComplexRecord") + val (complexRecordDDT, complexRecordT) = + VA.record( + complexRecordId, + HRecord( + fText = VA.text, + fBool = VA.bool, + fDecimal = VA.numeric(decimalScale), + fUnit = VA.unit, + fInt64 = VA.int64, + fParty = VA.party, + fContractId = VA.contractId, + fListOfText = VA.list(VA.text), + fListOfUnit = VA.list(VA.unit), + fDate = VA.date, + fTimestamp = VA.timestamp, + fOptionalText = VA.optional(VA.text), + fOptionalUnit = VA.optional(VA.unit), + fOptOptText = VA.optional(VA.optional(VA.text)), + fMap = VA.map(VA.int64), + fVariant = simpleVariantT, + fRecord = simpleRecordT, + ), + ) + @nowarn("msg=dubious usage of method asInstanceOf with unit value") + val complexRecordV: complexRecordT.Inj = + HRecord( + fText = "foo", + fBool = true, + fDecimal = Numeric assertFromString "100.0000000000", + fUnit = (), + fInt64 = 100L, + fParty = Ref.Party assertFromString "BANK1", + fContractId = ContractId.assertFromString("00" + "00" * 32 + "c0"), + fListOfText = Vector("foo", "bar"), + fListOfUnit = Vector((), ()), + fDate = Time.Date assertFromString "2019-01-28", + fTimestamp = Time.Timestamp.assertFromInstant(Instant.parse("2019-01-28T12:44:33.22Z")), + fOptionalText = None, + fOptionalUnit = Some(()), + fOptOptText = Some(Some("foo")), + fMap = SortedLookupList(Map("1" -> 1L, "2" -> 2L, "3" -> 3L)), + fVariant = simpleVariantV, + fRecord = simpleRecordV, + ) + + val colorId = defRef("Color") + val (colorGD, colorGT) = + VA.enumeration(colorId, Seq("Red", "Green", "Blue") map Ref.Name.assertFromString) + + val typeLookup: NavigatorModelAliases.DamlLfTypeLookup = + Map( + emptyRecordId -> emptyRecordDDT, + simpleRecordId -> simpleRecordDDT, + simpleVariantId -> simpleVariantDDT, + complexRecordId -> complexRecordDDT, + colorId -> colorGD, + ).lift + } + + protected def mustBeOne[A](as: Seq[A]): A = as match { + case Seq(x) => x + case xs @ _ => sys.error(s"Expected exactly one element, got: $xs") + } + + import C.typeLookup + + "API compressed JSON codec" when { + + "serializing and parsing a value" should { + + "work for arbitrary reference-free types" in forAll( + genTypeAndValue(coidGen), + minSuccessful(100), + ) { case (typ, value) => + serializeAndParse(value, typ) shouldBe Success(value) + } + + "work for many, many values in raw format" in forAll(genAddend, minSuccessful(100)) { va => + import va.injshrink + implicit val arbInj: Arbitrary[va.Inj] = va.injarb + forAll(minSuccessful(20)) { (v: va.Inj) => + roundtrip(va)(v) should ===(Some(v)) + } + } + + "handle nested optionals" in { + val va = VA.optional(VA.optional(VA.int64)) + val cases = Table( + "value", + None, + Some(None), + Some(Some(42L)), + ) + forEvery(cases) { ool => + roundtrip(va)(ool) should ===(Some(ool)) + } + } + + "handle lists of optionals" in { + val va = VA.optional(VA.optional(VA.list(VA.optional(VA.optional(VA.int64))))) + import va.injshrink + implicit val arbInj: Arbitrary[va.Inj] = va.injarb + forAll(minSuccessful(1000)) { (v: va.Inj) => + roundtrip(va)(v) should ===(Some(v)) + } + } + + def cr(typ: VA)(v: typ.Inj) = + (typ, v: Any, typ.inj(v)) + + val roundtrips = Table( + ("type", "original value", "Daml value"), + cr(C.emptyRecordT)(HRecord()), + cr(C.simpleRecordT)(C.simpleRecordV), + cr(C.simpleVariantT)(C.simpleVariantV), + cr(C.complexRecordT)(C.complexRecordV), + ) + "work for records and variants" in forAll(roundtrips) { (typ, origValue, damlValue) => + typ.prj(jsValueToApiValue(apiValueToJsValue(damlValue), typ.t, typeLookup)) should ===( + Some(origValue) + ) + } + /* + "work for Tree" in { + serializeAndParse(C.treeV, C.treeTC) shouldBe Success(C.treeV) + } + "work for Enum" in { + serializeAndParse(C.redV, C.redTC) shouldBe Success(C.redV) + } + */ + } + + def cn(canonical: String, numerically: String, typ: VA)( + expected: typ.Inj, + alternates: String* + )(implicit pos: source.Position) = + (pos.lineNumber, canonical, numerically, typ, expected, alternates) + + def c(canonical: String, typ: VA)(expected: typ.Inj, alternates: String*)(implicit + pos: source.Position + ) = + cn(canonical, canonical, typ)(expected, alternates*)(pos) + + object VAs { + val ooi = VA.optional(VA.optional(VA.int64)) + val oooi = VA.optional(ooi) + } + + val numCodec = ApiCodecCompressed.copy(false, false) + + @nowarn("cat=lint-infer-any") + val successes = Table( + ("line#", "serialized", "serializedNumerically", "type", "parsed", "alternates"), + c( + "\"0000000000000000000000000000000000000000000000000000000000000000000123\"", + VA.contractId, + )( + ContractId.assertFromString( + "0000000000000000000000000000000000000000000000000000000000000000000123" + ) + ), + cn("\"42.0\"", "42.0", VA.numeric(decimalScale))( + Numeric assertFromString "42.0000000000", + "\"42\"", + "42", + "42.0", + "\"+42\"", + ), + cn("\"2000.0\"", "2000", VA.numeric(decimalScale))( + Numeric assertFromString "2000.0000000000", + "\"2000\"", + "2000", + "2e3", + ), + cn("\"0.3\"", "0.3", VA.numeric(decimalScale))( + Numeric assertFromString "0.3000000000", + "\"0.30000000000000004\"", + "0.30000000000000004", + ), + cn( + "\"9999999999999999999999999999.9999999999\"", + "9999999999999999999999999999.9999999999", + VA.numeric(decimalScale), + )(Numeric assertFromString "9999999999999999999999999999.9999999999"), + cn("\"0.1234512346\"", "0.1234512346", VA.numeric(decimalScale))( + Numeric assertFromString "0.1234512346", + "0.12345123455", + "0.12345123465", + "\"0.12345123455\"", + "\"0.12345123465\"", + ), + cn("\"0.1234512345\"", "0.1234512345", VA.numeric(decimalScale))( + Numeric assertFromString "0.1234512345", + "0.123451234549", + "0.12345123445001", + "\"0.123451234549\"", + "\"0.12345123445001\"", + ), + c("\"1990-11-09T04:30:23.123456Z\"", VA.timestamp)( + Time.Timestamp.assertFromInstant(Instant.parse("1990-11-09T04:30:23.123456Z")), + "\"1990-11-09T04:30:23.1234569Z\"", + ), + c("\"1970-01-01T00:00:00Z\"", VA.timestamp)(Time.Timestamp assertFromLong 0), + // Ensure ISO 8601 timestamps with offsets are successfully parsed by comparing to (epoch - 1 hour) + c("\"1969-12-31T23:00:00Z\"", VA.timestamp)( + Time.Timestamp.assertFromLong(-3600000000L), + "\"1970-01-01T00:00:00+01:00\"", + ), + cn("\"42\"", "42", VA.int64)(42, "\"+42\""), + cn("\"0\"", "0", VA.int64)(0, "-0", "\"+0\"", "\"-0\""), + c("\"Alice\"", VA.party)(Ref.Party assertFromString "Alice"), + c("{}", VA.unit)(()), + c("\"2019-06-18\"", VA.date)(Time.Date assertFromString "2019-06-18"), + c("\"9999-12-31\"", VA.date)(Time.Date assertFromString "9999-12-31"), + c("\"0001-01-01\"", VA.date)(Time.Date assertFromString "0001-01-01"), + c("\"abc\"", VA.text)("abc"), + c("true", VA.bool)(true), + cn("""["1", "2", "3"]""", "[1, 2, 3]", VA.list(VA.int64))(Vector(1, 2, 3)), + c("""{"a": "b", "c": "d"}""", VA.map(VA.text))(SortedLookupList(Map("a" -> "b", "c" -> "d"))), + c("""[["a", "b"], ["c", "d"]]""", VA.genMap(VA.text, VA.text))(Map("a" -> "b", "c" -> "d")), + cn("\"42\"", "42", VA.optional(VA.int64))(Some(42)), + c("null", VA.optional(VA.int64))(None), + c("null", VAs.ooi)(None), + c("[]", VAs.ooi)(Some(None), "[null]"), + cn("""["42"]""", "[42]", VAs.ooi)(Some(Some(42))), + c("null", VAs.oooi)(None), + c("[]", VAs.oooi)(Some(None), "[null]"), + c("[[]]", VAs.oooi)(Some(Some(None)), "[[null]]"), + cn("""[["42"]]""", "[[42]]", VAs.oooi)(Some(Some(Some(42)))), + cn("""{"fA": "foo", "fB": "100"}""", """{"fA": "foo", "fB": 100}""", C.simpleRecordT)( + C.simpleRecordV + ), + c("""{"tag": "fA", "value": "foo"}""", C.simpleVariantT)(C.simpleVariantV), + c("\"Green\"", C.colorGT)( + C.colorGT get Ref.Name.assertFromString("Green") getOrElse sys.error("impossible") + ), + ) + + val failures = Table( + ("JSON", "type", "errorSubstring"), + ("42.3", VA.int64, ""), + ("\"42.3\"", VA.int64, ""), + ("9223372036854775808", VA.int64, ""), + ("-9223372036854775809", VA.int64, ""), + ("\"garbage\"", VA.int64, ""), + ("\" 42 \"", VA.int64, ""), + ("\"1970-01-01T00:00:00\"", VA.timestamp, ""), + ("\"1970-01-01T00:00:00+01:00[Europe/Paris]\"", VA.timestamp, ""), + ("\"0000-01-01\"", VA.date, "Invalid date: 0000-01-01"), + ("\"9999-99-99\"", VA.date, "Invalid date: 9999-99-99"), + ("\"9999-12-32\"", VA.date, "Invalid date: 9999-12-32"), + ("\"9999-13-31\"", VA.date, "Invalid date: 9999-13-31"), + ("\"10000-01-01\"", VA.date, "Invalid date: 10000-01-01"), + ("\"1-01-01\"", VA.date, "Invalid date: 1-01-01"), + ("\"0001-02-29\"", VA.date, "Invalid date: 0001-02-29"), + ("\"not-a-date\"", VA.date, "Invalid date: not-a-date"), + ("""{"a": "b", "c": "d"}""", VA.genMap(VA.text, VA.text), ""), + ("\"\"", VA.party, "Daml-LF Party is empty"), + (List.fill(256)('a').mkString("\"", "", "\""), VA.party, "Daml-LF Party is too long"), + ) + + "dealing with particular formats" should { + "succeed in cases" in forEvery(successes) { + (_, serialized, serializedNumerically, typ, expected, alternates) => + val json = serialized.parseJson + val numJson = serializedNumerically.parseJson + val parsed = jsValueToApiValue(json, typ.t, typeLookup) + jsValueToApiValue(numJson, typ.t, typeLookup) should ===(parsed) + typ.prj(parsed) should ===(Some(expected)) + apiValueToJsValue(parsed) should ===(json) + numCodec.apiValueToJsValue(parsed) should ===(numJson) + val tAlternates = Table("alternate", alternates*) + forEvery(tAlternates) { alternate => + val aJson = alternate.parseJson + typ.prj(jsValueToApiValue(aJson, typ.t, typeLookup)) should ===(Some(expected)) + } + } + + "fail in cases" in forEvery(failures) { (serialized, typ, errorSubstring) => + val json = serialized.parseJson // we don't test *the JSON decoder* + val exception = the[DeserializationException] thrownBy { + jsValueToApiValue(json, typ.t, typeLookup) + } + exception.getMessage should include(errorSubstring) + } + } + + import com.digitalasset.daml.lf.value.Value as LfValue + import ApiCodecCompressed.JsonImplicits.* + + val bazRecord = LfValue.ValueRecord( + None, + ImmArray(Some(Ref.Name.assertFromString("baz")) -> LfValue.ValueText("text abc")), + ) + + val bazVariant = LfValue.ValueVariant( + None, + Ref.Name.assertFromString("Baz"), + bazRecord, + ) + + val quxVariant = LfValue.ValueVariant( + None, + Ref.Name.assertFromString("Qux"), + LfValue.ValueUnit, + ) + + + "dealing with LF Variant" should { + "encode Foo/Baz to JSON" in { + val writer = implicitly[spray.json.JsonWriter[LfValue]] + (writer.write( + bazVariant + ): JsValue) shouldBe ("""{"tag":"Baz", "value":{"baz":"text abc"}}""".parseJson: JsValue) + } + + "encode Foo/Qux to JSON" in { + val writer = implicitly[spray.json.JsonWriter[LfValue]] + (writer.write( + quxVariant + ): JsValue) shouldBe ("""{"tag":"Qux", "value":{}}""".parseJson: JsValue) + } + } + } +} diff --git a/project/BuildCommon.scala b/project/BuildCommon.scala index baa9138b8c..3af0d703e9 100644 --- a/project/BuildCommon.scala +++ b/project/BuildCommon.scala @@ -1087,6 +1087,53 @@ object BuildCommon { ) } + lazy val `daml-lf-transaction-test-lib` = project + .in(file("canton/community/daml-lf/transaction-test-lib")) + .disablePlugins( + WartRemover + ) + .settings( + sharedCantonSettings, + Compile / unmanagedSources / includeFilter := + "*ValueGenerators.scala" || "TransactionBuilder.scala" || "NodeIdTransactionBuilder.scala" || "TestIdFactory.scala", + libraryDependencies ++= { + import CantonDependencies._ + Seq( + daml_lf_api_type_signature, + daml_lf_data, + daml_lf_language, + daml_lf_transaction, + scalacheck, + scala_logging, + scalatestScalacheck, + scalatest, + scalaz_core, + scalaz_scalacheck, + shapeless, + ) + }, + ) + .dependsOn( + `daml-lf-data-scalacheck` + ) + + lazy val `daml-lf-data-scalacheck` = project + .in(file("canton/community/daml-lf/data-scalacheck")) + .disablePlugins(WartRemover) + .settings( + sharedCantonSettings, + libraryDependencies ++= { + import CantonDependencies._ + Seq( + daml_lf_data, + scalacheck, + ) + }, + Test / scalacOptions ++= Seq( + "-Wconf:msg=match may not be exhaustive:s" + ), + ) + lazy val `canton-sequencer-driver-api` = { import CantonDependencies._ sbt.Project From 34ba87238522b506f6fa5e950bd17332deed1c23 Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Wed, 29 Jul 2026 20:17:45 +0200 Subject: [PATCH 149/329] [ci] Disable update history sanity check in SvOnboardingViaNonFoundingSvIntegrationTest (#6606) sv1 is stopped mid-test; the plugin accesses sv1Scan.automation after sv1's participant is down, throwing 'Node doesn't have any app state'. This also prevents proper port release, causing three subsequent tests to fail with 'Could not create Prometheus HTTP server'. Signed-off-by: Raymond Roestenburg --- .../tests/SvOnboardingViaNonFoundingSvIntegrationTest.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvOnboardingViaNonFoundingSvIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvOnboardingViaNonFoundingSvIntegrationTest.scala index f54acd0bfd..76029cc09f 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvOnboardingViaNonFoundingSvIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvOnboardingViaNonFoundingSvIntegrationTest.scala @@ -26,6 +26,8 @@ class SvOnboardingViaNonFoundingSvIntegrationTest with SvTestUtil with StandaloneCanton { + // sv1 is stopped mid-test, so neither history check is meaningful here. + override protected def runUpdateHistorySanityCheck: Boolean = false override protected def runEventHistorySanityCheck: Boolean = false override def dbsSuffix: String = "non_sv1_svs" From ea72627018ee7e34a856c0fe8c70037174d34f91 Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Wed, 29 Jul 2026 20:56:45 +0200 Subject: [PATCH 150/329] [ci] Close all scan resources even when one close fails; include legacy nodes and rewards store (#6605) Signed-off-by: Raymond Roestenburg --- .../splice/scan/ScanApp.scala | 35 +++++++++++-------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala index 5cfe864f60..0eaada7f91 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala @@ -633,20 +633,27 @@ object ScanApp { storage.isActive override def close(): Unit = { - LifeCycle.close(bftSequencersAdminConnections*)(logger) - LifeCycle.close(cleanups*)(logger) - bulkStorage.foreach(LifeCycle.close(_)(logger)) - LifeCycle.close( - automation, - verdictAutomation, - store, - storage, - synchronizerNodes.current, - participantAdminConnection, - )(logger) - synchronizerNodes.successor.foreach( - LifeCycle.close(_)(logger) - ) + // Close everything in one LifeCycle.close call: it closes every instance left to right + // even when some of them fail, whereas separate calls stop at the first failing call. + val instances: Seq[AutoCloseable] = + bftSequencersAdminConnections ++ + cleanups ++ + bulkStorage.toList ++ + Seq( + automation, + verdictAutomation, + store, + ) ++ + rewardsReferenceStoreO.toList ++ + Seq( + storage, + synchronizerNodes.current, + participantAdminConnection, + ) ++ + synchronizerNodes.successor.toList ++ + synchronizerNodes.legacy.toList ++ + synchronizerNodes.additionalLegacy + LifeCycle.close(instances*)(logger) } } } From 9534110d2ff112ad401b668c2487ec9bf650e515 Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Wed, 29 Jul 2026 20:56:55 +0200 Subject: [PATCH 151/329] [ci] Release netty buffers on zstd compression failures (#6603) Signed-off-by: Raymond Roestenburg --- .../splice/store/bulk/ZstdGroupedWeight.scala | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/bulk/ZstdGroupedWeight.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/bulk/ZstdGroupedWeight.scala index 66edda79fd..4d9cd8997f 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/bulk/ZstdGroupedWeight.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/bulk/ZstdGroupedWeight.scala @@ -9,6 +9,8 @@ import org.apache.pekko.stream.stage.{GraphStage, GraphStageLogic, InHandler, Ou import org.apache.pekko.stream.{Attributes, FlowShape, Inlet, Outlet} import org.apache.pekko.util.ByteString +import scala.util.control.NonFatal + /** A Pekko GraphStage that zstd-compresses a stream of bytestrings, and splits the output into zstd objects of size (minWeight + delta). * Somewhat similar to Pekko's built-in GroupedWeight, but outputs valid zstd compressed objects. */ @@ -47,15 +49,25 @@ case class ZstdGroupedWeight( val bufferAllocator = PooledByteBufAllocator.DEFAULT val tmpBuffer = bufferAllocator.directBuffer(zstdTmpBufferSize) - val tmpNioBuffer = tmpBuffer.nioBuffer(0, tmpBuffer.capacity()) - val compressingStream = - new ZstdDirectBufferCompressingStreamNoFinalizer(tmpNioBuffer, compressionLevel) + val (tmpNioBuffer, compressingStream) = + try { + val nioBuffer = tmpBuffer.nioBuffer(0, tmpBuffer.capacity()) + (nioBuffer, new ZstdDirectBufferCompressingStreamNoFinalizer(nioBuffer, compressionLevel)) + } catch { + // a failed construction never reaches close(), so release the buffer here + case NonFatal(e) => + val _ = tmpBuffer.release() + throw e + } def compress(input: ByteString): ByteString = { val inputBB = bufferAllocator.directBuffer(input.size) - inputBB.writeBytes(input.toArrayUnsafe()) - compressingStream.compress(inputBB.nioBuffer()) - inputBB.release() + try { + inputBB.writeBytes(input.toArrayUnsafe()) + compressingStream.compress(inputBB.nioBuffer()) + } finally { + val _ = inputBB.release() + } compressingStream.flush() tmpNioBuffer.flip() val result = ByteString.fromByteBuffer(tmpNioBuffer) @@ -72,8 +84,11 @@ case class ZstdGroupedWeight( } override def close(): Unit = { - compressingStream.close() - val _ = tmpBuffer.release() + try { + compressingStream.close() + } finally { + val _ = tmpBuffer.release() + } } } From 6de7b14f80b0cf435490acf60ed1d7a8adc79e70 Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Wed, 29 Jul 2026 21:01:44 +0200 Subject: [PATCH 152/329] [ci] Delete all toxiproxy proxies even when one delete fails (#6604) Signed-off-by: Raymond Roestenburg --- .../splice/integration/plugins/UseToxiproxy.scala | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/plugins/UseToxiproxy.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/plugins/UseToxiproxy.scala index 2734c2d90b..8d352abc67 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/plugins/UseToxiproxy.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/plugins/UseToxiproxy.scala @@ -11,6 +11,7 @@ import monocle.macros.syntax.lens.* import org.apache.pekko.http.scaladsl.model.Uri import scala.collection.mutable.Map +import scala.util.Try /** A test plugin which injects toxiproxy to certain connections, a much-simplified version of the equivalent plugin in Canton. * At the moment, we support only the SV apps' ledger api connections and the scan app's HTTP connections, but as we need to add more - we will generalize the code below. @@ -252,7 +253,11 @@ case class UseToxiproxy( override def afterEnvironmentDestroyed(config: SpliceConfig): Unit = { logger.debug("deleting all proxies. ") - proxies.foreach { case (_, p) => p.delete() } + // Delete every proxy even if one fails: leftovers in the shared toxiproxy daemon keep + // their listen ports bound and cause name conflicts for the next suite. + proxies.foreach { case (name, p) => + Try(p.delete()).failed.foreach(e => logger.warn(s"Failed to delete proxy $name", e)) + } } def disableConnectionViaProxy(connection: String): Unit = { From 0b45eb2d3ac319850472e6bda056a2ba41489ba7 Mon Sep 17 00:00:00 2001 From: Jaeyoon Cho Date: Thu, 30 Jul 2026 14:13:56 +0900 Subject: [PATCH 153/329] replace storage.underlying with storage in tests for DbAppActivityRecordStoreTest and DbScanAppRewardsStoreTest (#6564) Signed-off-by: JYC11 Co-authored-by: Divam <681060+dfordivam@users.noreply.github.com> --- .../splice/scan/store/db/DbAppActivityRecordStore.scala | 3 ++- .../splice/scan/store/DbAppActivityRecordStoreTest.scala | 6 +++--- .../splice/scan/store/DbScanAppRewardsStoreTest.scala | 8 ++++---- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbAppActivityRecordStore.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbAppActivityRecordStore.scala index 3016348a34..8f6595b203 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbAppActivityRecordStore.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbAppActivityRecordStore.scala @@ -293,7 +293,7 @@ class DbAppActivityRecordStore( (sql""" insert into #${Tables.appActivityRecords}( history_id, verdict_row_id, round_number, app_provider_parties, app_activity_weights - ) values """ ++ values).asUpdate + ) values """ ++ values ++ sql" ON CONFLICT DO NOTHING").asUpdate } } @@ -415,6 +415,7 @@ class DbAppActivityRecordStore( earliest_ingested_round, last_archived_round) values ($historyId, $codeVersion, $userVersion, $startedIngestingAt, $earliestIngestedRound, $lastArchivedRound) + ON CONFLICT DO NOTHING """.asUpdate private def updateLastArchivedRoundDBIO(round: Long) = diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbAppActivityRecordStoreTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbAppActivityRecordStoreTest.scala index 2ab1c4045c..da458938d1 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbAppActivityRecordStoreTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbAppActivityRecordStoreTest.scala @@ -1090,7 +1090,7 @@ class DbAppActivityRecordStoreTest val n = storeCounter.getAndIncrement() val participantId = mkParticipantId(s"activity-test-$n") val updateHistory = new UpdateHistory( - storage.underlying, + storage, migrationId, s"app_activity_test_$n", participantId, @@ -1103,7 +1103,7 @@ class DbAppActivityRecordStoreTest ) updateHistory.ingestionSink.initialize().map { _ => val store = new DbAppActivityRecordStore( - storage.underlying, + storage, updateHistory, versions, isFirstSv, @@ -1121,7 +1121,7 @@ class DbAppActivityRecordStoreTest ): Future[(DbAppActivityRecordStore, DbScanVerdictStore)] = { val participantId = mkParticipantId("activity-test") val updateHistory = new UpdateHistory( - storage.underlying, + storage, migrationId, "app_activity_combined_test", participantId, diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala index bdfd83565e..ec923cb82a 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala @@ -640,7 +640,7 @@ class DbScanAppRewardsStoreTest (store, historyId) <- newStore() _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0)) _ <- futureUnlessShutdownToFuture( - storage.underlying.queryAndUpdate( + storage.queryAndUpdate( store .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), "test.assertMintingAllowanceWithinMintingCurve", @@ -655,7 +655,7 @@ class DbScanAppRewardsStoreTest // Reward exceeds issuance by 2x tolerance (0.002 > 0.001) _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.002)) result <- futureUnlessShutdownToFuture( - storage.underlying.queryAndUpdate( + storage.queryAndUpdate( store.assertMintingAllowanceWithinMintingCurve( roundNumber, mkParams(BigDecimal(10.0)), @@ -673,7 +673,7 @@ class DbScanAppRewardsStoreTest (store, historyId) <- newStore() _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0005)) _ <- futureUnlessShutdownToFuture( - storage.underlying.queryAndUpdate( + storage.queryAndUpdate( store .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), "test.assertMintingAllowanceWithinMintingCurve", @@ -1214,7 +1214,7 @@ class DbScanAppRewardsStoreTest val n = storeCounter.getAndIncrement() val participantId = mkParticipantId(s"rewards-test-$n") val updateHistory = new UpdateHistory( - storage.underlying, + storage, migrationId, s"app_rewards_test_$n", participantId, From 2e32449e7ede8caa2407fe424abd2e8783e69677 Mon Sep 17 00:00:00 2001 From: Stanislav German-Evtushenko Date: Thu, 30 Jul 2026 18:03:54 +0900 Subject: [PATCH 154/329] helm, info, status: Updates and fixes (#6609) * helm, info, status: Print position and exit code on error Signed-off-by: Stanislav German-Evtushenko * helm, info, status: Ignore CantonBFT node when unable to fetch URL Before: - If the URL for a single CantonBFT node can't be fetched no results for all CantonBFT nodes are generated After: - If the URL for a single CantonBFT node can't be fetched the result only for this node is missing Signed-off-by: Stanislav German-Evtushenko --------- Signed-off-by: Stanislav German-Evtushenko --- cluster/helm/splice-info/scripts/get-status.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cluster/helm/splice-info/scripts/get-status.sh b/cluster/helm/splice-info/scripts/get-status.sh index 873e931bbb..107ad158ae 100755 --- a/cluster/helm/splice-info/scripts/get-status.sh +++ b/cluster/helm/splice-info/scripts/get-status.sh @@ -5,6 +5,9 @@ set -euo pipefail +set -E +trap 'echo "ERROR: On line $LINENO in function \"${FUNCNAME[0]}\". Exit code is $?." >&2' ERR + SV_METRICS_URL="${SV_METRICS_URL:-http://sv-app:10013/metrics}" SCAN_URL="${SCAN_URL:-http://scan-app:5012}" @@ -491,7 +494,7 @@ cantonbft_get_status_reachability() { run_parallel "$get_cantonbfts_info_cmds" | json_object_values_fromjson || echo '{}' } | - jq '{ bftSequencers: [.[] | .bftSequencers[]] }' + jq '{ bftSequencers: map(.bftSequencers[]?) }' ) local cantonbfts_info_for_serial; cantonbfts_info_for_serial=$( From 26437747873b569fae52cd442695d56adbc698e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Thu, 30 Jul 2026 12:31:28 +0200 Subject: [PATCH 155/329] SV UI strings adjustment (#6243) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../forms/grant-revoke-featured-app-form.test.tsx | 8 +++++--- .../__tests__/governance/proposal-summary.test.tsx | 14 +++++++------- ...reateUnallocatedUnclaimedActivityRecordForm.tsx | 2 +- .../forms/GrantRevokeFeaturedAppForm.tsx | 8 +++++--- .../src/components/forms/OffboardSvForm.tsx | 2 +- .../components/forms/SetAmuletConfigRulesForm.tsx | 2 +- .../src/components/forms/SetDsoConfigRulesForm.tsx | 2 +- .../components/forms/UpdateSvRewardWeightForm.tsx | 2 +- .../governance/ProposalDetailsContent.tsx | 2 +- .../src/components/governance/ProposalSummary.tsx | 2 +- .../src/hooks/useFeaturedAppRightPicker.ts | 2 +- 11 files changed, 25 insertions(+), 21 deletions(-) diff --git a/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx index 4b8665866c..1566a5ff9e 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx @@ -364,7 +364,9 @@ describe('Revoke Featured App Form', () => { fireEvent.blur(partyIdInput); await waitFor(() => { - expect(screen.queryByText('Loading featured app rights...')).not.toBeInTheDocument(); + expect( + screen.queryByText('Loading Featured Application Contract IDs...') + ).not.toBeInTheDocument(); }); const rightCidDropdown = screen.getByTestId('revoke-featured-app-rightCid-dropdown'); @@ -431,12 +433,12 @@ describe('Revoke Featured App Form', () => { await waitFor(() => { expect(screen.getByTestId('revoke-featured-app-rightCid')).toHaveTextContent( - 'No featured application rights found for this provider' + 'No Featured Application Contract IDs found for this provider' ); }); expect(screen.getByTestId('revoke-featured-app-rightCid-error')).not.toHaveTextContent( - 'No featured application rights found for this provider' + 'No Featured Application Contract IDs found for this provider' ); expect(screen.getByTestId('revoke-featured-app-rightCid-dropdown')).toBeDisabled(); diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx index 6d2c17a364..5bbd6213a1 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx @@ -38,7 +38,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); expect(screen.getByTestId('summary-field').textContent).toBe(summary); - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Threshold Deadline'); + expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); @@ -102,7 +102,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); expect(screen.getByTestId('summary-field').textContent).toBe(summary); - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Threshold Deadline'); + expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); @@ -142,7 +142,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); expect(screen.getByTestId('summary-field').textContent).toBe(summary); - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Threshold Deadline'); + expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); @@ -186,7 +186,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); expect(screen.getByTestId('summary-field').textContent).toBe(summary); - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Threshold Deadline'); + expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); @@ -236,7 +236,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); expect(screen.getByTestId('summary-field').textContent).toBe(summary); - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Threshold Deadline'); + expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); @@ -302,7 +302,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); expect(screen.getByTestId('summary-field').textContent).toBe(summary); - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Threshold Deadline'); + expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); @@ -374,7 +374,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); expect(screen.getByTestId('summary-field').textContent).toBe(summary); - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Threshold Deadline'); + expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); diff --git a/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx b/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx index 81f722e465..9ff397a7e5 100644 --- a/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx +++ b/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx @@ -185,7 +185,7 @@ export const CreateUnallocatedUnclaimedActivityRecordForm: React.FC = _ => { > {field => ( diff --git a/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx b/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx index 98c501f330..898fb0001d 100644 --- a/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx +++ b/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx @@ -263,7 +263,9 @@ export const GrantRevokeFeaturedAppForm: React.FC { picker.resetOptions(); @@ -288,7 +290,7 @@ export const GrantRevokeFeaturedAppForm: React.FC @@ -306,7 +308,7 @@ export const GrantRevokeFeaturedAppForm: React.FC {field => ( diff --git a/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx b/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx index 722fe87d9b..ce35a04548 100644 --- a/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx +++ b/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx @@ -137,7 +137,7 @@ export const OffboardSvForm: React.FC = _ => { > {field => ( diff --git a/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx b/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx index 56a7a311b9..bfd977dd36 100644 --- a/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx +++ b/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx @@ -250,7 +250,7 @@ export const SetAmuletConfigRulesForm: () => JSX.Element = () => { > {field => ( diff --git a/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx b/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx index 3d9b2be85d..8bb802af89 100644 --- a/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx +++ b/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx @@ -266,7 +266,7 @@ export const SetDsoConfigRulesForm: () => JSX.Element = () => { > {field => ( diff --git a/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx b/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx index 94838d058d..07028252f9 100644 --- a/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx +++ b/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx @@ -182,7 +182,7 @@ export const UpdateSvRewardWeightForm: React.FC = _ => { > {field => ( diff --git a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx index bb84be55b3..9b85fe9b42 100644 --- a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx @@ -328,7 +328,7 @@ export const ProposalDetailsContent: React.FC = pro /> diff --git a/apps/sv/frontend/src/components/governance/ProposalSummary.tsx b/apps/sv/frontend/src/components/governance/ProposalSummary.tsx index a831205927..508ef39599 100644 --- a/apps/sv/frontend/src/components/governance/ProposalSummary.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalSummary.tsx @@ -77,7 +77,7 @@ export const ProposalSummary: React.FC = props => { diff --git a/apps/sv/frontend/src/hooks/useFeaturedAppRightPicker.ts b/apps/sv/frontend/src/hooks/useFeaturedAppRightPicker.ts index f976b58845..f562bcb9b1 100644 --- a/apps/sv/frontend/src/hooks/useFeaturedAppRightPicker.ts +++ b/apps/sv/frontend/src/hooks/useFeaturedAppRightPicker.ts @@ -49,7 +49,7 @@ export const useFeaturedAppRightPicker = ( setRightOptions([]); setCurrentWeights({}); setProviderSearched(false); - return 'Could not load featured app rights for this provider'; + return 'Could not load Featured Application Contract IDs for this provider'; } }; From 4226f52419e5165cf254bc652508b1e990aa0b98 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:43:42 +0200 Subject: [PATCH 156/329] Upgrade Canton to 3.5.11-snapshot.20260730.19128.0.ve4f54d89 (#6613) [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index fd02437cb6..decbf39aff 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.10", - "oss_sha256": "sha256:1n14ghxn6id7n52vmywzs8arix187hxpcm7cwb0c2iqsjx68nagc", - "canton_base_image_sha256": "sha256:34fd5d1f134266bafb41f7449b519c45b5c76b4b2231f1fc508e536c0746c441", - "canton_participant_image_sha256": "sha256:3176915a24f584ce583fa01ea1e1dd7ba65ad3b1b862ca5ee1268238ba59d627", - "canton_mediator_image_sha256": "sha256:fb78554033eebb387e348a0e99419c5a08b0962700eb7e039dc5812c6d9ce342", - "canton_sequencer_image_sha256": "sha256:1737498d61ccaf5a2159fc6af14554bc4d05a0d120bc00a2ccf9348128f4ba8f" + "version": "3.5.11-snapshot.20260730.19128.0.ve4f54d89", + "oss_sha256": "sha256:1x1zz639ha8yd4w8wxsmsax5r39znq79psksr8lpdzflggjdqrk9", + "canton_base_image_sha256": "sha256:428a17d0eb7fd52c7e0a54be7170dbc1ed40cbdf20662e0032cb9b3656941e14", + "canton_participant_image_sha256": "sha256:c57a32b2496f9ceab18056ca4693f25635e54d94b9ece04d99596b2259d57da8", + "canton_mediator_image_sha256": "sha256:7ddb894c440b1abecd86424678ddc6f83d11f958b19659ad491a8646bf8ab9c0", + "canton_sequencer_image_sha256": "sha256:dfac05995ecc86ff5161b70e56e8e301d0b35d837e75e937f5997a995df94b70" } From 69b43eb761e38695052c983715aa855c8cb207fc Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 13:59:12 +0200 Subject: [PATCH 157/329] Change default cantonbft config to exponential blacklisting (#6612) To recover more quickly from nodes that are down. Not gonna do much for the cases where someone times out every X epochs but still a better default. [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- ...ReconcileBftSequencingParametersIntegrationTest.scala | 9 ++++----- .../splice/sv/config/SvAppConfig.scala | 8 +++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ReconcileBftSequencingParametersIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ReconcileBftSequencingParametersIntegrationTest.scala index 180ca61a20..4eb222f4b8 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ReconcileBftSequencingParametersIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ReconcileBftSequencingParametersIntegrationTest.scala @@ -39,9 +39,8 @@ class SvReconcileBftSequencingParametersIntegrationTest blacklistLeaderSelectionPolicyConfig = SequencingParameters.DefaultLeaderSelectionPolicyConfig.copy( howLongToBlacklist = - BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential( - initialValue = 1L, - maximumEpochBlacklisted = Some(250L), + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear( + maximumEpochBlacklisted = Some(250L) ) ), ) @@ -75,7 +74,7 @@ class SvReconcileBftSequencingParametersIntegrationTest bftParameters.pbftViewChangeTimeout shouldBe com.digitalasset.canton.time.PositiveFiniteDuration .tryOfSeconds(5) bftParameters.blacklistLeaderSelectionPolicyConfig.howLongToBlacklist shouldBe a[ - BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential ] sv1Backend.stop() actAndCheck( @@ -92,7 +91,7 @@ class SvReconcileBftSequencingParametersIntegrationTest .fromByteString(sv1Backend.config.localSynchronizerNodes.current.protocolVersion, bytes) .value bftParameters.blacklistLeaderSelectionPolicyConfig.howLongToBlacklist shouldBe a[ - BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Linear ] }, ) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala index 2c6661b4bc..8dedaf4757 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala @@ -463,7 +463,13 @@ case class SvAppBackendConfig( pbftViewChangeTimeout = PositiveFiniteDuration.ofSeconds(5), segmentLength = SequencingParameters.DefaultSegmentLength.length, blacklistLeaderSelectionPolicyConfig = - SequencingParameters.DefaultLeaderSelectionPolicyConfig, + SequencingParameters.DefaultLeaderSelectionPolicyConfig.copy( + howLongToBlacklist = + BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential( + initialValue = 1L, + maximumEpochBlacklisted = Some(250L), + ) + ), ) ), // Set to false to disable the DB-level exclusive lock that prevents two SV instances From 939a2f0b5de78831e02fa0a49db44936d59cc2db Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 17:41:33 +0200 Subject: [PATCH 158/329] fix bft scan connection init leaks (#6617) * [static] Close scan connections when BFT scan connection init fails Signed-off-by: Raymond Roestenburg * [ci] Format Signed-off-by: Raymond Roestenburg --------- Signed-off-by: Raymond Roestenburg --- .../admin/api/client/BftScanConnection.scala | 88 +++++++++++-------- 1 file changed, 51 insertions(+), 37 deletions(-) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala index 3c9c689cda..f7d9fa3326 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala @@ -1863,8 +1863,10 @@ object BftScanConnection { disableBackgroundRefresh = true, ) - // Use the temporary connection to get a consensus on the full list of scans - allScans <- Bft.getScansInDsoRules(tempBftConnection).andThen { case _ => + // Use the temporary connection to get a consensus on the full list of scans. + // Future.delegate turns a synchronous throw into a failed future, so the + // andThen cleanup always runs. + allScans <- Future.delegate(Bft.getScansInDsoRules(tempBftConnection)).andThen { case _ => tempBftConnection.close() } @@ -1921,23 +1923,29 @@ object BftScanConnection { connectionMetrics, ) - _ <- retryProvider.waitUntil( - RetryFor.WaitingOnInitDependency, - "refresh_initial_scan_list", - "Scan list is refreshed.", - scanList - .refresh(bftConnection) - .recoverWith { case NonFatal(ex) => - Future.failed( - Status.UNAVAILABLE - .withDescription("Failed to refresh scan list on init") - .withCause(ex) - .asException() - ) - } - .map(_ => ()), - loggerFactory.getTracedLogger(classOf[BftScanConnection]), - ) + _ <- retryProvider + .waitUntil( + RetryFor.WaitingOnInitDependency, + "refresh_initial_scan_list", + "Scan list is refreshed.", + scanList + .refresh(bftConnection) + .recoverWith { case NonFatal(ex) => + Future.failed( + Status.UNAVAILABLE + .withDescription("Failed to refresh scan list on init") + .withCause(ex) + .asException() + ) + } + .map(_ => ()), + loggerFactory.getTracedLogger(classOf[BftScanConnection]), + ) + .recoverWith { case NonFatal(ex) => + // do not leak the scan connections when initialization ultimately fails + bftConnection.close() + Future.failed(ex) + } } yield bftConnection case bft @ BftScanClientConfig.Bft(_, _, _, _) => @@ -1972,24 +1980,30 @@ object BftScanConnection { else { _ => Future.unit }, connectionMetrics, ) - _ <- retryProvider.waitUntil( - RetryFor.WaitingOnInitDependency, - "refresh_initial_scan_list", - "Scan list is refreshed.", - bftConnection.scanList - .asInstanceOf[AllDsoScansBft] - .refresh(bftConnection) - .recoverWith { case NonFatal(ex) => - Future.failed( - Status.UNAVAILABLE - .withDescription("Failed to refresh scan list on init") - .withCause(ex) - .asException() - ) - } - .map(_ => ()), - loggerFactory.getTracedLogger(classOf[BftScanConnection]), - ) + _ <- retryProvider + .waitUntil( + RetryFor.WaitingOnInitDependency, + "refresh_initial_scan_list", + "Scan list is refreshed.", + bftConnection.scanList + .asInstanceOf[AllDsoScansBft] + .refresh(bftConnection) + .recoverWith { case NonFatal(ex) => + Future.failed( + Status.UNAVAILABLE + .withDescription("Failed to refresh scan list on init") + .withCause(ex) + .asException() + ) + } + .map(_ => ()), + loggerFactory.getTracedLogger(classOf[BftScanConnection]), + ) + .recoverWith { case NonFatal(ex) => + // do not leak the seed scan connections when initialization ultimately fails + bftConnection.close() + Future.failed(ex) + } } yield bftConnection } } From 40feff120dbeb5f96d737d14ed7904cb72750e29 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:13:10 +0200 Subject: [PATCH 159/329] Clear out upcoming release notes (#6620) [static] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- docs/src/release_notes_upcoming.rst | 38 +---------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 0be48f0999..9d84ff09ad 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -5,40 +5,4 @@ .. NOTE: add your upcoming release notes below this line. They are included in the `release_notes.rst`. -.. release-notes:: Upcoming - - - PostgreSQL 18 - - - Splice now officially supports PostgreSQL 18. - ⚠️ Note that that PostgreSQL 14, which was the default until now, will reach End of Life on November 12, 2026. - You should upgrade before that date. - - - Scan app - - - Remove deprecated ``/transactions`` endpoint. - - - Validator app - - - The deprecated ``TransferCommand`` functionality consisting - of the endpoints - ``/v0/admin/external-party/transfer-preapproval/prepare-send``, - ``/v0/admin/external-party/transfer-preapproval/submit-send`` - and the automation to execute transfer commands is now - disabled by default. If you were still using those switch to - token standard transfers which also support 24h submission - delays since `cip 107 - `_. - If you need some time to migrate, you can temporarily - reenable it by setting - ``canton.validator-apps.validator_backend.enable-deprecated-transfer-command-support=true``. The - functionality is expected to be fully removed in 0.8.0 so - this only provides a bit more time to migrate but you must - complete the migration. - - - Deployment - - - The sequencer and mediator can now be configured with independent ``additionalJvmOptions`` via the new ``sequencer.additionalJvmOptions`` and ``mediator.additionalJvmOptions`` values in the ``splice-global-domain`` helm chart. - - - SV app - - - Add support for updating weight via ``UpdateFeaturedAppRight`` governance voting UI. +.. .. release-notes:: Upcoming From 46277a7fa1cba210d5dbf0cc67c3c1879da657c0 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 18:30:30 +0200 Subject: [PATCH 160/329] Upgrade Canton to 3.5.11 (#6619) [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index decbf39aff..5c0d3a759d 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.11-snapshot.20260730.19128.0.ve4f54d89", - "oss_sha256": "sha256:1x1zz639ha8yd4w8wxsmsax5r39znq79psksr8lpdzflggjdqrk9", - "canton_base_image_sha256": "sha256:428a17d0eb7fd52c7e0a54be7170dbc1ed40cbdf20662e0032cb9b3656941e14", - "canton_participant_image_sha256": "sha256:c57a32b2496f9ceab18056ca4693f25635e54d94b9ece04d99596b2259d57da8", - "canton_mediator_image_sha256": "sha256:7ddb894c440b1abecd86424678ddc6f83d11f958b19659ad491a8646bf8ab9c0", - "canton_sequencer_image_sha256": "sha256:dfac05995ecc86ff5161b70e56e8e301d0b35d837e75e937f5997a995df94b70" + "version": "3.5.11", + "oss_sha256": "sha256:19nrzwix6pkqg7ah3jbb4nx2g4772f9m966dklzfya545rqinx6h", + "canton_base_image_sha256": "sha256:6cdfea8af002ac46bcd1c4f6e2355cc99f20635bc87cb02df999f9b52738ba71", + "canton_participant_image_sha256": "sha256:b0b50bf86560d66ee382d9eebdc4e6829341564c03327d90699de478f1950324", + "canton_mediator_image_sha256": "sha256:a6f2bf118e1e293850fae4a29c01b33d30cb806d5e104dfe8e10c081fb20b0d5", + "canton_sequencer_image_sha256": "sha256:9af09befb80623cf16d70d38b112c65aa311ce744811711f5fc5b93e4548d5da" } From d2d68b6776a8aa52b2b8d1f6ce03d384b2a25d27 Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:35:25 +0200 Subject: [PATCH 161/329] [ci] Widen rate limit window in ScanIntegrationTest (#6624) maxAccepted was the exact theoretical ceiling with no slack, so any timing jitter that stretches the 5s emission window lets one more refill batch through (seen accepting 31). Signed-off-by: Raymond Roestenburg --- .../splice/integration/tests/ScanIntegrationTest.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala index 147e312ff0..a934e1469c 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala @@ -234,7 +234,11 @@ class ScanIntegrationTest // then 5 every second // first second is 5 (full capacity) + 5 (capacity added after consumption) // then 5 every second - val maxAccepted = 30 + // The 50 calls are emitted at 10/s, so ~5s of refill gives 30 in the ideal case. Allow one + // more second of refill: throttle jitter or a slow first call stretches the window past 5s + // and lets a further batch through (seen accepting 31). This is still far below the 50 + // attempted, so the assertion keeps proving that the limiter rejects. + val maxAccepted = 35 // account for bursts in the stream used to rate limit the calls in `runRateLimited` val minAccepted = 10 results.count(identity) should (be >= minAccepted and be <= maxAccepted) From 0c5cd293703e5dcd9b3ff7669eb8e169f59daa61 Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:37:15 +0200 Subject: [PATCH 162/329] [ci] Pin as_of_round in ScanTimeBasedIntegrationTest holdings summary (#6622) The at_or_before query ran after advanceTime, so it resolved a different earliest open mining round than the exact query and the holding fees differed by one round. Signed-off-by: Raymond Roestenburg --- .../integration/tests/ScanTimeBasedIntegrationTest.scala | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala index ea4371ab30..dbd17c3a86 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala @@ -412,6 +412,10 @@ class ScanTimeBasedIntegrationTest migrationId, ownerPartyIds = Vector(aliceUserParty), recordTimeMatch = Some(definitions.HoldingsSummaryRequest.RecordTimeMatch.AtOrBefore), + // as_of_round defaults to the earliest open mining round at request time, and the + // advanceTime above lets the rounds advance, so pin it to the round the exact query + // resolved. Otherwise the holding fees derived from it differ by one round's worth. + asOfRound = holdingsSummary.map(_.computedAsOfRound), ) holdingsSummaryAtOrBefore shouldBe holdingsSummary From 8cf35c2d6be25fd55c85af1b37f9ae8b16062529 Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:46:53 +0200 Subject: [PATCH 163/329] =?UTF-8?q?[ci]=20Close=20channel=20and=20snapshot?= =?UTF-8?q?=20stream=20on=20synchronous=20failures=20in=20Seq=E2=80=A6=20(?= =?UTF-8?q?#6618)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raymond Roestenburg --- .../SequencerAdminConnection.scala | 50 +++++++++++-------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SequencerAdminConnection.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SequencerAdminConnection.scala index 8c5f1b3248..8fa05b59ae 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SequencerAdminConnection.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SequencerAdminConnection.scala @@ -68,6 +68,7 @@ import java.nio.file.{Files, Path} import java.util.{Base64, Collections} import scala.concurrent.{ExecutionContextExecutor, Future, blocking} import scala.jdk.CollectionConverters.* +import scala.util.control.NonFatal import org.lfdecentralizedtrust.splice.store.bulk.ZstdGroupedWeight /** Connection to the subset of the Canton sequencer admin API that we rely @@ -302,29 +303,36 @@ class SequencerAdminConnection( logger, s"$serviceName connection", ) - // stub acts the client-side proxy to get access to raw grpc commands - val stub = request.createService(channel.channel) - // bridges the gRPC response stream to a Pekko Source and converts the Protobuf ByteString to a Pekko ByteString - val source = ClientAdapter - .serverStreaming( - request - .createRequestInternal() - .getOrElse(throw new IllegalStateException("Unable to create internal request.")), - (req: OnboardingStateV2Request, obs: StreamObserver[OnboardingStateV2Response]) => - stub.onboardingStateV2(req, obs), - ) - .map { response => - val proto: ByteString = response.onboardingStateForSequencer - PekkoByteString(proto.asReadOnlyByteBuffer()) + try { + // stub acts the client-side proxy to get access to raw grpc commands + val stub = request.createService(channel.channel) + // bridges the gRPC response stream to a Pekko Source and converts the Protobuf ByteString to a Pekko ByteString + val source = ClientAdapter + .serverStreaming( + request + .createRequestInternal() + .getOrElse(throw new IllegalStateException("Unable to create internal request.")), + (req: OnboardingStateV2Request, obs: StreamObserver[OnboardingStateV2Response]) => + stub.onboardingStateV2(req, obs), + ) + .map { response => + val proto: ByteString = response.onboardingStateForSequencer + PekkoByteString(proto.asReadOnlyByteBuffer()) + } + .via( + ZstdGroupedWeight(compressionLevel = 3, minSize = chunkSize.toLong) + ) // 3 is the default zstd compression level + val storageObject = source.runWith(sink) + storageObject.onComplete { _ => + channel.close() } - .via( - ZstdGroupedWeight(compressionLevel = 3, minSize = chunkSize.toLong) - ) // 3 is the default zstd compression level - val storageObject = source.runWith(sink) - storageObject.onComplete { _ => - channel.close() + storageObject + } catch { + // a throw before the onComplete callback is registered would leak the channel + case NonFatal(e) => + channel.close() + Future.failed(e) } - storageObject } /** This is used for initializing the sequencer when the domain is first bootstrapped. From 05ff88a9a3da505cc4e5f04da3d621084b832bba Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:00:33 +0200 Subject: [PATCH 164/329] Bump versions after 0.7.0 release (#6625) [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- LATEST_RELEASE | 2 +- VERSION | 2 +- docs/src/release_notes_upcoming.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LATEST_RELEASE b/LATEST_RELEASE index fcbaa84781..faef31a435 100644 --- a/LATEST_RELEASE +++ b/LATEST_RELEASE @@ -1 +1 @@ -0.6.14 +0.7.0 diff --git a/VERSION b/VERSION index faef31a435..39e898a4f9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.0 +0.7.1 diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 9d84ff09ad..5f5ee78c44 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -5,4 +5,4 @@ .. NOTE: add your upcoming release notes below this line. They are included in the `release_notes.rst`. -.. .. release-notes:: Upcoming +.. release-notes:: Upcoming From fc336be7aa70a51362d781ccc69a7e877208b4d2 Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Thu, 30 Jul 2026 21:19:57 +0200 Subject: [PATCH 165/329] [ci] Close additionalLegacy synchronizer nodes and isolate node closes (#6626) Signed-off-by: Raymond Roestenburg --- .../splice/sv/SvApp.scala | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala index 63e562ce9e..9c26cc8655 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/SvApp.scala @@ -220,6 +220,7 @@ class SvApp( localSynchronizerNodes.current.close() localSynchronizerNodes.successor.foreach(_.close()) localSynchronizerNodes.legacy.foreach(_.close()) + localSynchronizerNodes.additionalLegacy.foreach(_.close()) Future.failed(err) } ) @@ -743,12 +744,22 @@ object SvApp { override def closeAsync(): Seq[AsyncOrSyncCloseable] = Seq( + // One SyncCloseable per node so a failing close does not skip the others. SyncCloseable( - s"Domain connections", { - localSynchronizerNodes.current.close() - localSynchronizerNodes.successor.foreach(_.close()) - localSynchronizerNodes.legacy.foreach(_.close()) - }, + s"current domain connections", + localSynchronizerNodes.current.close(), + ), + SyncCloseable( + s"successor domain connections", + localSynchronizerNodes.successor.foreach(_.close()), + ), + SyncCloseable( + s"legacy domain connections", + localSynchronizerNodes.legacy.foreach(_.close()), + ), + SyncCloseable( + s"additional legacy domain connections", + localSynchronizerNodes.additionalLegacy.foreach(_.close()), ), SyncCloseable( s"Participant Admin connection", From 8a9170072e7122dff2786a8c04e22cf09b38ea7b Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Fri, 31 Jul 2026 12:12:52 +0200 Subject: [PATCH 166/329] =?UTF-8?q?[ci]=20Make=20wallet=20service=20closes?= =?UTF-8?q?=20failure-isolated=20and=20clean=20up=20on=20part=E2=80=A6=20(?= =?UTF-8?q?#6627)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [ci] Make wallet service closes failure-isolated and clean up on partial construction Signed-off-by: Raymond Roestenburg --- .../wallet/ExternalPartyWalletManager.scala | 47 +++++---- .../wallet/ExternalPartyWalletService.scala | 41 +++++--- .../splice/wallet/UserWalletManager.scala | 63 +++++++----- .../splice/wallet/UserWalletService.scala | 99 +++++++++++-------- 4 files changed, 147 insertions(+), 103 deletions(-) diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletManager.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletManager.scala index 19c50c6fcd..bea200160a 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletManager.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletManager.scala @@ -27,6 +27,7 @@ import org.lfdecentralizedtrust.splice.wallet.store.{ExternalPartyWalletStore, W import scala.collection.concurrent.TrieMap import scala.concurrent.{blocking, ExecutionContext} +import scala.util.control.NonFatal /** Manages all services comprising an external party wallets. */ class ExternalPartyWalletManager( @@ -134,8 +135,8 @@ class ExternalPartyWalletManager( )(TraceContext.empty) externalPartyRetryProviderAndWalletService.foreach { case (externalPartyRetryProvider, walletService) => - externalPartyRetryProvider.close() - walletService.close() + try externalPartyRetryProvider.close() + finally walletService.close() } UnlessShutdown.AbortedDueToShutdown } else { @@ -160,22 +161,32 @@ class ExternalPartyWalletManager( retryProvider.futureSupervisor, retryProvider.metricsFactory, ) - val walletService = new ExternalPartyWalletService( - ledgerClient, - key, - automationConfig, - clock, - domainTimeSync, - storage, - externalPartyRetryProvider, - partyLoggerFactory, - migrationId, - participantId, - params, - scanConnection, - packageVersionSupport, - rewardSharingConfigByParty.getOrElse(externalParty.toProtoPrimitive, RewardSharingConfig()), - ) + val walletService = + try { + new ExternalPartyWalletService( + ledgerClient, + key, + automationConfig, + clock, + domainTimeSync, + storage, + externalPartyRetryProvider, + partyLoggerFactory, + migrationId, + participantId, + params, + scanConnection, + packageVersionSupport, + rewardSharingConfigByParty.getOrElse( + externalParty.toProtoPrimitive, + RewardSharingConfig(), + ), + ) + } catch { + case NonFatal(e) => + externalPartyRetryProvider.close() + throw e + } (externalPartyRetryProvider, walletService) } diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletService.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletService.scala index d2c96ebd44..aa4acf27cc 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletService.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletService.scala @@ -3,7 +3,7 @@ package org.lfdecentralizedtrust.splice.wallet -import com.digitalasset.canton.lifecycle.{CloseContext, FlagCloseable} +import com.digitalasset.canton.lifecycle.{CloseContext, FlagCloseable, LifeCycle} import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.resource.DbStorage import com.digitalasset.canton.time.Clock @@ -20,6 +20,7 @@ import org.lfdecentralizedtrust.splice.wallet.config.RewardSharingConfig import org.lfdecentralizedtrust.splice.wallet.store.ExternalPartyWalletStore import scala.concurrent.ExecutionContext +import scala.util.control.NonFatal /** A service managing the treasury, automation, and store for an external party's wallet. */ class ExternalPartyWalletService( @@ -59,23 +60,31 @@ class ExternalPartyWalletService( params.defaultLimit, ) - val automation = new ExternalPartyWalletAutomationService( - store, - ledgerClient, - automationConfig, - clock, - domainTimeSync, - retryProvider, - params, - scanConnection, - loggerFactory, - packageVersionSupport, - rewardSharingConfig, - ) + val automation = + try { + new ExternalPartyWalletAutomationService( + store, + ledgerClient, + automationConfig, + clock, + domainTimeSync, + retryProvider, + params, + scanConnection, + loggerFactory, + packageVersionSupport, + rewardSharingConfig, + ) + } catch { + // a failed construction never reaches onClosed, so close the store here + case NonFatal(e) => + store.close() + throw e + } override def onClosed(): Unit = { - automation.close() - store.close() + // LifeCycle.close closes both in order even if the first close fails. + LifeCycle.close(automation, store)(logger) super.onClosed() } } diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletManager.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletManager.scala index dcd52b5750..026c5f4c07 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletManager.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletManager.scala @@ -37,6 +37,7 @@ import io.opentelemetry.api.trace.Tracer import scala.collection.concurrent.TrieMap import scala.concurrent.{blocking, ExecutionContext, Future} +import scala.util.control.NonFatal /** Manages all services comprising an end-user wallets. */ class UserWalletManager( @@ -185,8 +186,8 @@ class UserWalletManager( show"Detected race between adding wallet for party ${endUserParty} and shutdown: closing wallet." )(TraceContext.empty) userRetryProviderAndWalletService.foreach { case (userRetryProvider, walletService) => - userRetryProvider.close() - walletService.close() + try userRetryProvider.close() + finally walletService.close() } UnlessShutdown.AbortedDueToShutdown } else { @@ -213,29 +214,37 @@ class UserWalletManager( retryProvider.futureSupervisor, retryProvider.metricsFactory, ) - val walletService = new UserWalletService( - ledgerClient, - key, - this, - automationConfig, - clock, - domainTimeSync, - treasuryConfig, - storage, - userRetryProvider, - userLoggerFactory, - scanConnection, - packageVersionSupport, - migrationId, - participantId, - Option.when(endUserParty == store.walletKey.validatorParty)(validatorTopupConfig), - // TODO(DACH-NY/canton-network-node#12554): make it easier to configure the sweep functionality and guard better against operator errors (typos, etc.) - walletSweep.get(endUserParty.toProtoPrimitive), - autoAcceptTransfers.get(endUserParty.toProtoPrimitive), - rewardSharingConfigByParty.getOrElse(endUserParty.toProtoPrimitive, RewardSharingConfig()), - dedupDuration, - params, - ) + val walletService = + try { + new UserWalletService( + ledgerClient, + key, + this, + automationConfig, + clock, + domainTimeSync, + treasuryConfig, + storage, + userRetryProvider, + userLoggerFactory, + scanConnection, + packageVersionSupport, + migrationId, + participantId, + Option.when(endUserParty == store.walletKey.validatorParty)(validatorTopupConfig), + // TODO(DACH-NY/canton-network-node#12554): make it easier to configure the sweep functionality and guard better against operator errors (typos, etc.) + walletSweep.get(endUserParty.toProtoPrimitive), + autoAcceptTransfers.get(endUserParty.toProtoPrimitive), + rewardSharingConfigByParty + .getOrElse(endUserParty.toProtoPrimitive, RewardSharingConfig()), + dedupDuration, + params, + ) + } catch { + case NonFatal(e) => + userRetryProvider.close() + throw e + } (userRetryProvider, walletService) } @@ -246,8 +255,8 @@ class UserWalletManager( .withDescription(show"No wallet service found for user party ${userParty}") .asRuntimeException() case Some((userRetryProvider, walletService)) => - userRetryProvider.close() - walletService.close() + try userRetryProvider.close() + finally walletService.close() } } diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletService.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletService.scala index 1b3923127f..4c793f4575 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletService.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletService.scala @@ -19,7 +19,7 @@ import org.lfdecentralizedtrust.splice.wallet.config.{ import org.lfdecentralizedtrust.splice.wallet.store.UserWalletStore import org.lfdecentralizedtrust.splice.wallet.treasury.TreasuryService import org.lfdecentralizedtrust.splice.wallet.util.ValidatorTopupConfig -import com.digitalasset.canton.lifecycle.{CloseContext, FlagCloseable} +import com.digitalasset.canton.lifecycle.{CloseContext, FlagCloseable, LifeCycle} import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.resource.DbStorage import com.digitalasset.canton.time.Clock @@ -30,6 +30,7 @@ import org.apache.pekko.stream.Materializer import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import scala.concurrent.ExecutionContext +import scala.util.control.NonFatal /** A service managing the treasury, automation, and store for an end-user's wallet. */ class UserWalletService( @@ -78,47 +79,62 @@ class UserWalletService( params.defaultLimit, ) - val treasury: TreasuryService = new TreasuryService( - // The treasury gets its own connection, and is required to manage waiting for the store on its own. - ledgerClient.connection( - this.getClass.getSimpleName, - loggerFactory, - SpliceCircuitBreaker( - "treasury", - params.circuitBreakers.mediumPriority, + val treasury: TreasuryService = + try { + new TreasuryService( + // The treasury gets its own connection, and is required to manage waiting for the store on its own. + ledgerClient.connection( + this.getClass.getSimpleName, + loggerFactory, + SpliceCircuitBreaker( + "treasury", + params.circuitBreakers.mediumPriority, + clock, + store.dsoPartyId, + loggerFactory, + ), + ), + treasuryConfig, clock, - store.dsoPartyId, + store, + walletManager, + retryProvider, + scanConnection, + mintUnassignedRewardCouponsV2 = rewardSharingConfig.beneficiaries.isEmpty, loggerFactory, - ), - ), - treasuryConfig, - clock, - store, - walletManager, - retryProvider, - scanConnection, - mintUnassignedRewardCouponsV2 = rewardSharingConfig.beneficiaries.isEmpty, - loggerFactory, - ) + ) + } catch { + // a failed construction never reaches onClosed, so close the store here + case NonFatal(e) => + store.close() + throw e + } - val automation = new UserWalletAutomationService( - store, - treasury, - ledgerClient, - automationConfig, - clock, - domainTimeSync, - scanConnection, - retryProvider, - packageVersionSupport, - loggerFactory, - validatorTopupConfigO, - walletSweep, - autoAcceptTransfers, - rewardSharingConfig, - dedupDuration, - params, - ) + val automation = + try { + new UserWalletAutomationService( + store, + treasury, + ledgerClient, + automationConfig, + clock, + domainTimeSync, + scanConnection, + retryProvider, + packageVersionSupport, + loggerFactory, + validatorTopupConfigO, + walletSweep, + autoAcceptTransfers, + rewardSharingConfig, + dedupDuration, + params, + ) + } catch { + case NonFatal(e) => + LifeCycle.close(treasury, store)(logger) + throw e + } /** The connection to use when submitting commands based on reads from the WalletStore. * The submission will wait for the store to ingest the effect of the command before completing the future. @@ -132,9 +148,8 @@ class UserWalletService( // Close treasury early, that will result in it no longer accepting new requests // but in-flight requests can complete. If we close the automation first, // a task can get stuck forever waiting for store ingestion to complete. - treasury.close() - automation.close() - store.close() + // LifeCycle.close closes all of them in order even if one of them fails. + LifeCycle.close(treasury, automation, store)(logger) super.onClosed() } } From bfe7e35b8d001ca5a7f07dac2fe8e5353e1c6b65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Fri, 31 Jul 2026 12:20:16 +0200 Subject: [PATCH 167/329] Fix at threshold decoding in old proposals (#6632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../governance/governance-page.test.tsx | 39 +++++++++++++++++++ .../frontend/src/__tests__/mocks/constants.ts | 3 +- .../src/routes/voteRequestDetails.tsx | 16 ++++++-- 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx b/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx index b4e44cba28..2721724541 100644 --- a/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx @@ -4,6 +4,8 @@ import { render, screen, waitFor, within } from '@testing-library/react'; import { describe, expect, test } from 'vitest'; import { SvConfigProvider } from '../../utils'; import userEvent from '@testing-library/user-event'; +import dayjs from 'dayjs'; +import { dateTimeFormatISO } from '@canton-network/splice-common-frontend-utils'; import App from '../../App'; import { navigateToGovernancePage } from '../helpers'; import { voteResultsAmuletRules, voteResultsDsoRules } from '../mocks/constants'; @@ -124,6 +126,43 @@ describe('Governance Page', () => { expect(badge).toHaveTextContent(''); }); + test('vote history details show the actual effective time for closed votes without targetEffectiveAt', async () => { + const user = userEvent.setup(); + + await login(user); + + await navigateToGovernancePage(user); + + // The first DsoRules vote result simulates an old-model accepted vote: + // no targetEffectiveAt on the request, actual effective time on the outcome. + const closedVote = voteResultsDsoRules.dso_rules_vote_results[0]; + const effectiveAt = + closedVote.outcome.tag === 'VRO_Accepted' ? closedVote.outcome.value.effectiveAt : undefined; + const expectedEffectiveAt = dayjs(effectiveAt).format(dateTimeFormatISO); + + const rows = screen.getAllByTestId('vote-history-row'); + const targetRow = rows.find( + row => + within(row).getByTestId('vote-history-row-vote-takes-effect').textContent === + expectedEffectiveAt + ); + expect(targetRow).toBeDefined(); + + await user.click(within(targetRow!).getByTestId('vote-history-row-action-name')); + + const votingInformation = await screen.findByTestId('proposal-details-voting-information'); + + const voteTakesEffectDuration = within(votingInformation).getByTestId( + 'proposal-details-vote-takes-effect-duration' + ); + expect(voteTakesEffectDuration.textContent?.trim()).not.toBe('Threshold'); + + const voteTakesEffectIso = within(votingInformation).getByTestId( + 'proposal-details-vote-takes-effect-value' + ); + expect(voteTakesEffectIso).toHaveTextContent(expectedEffectiveAt); + }); + test('click on Details link to see Proposal Details (Action Required)', async () => { const user = userEvent.setup(); diff --git a/apps/sv/frontend/src/__tests__/mocks/constants.ts b/apps/sv/frontend/src/__tests__/mocks/constants.ts index 9abb24ca93..8db0757eb4 100644 --- a/apps/sv/frontend/src/__tests__/mocks/constants.ts +++ b/apps/sv/frontend/src/__tests__/mocks/constants.ts @@ -292,7 +292,8 @@ export const voteResultsDsoRules: ListDsoRulesVoteResultsResponse = { url: '', body: 'd', }, - trackingCid: null, + trackingCid: + '99f1a2cbcd5a2dc9ad2fb9d17fec183d75de19ca91f623cbd2eaaf634e8d7cb4b5ca101220b5c5c20442f608e151ca702e0c4f51341a338c5979c0547dfcc80f911061ca99', action: getDsoSetConfigAction({ new: '2200', base: '100' }), }, completedAt: '2024-10-01T22:10:01.253341Z', diff --git a/apps/sv/frontend/src/routes/voteRequestDetails.tsx b/apps/sv/frontend/src/routes/voteRequestDetails.tsx index 41ad05195f..1f920d54b0 100644 --- a/apps/sv/frontend/src/routes/voteRequestDetails.tsx +++ b/apps/sv/frontend/src/routes/voteRequestDetails.tsx @@ -111,13 +111,23 @@ export const VoteRequestDetails: React.FC = () => { previousRewardWeight !== undefined ? formatBasisPoints(previousRewardWeight) : ''; } + // For closed votes the outcome carries the actual effective time. Old vote + // requests (created before targetEffectiveAt existed) decode with + // targetEffectiveAt = None, so the request alone can't tell "effective at + // threshold" apart from "effective at expiry". + const voteTakesEffect = hasVoteRequest + ? request.targetEffectiveAt + ? dayjs(request.targetEffectiveAt).format(dateTimeFormatISO) + : 'Threshold' + : voteResult?.outcome.tag === 'VRO_Accepted' + ? dayjs(voteResult.outcome.value.effectiveAt).format(dateTimeFormatISO) + : dayjs(voteResult?.completedAt).format(dateTimeFormatISO); + const votingInformation: ProposalVotingInformation = { requester: request.requester, requesterIsYou: request.requester === svPartyId, votingThresholdDeadline: dayjs(request.voteBefore).format(dateTimeFormatISO), - voteTakesEffect: request.targetEffectiveAt - ? dayjs(request.targetEffectiveAt).format(dateTimeFormatISO) - : 'Threshold', + voteTakesEffect, status: hasVoteRequest ? 'In Progress' : getVoteResultStatus(voteResult?.outcome), }; From e97cdd965945870d78f17e53a554645cbecb189d Mon Sep 17 00:00:00 2001 From: krzysztofczyz-da Date: Fri, 31 Jul 2026 12:35:56 +0200 Subject: [PATCH 168/329] add per ip rate limits (#6631) Fixes #6599 This PR adds: per IP rate limits validation for the schema example usage: perIpLimits: maxTokens: 120 tokensPerFill: 120 fillInterval: 60s overrides: test: ips: - 192.68.78.50 maxTokens: 220 tokensPerFill: 220 fillInterval: 60s Note: the change is backwards-compatible. --- cluster/deployment/mock/config.yaml | 12 + cluster/expected/canton-network/expected.json | 54 ++++ cluster/expected/sv-runbook/expected.json | 27 ++ .../src/ratelimit/envoyRateLimiter.test.ts | 259 ++++++++++++++++++ .../common/src/ratelimit/envoyRateLimiter.ts | 210 ++++++++------ .../src/ratelimit/rateLimitSchema.test.ts | 128 +++++++++ .../common/src/ratelimit/rateLimitSchema.ts | 15 +- 7 files changed, 623 insertions(+), 82 deletions(-) create mode 100644 cluster/pulumi/common/src/ratelimit/envoyRateLimiter.test.ts create mode 100644 cluster/pulumi/common/src/ratelimit/rateLimitSchema.test.ts diff --git a/cluster/deployment/mock/config.yaml b/cluster/deployment/mock/config.yaml index a2e50a83bf..ca04106a6d 100644 --- a/cluster/deployment/mock/config.yaml +++ b/cluster/deployment/mock/config.yaml @@ -34,6 +34,18 @@ multiValidator: sv: synchronizer: skipInitialization: true + scan: + externalRateLimits: + rateLimits: + /registry/metadata/v1/info: + perIpLimits: + overrides: + test: + ips: + - 192.68.78.50 + maxTokens: 250 + tokensPerFill: 250 + fillInterval: 60s splitwell: maxDarVersion: '0.1.8' participantPruningSchedule: diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index 8abfa07fd2..35f2629ae5 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -1907,6 +1907,16 @@ "perIpLimits": { "fillInterval": "60s", "maxTokens": 120, + "overrides": { + "test": { + "fillInterval": "60s", + "ips": [ + "192.68.78.50" + ], + "maxTokens": 250, + "tokensPerFill": 250 + } + }, "tokensPerFill": 120 }, "tokensPerFill": 720, @@ -2286,6 +2296,16 @@ "perIpLimits": { "fillInterval": "60s", "maxTokens": 120, + "overrides": { + "test": { + "fillInterval": "60s", + "ips": [ + "192.68.78.50" + ], + "maxTokens": 250, + "tokensPerFill": 250 + } + }, "tokensPerFill": 120 }, "tokensPerFill": 720, @@ -3518,6 +3538,23 @@ "tokens_per_fill": 720 } }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip", + "value": "192.68.78.50" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 250, + "tokens_per_fill": 250 + } + }, { "entries": [ { @@ -5661,6 +5698,23 @@ "tokens_per_fill": 720 } }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip", + "value": "192.68.78.50" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 250, + "tokens_per_fill": 250 + } + }, { "entries": [ { diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index 4cac4780b2..17f2d0a2c7 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -1132,6 +1132,16 @@ "perIpLimits": { "fillInterval": "60s", "maxTokens": 120, + "overrides": { + "test": { + "fillInterval": "60s", + "ips": [ + "192.68.78.50" + ], + "maxTokens": 250, + "tokensPerFill": 250 + } + }, "tokensPerFill": 120 }, "tokensPerFill": 720, @@ -2316,6 +2326,23 @@ "tokens_per_fill": 720 } }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip", + "value": "192.68.78.50" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 250, + "tokens_per_fill": 250 + } + }, { "entries": [ { diff --git a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.test.ts b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.test.ts new file mode 100644 index 0000000000..17118ebd7a --- /dev/null +++ b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.test.ts @@ -0,0 +1,259 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { expect, jest, test } from '@jest/globals'; + +import { + buildRateLimitActions, + buildRateLimitDescriptors, + validateIpLimits, +} from './envoyRateLimiter'; + +jest.mock('@canton-network/splice-pulumi-common/src/config/envConfig', () => ({ + __esModule: true, + spliceEnvConfig: { + requireEnv() { + return 'dummy'; + }, + }, +})); + +const baseLimits = { + maxTokens: 720, + tokensPerFill: 720, + fillInterval: '60s', +}; + +const perIpLimits = { + maxTokens: 120, + tokensPerFill: 120, + fillInterval: '60s', +}; + +test('buildRateLimitDescriptors generates per-endpoint and generic per-IP descriptors', () => { + const descriptors = buildRateLimitDescriptors({ + '/registry/metadata/v1/info': { + name: 'registry-metadata-info', + type: 'limited', + ...baseLimits, + perIpLimits, + }, + }); + + expect(descriptors).toHaveLength(2); + expect(descriptors[0]).toEqual({ + entries: [{ key: 'header_match', value: 'registry-metadata-info' }], + token_bucket: { + max_tokens: 720, + tokens_per_fill: 720, + fill_interval: '60s', + }, + }); + expect(descriptors[1]).toEqual({ + entries: [{ key: 'header_match', value: 'registry-metadata-info' }, { key: 'client_ip' }], + token_bucket: { + max_tokens: 120, + tokens_per_fill: 120, + fill_interval: '60s', + }, + }); +}); + +test('buildRateLimitDescriptors emits named IP overrides before generic per-IP descriptor', () => { + const descriptors = buildRateLimitDescriptors({ + '/registry/metadata/v1/info': { + name: 'registry-metadata-info', + type: 'limited', + ...baseLimits, + perIpLimits: { + ...perIpLimits, + overrides: { + 'single-validator': { + ips: ['192.68.78.50'], + maxTokens: 220, + tokensPerFill: 220, + fillInterval: '60s', + }, + }, + }, + }, + }); + + expect(descriptors).toHaveLength(3); + expect(descriptors[0]).toEqual( + expect.objectContaining({ + entries: [{ key: 'header_match', value: 'registry-metadata-info' }], + }) + ); + expect(descriptors[1]).toEqual({ + entries: [ + { key: 'header_match', value: 'registry-metadata-info' }, + { key: 'client_ip', value: '192.68.78.50' }, + ], + token_bucket: { + max_tokens: 220, + tokens_per_fill: 220, + fill_interval: '60s', + }, + }); + expect(descriptors[2]).toEqual( + expect.objectContaining({ + entries: [{ key: 'header_match', value: 'registry-metadata-info' }, { key: 'client_ip' }], + }) + ); +}); + +test('buildRateLimitDescriptors emits descriptors for named overrides with multiple ips', () => { + const descriptors = buildRateLimitDescriptors({ + '/registry/metadata/v1/info': { + name: 'registry-metadata-info', + type: 'limited', + ...baseLimits, + perIpLimits: { + ...perIpLimits, + overrides: { + 'multi-validators': { + ips: ['192.68.78.51', '192.68.78.52'], + maxTokens: 250, + tokensPerFill: 250, + fillInterval: '60s', + }, + }, + }, + }, + }); + + expect(descriptors).toHaveLength(4); + expect(descriptors[1]).toEqual({ + entries: [ + { key: 'header_match', value: 'registry-metadata-info' }, + { key: 'client_ip', value: '192.68.78.51' }, + ], + token_bucket: { + max_tokens: 250, + tokens_per_fill: 250, + fill_interval: '60s', + }, + }); + expect(descriptors[2]).toEqual({ + entries: [ + { key: 'header_match', value: 'registry-metadata-info' }, + { key: 'client_ip', value: '192.68.78.52' }, + ], + token_bucket: { + max_tokens: 250, + tokens_per_fill: 250, + fill_interval: '60s', + }, + }); +}); + +test('buildRateLimitActions emits per-endpoint and per-IP actions', () => { + const actions = buildRateLimitActions({ + '/registry/metadata/v1/info': { + name: 'registry-metadata-info', + type: 'limited', + ...baseLimits, + perIpLimits, + }, + }); + + expect(actions).toHaveLength(2); + expect(actions[0]).toEqual({ + actions: [ + { + header_value_match: { + descriptor_value: 'registry-metadata-info', + expect_match: true, + headers: [ + { + name: ':path', + string_match: { + prefix: '/registry/metadata/v1/info', + ignore_case: true, + }, + }, + ], + }, + }, + ], + }); + expect(actions[1]).toEqual({ + actions: [ + { + header_value_match: { + descriptor_value: 'registry-metadata-info', + expect_match: true, + headers: [ + { + name: ':path', + string_match: { + prefix: '/registry/metadata/v1/info', + ignore_case: true, + }, + }, + ], + }, + }, + { + request_headers: { + descriptor_key: 'client_ip', + header_name: 'x-forwarded-for', + }, + }, + ], + }); +}); + +test('validateIpLimits throws on duplicate IP between two named overrides', () => { + expect(() => + validateIpLimits('/registry/metadata/v1/info', { + name: 'registry-metadata-info', + type: 'limited', + ...baseLimits, + perIpLimits: { + ...perIpLimits, + overrides: { + 'group-a': { + ips: ['192.68.78.50', '192.68.78.51'], + maxTokens: 250, + tokensPerFill: 250, + fillInterval: '60s', + }, + 'group-b': { + ips: ['192.68.78.51'], + maxTokens: 250, + tokensPerFill: 250, + fillInterval: '60s', + }, + }, + }, + }) + ).toThrow("192.68.78.51 (in override 'group-b')"); +}); + +test('validateIpLimits accepts unique IPs across named overrides', () => { + expect(() => + validateIpLimits('/registry/metadata/v1/info', { + name: 'registry-metadata-info', + type: 'limited', + ...baseLimits, + perIpLimits: { + ...perIpLimits, + overrides: { + 'single-validator': { + ips: ['192.68.78.50'], + maxTokens: 220, + tokensPerFill: 220, + fillInterval: '60s', + }, + 'multi-validators': { + ips: ['192.68.78.51', '192.68.78.52'], + maxTokens: 250, + tokensPerFill: 250, + fillInterval: '60s', + }, + }, + }, + }) + ).not.toThrow(); +}); diff --git a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts index 2d56ca7e8c..d2d8839868 100644 --- a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts +++ b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts @@ -11,9 +11,13 @@ interface Limits { fillInterval: string; } +interface PerIpLimits extends Limits { + overrides?: Record; +} + interface MatchedLimits extends Limits { type: 'limited'; - perIpLimits?: Limits; + perIpLimits?: PerIpLimits; } interface Banned { @@ -96,6 +100,29 @@ function validateEndpointCoverage( return { missing, orphaned }; } +export function validateIpLimits(pathPrefix: string, rateLimit: LocalLimit): void { + if (!rateLimit.perIpLimits) { + return; + } + + const seenIps = new Set(); + const duplicates: string[] = []; + + Object.entries(rateLimit.perIpLimits.overrides || {}).forEach(([overrideKey, override]) => { + override.ips.forEach(ip => { + if (seenIps.has(ip)) { + duplicates.push(`${ip} (in override '${overrideKey}')`); + } else { + seenIps.add(ip); + } + }); + }); + + if (duplicates.length > 0) { + throw new Error(`${pathPrefix}: duplicate IPs in per-IP rate limits: ${duplicates.join(', ')}`); + } +} + function validateEffectiveRateLimits( args: RateLimitEnvoyFilterArgs ): LocalLimits | undefined { @@ -145,7 +172,7 @@ function validateEffectiveRateLimits( } // Filter out banned and unlimited entries - return Object.fromEntries( + const effectiveRateLimits = Object.fromEntries( Object.entries(args.rateLimits || {}).filter( (ent): ent is [string, LocalLimit] => { // TODO (#4201): in banned case, implement actual banning with special short-circuit for whitelisted IPs @@ -156,6 +183,104 @@ function validateEffectiveRateLimits( } ) ); + + Object.entries(effectiveRateLimits).forEach(([pathPrefix, rateLimit]) => { + validateIpLimits(pathPrefix, rateLimit); + }); + + return effectiveRateLimits; +} + +export function buildRateLimitActions(effectiveRateLimits: LocalLimits): unknown[] { + return Object.entries(effectiveRateLimits).flatMap(([pathPrefix, rateLimit]) => { + const actions = []; + + // Action 1: generate the per-endpoint action + const baseAction = { + header_value_match: { + descriptor_value: rateLimit.name, + expect_match: true, + headers: [ + { + name: ':path', + string_match: { + prefix: pathPrefix, + ignore_case: true, + }, + }, + ], + }, + }; + + actions.push({ actions: [baseAction] }); + + // Action 2: generate the per-IP action if perIpLimits exists + if (rateLimit.perIpLimits) { + actions.push({ + actions: [ + baseAction, + { + request_headers: { + descriptor_key: clientIpEntryKey, + header_name: 'x-forwarded-for', + }, + }, + ], + }); + } + + return actions; + }); +} + +export function buildRateLimitDescriptors( + effectiveRateLimits: LocalLimits +): unknown[] { + return Object.values(effectiveRateLimits).flatMap(rateLimit => { + const descs = []; + + // per-endpoint bucket + descs.push({ + entries: [{ key: 'header_match', value: rateLimit.name }], + token_bucket: { + max_tokens: rateLimit.maxTokens, + tokens_per_fill: rateLimit.tokensPerFill, + fill_interval: rateLimit.fillInterval, + }, + }); + + // generate the per-IP buckets if configured + if (rateLimit.perIpLimits) { + // IP-specific overrides first, so they take precedence over the generic per-IP bucket + Object.entries(rateLimit.perIpLimits.overrides || {}).forEach(([, override]) => { + override.ips.forEach(ip => { + descs.push({ + entries: [ + { key: 'header_match', value: rateLimit.name }, + { key: clientIpEntryKey, value: ip }, + ], + token_bucket: { + max_tokens: override.maxTokens, + tokens_per_fill: override.tokensPerFill, + fill_interval: override.fillInterval, + }, + }); + }); + }); + + // Generic per-IP fallback last + descs.push({ + entries: [{ key: 'header_match', value: rateLimit.name }, { key: clientIpEntryKey }], + token_bucket: { + max_tokens: rateLimit.perIpLimits.maxTokens, + tokens_per_fill: rateLimit.perIpLimits.tokensPerFill, + fill_interval: rateLimit.perIpLimits.fillInterval, + }, + }); + } + + return descs; + }); } export class RateLimitEnvoyFilter extends pulumi.ComponentResource { @@ -169,46 +294,7 @@ export class RateLimitEnvoyFilter extends pulumi.ComponentResource { super('splice:RateLimit', `splice-${args.namespace}-${name}`, args, opts); const effectiveRateLimits = validateEffectiveRateLimits(args); - const rateLimitActions: unknown[] = - Object.entries(effectiveRateLimits || {}).flatMap(([pathPrefix, rateLimit]) => { - const actions = []; - - // Action 1: generate the per-endpoint action - const baseAction = { - header_value_match: { - descriptor_value: rateLimit.name, - expect_match: true, - headers: [ - { - name: ':path', - string_match: { - prefix: pathPrefix, - ignore_case: true, - }, - }, - ], - }, - }; - - actions.push({ actions: [baseAction] }); - - // Action 2: generate the per-IP action if perIpLimits exists - if (rateLimit.perIpLimits) { - actions.push({ - actions: [ - baseAction, - { - request_headers: { - descriptor_key: 'client_ip', - header_name: 'x-forwarded-for', - }, - }, - ], - }); - } - - return actions; - }) || []; + const rateLimitActions = buildRateLimitActions(effectiveRateLimits || {}); const enableEnvoyRateLimitMetricsAnnotation = ` proxyStatsMatcher: @@ -317,45 +403,7 @@ proxyStatsMatcher: // simplified descriptors by combining with actions and requiring all the tokens of an action to be set // a descriptor in practice is a subset of tags from a rate limit // but important to note that for each rate limit only one descriptor can match, if multiple descriptors match, the first one is used - descriptors: Object.values(effectiveRateLimits || {}).flatMap(rateLimit => { - const descs = []; - - // per-endpoint bucket - - descs.push({ - entries: [ - { - key: 'header_match', - value: rateLimit.name, - }, - ], - token_bucket: { - max_tokens: rateLimit.maxTokens, - tokens_per_fill: rateLimit.tokensPerFill, - fill_interval: rateLimit.fillInterval, - }, - }); - - // generate the per-IP bucket if configured - if (rateLimit.perIpLimits) { - descs.push({ - entries: [ - { - key: 'header_match', - value: rateLimit.name, - }, - { key: clientIpEntryKey }, - ], - token_bucket: { - max_tokens: rateLimit.perIpLimits.maxTokens, - tokens_per_fill: rateLimit.perIpLimits.tokensPerFill, - fill_interval: rateLimit.perIpLimits.fillInterval, - }, - }); - } - - return descs; - }), + descriptors: buildRateLimitDescriptors(effectiveRateLimits || {}), }, }, }, diff --git a/cluster/pulumi/common/src/ratelimit/rateLimitSchema.test.ts b/cluster/pulumi/common/src/ratelimit/rateLimitSchema.test.ts new file mode 100644 index 0000000000..2ca5ae6fdb --- /dev/null +++ b/cluster/pulumi/common/src/ratelimit/rateLimitSchema.test.ts @@ -0,0 +1,128 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { expect, test } from '@jest/globals'; + +import { RateLimitSchema } from './rateLimitSchema'; + +const validConfig = { + globalLimits: { + maxTokens: 1000, + tokensPerFill: 1000, + fillInterval: '60s', + }, + rateLimits: { + '/registry/metadata/v1/info': { + name: 'registry-metadata-info', + type: 'limited', + maxTokens: 720, + tokensPerFill: 720, + fillInterval: '60s', + perIpLimits: { + maxTokens: 120, + tokensPerFill: 120, + fillInterval: '60s', + }, + }, + }, +}; + +test('RateLimitSchema accepts config without overrides', () => { + expect(() => RateLimitSchema.parse(validConfig)).not.toThrow(); +}); + +test('RateLimitSchema accepts named overrides with ips', () => { + const config = { + ...validConfig, + rateLimits: { + '/registry/metadata/v1/info': { + ...validConfig.rateLimits['/registry/metadata/v1/info'], + perIpLimits: { + ...validConfig.rateLimits['/registry/metadata/v1/info'].perIpLimits, + overrides: { + 'single-validator': { + ips: ['192.68.78.50'], + maxTokens: 220, + tokensPerFill: 220, + fillInterval: '60s', + }, + 'multi-validators': { + ips: ['192.68.78.51', '192.68.78.52'], + maxTokens: 250, + tokensPerFill: 250, + fillInterval: '60s', + }, + }, + }, + }, + }, + }; + expect(() => RateLimitSchema.parse(config)).not.toThrow(); +}); + +test('RateLimitSchema rejects override without ips', () => { + const config = { + ...validConfig, + rateLimits: { + '/registry/metadata/v1/info': { + ...validConfig.rateLimits['/registry/metadata/v1/info'], + perIpLimits: { + ...validConfig.rateLimits['/registry/metadata/v1/info'].perIpLimits, + overrides: { + '192.68.78.50': { + maxTokens: 220, + tokensPerFill: 220, + fillInterval: '60s', + }, + }, + }, + }, + }, + }; + expect(() => RateLimitSchema.parse(config)).toThrow(); +}); + +test('RateLimitSchema rejects non-IPv4 addresses in ips', () => { + const config = { + ...validConfig, + rateLimits: { + '/registry/metadata/v1/info': { + ...validConfig.rateLimits['/registry/metadata/v1/info'], + perIpLimits: { + ...validConfig.rateLimits['/registry/metadata/v1/info'].perIpLimits, + overrides: { + 'multi-validators': { + ips: ['2001:db8::1'], + maxTokens: 250, + tokensPerFill: 250, + fillInterval: '60s', + }, + }, + }, + }, + }, + }; + expect(() => RateLimitSchema.parse(config)).toThrow(); +}); + +test('RateLimitSchema rejects empty override ips array', () => { + const config = { + ...validConfig, + rateLimits: { + '/registry/metadata/v1/info': { + ...validConfig.rateLimits['/registry/metadata/v1/info'], + perIpLimits: { + ...validConfig.rateLimits['/registry/metadata/v1/info'].perIpLimits, + overrides: { + 'empty-group': { + ips: [], + maxTokens: 250, + tokensPerFill: 250, + fillInterval: '60s', + }, + }, + }, + }, + }, + }; + expect(() => RateLimitSchema.parse(config)).toThrow(); +}); diff --git a/cluster/pulumi/common/src/ratelimit/rateLimitSchema.ts b/cluster/pulumi/common/src/ratelimit/rateLimitSchema.ts index 39fbf163d5..a08c3e78d3 100644 --- a/cluster/pulumi/common/src/ratelimit/rateLimitSchema.ts +++ b/cluster/pulumi/common/src/ratelimit/rateLimitSchema.ts @@ -1,5 +1,6 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import { isIP } from 'net'; import { z } from 'zod'; export const BucketRateLimitSchema = z.object({ @@ -8,9 +9,21 @@ export const BucketRateLimitSchema = z.object({ fillInterval: z.string(), }); +const Ipv4AddressSchema = z.string().refine(ip => isIP(ip) === 4, { + message: 'Expected IPv4 address', +}); + +const OverrideSchema = BucketRateLimitSchema.extend({ + ips: z.array(Ipv4AddressSchema).min(1), +}); + +export const PerIpLimitsSchema = BucketRateLimitSchema.extend({ + overrides: z.record(z.string().min(1), OverrideSchema).optional(), +}); + const BucketMatchedRateLimitSchema = BucketRateLimitSchema.extend({ type: z.literal('limited'), - perIpLimits: BucketRateLimitSchema.optional(), + perIpLimits: PerIpLimitsSchema.optional(), }); export const BannedSchema = z.object({ From d08cf34d777e960827a61e49f44146d24c2eb7b5 Mon Sep 17 00:00:00 2001 From: Puneet Bharti Date: Fri, 31 Jul 2026 15:19:05 +0200 Subject: [PATCH 169/329] SV UI Redesign Look & Feel: Component: Layout (#6383) Signed-off-by: Puneet Bharti --- .../tests/SvFrontendIntegrationTest.scala | 2 +- apps/sv/frontend/src/App.tsx | 6 +- apps/sv/frontend/src/__tests__/sv.test.tsx | 19 +--- apps/sv/frontend/src/components/Layout.tsx | 107 ++++++++---------- .../src/components/layout/LogoutButton.tsx | 68 +++++++++++ .../src/components/layout/LogoutIcon.tsx | 33 ++++++ .../components/layout/NavAttentionIcon.tsx | 31 +++++ .../src/components/layout/NavCountBadge.tsx | 46 ++++++++ .../src/components/layout/SvNavLink.tsx | 73 ++++++++++++ .../components/layout/SvNavigationShell.tsx | 41 +++++++ .../src/components/layout/SvTopNav.tsx | 83 ++++++++++++++ .../frontend/src/routes/delegateElection.tsx | 14 +++ apps/sv/frontend/src/theme/tokens.ts | 63 +++++++++++ 13 files changed, 510 insertions(+), 76 deletions(-) create mode 100644 apps/sv/frontend/src/components/layout/LogoutButton.tsx create mode 100644 apps/sv/frontend/src/components/layout/LogoutIcon.tsx create mode 100644 apps/sv/frontend/src/components/layout/NavAttentionIcon.tsx create mode 100644 apps/sv/frontend/src/components/layout/NavCountBadge.tsx create mode 100644 apps/sv/frontend/src/components/layout/SvNavLink.tsx create mode 100644 apps/sv/frontend/src/components/layout/SvNavigationShell.tsx create mode 100644 apps/sv/frontend/src/components/layout/SvTopNav.tsx create mode 100644 apps/sv/frontend/src/routes/delegateElection.tsx create mode 100644 apps/sv/frontend/src/theme/tokens.ts diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala index db2bf98c48..1e39e21e7b 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala @@ -63,7 +63,7 @@ class SvFrontendIntegrationTest }, )( "logged in in the sv ui", - _ => find(id("app-title")).value.text should matchText("SUPER VALIDATOR OPERATIONS"), + _ => find(id("app-title")).value.text should matchText("Supervalidator Operations"), ) } } diff --git a/apps/sv/frontend/src/App.tsx b/apps/sv/frontend/src/App.tsx index 2ec65affcb..812a5385cf 100644 --- a/apps/sv/frontend/src/App.tsx +++ b/apps/sv/frontend/src/App.tsx @@ -39,6 +39,7 @@ import { useConfigPollInterval, useSvConfig } from './utils'; import { Governance } from './routes/governance'; import { VoteRequestDetails } from './routes/voteRequestDetails'; import { CreateProposal } from './routes/createProposal'; +import DelegateElection from './routes/delegateElection'; const Providers: React.FC = ({ children }) => { const config = useSvConfig(); @@ -95,6 +96,7 @@ const App: React.FC = () => { } /> } /> } /> + } /> } /> } /> @@ -111,8 +113,8 @@ const App: React.FC = () => { - Super Validator Operations - + Supervalidator Operations + diff --git a/apps/sv/frontend/src/__tests__/sv.test.tsx b/apps/sv/frontend/src/__tests__/sv.test.tsx index dc1f618048..fa6a398bc1 100644 --- a/apps/sv/frontend/src/__tests__/sv.test.tsx +++ b/apps/sv/frontend/src/__tests__/sv.test.tsx @@ -41,19 +41,12 @@ describe('SV user can', () => { expect(await screen.findAllByDisplayValue(svPartyId)).toBeDefined(); }); - test('can see the network name banner', async () => { - userEvent.setup(); - render(); - - await screen.findByText('You are on ScratchNet'); - }); - test('browse to the validator onboarding tab', async () => { const user = userEvent.setup(); render(); - expect(await screen.findByText('Validator Onboarding')).toBeDefined(); - await user.click(screen.getByText('Validator Onboarding')); + expect(await screen.findByText('Validators')).toBeDefined(); + await user.click(screen.getByText('Validators')); expect(await screen.findByText('Validator Onboarding Secrets')).toBeDefined(); }); @@ -62,8 +55,8 @@ describe('SV user can', () => { const user = userEvent.setup(); render(); - expect(await screen.findByText('Validator Onboarding')).toBeDefined(); - await user.click(screen.getByText('Validator Onboarding')); + expect(await screen.findByText('Validators')).toBeDefined(); + await user.click(screen.getByText('Validators')); const partyHintInput = screen.getByTestId('create-party-hint'); await user.type(partyHintInput, 'wrong-input'); @@ -306,8 +299,8 @@ describe('An AddFutureAmuletConfigSchedule request', () => { const user = userEvent.setup(); render(); - expect(await screen.findByText('Validator Onboarding')).toBeDefined(); - await user.click(screen.getByText('Validator Onboarding')); + expect(await screen.findByText('Validators')).toBeDefined(); + await user.click(screen.getByText('Validators')); expect(await screen.findByText('Validator Licenses')).toBeDefined(); diff --git a/apps/sv/frontend/src/components/Layout.tsx b/apps/sv/frontend/src/components/Layout.tsx index 80c3facaea..caee956cc7 100644 --- a/apps/sv/frontend/src/components/Layout.tsx +++ b/apps/sv/frontend/src/components/Layout.tsx @@ -1,34 +1,48 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import * as React from 'react'; -import { - Header, - Loading, - useUserState, - useVotesHooks, -} from '@canton-network/splice-common-frontend'; +import { Loading, useUserState, useVotesHooks } from '@canton-network/splice-common-frontend'; -import { Logout } from '@mui/icons-material'; -import { Box, Button, Divider, GlobalStyles, Stack, Typography } from '@mui/material'; -import Container from '@mui/material/Container'; -import Link from '@mui/material/Link'; +import { Box, Container, GlobalStyles } from '@mui/material'; +import { useLocation } from 'react-router'; import { partyIdScrollGlobalStyles } from './beta/identifierStyles'; import PartyIdScrollTracks from './PartyIdScrollTracks'; - +import SvNavigationShell from './layout/SvNavigationShell'; +import { SvNavLinkItem } from './layout/SvNavLink'; import { useFeatureSupport } from '../contexts/SvContext'; -import { useNetworkInstanceName } from '../hooks/index'; +import { CONTENT_MAX_WIDTH, layoutTokens, PAGE_PX } from '../theme/tokens'; import { useSvConfig } from '../utils'; interface LayoutProps { children: React.ReactNode; } -const Layout: React.FC = (props: LayoutProps) => { +const pathnameToPageName = (pathname: string, amuletName: string): string => { + if (pathname.startsWith('/governance')) { + return 'Governance'; + } + if (pathname.startsWith('/validator-onboarding')) { + return 'Validators'; + } + if (pathname.startsWith('/amulet-price')) { + return `${amuletName} Price`; + } + return 'Global Synchronizer Information'; +}; + +/** Figma content-width 1583px centered — nav uses full width inside Navigation shell */ +const contentShellSx = { + maxWidth: CONTENT_MAX_WIDTH, + mx: 'auto', + px: PAGE_PX, + width: '100%', +}; + +const Layout: React.FC = ({ children }) => { const config = useSvConfig(); const { logout } = useUserState(); - const networkInstanceName = useNetworkInstanceName(); - const networkInstanceNameColor = `colors.${networkInstanceName?.toLowerCase()}`; + const location = useLocation(); const featureSupport = useFeatureSupport(); const votesHooks = useVotesHooks(); @@ -43,57 +57,30 @@ const Layout: React.FC = (props: LayoutProps) => { return ; } - const navLinks = [ - { name: 'Information', path: 'dso' }, - { name: 'Validator Onboarding', path: 'validator-onboarding' }, - { name: `${config.spliceInstanceNames.amuletName} Price`, path: 'amulet-price' }, - { name: 'Governance', path: 'governance', badgeCount: actionsPending?.length }, + const navLinks: SvNavLinkItem[] = [ + { name: 'Global Synchronizer Information', path: '/dso' }, + { + name: 'Governance', + path: '/governance', + end: false, + badgeCount: actionsPending?.length, + }, + { name: `${config.spliceInstanceNames.amuletName} Price`, path: '/amulet-price' }, + { name: 'Validators', path: '/validator-onboarding' }, ]; + const pageName = pathnameToPageName(location.pathname, config.spliceInstanceNames.amuletName); + return ( - + - {networkInstanceName === undefined ? ( - <> - ) : ( - - - You are on {networkInstanceName} - - - )} - -
- - - - -
-
+ - - {props.children} + + + {children} + ); diff --git a/apps/sv/frontend/src/components/layout/LogoutButton.tsx b/apps/sv/frontend/src/components/layout/LogoutButton.tsx new file mode 100644 index 0000000000..fe4912914a --- /dev/null +++ b/apps/sv/frontend/src/components/layout/LogoutButton.tsx @@ -0,0 +1,68 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; + +import { Box, Typography } from '@mui/material'; + +import { layoutTokens, navItemTypography, NAV_PILL_PX } from '../../theme/tokens'; +import LogoutIcon from './LogoutIcon'; + +interface LogoutButtonProps { + onLogout: () => void; +} + +/** + * Figma Dev Mode — content box 66x17, 10px padding on each side, gap-2.5 (10px) + * between icon and label. Plain `Box component="button"` (matching `SvNavLink`'s + * pattern) instead of MUI `Button` — MUI's own min-height/padding/ripple defaults + * previously inflated this to ~89.78x38 despite the padding value being correct. + */ +const LogoutButton: React.FC = ({ onLogout }) => ( + + + + Logout + + +); + +export default LogoutButton; diff --git a/apps/sv/frontend/src/components/layout/LogoutIcon.tsx b/apps/sv/frontend/src/components/layout/LogoutIcon.tsx new file mode 100644 index 0000000000..d979051d7c --- /dev/null +++ b/apps/sv/frontend/src/components/layout/LogoutIcon.tsx @@ -0,0 +1,33 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; + +import { Box } from '@mui/material'; + +/** + * Figma logout glyph — door bracket (open right) with an arrow exiting through it. + * Path data reconstructed from the true Figma vector export (CF-design-system/svgs), + * not the lossy Tailwind HTML export — the div-based export flattens this icon into + * two filled bars that don't resemble a logout glyph at all. + */ +const LogoutIcon: React.FC = () => ( + + + + + + + +); + +export default LogoutIcon; diff --git a/apps/sv/frontend/src/components/layout/NavAttentionIcon.tsx b/apps/sv/frontend/src/components/layout/NavAttentionIcon.tsx new file mode 100644 index 0000000000..960fce26b7 --- /dev/null +++ b/apps/sv/frontend/src/components/layout/NavAttentionIcon.tsx @@ -0,0 +1,31 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; + +import { Box } from '@mui/material'; + +/** Figma nav warning icon for Delegate Election (yellow triangle + exclamation cutout). */ +const NavAttentionIcon: React.FC = () => ( + + + + + +); + +export default NavAttentionIcon; diff --git a/apps/sv/frontend/src/components/layout/NavCountBadge.tsx b/apps/sv/frontend/src/components/layout/NavCountBadge.tsx new file mode 100644 index 0000000000..9581cb8fb6 --- /dev/null +++ b/apps/sv/frontend/src/components/layout/NavCountBadge.tsx @@ -0,0 +1,46 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; + +import { Box } from '@mui/material'; + +import { layoutTokens } from '../../theme/tokens'; + +interface NavCountBadgeProps { + count: number; + id?: string; +} + +/** Figma nav notification badge — size-5 bg-red-400 rounded-3xl, text-xs Inter. */ +const NavCountBadge: React.FC = ({ count, id }) => { + if (count <= 0) { + return null; + } + + return ( + 9 ? 0.5 : 0, + borderRadius: '24px', + bgcolor: layoutTokens.notificationBadge, + color: 'common.white', + fontFamily: '"Inter", sans-serif', + fontSize: '0.75rem', + fontWeight: 400, + lineHeight: 1, + }} + > + {count} + + ); +}; + +export default NavCountBadge; diff --git a/apps/sv/frontend/src/components/layout/SvNavLink.tsx b/apps/sv/frontend/src/components/layout/SvNavLink.tsx new file mode 100644 index 0000000000..6bb2538f29 --- /dev/null +++ b/apps/sv/frontend/src/components/layout/SvNavLink.tsx @@ -0,0 +1,73 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; +import { NavLink } from 'react-router'; + +import { Box } from '@mui/material'; + +import { layoutTokens, navItemTypography, NAV_PILL_PX } from '../../theme/tokens'; +import NavAttentionIcon from './NavAttentionIcon'; +import NavCountBadge from './NavCountBadge'; + +export interface SvNavLinkItem { + name: string; + path: string; + badgeCount?: number; + hasAlert?: boolean; + /** When false, nav stays active on nested paths (e.g. /governance/proposals). */ + end?: boolean; +} + +interface SvNavLinkProps { + link: SvNavLinkItem; +} + +/** Figma: badge accessory uses gap-1.5 (6px), alert-icon accessory uses gap-2.5 (10px). */ +const navLinkSx = (isActive: boolean, accessoryGap: string) => ({ + display: 'inline-flex', + alignItems: 'center', + gap: accessoryGap, + p: NAV_PILL_PX, + borderRadius: '20px', + textDecoration: 'none', + whiteSpace: 'nowrap', + color: layoutTokens.lightText, + fontFamily: layoutTokens.fontUi, + fontSize: '0.875rem', + fontWeight: 700, + ...navItemTypography, + border: '2px solid transparent', + boxSizing: 'border-box', + ...(isActive && { borderColor: layoutTokens.navActiveOutline }), + '&:focus': { outline: 'none' }, + '&:focus-visible': { + outline: '2px solid', + outlineColor: layoutTokens.navActiveOutline, + outlineOffset: '2px', + }, +}); + +const SvNavLink: React.FC = ({ link }) => ( + + {({ isActive }) => ( + + {link.name} + {link.badgeCount !== undefined && link.badgeCount > 0 ? ( + + ) : null} + {link.hasAlert ? : null} + + )} + +); + +export default SvNavLink; diff --git a/apps/sv/frontend/src/components/layout/SvNavigationShell.tsx b/apps/sv/frontend/src/components/layout/SvNavigationShell.tsx new file mode 100644 index 0000000000..0d17b031ef --- /dev/null +++ b/apps/sv/frontend/src/components/layout/SvNavigationShell.tsx @@ -0,0 +1,41 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; + +import { Box } from '@mui/material'; + +import { HEADER_PB, HEADER_PT, layoutTokens, PAGE_PX } from '../../theme/tokens'; +import SvTopNav from './SvTopNav'; +import { SvNavLinkItem } from './SvNavLink'; + +interface SvNavigationShellProps { + navLinks: SvNavLinkItem[]; + onLogout: () => void; + pageName: string; +} + +/** + * Figma "Navigation" component — nav row (network banner to be restored later). + * Dev Mode: padding-bottom 64px, background #272727. + * `HEADER_PT` is temporary breathing room until the banner returns. + */ +const SvNavigationShell: React.FC = ({ navLinks, onLogout, pageName }) => ( + + + + + +); + +export default SvNavigationShell; diff --git a/apps/sv/frontend/src/components/layout/SvTopNav.tsx b/apps/sv/frontend/src/components/layout/SvTopNav.tsx new file mode 100644 index 0000000000..d9ec57f9ac --- /dev/null +++ b/apps/sv/frontend/src/components/layout/SvTopNav.tsx @@ -0,0 +1,83 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; + +import { Box, Stack, Typography } from '@mui/material'; + +import { + BRAND_TITLE, + layoutTokens, + NAV_BRAND_GAP, + NAV_GAP, + NAV_PILL_PX, + NAV_ROW_MIN_HEIGHT, +} from '../../theme/tokens'; +import LogoutButton from './LogoutButton'; +import SvNavLink, { SvNavLinkItem } from './SvNavLink'; + +interface SvTopNavProps { + navLinks: SvNavLinkItem[]; + onLogout: () => void; +} + +/** + * Figma nav row (Frame11): brand pinned left, a fixed 145px gap (`NAV_BRAND_GAP`) + * to the nav cluster, then a flexible spacer that pushes logout to the pinned + * right edge. The fixed left gap matches the Dev Mode measurement exactly; the + * flexible right gap lets logout track the row's right edge at any viewport width. + */ +const SvTopNav: React.FC = ({ navLinks, onLogout }) => ( + + + + {BRAND_TITLE} + + + + + + + {navLinks.map(link => ( + + ))} + + + + + + +); + +export default SvTopNav; diff --git a/apps/sv/frontend/src/routes/delegateElection.tsx b/apps/sv/frontend/src/routes/delegateElection.tsx new file mode 100644 index 0000000000..5b1e584b2a --- /dev/null +++ b/apps/sv/frontend/src/routes/delegateElection.tsx @@ -0,0 +1,14 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; + +import { Typography } from '@mui/material'; + +/** Placeholder for #2594 layout nav — page content is a separate issue. */ +const DelegateElection: React.FC = () => ( + + Delegate Election + +); + +export default DelegateElection; diff --git a/apps/sv/frontend/src/theme/tokens.ts b/apps/sv/frontend/src/theme/tokens.ts new file mode 100644 index 0000000000..7f3dcee073 --- /dev/null +++ b/apps/sv/frontend/src/theme/tokens.ts @@ -0,0 +1,63 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** Figma tokens from CF-design-system (tokens.md + delegate-election-2 Dev Mode). */ +export const layoutTokens = { + /** Figma surface-page — bg-neutral-800 */ + page: '#262626', + /** Figma Dev Mode Background-lighter on Navigation component */ + navBackground: '#272727', + /** Figma Dev Mode --Light-text — brand wordmark and nav labels */ + lightText: '#E2E2E2', + /** Figma red00 — Governance count badge */ + notificationBadge: '#FD8575', + /** Figma Purple (Navigation) — active nav pill border */ + navActiveOutline: '#875CFF', + navAttention: '#F3FF97', + /** Brand wordmark — Inter until a production-licensed Termina is available */ + fontBrand: '"Inter", sans-serif', + fontUi: '"Inter", sans-serif', +} as const; + +/** Figma Dev Mode — nav row horizontal padding 50px */ +export const PAGE_PX = '50px'; + +/** Figma p-2.5 — 10px padding on brand box and nav pills */ +export const NAV_PILL_PX = '10px'; + +/** Figma content max width (nav row is full width; content uses this) */ +export const CONTENT_MAX_WIDTH = 1583; + +/** + * Temporary top padding while NetworkBanner is omitted (matches old 50px banner height). + * Remove when the banner is restored (e.g. with #6087). + */ +export const HEADER_PT = '50px'; + +/** Figma Dev Mode — 64px space below nav row, present on every page */ +export const HEADER_PB = 8; + +/** Figma Dev Mode — fixed 145px gap between brand wordmark and nav cluster. */ +export const NAV_BRAND_GAP = '145px'; + +/** Figma Dev Mode — fixed 60px between nav pills (not responsive). */ +export const NAV_GAP = '60px'; + +/** Figma Dev Mode — nav row height 44px */ +export const NAV_ROW_MIN_HEIGHT = 44; + +/** + * Figma Dev Mode — Inter nav/logout typography (letter spacing: 0px, 140% line-height). + * Without an explicit `letterSpacing` reset, these Box/Typography elements inherit + * MUI's default body1 letter-spacing (0.00938em) from an ancestor, rendering as a + * ~0.13-0.15px leak that's invisible in a screenshot but measurable via computed + * styles and doesn't match Figma's 0px spec. `lineHeight: 'normal'` also falls back + * to Inter's own metrics rather than the explicit 140% Figma spec, so it's pinned here. + */ +export const navItemTypography = { + fontFeatureSettings: "'liga' off, 'clig' off", + lineHeight: 1.4, + letterSpacing: 0, +} as const; + +export const BRAND_TITLE = 'Supervalidator Operations'; From fca2076ec43a7f124687ef97b38ca5ea547dcb80 Mon Sep 17 00:00:00 2001 From: Raymond Roestenburg <98821776+ray-roestenburg-da@users.noreply.github.com> Date: Fri, 31 Jul 2026 16:51:23 +0200 Subject: [PATCH 170/329] =?UTF-8?q?[ci]=20Recover=20accepted=20DUPLICATE?= =?UTF-8?q?=5FCOMMAND=20in=20SpliceLedgerConnection=20ded=E2=80=A6=20(#660?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Raymond Roestenburg --- ...TokenStandardTransferIntegrationTest.scala | 34 +++++---- ...kenStandardV2TransferIntegrationTest.scala | 34 +++++---- ...lletBuyTrafficRequestIntegrationTest.scala | 6 ++ .../tests/WalletIntegrationTest.scala | 15 ++-- .../tests/WalletPaymentIntegrationTest.scala | 16 ++-- .../splice/automation/PollingTrigger.scala | 2 + .../splice/environment/RetryFor.scala | 7 ++ .../splice/environment/RetryProvider.scala | 17 +++++ .../environment/SpliceLedgerConnection.scala | 47 +++++++++++- .../environment/ledger/api/LedgerClient.scala | 73 ++++++++++++++++++- .../sv/admin/http/HttpSvPublicHandler.scala | 1 + .../http/HttpValidatorAdminHandler.scala | 1 + .../wallet/admin/http/HttpWalletHandler.scala | 9 +++ .../admin/http/HttpWalletHandlerUtil.scala | 1 + .../wallet/treasury/TreasuryService.scala | 15 +++- docs/src/release_notes_upcoming.rst | 8 ++ 16 files changed, 236 insertions(+), 50 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardTransferIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardTransferIntegrationTest.scala index 94e7fe219c..fb8da38f96 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardTransferIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardTransferIntegrationTest.scala @@ -1,7 +1,6 @@ package org.lfdecentralizedtrust.splice.integration.tests import com.digitalasset.canton.concurrent.Threading -import com.digitalasset.canton.console.CommandFailure import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.{HasActorSystem, HasExecutionContext} import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.metadatav1 @@ -428,25 +427,28 @@ class TokenStandardTransferIntegrationTest trackingId, ) - assertThrows[CommandFailure]( - loggerFactory.assertLogs( - aliceWalletClient.createTokenStandardTransfer( - bobUserParty, - 10, - "not ok, resubmitted same trackingId so should be rejected", - expiration, - trackingId, - ), - _.errorMessage should include("Command submission already exists"), - ) + val createdCid = created.output match { + case members.TransferInstructionPending(value) => value.transferInstructionCid + case x => fail(s"Expected pending transfer, got $x") + } + + // Resubmitting the same trackingId is deduplicated idempotently: the accepted duplicate is + // recovered centrally and returns the original result instead of failing. + val resubmitted = aliceWalletClient.createTokenStandardTransfer( + bobUserParty, + 10, + "resubmitted with the same trackingId", + expiration, + trackingId, ) + inside(resubmitted.output) { case members.TransferInstructionPending(value) => + value.transferInstructionCid shouldBe createdCid + } + // Still exactly one transfer instruction, i.e. no duplicate was created. eventually() { inside(aliceWalletClient.listTokenStandardTransfers()) { case Seq(t) => - t.contractId.contractId should be(created.output match { - case members.TransferInstructionPending(value) => value.transferInstructionCid - case x => fail(s"Expected pending transfer, got $x") - }) + t.contractId.contractId should be(createdCid) } } } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2TransferIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2TransferIntegrationTest.scala index 385e16d5c7..574af2078b 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2TransferIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardV2TransferIntegrationTest.scala @@ -1,7 +1,6 @@ package org.lfdecentralizedtrust.splice.integration.tests import com.digitalasset.canton.concurrent.Threading -import com.digitalasset.canton.console.CommandFailure import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.{HasActorSystem, HasExecutionContext} import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.transferinstructionv2.transferinstructionresult_output.TransferInstructionResult_Completed @@ -381,25 +380,28 @@ class TokenStandardV2TransferIntegrationTest trackingId, ) - assertThrows[CommandFailure]( - loggerFactory.assertLogs( - aliceWalletClient.createTokenStandardTransferV2( - bobUserParty, - 10, - "not ok, resubmitted same trackingId so should be rejected", - expiration, - trackingId, - ), - _.errorMessage should include("Command submission already exists"), - ) + val createdCid = created.output match { + case members.TransferInstructionPending(value) => value.transferInstructionCid + case x => fail(s"Expected pending transfer, got $x") + } + + // Resubmitting the same trackingId is deduplicated idempotently: the accepted duplicate is + // recovered centrally and returns the original result instead of failing. + val resubmitted = aliceWalletClient.createTokenStandardTransferV2( + bobUserParty, + 10, + "resubmitted with the same trackingId", + expiration, + trackingId, ) + inside(resubmitted.output) { case members.TransferInstructionPending(value) => + value.transferInstructionCid shouldBe createdCid + } + // Still exactly one transfer instruction, i.e. no duplicate was created. eventually() { inside(aliceWalletClient.listTokenStandardTransfers()) { case Seq(t) => - t.contractId.contractId should be(created.output match { - case members.TransferInstructionPending(value) => value.transferInstructionCid - case x => fail(s"Expected pending transfer, got $x") - }) + t.contractId.contractId should be(createdCid) } } } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletBuyTrafficRequestIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletBuyTrafficRequestIntegrationTest.scala index b05a07e46e..412a5d8cb9 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletBuyTrafficRequestIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletBuyTrafficRequestIntegrationTest.scala @@ -307,6 +307,12 @@ class WalletBuyTrafficRequestIntegrationTest )) ), ) + // All 10 calls share the same tracking id, but the dedup recovery does not apply here: + // reading back an accepted duplicate needs a completed submission, which concurrent + // submissions do not have. So one call wins and the other 9 are rejected, mostly with + // SUBMISSION_ALREADY_IN_FLIGHT from the participant and some with 429 from the rate + // limiter. Duplicates of an already-completed submission are covered by the sequential + // dedup tests. successes shouldBe 1 } } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletIntegrationTest.scala index 45d888648a..3cc5e4bfc3 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletIntegrationTest.scala @@ -91,10 +91,8 @@ class WalletIntegrationTest "tap deduplicates" in { implicit env => onboardWalletUser(aliceWalletClient, aliceValidatorBackend) aliceWalletClient.tap(50.0, Some("dedup-test")) - assertThrowsAndLogsCommandFailures( - aliceWalletClient.tap(50.0, Some("dedup-test")), - _.errorMessage should include("409 Conflict"), - ) + // Duplicate tap with the same command id returns the original result idempotently (200, not 409). + aliceWalletClient.tap(50.0, Some("dedup-test")) } "allow two wallet app users to connect to one wallet backend and tap" in { implicit env => @@ -548,10 +546,11 @@ class WalletIntegrationTest aliceWalletClient.balance().unlockedQty should be(40.0) }, ) - assertThrowsAndLogsCommandFailures( - bobWalletClient.transferPreapprovalSend(aliceUserParty, 40.0, deduplicationId), - _.errorMessage should include("409 Conflict"), - ) + // Duplicate send with same deduplication id returns the original result idempotently (200, not 409). + bobWalletClient.transferPreapprovalSend(aliceUserParty, 40.0, deduplicationId) + // Balance is unchanged — idempotent + bobWalletClient.balance().unlockedQty should be(60.0) + aliceWalletClient.balance().unlockedQty should be(40.0) clue("Preapproval sends work if provider has a featured app right") { // Feature alice validator to test a transfer with a featured preapproval provider diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletPaymentIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletPaymentIntegrationTest.scala index 40040b601d..8687bbf9e9 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletPaymentIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletPaymentIntegrationTest.scala @@ -252,6 +252,15 @@ class WalletPaymentIntegrationTest extends IntegrationTest with WalletTestUtil { trackingId, ) + // Wait for the offer to be ingested, so that the resubmission hits the trackingId check + // instead of racing it. A duplicate that loses the race is recovered from the ledger and + // returns the original offer rather than failing. + eventually() { + inside(aliceWalletClient.listTransferOffers()) { case Seq(t) => + t.contractId should be(offerId) + } + } + assertThrows[CommandFailure]( loggerFactory.assertLogs( aliceWalletClient.createTransferOffer( @@ -261,8 +270,8 @@ class WalletPaymentIntegrationTest extends IntegrationTest with WalletTestUtil { expiration, trackingId, ), - _.errorMessage should include("Command submission already exists").or( - include(s"Transfer offer with trackingId ${trackingId} already exists.") + _.errorMessage should include( + s"Transfer offer with trackingId ${trackingId} already exists." ), ) ) @@ -271,9 +280,6 @@ class WalletPaymentIntegrationTest extends IntegrationTest with WalletTestUtil { inside(aliceWalletClient.listTransferOffers()) { case Seq(t) => t.contractId should be(offerId) } - inside(aliceWalletClient.listTransferOffers()) { case Seq(t) => - t.contractId should be(offerId) - } } } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/PollingTrigger.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/PollingTrigger.scala index 12274243f7..ea48f78312 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/PollingTrigger.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/PollingTrigger.scala @@ -112,6 +112,8 @@ trait PollingTrigger extends Trigger with FlagCloseableAsync { context.metricsFactory, mc.labels, context.retryProvider, + // Built outside the RetryFor typeclass, so the automation default has to be restated. + duplicateCommandIsFatal = false, ) override def isHealthy: Boolean = pollingLoopRef diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/RetryFor.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/RetryFor.scala index d0d3e8de11..6cc84f82c8 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/RetryFor.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/RetryFor.scala @@ -8,12 +8,17 @@ import scala.concurrent.duration.* /** The intended use of a retry, expressed in terms like number of retries and * backoff. Use a definition in the companion rather than constructing ''ad hoc''. + * + * `duplicateCommandIsFatal` gives up on DUPLICATE_COMMAND instead of retrying. It is set + * for client calls, where a duplicate cannot resolve within the deduplication window, and + * left off for automation, which retries and then finds its task stale. */ final case class RetryFor private ( maxRetries: Int, initialDelay: FiniteDuration, maxDelay: Duration, resetRetriesAfter: Option[FiniteDuration], + duplicateCommandIsFatal: Boolean = false, ) object RetryFor { @@ -67,6 +72,7 @@ object RetryFor { initialDelay = 100.millis, maxDelay = 1.seconds, resetRetriesAfter = None, + duplicateCommandIsFatal = true, ) /** A retry intended for client calls during the init phase, timing out slower compared to the regular client calls to allow for more contention that happens during initialization. */ @@ -75,5 +81,6 @@ object RetryFor { initialDelay = 100.millis, maxDelay = 3.seconds, resetRetriesAfter = None, + duplicateCommandIsFatal = true, ) } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/RetryProvider.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/RetryProvider.scala index cfbbd329c1..998bf52637 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/RetryProvider.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/RetryProvider.scala @@ -472,6 +472,7 @@ final class RetryProvider( "operation" -> operationId ), this, + retryConfig.duplicateCommandIsFatal, ), ) } @@ -516,6 +517,7 @@ object RetryProvider { metricsFactory: LabeledMetricsFactory, additionalMetricsLabels: Map[String, String], flagCloseable: FlagCloseable, + duplicateCommandIsFatal: Boolean, ) extends ExceptionRetryPolicy { // Additional categories that are not marked as retryable but we // can safely retry since we know there are other apps or @@ -600,9 +602,18 @@ object RetryProvider { case _ => false } + val isDuplicateCommand = errorDetails.exists { + case detail: ErrorDetails.ErrorInfoDetail => + (detail.errorCodeId: String) == "DUPLICATE_COMMAND" + case _ => false + } + errorCategory match { // Pruning errors fall under FAILED_PRECONDITION which we usually retry but there is no chance to recover from it so we instead treat it as a fatal error. case _ if isPruningError => fatalError + // Accepted duplicates are recovered centrally in SpliceLedgerConnection; a rejected + // one can never succeed within the dedup window, so client calls give up here. + case _ if isDuplicateCommand && duplicateCommandIsFatal => fatalError case Some(cat) if cat.retryable.nonEmpty || extraRetryableCategories.contains(cat) => // don't log the stack traces of transient gRPC exceptions to make the logs less noisy. val msg = @@ -803,6 +814,7 @@ object RetryProvider { metricsFactory: LabeledMetricsFactory, additionalMetricsLabels: Map[String, String], flagCloseable: FlagCloseable, + duplicateCommandIsFatal: Boolean, ): ExceptionRetryPolicy } @@ -815,6 +827,7 @@ object RetryProvider { metricsFactory: LabeledMetricsFactory, additionalMetricsLabels: Map[String, String], flagCloseable: FlagCloseable, + duplicateCommandIsFatal: Boolean, ) = a(operationName) } @@ -825,6 +838,7 @@ object RetryProvider { metricsFactory: LabeledMetricsFactory, additionalMetricsLabels: Map[String, String], flagCloseable: FlagCloseable, + duplicateCommandIsFatal: Boolean, ): RetryableError = RetryProvider.RetryableError( operationName, additionalCodes, @@ -835,6 +849,7 @@ object RetryProvider { metricsFactory, additionalMetricsLabels, flagCloseable, + duplicateCommandIsFatal, ) } @@ -846,6 +861,7 @@ object RetryProvider { metricsFactory: LabeledMetricsFactory, additionalMetricsLabels: Map[String, String], flagCloseable: FlagCloseable, + duplicateCommandIsFatal: Boolean, ): RetryableError = RetryProvider.RetryableError( operationName, Seq.empty, @@ -856,6 +872,7 @@ object RetryProvider { metricsFactory, additionalMetricsLabels, flagCloseable, + duplicateCommandIsFatal, ) } } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SpliceLedgerConnection.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SpliceLedgerConnection.scala index 1e92b234ee..44040cfe1e 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SpliceLedgerConnection.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SpliceLedgerConnection.scala @@ -12,7 +12,8 @@ import com.daml.ledger.javaapi.data.codegen.{ContractId, Created, Exercised, Has import com.daml.ledger.javaapi.data.{Command, CreatedEvent, ExercisedEvent, Transaction, User} import com.digitalasset.base.error.ErrorResource import com.digitalasset.base.error.utils.ErrorDetails -import com.digitalasset.base.error.utils.ErrorDetails.ResourceInfoDetail +import com.digitalasset.base.error.utils.ErrorDetails.{ErrorInfoDetail, ResourceInfoDetail} +import io.grpc.protobuf.{StatusProto as GrpcStatusProto} import com.digitalasset.canton.SynchronizerAlias import com.digitalasset.canton.admin.api.client.data.parties.PartyDetails import com.digitalasset.canton.config.NonNegativeFiniteDuration @@ -777,6 +778,23 @@ class SpliceLedgerConnection( callCallbacksOnCompletion(result)(x => (None, x)) } + // Returns Some(completionOffset) when the exception is DUPLICATE_COMMAND with accepted=true, + // allowing callers to fetch the already-completed transaction instead of failing. + private def parseDuplicateCommandAccepted(ex: StatusRuntimeException): Option[Long] = { + val statusProto = GrpcStatusProto.fromThrowable(ex) + if (statusProto == null) None + else + ErrorDetails + .from(statusProto) + .collectFirst { + case ErrorInfoDetail(errorCodeId, metadata) + if errorCodeId == "DUPLICATE_COMMAND" && + metadata.get("accepted").contains("true") => + metadata.get("completion_offset").flatMap(co => Try(co.toLong).toOption) + } + .flatten + } + private def verifyEnoughExtraTrafficRemains( synchronizerId: SynchronizerId, commandPriority: CommandPriority, @@ -863,6 +881,7 @@ class SpliceLedgerConnection( priority: CommandPriority, deadline: Option[NonNegativeFiniteDuration] = None, preferredPackageIds: Seq[String] = Seq.empty, + recoverAcceptedDuplicates: Boolean = false, ) { private type DedupNotSpecifiedYet = CmdId =:= Any private type SynchronizerIdRequired = DomId <:< SynchronizerId @@ -873,6 +892,7 @@ class SpliceLedgerConnection( disclosedContracts: DisclosedContracts = this.disclosedContracts, deadline: Option[NonNegativeFiniteDuration] = this.deadline, preferredPackageIds: Seq[String] = this.preferredPackageIds, + recoverAcceptedDuplicates: Boolean = this.recoverAcceptedDuplicates, ): submit[C, CmdId0, DomId0] = new submit( actAs, @@ -884,8 +904,18 @@ class SpliceLedgerConnection( priority, deadline, preferredPackageIds, + recoverAcceptedDuplicates, ) + /** Read an already-accepted duplicate back from the ledger and return its result, rather + * than failing the submission. + * + * For client calls, which cannot do anything useful with the failure. Automation leaves + * this off: a trigger needs the error so that its own retry re-runs the staleness check. + */ + def recoveringAcceptedDuplicates(enabled: Boolean = true): submit[C, CmdId, DomId] = + copy(recoverAcceptedDuplicates = enabled) + def withDedup(commandId: CommandId, deduplicationOffset: Long)(implicit cid: DedupNotSpecifiedYet ): submit[C, (CommandId, Long), DomId] = @@ -998,6 +1028,21 @@ class SpliceLedgerConnection( .withCause(ex.getCause) .asRuntimeException } + .recoverWith { + case ex: StatusRuntimeException + if recoverAcceptedDuplicates && + ex.getStatus.getCode == Status.Code.ALREADY_EXISTS => + parseDuplicateCommandAccepted(ex) match { + case Some(completionOffset) => + client.recoverFromDuplicateCommand( + waitFor, + completionOffset, + actAs.map(_.toProtoPrimitive), + ) + case None => + Future.failed(ex) + } + } )(getOffsetAndResult) @annotation.tailrec diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ledger/api/LedgerClient.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ledger/api/LedgerClient.scala index 6fd6f0584b..00b6a39c31 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ledger/api/LedgerClient.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ledger/api/LedgerClient.scala @@ -27,6 +27,7 @@ import com.daml.ledger.javaapi.data.{ CreateUserResponse, ListUserRightsResponse, OffsetCheckpoint, + Transaction, User, } import com.daml.ledger.javaapi.data.codegen.ContractId @@ -223,6 +224,34 @@ private[environment] class LedgerClient( ) } + private[environment] def getTransactionByOffset( + offset: Long, + actAs: Seq[String], + )(implicit tc: TraceContext): Future[Transaction] = { + import lapi.update_service.GetUpdateResponse.Update as U + val updateFormat = LedgerClient.ledgerEffectsUpdateFormat(actAs) + for { + stub <- withCredentialsAndTraceContext(updateServiceStub) + response <- stub.getUpdateByOffset( + lapi.update_service + .GetUpdateByOffsetRequest(offset = offset, updateFormat = Some(updateFormat)) + ) + } yield response.update match { + case U.Transaction(tree) => LedgerClient.lapiTreeToJavaTree(tree) + case other => + throw GrpcStatus.INTERNAL + .withDescription(s"Expected transaction at offset $offset but got $other") + .asRuntimeException() + } + } + + private[environment] def recoverFromDuplicateCommand[W]( + waitFor: SubmitAndWaitFor[W], + completionOffset: Long, + actAs: Seq[String], + )(implicit tc: TraceContext): Future[W] = + waitFor.recoverFromDuplicate(completionOffset, getTransactionByOffset(_, actAs)) + def submitAndWait[Z]( synchronizerId: String, userId: String, @@ -806,6 +835,10 @@ object LedgerClient { private[LedgerClient] type RawResponse private[LedgerClient] val stubSubmit: StubSubmit[RawResponse] private[LedgerClient] val mapResponse: RawResponse => Z + private[LedgerClient] def recoverFromDuplicate( + completionOffset: Long, + fetchTransaction: Long => Future[Transaction], + ): Future[Z] } private[environment] object SubmitAndWaitFor { @@ -823,7 +856,7 @@ object LedgerClient { ) .map(r => command_service.SubmitAndWaitResponse.toJavaProto(r))(ec) } - ) + )((offset, _) => Future.successful(offset)) val TransactionTree: SubmitAndWaitFor[jdata.Transaction] = impl((response: CSOC.SubmitAndWaitForTransactionResponse) => @@ -836,7 +869,7 @@ object LedgerClient { ) .map(r => command_service.SubmitAndWaitForTransactionResponse.toJavaProto(r))(ec) } - } + }((offset, fetch) => fetch(offset)) private type StubSubmit[R] = ( CommandServiceGrpc.CommandServiceStub, @@ -846,16 +879,52 @@ object LedgerClient { private[this] def impl[R, Z](mapResponse0: R => Z)( stubSubmit0: StubSubmit[R] + )( + recover0: (Long, Long => Future[jdata.Transaction]) => Future[Z] ): SubmitAndWaitFor[Z] = new SubmitAndWaitFor[Z] { type RawResponse = R override val stubSubmit = stubSubmit0 override val mapResponse = mapResponse0 + override def recoverFromDuplicate( + completionOffset: Long, + fetchTransaction: Long => Future[jdata.Transaction], + ): Future[Z] = recover0(completionOffset, fetchTransaction) } } final case class GetTreeUpdatesResponse( updateOrCheckpoint: TreeUpdateOrOffsetCheckpoint ) + private def wildcardFilter(party: String) = + party -> transaction_filter.Filters( + Seq( + transaction_filter.CumulativeFilter( + transaction_filter.CumulativeFilter.IdentifierFilter + .WildcardFilter(transaction_filter.WildcardFilter(false)) + ) + ) + ) + + private[environment] def ledgerEffectsUpdateFormat( + actAs: Seq[String] + ): transaction_filter.UpdateFormat = + transaction_filter.UpdateFormat( + includeTransactions = Some( + transaction_filter.TransactionFormat( + eventFormat = Some( + transaction_filter.EventFormat( + filtersByParty = actAs.map(wildcardFilter).toMap, + filtersForAnyParty = None, + verbose = false, + ) + ), + transactionShape = transaction_filter.TransactionShape.TRANSACTION_SHAPE_LEDGER_EFFECTS, + ) + ), + includeReassignments = None, + includeTopologyEvents = None, + ) + def lapiTreeToJavaTree( tree: lapi.transaction.Transaction ): com.daml.ledger.javaapi.data.Transaction = { diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala index 9605f13d3e..997a2df1ea 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/admin/http/HttpSvPublicHandler.scala @@ -880,6 +880,7 @@ class HttpSvPublicHandler( ), deduplicationOffset = offset, ) + .recoveringAcceptedDuplicates() .yieldUnit() } .value diff --git a/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/admin/http/HttpValidatorAdminHandler.scala b/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/admin/http/HttpValidatorAdminHandler.scala index 4dc6ad2b79..8f9bb3b47d 100644 --- a/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/admin/http/HttpValidatorAdminHandler.scala +++ b/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/admin/http/HttpValidatorAdminHandler.scala @@ -464,6 +464,7 @@ class HttpValidatorAdminHandler( BaseLedgerConnection.sanitizeUserIdToPartyString(body.userPartyId), ), DedupOffset(implicitly[Ordering[Long]].min(offsetESP, offsetTP)), + recoverAcceptedDuplicates = true, ) ), ) diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala index c2d6b3e98f..bb5b781c4d 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandler.scala @@ -576,6 +576,7 @@ class HttpWalletHandler( AmuletOperationDedupConfig( commandId, dedupDuration, + recoverAcceptedDuplicates = true, ) ), ), @@ -687,6 +688,7 @@ class HttpWalletHandler( AmuletOperationDedupConfig( commandId, dedupDuration, + recoverAcceptedDuplicates = true, ) ), ) @@ -809,6 +811,7 @@ class HttpWalletHandler( ), deduplicationOffset = dedupOffset, ) + .recoveringAcceptedDuplicates() .withSynchronizerId(domain) .yieldResult() .map(_.contractId) @@ -864,6 +867,7 @@ class HttpWalletHandler( body.deduplicationId, ), dedupDuration, + recoverAcceptedDuplicates = true, ) ), ) @@ -887,6 +891,7 @@ class HttpWalletHandler( val dedupConfig = AmuletOperationDedupConfig( commandId, dedupDuration, + recoverAcceptedDuplicates = true, ) (for { result <- userWallet.treasury.enqueueTokenStandardTransferOperationV1( @@ -1072,6 +1077,7 @@ class HttpWalletHandler( val dedupConfig = AmuletOperationDedupConfig( commandId, dedupDuration, + recoverAcceptedDuplicates = true, ) (for { result <- userWallet.treasury.enqueueTokenStandardTransferOperationV2( @@ -1235,6 +1241,7 @@ class HttpWalletHandler( val dedupConfig = AmuletOperationDedupConfig( commandId, dedupDuration, + recoverAcceptedDuplicates = true, ) for { result <- userWallet.treasury.enqueueAmuletAllocationOperation( @@ -1311,6 +1318,7 @@ class HttpWalletHandler( commandId, // Overriden to be low enough (5m) that we allow the same allocation to be re-created after being withdrawn DedupDuration(com.google.protobuf.Duration.newBuilder().setSeconds(5L * 60L).build()), + recoverAcceptedDuplicates = true, ) for { result <- userWallet.treasury.enqueueAmuletAllocationOperation( @@ -1824,6 +1832,7 @@ class HttpWalletHandler( ), deduplicationConfig = dedupDuration, ) + .recoveringAcceptedDuplicates() .withDisclosedContracts( userWallet.connection .disclosedContracts(amuletRules, unclaimedDevelopmentFundCouponsToAllocate*) diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandlerUtil.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandlerUtil.scala index 72ada24586..39d4e2e4e4 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandlerUtil.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/admin/http/HttpWalletHandlerUtil.scala @@ -91,6 +91,7 @@ trait HttpWalletHandlerUtil extends Spanning with NamedLogging { priority = priority, ) .withDedup(commandId, dedupConfig) + .recoveringAcceptedDuplicates() .withDisclosedContracts(disclosedContracts(userWallet.connection)) .yieldResult() } diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/treasury/TreasuryService.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/treasury/TreasuryService.scala index c1162d135f..ba8dffb809 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/treasury/TreasuryService.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/treasury/TreasuryService.scala @@ -648,7 +648,10 @@ class TreasuryService( (offset, result) <- batch.dedup match { case None => baseSubmission.noDedup.yieldResultAndOffset() case Some(dedup) => - baseSubmission.withDedup(dedup.commandId, dedup.config).yieldResultAndOffset() + baseSubmission + .withDedup(dedup.commandId, dedup.config) + .recoveringAcceptedDuplicates(dedup.recoverAcceptedDuplicates) + .yieldResultAndOffset() } // wait for store to ingest the new amulet holdings, then return all outcomes to the callers @@ -813,7 +816,10 @@ class TreasuryService( (offset, result) <- operation.dedup match { case None => baseSubmission.noDedup.yieldResultAndOffset() case Some(dedup) => - baseSubmission.withDedup(dedup.commandId, dedup.config).yieldResultAndOffset() + baseSubmission + .withDedup(dedup.commandId, dedup.config) + .recoveringAcceptedDuplicates(dedup.recoverAcceptedDuplicates) + .yieldResultAndOffset() } _ <- userStore.signalWhenIngestedOrShutdown(offset) } yield { @@ -1559,9 +1565,14 @@ object TreasuryService { } } + /** @param recoverAcceptedDuplicates + * set by client calls, so that a duplicate of an already-accepted submission returns the + * original result. Automation leaves it off and lets its own retry handle the duplicate. + */ final case class AmuletOperationDedupConfig( commandId: CommandId, config: DedupConfig, + recoverAcceptedDuplicates: Boolean = false, ) extends PrettyPrinting { override def pretty: Pretty[AmuletOperationDedupConfig.this.type] = prettyNode("DedupConfig", param("commandId", _.commandId), param("config", _.config)) diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 5f5ee78c44..c8975afa6e 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -6,3 +6,11 @@ .. NOTE: add your upcoming release notes below this line. They are included in the `release_notes.rst`. .. release-notes:: Upcoming + + - Wallet app + + - Duplicate wallet operations submitted with the same command id (e.g. tap, transfer, + token standard transfers) now return the original result idempotently instead of HTTP 409. + This aligns with standard idempotency-key semantics: a second request with a previously + accepted command id receives a 200 response with the same result as the first. + Concurrent duplicates, where no submission has completed yet, are still rejected. From e2001800104083c02936aae269bdb4569bab79f2 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Mon, 3 Aug 2026 10:34:26 +0200 Subject: [PATCH 171/329] Update to latest version of cantonbft dashboards (#6651) [static] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- cluster/expected/observability/expected.json | 2 +- .../canton-bft/bft-ordering.json | 250 ++++++++++++++++-- 2 files changed, 231 insertions(+), 21 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 7f4423f6ef..1cb396836c 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -116,7 +116,7 @@ "apiVersion": "v1", "data": { "bft-ordering-performance.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 8,\n \"links\": [],\n \"liveNow\": false,\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 16,\n \"panels\": [],\n \"title\": \"Global\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Wall-clock duration between the instant a request is received by the CantonBFT orderer and the instant the fully assembled ordered block metadata is stored, just before the request is pushed to the post-ordering stages. Its meaningfulness requires wall-clock synchronization, as not all ordered requests are received and timestamped on this node.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 12,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_global_requests_ordering_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests end-to-end ordering latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 12,\n \"x\": 12,\n \"y\": 1\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node, ordering_stage) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-availability-total\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total batch dissemination and consensus time (permanence in availability module)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of blocks ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 17,\n \"w\": 8,\n \"x\": 0,\n \"y\": 13\n },\n \"id\": 107,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node, is_block_empty) (irate(daml_sequencer_bftordering_global_ordered_blocks_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}.{{node}} (empty: {{is_block_empty}})\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - blocks/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of batches (proofs of availability) ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 17,\n \"w\": 8,\n \"x\": 8,\n \"y\": 13\n },\n \"id\": 108,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_global_ordered_batches_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"interval\": \"\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - batches/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 17,\n \"w\": 8,\n \"x\": 16,\n \"y\": 13\n },\n \"id\": 109,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_global_ordered_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - requests/s\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 30\n },\n \"id\": 12,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-PekkoP2PGrpcConnectionManagingActor\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"PekkoP2PGrpcConnectionManagingActor queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 31\n },\n \"id\": 103,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (daml_sequencer_bftordering_performance_moduleQueueSize_PekkoP2PGrpcConnectionManagingActor{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"})\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"PekkoP2PGrpcConnectionManagingActor queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Latency between sending a message and receiving it on the other side\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 13,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node, source_sequencer) (rate(daml_sequencer_bftordering_p2p_send_grpc_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{source_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P gRPC network latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The rate of gRPC message send retries\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_p2p_send_sends_retried{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"gRPC send retry rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 17,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_p2p_send_network_write_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P network client write latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node) (rate(daml_sequencer_bftordering_p2p_receive_processing_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P receive processing latency\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Networking\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 7,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 18,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-mempool\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Mempool module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 32\n },\n \"id\": 101,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_mempool{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Mempool module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-availability\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Availability module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 38\n },\n \"id\": 102,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_availability{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Availability module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time waited for a batch while there are outstanding consensus proposal requests and no dissemination is in progress\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 44\n },\n \"id\": 24,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Time spent waiting for batches\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Cumulative latency of a batch dissemination until a PoA is formed and it can be present multiple times for a given batch if it regresses due to topology changes\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 44\n },\n \"id\": 21,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-dissemination-total\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total dissemination time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Insertion in proposal queue until next event, like re-sign, re-dissemination, [re-]proposal or batch ordered (emitted 1 or more times per batch)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 50\n },\n \"id\": 23,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-queued-for-block-inclusion\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Time waited for batch inclusion in consensus proposal\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of regressions of a single batch due to topology changes\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 50\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_availability_batch_regressions{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}, {{stage}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch dissemination regressions rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 56\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-hash-batchId\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch ID hashing latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 56\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-sign-local-batchId\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Local Batch ID signing latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Insertion in batch queue until extraction and batch send to availability\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 62\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"request-queued-for-batch-inclusion\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Request batch inclusion latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 62\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-sign-remote-batchId\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Remote batch ID signing latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 68\n },\n \"id\": 26,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-sign-local-batches\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sign local batches\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 68\n },\n \"id\": 8,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-signature-verify-ack\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch ID signature verification\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 74\n },\n \"id\": 82,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.availability.data.db.DbAvailabilityStore.addBatch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert batch\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 74\n },\n \"id\": 28,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.availability.data.db.DbAvailabilityStore.fetchBatches\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Fetch local batches from DB\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 80\n },\n \"id\": 22,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-validation\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch validation\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Summary of cache hits, misses, and evictions for the Availability store's batch data cache\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"hideTimeOverride\": false,\n \"id\": 110,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_cache_hits{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\", cache=\\\"batch-cache\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} Cache Hits\",\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_cache_misses{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\", cache=\\\"batch-cache\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} Cache Misses\",\n \"refId\": \"B\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_cache_evictions{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\", cache=\\\"batch-cache\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} Cache Evictions\",\n \"refId\": \"C\"\n }\n ],\n \"title\": \"Availability Store Cache Statistics\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Mempool+Availability\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 45,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 33\n },\n \"id\": 80,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-consensus\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 33\n },\n \"id\": 104,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_consensus{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 40\n },\n \"id\": 81,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-segment-module\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Segment module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 40\n },\n \"id\": 105,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_segment_module{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Segment module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Block commit time at the consensus (PBFT) level.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [\n {\n \"options\": {\n \"match\": \"null\",\n \"result\": {\n \"text\": \"N/A\"\n }\n },\n \"type\": \"special\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 2\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 47\n },\n \"hideTimeOverride\": true,\n \"id\": 39,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_consensus_commit_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus Block Latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time elapsed between sending a PrePrepare and seeing that the block has been ordered\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 46,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-segment-proposal-to-commit-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus segment block proposal to commit latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time elapsed between ordered blocks proposed by a segment\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 54\n },\n \"id\": 88,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-segment-block-commit-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus segment block ordering latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time between a proposal request to the availability module needed to continue a segment and a PrePrepare being built\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 54\n },\n \"id\": 87,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-block-proposal-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Wait for proposal\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time between the epoch completion and the start of the next epoch\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 60\n },\n \"id\": 86,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-epoch-start-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Wait for epoch start\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time between the last led segment's completion and the epoch completion\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 60\n },\n \"id\": 83,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-epoch-completion-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Wait for epoch completion\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 67\n },\n \"id\": 63,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.startEpoch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Start epoch (DB)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 67\n },\n \"id\": 47,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.completeEpoch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Complete epoch (DB)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 73\n },\n \"id\": 64,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addPrePrepare\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert pre-prepare\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 73\n },\n \"id\": 50,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addPreparesAtomically\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert prepares\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 80\n },\n \"id\": 52,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addOrderedBlockAtomically\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert ordered block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"id\": 66,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-signature-verify-poa-ack\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Check PoA ack signature\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 86\n },\n \"id\": 68,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-validate-signed-message\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Validate message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 86\n },\n \"id\": 76,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"sign-BftSignedConsensusMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sign message\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - main protocol\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 33\n },\n \"id\": 89,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 24,\n \"x\": 0,\n \"y\": 34\n },\n \"id\": 51,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_epoch_view_changes{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"View changes occurred (rate)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 41\n },\n \"id\": 84,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addViewChangeMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert view change message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 41\n },\n \"id\": 77,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-postponed-view-messages-queue-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 48\n },\n \"id\": 97,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_postponed_view_messages_queue_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 48\n },\n \"id\": 95,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_postponed_view_messages_queue_max_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages queue max size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 55\n },\n \"id\": 94,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_postponed_view_messages_dropped_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages dropped (rate)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 55\n },\n \"id\": 100,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_postponed_view_messages_duplicates_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages duplicated (rate)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 62\n },\n \"id\": 112,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, Leader) (rate(daml_sequencer_bftordering_consensus_view_change_progress_latency_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"interval\": \"\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{Leader}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Time until progress in view\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - view change\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 34\n },\n \"id\": 90,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 35\n },\n \"id\": 72,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-validate-consensus-certificate\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Validate state transfer consensus certificate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to transfer a given epoch from peer(s)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"id\": 111,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\", ordering_stage=~\\\"state-transfer-total-epoch-transfer-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"Data transfer\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\", ordering_stage=~\\\"state-transfer-store-epochs\\\"}[$__rate_interval])))\",\n \"hide\": false,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Epoch Transfer Duration\",\n \"transformations\": [\n {\n \"id\": \"calculateField\",\n \"options\": {\n \"alias\": \"Total epoch transfer duration\",\n \"binary\": {\n \"left\": \"Data transfer\",\n \"reducer\": \"sum\",\n \"right\": \"Complete + start DB query\"\n },\n \"mode\": \"binary\",\n \"reduce\": {\n \"reducer\": \"sum\"\n }\n }\n }\n ],\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 42\n },\n \"id\": 91,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"state-transfer-postponed-consensus-messages-queue-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 42\n },\n \"id\": 96,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_state_transfer_postponed_consensus_messages_queue_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 49\n },\n \"id\": 98,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_state_transfer_postponed_consensus_messages_queue_max_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages queue max size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 49\n },\n \"id\": 99,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_state_transfer_postponed_consensus_messages_dropped{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages dropped (rate)\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - state transfer\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 35\n },\n \"id\": 38,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 36\n },\n \"id\": 85,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_incoming_retransmission_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Incoming retransmission requests rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 36\n },\n \"id\": 40,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_outgoing_retransmission_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Outgoing retransmission requests rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 42\n },\n \"id\": 41,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_retransmitted_messages_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmitted messages rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 42\n },\n \"id\": 42,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_retransmitted_commit_certificates_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmitted commit certificates rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 48\n },\n \"id\": 78,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"sign-BftSignedRetransmissionMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sign retransmission message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 48\n },\n \"id\": 79,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"verify-signature-BftSignedRetransmissionMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Verify retransmission message signature\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 54\n },\n \"id\": 43,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_discarded_wrong_epoch_retransmission_responses_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmission responses discarded due to wrong epoch - rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 54\n },\n \"id\": 44,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_discarded_rate_limited_retransmission_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmission requests discarded due to rate limiting - rate\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - retransmissions\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 36\n },\n \"id\": 59,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 37\n },\n \"id\": 48,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.latestEpoch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load latest epoch\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 37\n },\n \"id\": 53,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadEpochProgress\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load epoch progress\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 45\n },\n \"id\": 54,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadCompleteBlocks\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load completed blocks\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - CFT\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 37\n },\n \"id\": 29,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 55,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-output\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Output module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 38\n },\n \"id\": 106,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_output{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Output module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time needed to inspect requests within a block\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 45\n },\n \"id\": 36,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"output-block-inspection\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block inspection\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 45\n },\n \"id\": 30,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"output-block-fetch-batches\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total block batches fetch time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 52\n },\n \"id\": 37,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.output.data.db.DbOutputMetadataStore.insertBlockIfMissing\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 52\n },\n \"id\": 34,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": false\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_topology_query_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Query topology\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 59\n },\n \"id\": 31,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.output.data.db.DbOutputMetadataStore.insertLeaderSelectionPolicyState\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Save blacklist leader selection policy state\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 59\n },\n \"id\": 32,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.output.data.db.DbOutputMetadataStore.insertEpochIfMissing\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert epoch metadata\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 66\n },\n \"id\": 113,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"output-backpressure\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sequencer core backpressure duration\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Output\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 60,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 35,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadEpochInfo\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load epoch info\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 56,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadOrderedBlocks\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load ordered blocks\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Output - CFT\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 62,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to compute the pruning point, based on retention factors and current time\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 40\n },\n \"id\": 25,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"pruning-compute-pruning-point\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Compute pruning point\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to evict unordered, but expired, batches in the availability DB\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 40\n },\n \"id\": 27,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.availability.data.db.DbAvailabilityStore.gc\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Availability DB - evict expired batches\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to create new table partitions, when necessary\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 61,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.pruning.PartitionManager.PartitionCreatorImpl.createPartitionsIfNeeded\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Create partitions\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to drop table partitions that can be pruned\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 67,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.pruning.PartitionManager.PartitionPrunerImpl.prune\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Prune (Delete partitions)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to start the Partition Manager, which may include creating new partitions\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 69,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.pruning.PartitionManager.create\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Partition manager startup\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Pruning\",\n \"type\": \"row\"\n }\n ],\n \"refresh\": \"30s\",\n \"schemaVersion\": 38,\n \"style\": \"dark\",\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": false,\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status,namespace}\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"label\": \"Namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"label\": \"Job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"node\",\n \"label\": \"Node\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"Percentile\",\n \"multi\": false,\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": false,\n \"text\": \".5\",\n \"value\": \".5\"\n },\n {\n \"selected\": false,\n \"text\": \".6\",\n \"value\": \".6\"\n },\n {\n \"selected\": false,\n \"text\": \".7\",\n \"value\": \".7\"\n },\n {\n \"selected\": false,\n \"text\": \".75\",\n \"value\": \".75\"\n },\n {\n \"selected\": false,\n \"text\": \".8\",\n \"value\": \".8\"\n },\n {\n \"selected\": false,\n \"text\": \".9\",\n \"value\": \".9\"\n },\n {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n {\n \"selected\": false,\n \"text\": \".99\",\n \"value\": \".99\"\n },\n {\n \"selected\": false,\n \"text\": \".999\",\n \"value\": \".999\"\n }\n ],\n \"query\": \".5,.6,.7,.75,.8,.9,.95,.99,.999\",\n \"queryValue\": \"\",\n \"skipUrlSync\": false,\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-5m\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"BFT ordering (performance)\",\n \"uid\": \"c2237cb1-9018-41ac-8eef-24a28ab28f20\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", - "bft-ordering.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"description\": \"\",\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 8,\n \"links\": [],\n \"liveNow\": false,\n \"panels\": [\n {\n \"collapsed\": false,\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 64,\n \"panels\": [],\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Global overview (instance view)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"ISS proceeds in epochs with stable leader assignments to \\\"segments\\\" and stable topology (i.e., sequencers can be added and remove only at epoch boundaries and leaders are reassigned to segments also at epoch boundaries). This is the current epoch.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 5,\n \"x\": 0,\n \"y\": 1\n },\n \"hideTimeOverride\": false,\n \"id\": 72,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_epoch{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Current Epoch\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size in blocks of the current epoch.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 3,\n \"x\": 5,\n \"y\": 1\n },\n \"hideTimeOverride\": false,\n \"id\": 80,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_epoch_length{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Epoch Length\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of active validators\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 3,\n \"x\": 8,\n \"y\": 1\n },\n \"hideTimeOverride\": false,\n \"id\": 75,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_validators{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Validators\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of connected and authenticated P2P peers for the selected sequencer. It should be > 2/3 at all times.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"percentage\",\n \"steps\": [\n {\n \"color\": \"dark-red\",\n \"value\": null\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 67\n },\n {\n \"color\": \"dark-green\",\n \"value\": 100\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 13,\n \"x\": 11,\n \"y\": 1\n },\n \"hideTimeOverride\": true,\n \"id\": 53,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"center\",\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"last\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"textMode\": \"auto\"\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_p2p_connections_connected{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": true,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"Connected ({{namespace}}.{{node}})\",\n \"range\": false,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_p2p_connections_authenticated{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"hide\": false,\n \"instant\": true,\n \"legendFormat\": \"Authenticated ({{namespace}}.{{node}})\",\n \"range\": false,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Peers count\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 3,\n \"x\": 0,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 99,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_max_tolerated_faults{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Max tolerated faults\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The size of a weak quorum or non-faulty nodes, needed e.g. to disseminate batches\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 3,\n \"x\": 3,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 100,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_weak_quorum{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Weak quorum\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The size of a strong quorum, needed for ordering consensus\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 3,\n \"x\": 6,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 101,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_strong_quorum{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Strong quorum\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Latest block number ordered and available for the read path. When there is no traffic, a block can be empty.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 7,\n \"x\": 9,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 4,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job, node) (daml_sequencer_bftordering_global_ordered_blocks_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"})\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"instant\": true,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Output Blocks\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Total number of requests ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 8,\n \"x\": 16,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 40,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace,job,node) (histogram_sum(daml_sequencer_bftordering_output_block_size_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}))\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Output Requests\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Sequencers that are part of the BFT ordering topology\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisGridShow\": true,\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 12,\n \"x\": 0,\n \"y\": 24\n },\n \"id\": 91,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_topology_member{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"interval\": \"\",\n \"legendFormat\": \" (reporter: {{namespace}}.{{node}}) {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"BFT ordering members\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Sequencers that are allowed to propose blocks for ordering\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 12,\n \"x\": 12,\n \"y\": 24\n },\n \"id\": 95,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_topology_leader{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \" (reporter: {{namespace}}.{{node}}) {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"BFT ordering leaders\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 8,\n \"x\": 0,\n \"y\": 45\n },\n \"id\": 96,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_p2p_authenticated_endpoint{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{endpoint}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P authenticated endpoints\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 8,\n \"x\": 8,\n \"y\": 45\n },\n \"id\": 97,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_p2p_unauthenticated_endpoint{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{endpoint}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P connected but unauthenticated endpoints\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"hidden\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 8,\n \"x\": 16,\n \"y\": 45\n },\n \"id\": 98,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_p2p_disconnected_endpoint{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{endpoint}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P disconnected endpoints\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Wall-clock duration between the instant a request is received by the CantonBFT orderer and the instant the fully assembled ordered block metadata is stored, just before the request is pushed to the post-ordering stages. Its meaningfulness requires wall-clock synchronization, as not all ordered requests are received and timestamped on this node.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 66\n },\n \"id\": 87,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_global_requests_ordering_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests end-to-end ordering latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of requests ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 66\n },\n \"hideTimeOverride\": true,\n \"id\": 88,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\",\n \"sum\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_global_ordered_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - requests/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of requests per consensus block.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 73\n },\n \"hideTimeOverride\": true,\n \"id\": 49,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\",\n \"sum\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_sum(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))) / histogram_count(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} ({{mode}})\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests / Block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of blocks ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 73\n },\n \"hideTimeOverride\": true,\n \"id\": 68,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by (namespace, job, node, is_block_empty) (irate(daml_sequencer_bftordering_global_ordered_blocks_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} (empty: {{is_block_empty}})\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Throughput - blocks/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches (proofs of availability) per consensus block. Note that < 1 average batch per block likely means empty blocks are being created.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 80\n },\n \"hideTimeOverride\": true,\n \"id\": 89,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\",\n \"sum\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_sum(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))) / histogram_count(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} ({{mode}})\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batches (PoAs) / Block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of batches (proofs of availability) ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"hideTimeOverride\": true,\n \"id\": 90,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_global_ordered_batches_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - batches/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Delay introduced by the sequencer core being too slow consuming blocks from ordering, in milliseconds\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"ms\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 87\n },\n \"hideTimeOverride\": true,\n \"id\": 102,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_sequencer_core_backpressure_current_delay_millis{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer core backpressure delay (ms)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the buffer in the subscription from the orderer to the sequencer core, in blocks\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 87\n },\n \"hideTimeOverride\": true,\n \"id\": 103,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\",\n \"min\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_sequencer_core_subscription_buffer_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\\n\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer core buffer size (blocks #)\",\n \"type\": \"timeseries\"\n },\n\n\n\n\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"How many epochs a sequencer is blacklisted\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 94\n },\n \"id\": 86,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"builder\",\n \"expr\": \"daml_sequencer_bftordering_blacklist_sequencer{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{blacklist_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Blacklisted Sequencers\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the stream buffer at various stages\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 94\n },\n \"id\": 104,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_block_stream_buffer_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}.{{element}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Stream Buffer Metrics\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Wall-clock time of the ordered block being provided to the sequencer minus BFT time of the block\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [\n {\n \"options\": {\n \"match\": \"null\",\n \"result\": {\n \"text\": \"N/A\"\n }\n },\n \"type\": \"special\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 2\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 6,\n \"x\": 12,\n \"y\": 94\n },\n \"hideTimeOverride\": true,\n \"id\": 85,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_delay_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block delay\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the payloads of blocks ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 6,\n \"x\": 18,\n \"y\": 94\n },\n \"hideTimeOverride\": false,\n \"id\": 50,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_bytes{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}.{{mode}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Output Block Payload Size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Percentage of votes received by the currently selected sequencer at every major PBFT stage. Should be > 2/3 at all times, else a leader change (\\\"view change\\\" in PBFT parlance) occurs automatically.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"fillOpacity\": 70,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"decimals\": 0,\n \"links\": [],\n \"mappings\": [],\n \"max\": 1,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"percentage\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": null\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 67\n },\n {\n \"color\": \"#6ED0E0\",\n \"value\": 80\n },\n {\n \"color\": \"dark-green\",\n \"value\": 100\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 28,\n \"w\": 24,\n \"x\": 0,\n \"y\": 101\n },\n \"hideTimeOverride\": false,\n \"id\": 73,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"always\",\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_consensus_prepare_votes_percent{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) Prepare {{voting_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_commit_votes_percent{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) Commit {{voting_sequencer}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"PBFT Voting Power %\",\n \"transformations\": [\n {\n \"id\": \"renameByRegex\",\n \"options\": {\n \"regex\": \"Prepare SEQ::(.*)::.*\",\n \"renamePattern\": \"Prepare $1\"\n }\n },\n {\n \"id\": \"renameByRegex\",\n \"options\": {\n \"regex\": \"Commit SEQ::(.*)::.*\",\n \"renamePattern\": \"Commit $1\"\n }\n }\n ],\n \"type\": \"state-timeline\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Count of non-compliant and possibly byzantine protocol behaviors detected by the currently selected sequencer.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 129\n },\n \"hideTimeOverride\": false,\n \"id\": 74,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_security_noncompliant_behavior_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Non-compliant behaviors\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the incoming requests buffer (\\\"mempool\\\") for the selected sequencer. Its maximum size in bytes and requests count can be currently set via static configuration.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 129\n },\n \"hideTimeOverride\": true,\n \"id\": 78,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_ingress_requests_queued{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Mempool Size (requests)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of proposals requested by consensus.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 136\n },\n \"hideTimeOverride\": true,\n \"id\": 77,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_requested_proposals{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block proposals requested by consensus\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches requested to be proposed in consensus.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 136\n },\n \"hideTimeOverride\": true,\n \"id\": 79,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_requested_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block proposals requested by consensus (batches)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches that are being disseminated.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 143\n },\n \"hideTimeOverride\": true,\n \"id\": 82,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_disseminating_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ready_for_consensus=~\\\"false|true\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batches being disseminated\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches requested by the availability module and not yet provided.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 143\n },\n \"hideTimeOverride\": true,\n \"id\": 76,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_mempool_requested_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Outstanding batch requests\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 150\n },\n \"id\": 55,\n \"panels\": [],\n \"repeat\": \"instance\",\n \"repeatDirection\": \"h\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sequencer overview: $reporting_sequencer\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of requests in batches that are being disseminated.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 151\n },\n \"hideTimeOverride\": true,\n \"id\": 83,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_disseminating_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ready_for_consensus=~\\\"false|true\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests being disseminated\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Total network ingress for the currently selected sequencer split by sender.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 100,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"never\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"links\": [],\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 6,\n \"x\": 12,\n \"y\": 151\n },\n \"id\": 59,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job, node, source_sequencer) (daml_sequencer_bftordering_p2p_receive_received_bytes_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"})\",\n \"format\": \"time_series\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{source_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Network Input\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Total network egress for the currently selected sequencer split by destination.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 100,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"never\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"links\": [],\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 6,\n \"x\": 18,\n \"y\": 151\n },\n \"id\": 58,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job, node, target_sequencer) (daml_sequencer_bftordering_p2p_send_sent_bytes_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"})\",\n \"format\": \"time_series\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{target_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Network Output\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of payload bytes in requests within batches that are being disseminated.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 158\n },\n \"hideTimeOverride\": true,\n \"id\": 84,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_disseminating_bytes{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ready_for_consensus=~\\\"false|true\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Bytes being disseminated\",\n \"type\": \"timeseries\"\n }\n ],\n \"refresh\": \"30s\",\n \"schemaVersion\": 38,\n \"style\": \"dark\",\n \"tags\": [\n \"bft-sequencers\"\n ],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status,namespace}\",\n \"hide\": 0,\n \"includeAll\": true,\n \"label\": \"Namespace\",\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"label\": \"Job\",\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"label\": \"Node\",\n \"multi\": true,\n \"name\": \"node\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"Percentile\",\n \"multi\": false,\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": false,\n \"text\": \".5\",\n \"value\": \".5\"\n },\n {\n \"selected\": false,\n \"text\": \".6\",\n \"value\": \".6\"\n },\n {\n \"selected\": false,\n \"text\": \".7\",\n \"value\": \".7\"\n },\n {\n \"selected\": false,\n \"text\": \".75\",\n \"value\": \".75\"\n },\n {\n \"selected\": false,\n \"text\": \".8\",\n \"value\": \".8\"\n },\n {\n \"selected\": false,\n \"text\": \".9\",\n \"value\": \".9\"\n },\n {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n {\n \"selected\": false,\n \"text\": \".99\",\n \"value\": \".99\"\n },\n {\n \"selected\": false,\n \"text\": \".999\",\n \"value\": \".999\"\n }\n ],\n \"query\": \".5,.6,.7,.75,.8,.9,.95,.99,.999\",\n \"queryValue\": \"\",\n \"skipUrlSync\": false,\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-15m\",\n \"to\": \"now\"\n },\n \"timepicker\": {\n \"refresh_intervals\": [\n \"5s\",\n \"10s\",\n \"30s\",\n \"1m\",\n \"5m\",\n \"15m\",\n \"30m\",\n \"1h\",\n \"2h\",\n \"1d\"\n ],\n \"time_options\": [\n \"5m\",\n \"15m\",\n \"1h\",\n \"6h\",\n \"12h\",\n \"24h\",\n \"2d\",\n \"7d\",\n \"30d\"\n ]\n },\n \"timezone\": \"\",\n \"title\": \"BFT ordering\",\n \"uid\": \"UJyurCTWz\",\n \"version\": 2,\n \"weekStart\": \"\"\n}\n" + "bft-ordering.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"description\": \"\",\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 8,\n \"links\": [],\n \"liveNow\": false,\n \"panels\": [\n {\n \"collapsed\": false,\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 64,\n \"panels\": [],\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Global overview (instance view)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"ISS proceeds in epochs with stable leader assignments to \\\"segments\\\" and stable topology (i.e., sequencers can be added and remove only at epoch boundaries and leaders are reassigned to segments also at epoch boundaries). This is the current epoch.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 5,\n \"x\": 0,\n \"y\": 1\n },\n \"hideTimeOverride\": false,\n \"id\": 72,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_epoch{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Current Epoch\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size in blocks of the current epoch.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 3,\n \"x\": 5,\n \"y\": 1\n },\n \"hideTimeOverride\": false,\n \"id\": 80,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_epoch_length{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Epoch Length\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of active validators\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 3,\n \"x\": 8,\n \"y\": 1\n },\n \"hideTimeOverride\": false,\n \"id\": 75,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_validators{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Validators\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of connected and authenticated P2P peers for the selected sequencer. It should be > 2/3 at all times.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"percentage\",\n \"steps\": [\n {\n \"color\": \"dark-red\",\n \"value\": null\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 67\n },\n {\n \"color\": \"dark-green\",\n \"value\": 100\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 13,\n \"x\": 11,\n \"y\": 1\n },\n \"hideTimeOverride\": true,\n \"id\": 53,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"center\",\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"last\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"textMode\": \"auto\"\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_p2p_connections_connected{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": true,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"Connected ({{namespace}}.{{node}})\",\n \"range\": false,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_p2p_connections_authenticated{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"hide\": false,\n \"instant\": true,\n \"legendFormat\": \"Authenticated ({{namespace}}.{{node}})\",\n \"range\": false,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Peers count\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 3,\n \"x\": 0,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 99,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_max_tolerated_faults{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Max tolerated faults\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The size of a weak quorum or non-faulty nodes, needed e.g. to disseminate batches\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 3,\n \"x\": 3,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 100,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_weak_quorum{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Weak quorum\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The size of a strong quorum, needed for ordering consensus\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 3,\n \"x\": 6,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 101,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_strong_quorum{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Strong quorum\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Latest block number ordered and available for the read path. When there is no traffic, a block can be empty.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 7,\n \"x\": 9,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 4,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job, node) (daml_sequencer_bftordering_global_ordered_blocks_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"})\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"instant\": true,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Output Blocks\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Total number of requests ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 8,\n \"x\": 16,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 40,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace,job,node) (histogram_sum(daml_sequencer_bftordering_output_block_size_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}))\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Output Requests\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Sequencers that are part of the BFT ordering topology\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisGridShow\": true,\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 12,\n \"x\": 0,\n \"y\": 24\n },\n \"id\": 91,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_topology_member{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"interval\": \"\",\n \"legendFormat\": \" (reporter: {{namespace}}.{{node}}) {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"BFT ordering members\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Sequencers that are allowed to propose blocks for ordering\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 12,\n \"x\": 12,\n \"y\": 24\n },\n \"id\": 95,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_topology_leader{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \" (reporter: {{namespace}}.{{node}}) {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"BFT ordering leaders\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 8,\n \"x\": 0,\n \"y\": 45\n },\n \"id\": 96,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_p2p_authenticated_endpoint{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{endpoint}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P authenticated endpoints\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 8,\n \"x\": 8,\n \"y\": 45\n },\n \"id\": 97,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_p2p_unauthenticated_endpoint{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{endpoint}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P connected but unauthenticated endpoints\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"hidden\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 8,\n \"x\": 16,\n \"y\": 45\n },\n \"id\": 98,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_p2p_disconnected_endpoint{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{endpoint}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P disconnected endpoints\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Wall-clock duration between the instant a request is received by the CantonBFT orderer and the instant the fully assembled ordered block metadata is stored, just before the request is pushed to the post-ordering stages. Its meaningfulness requires wall-clock synchronization, as not all ordered requests are received and timestamped on this node.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 66\n },\n \"id\": 87,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_global_requests_ordering_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests end-to-end ordering latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of requests ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 66\n },\n \"hideTimeOverride\": true,\n \"id\": 88,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\",\n \"sum\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_global_ordered_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - requests/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of requests per consensus block.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 73\n },\n \"hideTimeOverride\": true,\n \"id\": 49,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\",\n \"sum\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_sum(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))) / histogram_count(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} ({{mode}})\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests / Block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of blocks ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 73\n },\n \"hideTimeOverride\": true,\n \"id\": 68,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by (namespace, job, node, is_block_empty) (irate(daml_sequencer_bftordering_global_ordered_blocks_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} (empty: {{is_block_empty}})\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Throughput - blocks/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches (proofs of availability) per consensus block. Note that < 1 average batch per block likely means empty blocks are being created.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 80\n },\n \"hideTimeOverride\": true,\n \"id\": 89,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\",\n \"sum\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_sum(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))) / histogram_count(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} ({{mode}})\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batches (PoAs) / Block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of batches (proofs of availability) ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"hideTimeOverride\": true,\n \"id\": 90,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_global_ordered_batches_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - batches/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Delay introduced by the sequencer core being too slow consuming blocks from ordering, in milliseconds\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"ms\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 87\n },\n \"hideTimeOverride\": true,\n \"id\": 102,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_sequencer_core_backpressure_current_delay_millis{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer core backpressure delay (ms)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the buffer in the subscription from the orderer to the sequencer core, in blocks\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 87\n },\n \"hideTimeOverride\": true,\n \"id\": 103,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\",\n \"min\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_sequencer_core_subscription_buffer_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\\n\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer core buffer size (blocks #)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"How many epochs a sequencer is blacklisted\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 94\n },\n \"id\": 86,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"builder\",\n \"expr\": \"daml_sequencer_bftordering_blacklist_sequencer{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{blacklist_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Blacklisted Sequencers\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 94\n },\n \"hideTimeOverride\": true,\n \"id\": 105,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\",\n \"min\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"increase(daml_sequencer_bftordering_topology_blacklisted_epochs{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[5m])\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}): {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Total number of epochs a sequencer was blacklisted in the last 5m\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 94\n },\n \"hideTimeOverride\": true,\n \"id\": 105,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\",\n \"min\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_topology_blacklisted_epochs{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\\n\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}): {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Total number of epochs a sequencer is blacklisted\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the stream buffer at various stages\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 101\n },\n \"id\": 104,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_block_stream_buffer_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}.{{element}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Stream Buffer Metrics\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Wall-clock time of the ordered block being provided to the sequencer minus BFT time of the block\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [\n {\n \"options\": {\n \"match\": \"null\",\n \"result\": {\n \"text\": \"N/A\"\n }\n },\n \"type\": \"special\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 2\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 6,\n \"x\": 12,\n \"y\": 101\n },\n \"hideTimeOverride\": true,\n \"id\": 85,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_delay_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block delay\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the payloads of blocks ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 6,\n \"x\": 18,\n \"y\": 101\n },\n \"hideTimeOverride\": false,\n \"id\": 50,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_bytes{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}.{{mode}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Output Block Payload Size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Percentage of votes received by the currently selected sequencer at every major PBFT stage. Should be > 2/3 at all times, else a leader change (\\\"view change\\\" in PBFT parlance) occurs automatically.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"fillOpacity\": 70,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"decimals\": 0,\n \"links\": [],\n \"mappings\": [],\n \"max\": 1,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"percentage\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": null\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 67\n },\n {\n \"color\": \"#6ED0E0\",\n \"value\": 80\n },\n {\n \"color\": \"dark-green\",\n \"value\": 100\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 28,\n \"w\": 24,\n \"x\": 0,\n \"y\": 108\n },\n \"hideTimeOverride\": false,\n \"id\": 73,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"always\",\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_consensus_prepare_votes_percent{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) Prepare {{voting_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_commit_votes_percent{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) Commit {{voting_sequencer}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"PBFT Voting Power %\",\n \"transformations\": [\n {\n \"id\": \"renameByRegex\",\n \"options\": {\n \"regex\": \"Prepare SEQ::(.*)::.*\",\n \"renamePattern\": \"Prepare $1\"\n }\n },\n {\n \"id\": \"renameByRegex\",\n \"options\": {\n \"regex\": \"Commit SEQ::(.*)::.*\",\n \"renamePattern\": \"Commit $1\"\n }\n }\n ],\n \"type\": \"state-timeline\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Count of non-compliant and possibly byzantine protocol behaviors detected by the currently selected sequencer.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 136\n },\n \"hideTimeOverride\": false,\n \"id\": 74,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_security_noncompliant_behavior_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Non-compliant behaviors\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the incoming requests buffer (\\\"mempool\\\") for the selected sequencer. Its maximum size in bytes and requests count can be currently set via static configuration.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 136\n },\n \"hideTimeOverride\": true,\n \"id\": 78,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_ingress_requests_queued{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Mempool Size (requests)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of proposals requested by consensus.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 143\n },\n \"hideTimeOverride\": true,\n \"id\": 77,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_requested_proposals{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block proposals requested by consensus\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches requested to be proposed in consensus.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 143\n },\n \"hideTimeOverride\": true,\n \"id\": 79,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_requested_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block proposals requested by consensus (batches)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches that are being disseminated.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 150\n },\n \"hideTimeOverride\": true,\n \"id\": 82,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_disseminating_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ready_for_consensus=~\\\"false|true\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batches being disseminated\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches requested by the availability module and not yet provided.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 150\n },\n \"hideTimeOverride\": true,\n \"id\": 76,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_mempool_requested_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Outstanding batch requests\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 157\n },\n \"id\": 55,\n \"panels\": [],\n \"repeat\": \"instance\",\n \"repeatDirection\": \"h\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sequencer overview: $reporting_sequencer\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of requests in batches that are being disseminated.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 158\n },\n \"hideTimeOverride\": true,\n \"id\": 83,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_disseminating_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ready_for_consensus=~\\\"false|true\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests being disseminated\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Total network ingress for the currently selected sequencer split by sender.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 100,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"never\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"links\": [],\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 6,\n \"x\": 12,\n \"y\": 158\n },\n \"id\": 59,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job, node, source_sequencer) (daml_sequencer_bftordering_p2p_receive_received_bytes_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"})\",\n \"format\": \"time_series\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{source_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Network Input\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Total network egress for the currently selected sequencer split by destination.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 100,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"never\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"links\": [],\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 6,\n \"x\": 18,\n \"y\": 158\n },\n \"id\": 58,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job, node, target_sequencer) (daml_sequencer_bftordering_p2p_send_sent_bytes_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"})\",\n \"format\": \"time_series\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{target_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Network Output\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of payload bytes in requests within batches that are being disseminated.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 165\n },\n \"hideTimeOverride\": true,\n \"id\": 84,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_disseminating_bytes{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ready_for_consensus=~\\\"false|true\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Bytes being disseminated\",\n \"type\": \"timeseries\"\n }\n ],\n \"refresh\": \"30s\",\n \"schemaVersion\": 38,\n \"style\": \"dark\",\n \"tags\": [\n \"bft-sequencers\"\n ],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status,namespace}\",\n \"hide\": 0,\n \"includeAll\": true,\n \"label\": \"Namespace\",\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"label\": \"Job\",\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"label\": \"Node\",\n \"multi\": true,\n \"name\": \"node\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"Percentile\",\n \"multi\": false,\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": false,\n \"text\": \".5\",\n \"value\": \".5\"\n },\n {\n \"selected\": false,\n \"text\": \".6\",\n \"value\": \".6\"\n },\n {\n \"selected\": false,\n \"text\": \".7\",\n \"value\": \".7\"\n },\n {\n \"selected\": false,\n \"text\": \".75\",\n \"value\": \".75\"\n },\n {\n \"selected\": false,\n \"text\": \".8\",\n \"value\": \".8\"\n },\n {\n \"selected\": false,\n \"text\": \".9\",\n \"value\": \".9\"\n },\n {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n {\n \"selected\": false,\n \"text\": \".99\",\n \"value\": \".99\"\n },\n {\n \"selected\": false,\n \"text\": \".999\",\n \"value\": \".999\"\n }\n ],\n \"query\": \".5,.6,.7,.75,.8,.9,.95,.99,.999\",\n \"queryValue\": \"\",\n \"skipUrlSync\": false,\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-15m\",\n \"to\": \"now\"\n },\n \"timepicker\": {\n \"refresh_intervals\": [\n \"5s\",\n \"10s\",\n \"30s\",\n \"1m\",\n \"5m\",\n \"15m\",\n \"30m\",\n \"1h\",\n \"2h\",\n \"1d\"\n ],\n \"time_options\": [\n \"5m\",\n \"15m\",\n \"1h\",\n \"6h\",\n \"12h\",\n \"24h\",\n \"2d\",\n \"7d\",\n \"30d\"\n ]\n },\n \"timezone\": \"\",\n \"title\": \"BFT ordering\",\n \"uid\": \"UJyurCTWz\",\n \"version\": 2,\n \"weekStart\": \"\"\n}\n" }, "kind": "ConfigMap", "metadata": { diff --git a/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering.json b/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering.json index c3abbf29b7..dbbe7dd3ec 100644 --- a/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering.json +++ b/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering.json @@ -2165,10 +2165,6 @@ "title": "Sequencer core buffer size (blocks #)", "type": "timeseries" }, - - - - { "datasource": { "type": "prometheus", @@ -2267,7 +2263,7 @@ "type": "prometheus", "uid": "prometheus" }, - "description": "Size of the stream buffer at various stages", + "description": "", "fieldConfig": { "defaults": { "color": { @@ -2310,7 +2306,7 @@ "steps": [ { "color": "green", - "value": 0 + "value": null } ] }, @@ -2324,6 +2320,220 @@ "x": 12, "y": 94 }, + "hideTimeOverride": true, + "id": 105, + "options": { + "legend": { + "calcs": [ + "max", + "min", + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.4.0", + "targets": [ + { + "datasource": { + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "increase(daml_sequencer_bftordering_topology_blacklisted_epochs{namespace=~\"$namespace\",job=~\"$job\",node=~\"$node\"}[5m])", + "format": "time_series", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "(reporter: {{namespace}}.{{node}}): {{sequencer_id}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total number of epochs a sequencer was blacklisted in the last 5m", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 94 + }, + "hideTimeOverride": true, + "id": 105, + "options": { + "legend": { + "calcs": [ + "max", + "min", + "mean" + ], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "10.4.0", + "targets": [ + { + "datasource": { + "uid": "prometheus" + }, + "disableTextWrap": false, + "editorMode": "code", + "exemplar": false, + "expr": "daml_sequencer_bftordering_topology_blacklisted_epochs{namespace=~\"$namespace\",job=~\"$job\",node=~\"$node\"}\n", + "format": "time_series", + "fullMetaSearch": false, + "hide": false, + "includeNullMetadata": true, + "instant": false, + "interval": "", + "intervalFactor": 1, + "legendFormat": "(reporter: {{namespace}}.{{node}}): {{sequencer_id}}", + "range": true, + "refId": "A", + "useBackend": false + } + ], + "title": "Total number of epochs a sequencer is blacklisted", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Size of the stream buffer at various stages", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + } + ] + }, + "unit": "none" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 101 + }, "id": 104, "options": { "legend": { @@ -2437,7 +2647,7 @@ "h": 7, "w": 6, "x": 12, - "y": 94 + "y": 101 }, "hideTimeOverride": true, "id": 85, @@ -2571,7 +2781,7 @@ "h": 7, "w": 6, "x": 18, - "y": 94 + "y": 101 }, "hideTimeOverride": false, "id": 50, @@ -2659,7 +2869,7 @@ "h": 28, "w": 24, "x": 0, - "y": 101 + "y": 108 }, "hideTimeOverride": false, "id": 73, @@ -2821,7 +3031,7 @@ "h": 7, "w": 12, "x": 0, - "y": 129 + "y": 136 }, "hideTimeOverride": false, "id": 74, @@ -2927,7 +3137,7 @@ "h": 7, "w": 12, "x": 12, - "y": 129 + "y": 136 }, "hideTimeOverride": true, "id": 78, @@ -3032,7 +3242,7 @@ "h": 7, "w": 12, "x": 0, - "y": 136 + "y": 143 }, "hideTimeOverride": true, "id": 77, @@ -3137,7 +3347,7 @@ "h": 7, "w": 12, "x": 12, - "y": 136 + "y": 143 }, "hideTimeOverride": true, "id": 79, @@ -3242,7 +3452,7 @@ "h": 7, "w": 12, "x": 0, - "y": 143 + "y": 150 }, "hideTimeOverride": true, "id": 82, @@ -3347,7 +3557,7 @@ "h": 7, "w": 12, "x": 12, - "y": 143 + "y": 150 }, "hideTimeOverride": true, "id": 76, @@ -3394,7 +3604,7 @@ "h": 1, "w": 24, "x": 0, - "y": 150 + "y": 157 }, "id": 55, "panels": [], @@ -3480,7 +3690,7 @@ "h": 7, "w": 12, "x": 0, - "y": 151 + "y": 158 }, "hideTimeOverride": true, "id": 83, @@ -3581,7 +3791,7 @@ "h": 14, "w": 6, "x": 12, - "y": 151 + "y": 158 }, "id": 59, "options": { @@ -3674,7 +3884,7 @@ "h": 14, "w": 6, "x": 18, - "y": 151 + "y": 158 }, "id": 58, "options": { @@ -3775,7 +3985,7 @@ "h": 7, "w": 12, "x": 0, - "y": 158 + "y": 165 }, "hideTimeOverride": true, "id": 84, From 6f52e081b706b3051b62241b5ba4df278dd11a3c Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Mon, 3 Aug 2026 16:57:28 +0200 Subject: [PATCH 172/329] Add timeout to the grpcClients in splice (#6597) Signed-off-by: Julien Tinguely --- .../environment/SpliceLedgerClient.scala | 2 +- .../environment/ledger/api/LedgerClient.scala | 78 ++++++++++++------- 2 files changed, 49 insertions(+), 31 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SpliceLedgerClient.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SpliceLedgerClient.scala index e5ade0c2c5..1b08274706 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SpliceLedgerClient.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SpliceLedgerClient.scala @@ -64,7 +64,7 @@ class SpliceLedgerClient( ) val channel = builder.build - new LedgerClient(channel, applicationId, getToken, loggerFactory) + new LedgerClient(channel, applicationId, getToken, loggerFactory, timeouts) } private val inactiveContractsCallbacks = new AtomicReference[Seq[String => Unit]](Seq()) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ledger/api/LedgerClient.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ledger/api/LedgerClient.scala index 00b6a39c31..d51253eaef 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ledger/api/LedgerClient.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ledger/api/LedgerClient.scala @@ -38,7 +38,11 @@ import org.lfdecentralizedtrust.splice.store.MultiDomainAcsStore.IngestionFilter import org.lfdecentralizedtrust.splice.util.DisclosedContracts import com.digitalasset.canton.SynchronizerAlias import com.digitalasset.canton.admin.api.client.data.parties.PartyDetails -import com.digitalasset.canton.config.NonNegativeFiniteDuration +import com.digitalasset.canton.config.{ + NonNegativeDuration, + NonNegativeFiniteDuration, + ProcessingTimeout, +} import com.digitalasset.canton.crypto.Fingerprint import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.ledger.client.GrpcChannel @@ -50,13 +54,14 @@ import com.digitalasset.canton.tracing.{TraceContext, TraceContextGrpc} import com.digitalasset.canton.util.ErrorUtil import com.google.protobuf.{ByteString, Duration} import com.google.protobuf.field_mask.FieldMask -import io.grpc.{Channel, StatusRuntimeException, Status as GrpcStatus} +import io.grpc.{Channel, Deadline, StatusRuntimeException, Status as GrpcStatus} import io.grpc.stub.{AbstractStub, StreamObserver} import org.apache.pekko.NotUsed import org.apache.pekko.stream.scaladsl.Source import java.io.Closeable import java.util.concurrent.TimeUnit +import scala.concurrent.duration.FiniteDuration import scala.concurrent.{ExecutionContext, Future, Promise} import scala.jdk.CollectionConverters.* @@ -100,6 +105,7 @@ private[environment] class LedgerClient( expectedTokenUser: String, getToken: () => Future[Option[AuthToken]], override protected val loggerFactory: NamedLoggerFactory, + timeouts: ProcessingTimeout, )(implicit esf: ExecutionSequencerFactory, ec: ExecutionContext, @@ -119,17 +125,23 @@ private[environment] class LedgerClient( }) } - private def withCredentialsAndTraceContext[T <: AbstractStub[T]]( - stub: T + private def withGrpcContext[T <: AbstractStub[T]]( + stub: T, + timeout: Option[NonNegativeDuration] = Some(timeouts.default), )(implicit tc: TraceContext): Future[T] = { getToken().map { token => - token.fold(stub) { token => + val authedStub = token.fold(stub) { token => checkTokenUser(token) TraceContextGrpc.addTraceContextToCallOptions( stub .withCallCredentials(new AuthCallCredentials(token.accessToken)) ) } + timeout.map(_.duration) match { + case Some(finite: FiniteDuration) => + authedStub.withDeadline(Deadline.after(finite.length, finite.unit)) + case _ => authedStub + } } } private val commandServiceStub: CommandServiceGrpc.CommandServiceStub = @@ -170,7 +182,7 @@ private[environment] class LedgerClient( ): Future[Long] = { val req = lapi.state_service.GetLedgerEndRequest() for { - stub <- withCredentialsAndTraceContext(stateServiceStub) + stub <- withGrpcContext(stateServiceStub) resp <- stub.getLedgerEnd(req) } yield resp.offset } @@ -180,7 +192,7 @@ private[environment] class LedgerClient( ): Future[Long] = { val req = lapi.state_service.GetLatestPrunedOffsetsRequest() for { - stub <- withCredentialsAndTraceContext(stateServiceStub) + stub <- withGrpcContext(stateServiceStub) resp <- stub.getLatestPrunedOffsets(req) } yield resp.participantPrunedUpToInclusive } @@ -190,7 +202,7 @@ private[environment] class LedgerClient( )(implicit tc: TraceContext): Source[lapi.state_service.GetActiveContractsResponse, NotUsed] = toSource( for { - stub <- withCredentialsAndTraceContext(stateServiceStub) + stub <- withGrpcContext(stateServiceStub, timeout = Some(timeouts.unbounded)) } yield ClientAdapter .serverStreaming(request, stub.getActiveContracts) ) @@ -199,7 +211,7 @@ private[environment] class LedgerClient( tc: TraceContext ): Future[Option[CreatedEvent]] = { (for { - stub <- withCredentialsAndTraceContext(contractServiceStub) + stub <- withGrpcContext(contractServiceStub) contract <- stub.getContract( new lapi.contract_service.GetContractRequest( contractId.contractId, @@ -217,7 +229,10 @@ private[environment] class LedgerClient( )(implicit tc: TraceContext): Source[LedgerClient.GetTreeUpdatesResponse, NotUsed] = { toSource( for { - stub <- withCredentialsAndTraceContext(updateServiceStub) + stub <- withGrpcContext( + updateServiceStub, + timeout = Some(timeouts.unbounded), + ) } yield ClientAdapter .serverStreaming(request.toProto, stub.getUpdates) .mapConcat(GetTreeUpdatesResponse.fromProto) @@ -231,7 +246,7 @@ private[environment] class LedgerClient( import lapi.update_service.GetUpdateResponse.Update as U val updateFormat = LedgerClient.ledgerEffectsUpdateFormat(actAs) for { - stub <- withCredentialsAndTraceContext(updateServiceStub) + stub <- withGrpcContext(updateServiceStub) response <- stub.getUpdateByOffset( lapi.update_service .GetUpdateByOffsetRequest(offset = offset, updateFormat = Some(updateFormat)) @@ -320,7 +335,7 @@ private[environment] class LedgerClient( ) .build() for { - stubWithCredsAndTraceContext <- withCredentialsAndTraceContext(commandServiceStub) + stubWithCredsAndTraceContext <- withGrpcContext(commandServiceStub, Some(timeouts.unbounded)) stub = deadline .map(duration => stubWithCredsAndTraceContext @@ -346,7 +361,7 @@ private[environment] class LedgerClient( tc: TraceContext, ): Future[lapi.interactive.interactive_submission_service.PrepareSubmissionResponse] = { for { - stub <- withCredentialsAndTraceContext(interactiveSubmissionServiceStub) + stub <- withGrpcContext(interactiveSubmissionServiceStub) result <- stub.prepareSubmission( lapi.interactive.interactive_submission_service.PrepareSubmissionRequest( commands = commands.map(c => lapi.commands.Command.fromJavaProto(c.toProtoCommand)), @@ -381,7 +396,7 @@ private[environment] class LedgerClient( tc: TraceContext, ): Future[lapi.interactive.interactive_submission_service.ExecuteSubmissionResponse] = for { - stub <- withCredentialsAndTraceContext(interactiveSubmissionServiceStub) + stub <- withGrpcContext(interactiveSubmissionServiceStub) result <- stub.executeSubmission( lapi.interactive.interactive_submission_service.ExecuteSubmissionRequest( preparedTransaction = Some(preparedTransaction), @@ -414,7 +429,7 @@ private[environment] class LedgerClient( def listPackages()(implicit ec: ExecutionContext, tc: TraceContext): Future[Seq[String]] = { val request = ListPackagesRequest() for { - stub <- withCredentialsAndTraceContext(packageServiceStub) + stub <- withGrpcContext(packageServiceStub) res <- stub .listPackages(request) .map(_.packageIds) @@ -427,7 +442,7 @@ private[environment] class LedgerClient( )(implicit ec: ExecutionContext, tc: TraceContext): Future[Unit] = { val request = v1User.DeleteUserRequest(userId, identityProviderId.getOrElse("")) for { - stub <- withCredentialsAndTraceContext(userManagementServiceStub) + stub <- withGrpcContext(userManagementServiceStub) res <- stub.deleteUser(request).map(_ => ()) } yield res } @@ -447,7 +462,7 @@ private[environment] class LedgerClient( identityProviderId.getOrElse(""), ) for { - stub <- withCredentialsAndTraceContext(userManagementServiceStub) + stub <- withGrpcContext(userManagementServiceStub) res <- stub.listUsers(requestBuilder) } yield ( res.users.map(v1User.User.toJavaProto), @@ -472,7 +487,7 @@ private[environment] class LedgerClient( ): Future[UserManagementServiceOuterClass.User] = { val requestBuilder = v1User.GetUserRequest(userId, identityProviderId.getOrElse("")) for { - stub <- withCredentialsAndTraceContext(userManagementServiceStub) + stub <- withGrpcContext(userManagementServiceStub) res <- stub.getUser(requestBuilder).map(u => v1User.User.toJavaProto(u.getUser)) } yield res } @@ -504,7 +519,7 @@ private[environment] class LedgerClient( )(implicit ec: ExecutionContext, tc: TraceContext): Future[Seq[PartyDetails]] = { val request = GetPartiesRequest(parties.map(_.toProtoPrimitive), "") for { - stub <- withCredentialsAndTraceContext(partyManagementServiceStub) + stub <- withGrpcContext(partyManagementServiceStub) res <- stub .getParties(request) .map(r => r.partyDetails.map(details => PartyDetails.fromProtoPartyDetails(details))) @@ -538,7 +553,7 @@ private[environment] class LedgerClient( initialRights.map(javaRightToV1Right), ) for { - stub <- withCredentialsAndTraceContext(userManagementServiceStub) + stub <- withGrpcContext(userManagementServiceStub) res <- stub .createUser(request) .map(r => CreateUserResponse.fromProto(v1User.CreateUserResponse.toJavaProto(r)).getUser) @@ -589,7 +604,7 @@ private[environment] class LedgerClient( Some(mask), ) for { - stub <- withCredentialsAndTraceContext(userManagementServiceStub) + stub <- withGrpcContext(userManagementServiceStub) res <- stub.updateUser(request) } yield res }.map(_ => ()) @@ -600,7 +615,7 @@ private[environment] class LedgerClient( ): Future[Seq[User.Right]] = { val request = v1User.ListUserRightsRequest(userId, identityProviderId.getOrElse("")) for { - stub <- withCredentialsAndTraceContext(userManagementServiceStub) + stub <- withGrpcContext(userManagementServiceStub) res <- stub .listUserRights(request) .map(r => @@ -627,7 +642,7 @@ private[environment] class LedgerClient( ) for { - stub <- withCredentialsAndTraceContext(userManagementServiceStub) + stub <- withGrpcContext(userManagementServiceStub) res <- stub.grantUserRights(request).map(_ => ()) } yield res } @@ -646,7 +661,7 @@ private[environment] class LedgerClient( "", ) for { - stub <- withCredentialsAndTraceContext(userManagementServiceStub) + stub <- withGrpcContext(userManagementServiceStub) res <- stub.revokeUserRights(request).map(_ => ()) } yield res } @@ -660,7 +675,7 @@ private[environment] class LedgerClient( command: LedgerClient.ReassignmentCommand, )(implicit traceContext: TraceContext): Future[Unit] = for { - stub <- withCredentialsAndTraceContext(commandSubmissionServiceStub) + stub <- withGrpcContext(commandSubmissionServiceStub) res <- stub .submitReassignment( LedgerClient @@ -683,7 +698,10 @@ private[environment] class LedgerClient( )(implicit tc: TraceContext): Source[CompletionStreamResponse, NotUsed] = toSource( for { - stub <- withCredentialsAndTraceContext(multidomainCompletionServiceStub) + stub <- withGrpcContext( + multidomainCompletionServiceStub, + timeout = Some(timeouts.unbounded), + ) } yield ClientAdapter.serverStreaming( lapi.command_completion_service.CompletionStreamRequest( userId = userId, @@ -703,7 +721,7 @@ private[environment] class LedgerClient( "", ) for { - stub <- withCredentialsAndTraceContext(stateServiceStub) + stub <- withGrpcContext(stateServiceStub) res <- stub.getConnectedSynchronizers(req).map { resp => resp.connectedSynchronizers.map { cd => SynchronizerAlias.tryCreate(cd.synchronizerAlias) -> SynchronizerId.tryFromString( @@ -719,7 +737,7 @@ private[environment] class LedgerClient( tc: TraceContext ): Future[Seq[identity_provider_config_service.IdentityProviderConfig]] = { for { - stub <- withCredentialsAndTraceContext(identityProviderConfigServiceStub) + stub <- withGrpcContext(identityProviderConfigServiceStub) res <- stub .listIdentityProviderConfigs( identity_provider_config_service.ListIdentityProviderConfigsRequest() @@ -735,7 +753,7 @@ private[environment] class LedgerClient( audience: String, )(implicit tc: TraceContext): Future[Unit] = { for { - stub <- withCredentialsAndTraceContext(identityProviderConfigServiceStub) + stub <- withGrpcContext(identityProviderConfigServiceStub) _ <- stub.createIdentityProviderConfig( identity_provider_config_service.CreateIdentityProviderConfigRequest( Some( @@ -758,7 +776,7 @@ private[environment] class LedgerClient( vettingAsOfTime: CantonTimestamp, )(implicit tc: TraceContext): Future[Seq[PackageReference]] = { for { - stub <- withCredentialsAndTraceContext(interactiveSubmissionServiceStub) + stub <- withGrpcContext(interactiveSubmissionServiceStub) response <- stub.getPreferredPackages( lapi.interactive.interactive_submission_service.GetPreferredPackagesRequest( packageVettingRequirements = packageRequirements.map { case (pkg, parties) => From 69df645a742f76b6a2969f952e64e1c3c58d7d95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Tue, 4 Aug 2026 15:31:41 +0200 Subject: [PATCH 173/329] Support splice helm chart migration to Postgres 18 image (#6637) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Signed-off-by: Oriol Muñoz Co-authored-by: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> --- .../expected/multi-validator/expected.json | 282 +++++---- .../expected/validator-runbook/expected.json | 266 +++++++-- .../src/physicalSynchronizerConfig.ts | 11 + cluster/pulumi/common-sv/src/sv.ts | 3 + .../common-validator/src/participant.ts | 1 + cluster/pulumi/common/src/config/cloudSql.ts | 26 - .../pulumi/common/src/config/configSchema.ts | 6 +- cluster/pulumi/common/src/config/database.ts | 52 ++ cluster/pulumi/common/src/config/index.ts | 2 +- cluster/pulumi/common/src/postgres.ts | 562 ++++++++++++++++-- cluster/pulumi/multi-validator/src/config.ts | 6 +- .../pulumi/multi-validator/src/installNode.ts | 6 +- .../src/multiNodeDeployment.ts | 17 +- .../pulumi/multi-validator/src/postgres.ts | 27 +- cluster/pulumi/observability/src/config.ts | 11 +- .../pulumi/observability/src/observability.ts | 17 +- cluster/pulumi/splitwell/src/splitwell.ts | 3 + cluster/pulumi/sv-canton/src/canton.ts | 3 + cluster/pulumi/sv-runbook/src/postgres.ts | 9 +- cluster/pulumi/sv/src/participant.ts | 1 + .../validator-runbook/src/installNode.ts | 17 +- cluster/pulumi/validator1/src/validator1.ts | 2 + 22 files changed, 1039 insertions(+), 291 deletions(-) delete mode 100644 cluster/pulumi/common/src/config/cloudSql.ts create mode 100644 cluster/pulumi/common/src/config/database.ts diff --git a/cluster/expected/multi-validator/expected.json b/cluster/expected/multi-validator/expected.json index 9a039ab50e..0d8ab6e018 100644 --- a/cluster/expected/multi-validator/expected.json +++ b/cluster/expected/multi-validator/expected.json @@ -348,7 +348,7 @@ "env": [ { "name": "CANTON_PARTICIPANT_POSTGRES_SERVER", - "value": "postgres-0" + "value": "postgres-0.multi-validator.svc.cluster.local" }, { "name": "CANTON_PARTICIPANT_POSTGRES_DB", @@ -547,7 +547,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-0 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-0.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " ], "env": [ { @@ -560,7 +560,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -796,7 +796,7 @@ "env": [ { "name": "CANTON_PARTICIPANT_POSTGRES_SERVER", - "value": "postgres-1" + "value": "postgres-1.multi-validator.svc.cluster.local" }, { "name": "CANTON_PARTICIPANT_POSTGRES_DB", @@ -995,7 +995,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-1 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-1.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " ], "env": [ { @@ -1008,7 +1008,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -1244,7 +1244,7 @@ "env": [ { "name": "CANTON_PARTICIPANT_POSTGRES_SERVER", - "value": "postgres-2" + "value": "postgres-2.multi-validator.svc.cluster.local" }, { "name": "CANTON_PARTICIPANT_POSTGRES_DB", @@ -1443,7 +1443,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-2 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-2.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " ], "env": [ { @@ -1456,7 +1456,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -1692,7 +1692,7 @@ "env": [ { "name": "CANTON_PARTICIPANT_POSTGRES_SERVER", - "value": "postgres-3" + "value": "postgres-3.multi-validator.svc.cluster.local" }, { "name": "CANTON_PARTICIPANT_POSTGRES_DB", @@ -1891,7 +1891,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-3 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-3.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " ], "env": [ { @@ -1904,7 +1904,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -2140,7 +2140,7 @@ "env": [ { "name": "CANTON_PARTICIPANT_POSTGRES_SERVER", - "value": "postgres-4" + "value": "postgres-4.multi-validator.svc.cluster.local" }, { "name": "CANTON_PARTICIPANT_POSTGRES_DB", @@ -2339,7 +2339,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-4 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-4.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb participant_2_00\ncreateDb participant_2_01\ncreateDb participant_2_02\ncreateDb participant_2_03\ncreateDb participant_2_04\ncreateDb participant_2_05\ncreateDb participant_2_06\ncreateDb participant_2_07\ncreateDb participant_2_08\ncreateDb participant_2_09\n " ], "env": [ { @@ -2352,7 +2352,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -2646,7 +2646,7 @@ }, { "name": "SPLICE_APP_POSTGRES_HOST", - "value": "postgres-0" + "value": "postgres-0.multi-validator.svc.cluster.local" }, { "name": "SPLICE_APP_POSTGRES_PORT", @@ -2820,7 +2820,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-0 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-0.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " ], "env": [ { @@ -2833,7 +2833,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -3095,7 +3095,7 @@ }, { "name": "SPLICE_APP_POSTGRES_HOST", - "value": "postgres-1" + "value": "postgres-1.multi-validator.svc.cluster.local" }, { "name": "SPLICE_APP_POSTGRES_PORT", @@ -3269,7 +3269,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-1 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-1.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " ], "env": [ { @@ -3282,7 +3282,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -3544,7 +3544,7 @@ }, { "name": "SPLICE_APP_POSTGRES_HOST", - "value": "postgres-2" + "value": "postgres-2.multi-validator.svc.cluster.local" }, { "name": "SPLICE_APP_POSTGRES_PORT", @@ -3718,7 +3718,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-2 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-2.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " ], "env": [ { @@ -3731,7 +3731,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -3993,7 +3993,7 @@ }, { "name": "SPLICE_APP_POSTGRES_HOST", - "value": "postgres-3" + "value": "postgres-3.multi-validator.svc.cluster.local" }, { "name": "SPLICE_APP_POSTGRES_PORT", @@ -4167,7 +4167,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-3 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-3.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " ], "env": [ { @@ -4180,7 +4180,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -4442,7 +4442,7 @@ }, { "name": "SPLICE_APP_POSTGRES_HOST", - "value": "postgres-4" + "value": "postgres-4.multi-validator.svc.cluster.local" }, { "name": "SPLICE_APP_POSTGRES_PORT", @@ -4616,7 +4616,7 @@ "command": [ "bash", "-c", - "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-4 --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " + "\n function createDb() {\n local dbname=\"$1\"\n\n until errmsg=$(psql -h postgres-4.multi-validator.svc.cluster.local --username=cnadmin --dbname=cantonnet -c \"create database $dbname\" 2>&1); do\n if [[ $errmsg == *\"already exists\"* ]]; then\n echo \"Database $dbname already exists. Done.\"\n break\n fi\n\n echo \"trying to create postgres database $dbname, last error: $errmsg\";\n sleep 2;\n done\n }\n\n createDb cantonnet_v_00\ncreateDb cantonnet_v_01\ncreateDb cantonnet_v_02\ncreateDb cantonnet_v_03\ncreateDb cantonnet_v_04\ncreateDb cantonnet_v_05\ncreateDb cantonnet_v_06\ncreateDb cantonnet_v_07\ncreateDb cantonnet_v_08\ncreateDb cantonnet_v_09\n " ], "env": [ { @@ -4629,7 +4629,7 @@ } } ], - "image": "postgres:14", + "image": "postgres:18", "name": "pg-init" } ], @@ -4789,79 +4789,12 @@ "type": "random:index/randomPassword:RandomPassword" }, { - "custom": true, - "id": "", - "inputs": { - "length": 16, - "overrideSpecial": "_%@", - "special": true - }, - "name": "multi-validator-postgres-1-passwd", - "provider": "", - "type": "random:index/randomPassword:RandomPassword" - }, - { - "custom": true, - "id": "", - "inputs": { - "length": 16, - "overrideSpecial": "_%@", - "special": true - }, - "name": "multi-validator-postgres-2-passwd", - "provider": "", - "type": "random:index/randomPassword:RandomPassword" - }, - { - "custom": true, - "id": "", - "inputs": { - "length": 16, - "overrideSpecial": "_%@", - "special": true - }, - "name": "multi-validator-postgres-3-passwd", - "provider": "", - "type": "random:index/randomPassword:RandomPassword" - }, - { - "custom": true, - "id": "", - "inputs": { - "length": 16, - "overrideSpecial": "_%@", - "special": true - }, - "name": "multi-validator-postgres-4-passwd", - "provider": "", - "type": "random:index/randomPassword:RandomPassword" - }, - { - "custom": true, + "custom": false, "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "labels": { - "istio-injection": "enabled" - }, - "name": "multi-validator" - } - }, - "name": "multi-validator", - "provider": "", - "type": "kubernetes:core/v1:Namespace" - }, - { - "custom": true, - "id": "organization/infra/infra.mock", - "inputs": { - "name": "organization/infra/infra.mock" - }, - "name": "organization/infra/infra.mock", + "inputs": {}, + "name": "multi-validator-postgres-0", "provider": "", - "type": "pulumi:pulumi:StackReference" + "type": "canton:network:postgres" }, { "custom": true, @@ -4929,6 +4862,12 @@ } ] }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, "db": { "maxConnections": 1000, "pvcTemplateName": "pg-data-hd", @@ -4957,10 +4896,30 @@ }, "version": "0.3.20" }, - "name": "postgres-0", + "name": "multi-validator-postgres-0", "provider": "", "type": "kubernetes:helm.sh/v3:Release" }, + { + "custom": true, + "id": "", + "inputs": { + "length": 16, + "overrideSpecial": "_%@", + "special": true + }, + "name": "multi-validator-postgres-1-passwd", + "provider": "", + "type": "random:index/randomPassword:RandomPassword" + }, + { + "custom": false, + "id": "", + "inputs": {}, + "name": "multi-validator-postgres-1", + "provider": "", + "type": "canton:network:postgres" + }, { "custom": true, "id": "", @@ -5027,6 +4986,12 @@ } ] }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, "db": { "maxConnections": 1000, "pvcTemplateName": "pg-data-hd", @@ -5055,10 +5020,30 @@ }, "version": "0.3.20" }, - "name": "postgres-1", + "name": "multi-validator-postgres-1", "provider": "", "type": "kubernetes:helm.sh/v3:Release" }, + { + "custom": true, + "id": "", + "inputs": { + "length": 16, + "overrideSpecial": "_%@", + "special": true + }, + "name": "multi-validator-postgres-2-passwd", + "provider": "", + "type": "random:index/randomPassword:RandomPassword" + }, + { + "custom": false, + "id": "", + "inputs": {}, + "name": "multi-validator-postgres-2", + "provider": "", + "type": "canton:network:postgres" + }, { "custom": true, "id": "", @@ -5125,6 +5110,12 @@ } ] }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, "db": { "maxConnections": 1000, "pvcTemplateName": "pg-data-hd", @@ -5153,10 +5144,30 @@ }, "version": "0.3.20" }, - "name": "postgres-2", + "name": "multi-validator-postgres-2", "provider": "", "type": "kubernetes:helm.sh/v3:Release" }, + { + "custom": true, + "id": "", + "inputs": { + "length": 16, + "overrideSpecial": "_%@", + "special": true + }, + "name": "multi-validator-postgres-3-passwd", + "provider": "", + "type": "random:index/randomPassword:RandomPassword" + }, + { + "custom": false, + "id": "", + "inputs": {}, + "name": "multi-validator-postgres-3", + "provider": "", + "type": "canton:network:postgres" + }, { "custom": true, "id": "", @@ -5223,6 +5234,12 @@ } ] }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, "db": { "maxConnections": 1000, "pvcTemplateName": "pg-data-hd", @@ -5251,10 +5268,30 @@ }, "version": "0.3.20" }, - "name": "postgres-3", + "name": "multi-validator-postgres-3", "provider": "", "type": "kubernetes:helm.sh/v3:Release" }, + { + "custom": true, + "id": "", + "inputs": { + "length": 16, + "overrideSpecial": "_%@", + "special": true + }, + "name": "multi-validator-postgres-4-passwd", + "provider": "", + "type": "random:index/randomPassword:RandomPassword" + }, + { + "custom": false, + "id": "", + "inputs": {}, + "name": "multi-validator-postgres-4", + "provider": "", + "type": "canton:network:postgres" + }, { "custom": true, "id": "", @@ -5321,6 +5358,12 @@ } ] }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, "db": { "maxConnections": 1000, "pvcTemplateName": "pg-data-hd", @@ -5349,8 +5392,35 @@ }, "version": "0.3.20" }, - "name": "postgres-4", + "name": "multi-validator-postgres-4", "provider": "", "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "labels": { + "istio-injection": "enabled" + }, + "name": "multi-validator" + } + }, + "name": "multi-validator", + "provider": "", + "type": "kubernetes:core/v1:Namespace" + }, + { + "custom": true, + "id": "organization/infra/infra.mock", + "inputs": { + "name": "organization/infra/infra.mock" + }, + "name": "organization/infra/infra.mock", + "provider": "", + "type": "pulumi:pulumi:StackReference" } ] diff --git a/cluster/expected/validator-runbook/expected.json b/cluster/expected/validator-runbook/expected.json index 4a9823646f..a2be062bb4 100644 --- a/cluster/expected/validator-runbook/expected.json +++ b/cluster/expected/validator-runbook/expected.json @@ -480,8 +480,8 @@ }, "persistence": { "databaseName": "participant_2", - "host": "postgres.validator.svc.cluster.local", - "postgresName": "postgres", + "host": "postgres-helmless.validator.svc.cluster.local", + "postgresName": "postgres-helmless", "schema": "participant", "secretName": "postgres-secrets" }, @@ -582,19 +582,55 @@ "custom": true, "id": "", "inputs": { - "length": 16, - "overrideSpecial": "_%@", - "special": true + "apiVersion": "v1", + "data": { + "PGDATA": "/var/lib/postgresql/data/pgdata", + "POSTGRES_DB": "cantonnet", + "POSTGRES_INITDB_ARGS": "--data-checksums", + "POSTGRES_USER": "cnadmin" + }, + "kind": "ConfigMap", + "metadata": { + "name": "postgres-helmless-configuration", + "namespace": "validator" + } }, - "name": "validator-postgres-passwd", + "name": "validator-postgres-helmless-configuration", "provider": "", - "type": "random:index/randomPassword:RandomPassword" + "type": "kubernetes:core/v1:ConfigMap" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "postgres-helmless", + "namespace": "validator" + }, + "spec": { + "ports": [ + { + "name": "postgresdb", + "port": 5432, + "protocol": "TCP" + } + ], + "selector": { + "app": "postgres-helmless" + } + } + }, + "name": "validator-postgres-helmless-svc", + "provider": "", + "type": "kubernetes:core/v1:Service" }, { "custom": false, "id": "", "inputs": {}, - "name": "validator-postgres", + "name": "validator-postgres-helmless", "provider": "", "type": "canton:network:postgres" }, @@ -602,64 +638,186 @@ "custom": true, "id": "", "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-postgres", - "compat": "true", - "maxHistory": 10, - "name": "postgres", - "namespace": "validator", - "timeout": 600, - "values": { - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, + "apiVersion": "apps/v1", + "kind": "StatefulSet", + "metadata": { + "name": "postgres-helmless", + "namespace": "validator" + }, + "spec": { + "replicas": 1, + "selector": { + "matchLabels": { + "app": "postgres-helmless" + } + }, + "serviceName": "postgres-helmless", + "template": { + "metadata": { + "labels": { + "app": "postgres-helmless", + "namespace": "validator" + } + }, + "spec": { + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } ] } ] } - ] - } + } + }, + "containers": [ + { + "args": [ + "-c", + "max_connections=300", + "-c", + "max_wal_size=2GB" + ], + "env": [ + { + "name": "POSTGRES_PASSWORD", + "valueFrom": { + "secretKeyRef": { + "key": "postgresPassword", + "name": "postgres-secrets" + } + } + } + ], + "envFrom": [ + { + "configMapRef": { + "name": "postgres-helmless-configuration" + } + } + ], + "image": "postgres:18", + "imagePullPolicy": "IfNotPresent", + "livenessProbe": { + "exec": { + "command": [ + "psql", + "-U", + "cnadmin", + "-d", + "template1", + "-c", + "SELECT 1" + ] + }, + "failureThreshold": 3, + "periodSeconds": 10, + "successThreshold": 1, + "timeoutSeconds": 1 + }, + "name": "postgres-helmless", + "ports": [ + { + "containerPort": 5432, + "name": "postgresdb", + "protocol": "TCP" + } + ], + "resources": { + "limits": { + "memory": "12Gi" + }, + "requests": { + "cpu": "0.5", + "memory": "1Gi" + } + }, + "securityContext": { + "allowPrivilegeEscalation": false, + "capabilities": { + "drop": [ + "ALL" + ] + }, + "privileged": false, + "runAsGroup": 999, + "runAsNonRoot": true, + "runAsUser": 999 + }, + "volumeMounts": [ + { + "mountPath": "/var/lib/postgresql/data", + "name": "pg-data-hd" + } + ] + } + ], + "restartPolicy": "Always", + "securityContext": { + "fsGroup": 999, + "fsGroupChangePolicy": "OnRootMismatch", + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] } }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "db": { - "pvcTemplateName": "pg-data-hd", - "volumeSize": "200Gi", - "volumeStorageClass": "hyperdisk-standard-rwo" - }, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "persistence": { - "secretName": "postgres-secrets" - }, - "tolerations": [ + "volumeClaimTemplates": [ { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" + "metadata": { + "name": "pg-data-hd" + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "200Gi" + } + }, + "storageClassName": "hyperdisk-standard-rwo", + "volumeMode": "Filesystem" + } } ] - }, - "version": "0.3.20" + } }, - "name": "validator-postgres", + "name": "validator-postgres-helmless", "provider": "", - "type": "kubernetes:helm.sh/v3:Release" + "type": "kubernetes:apps/v1:StatefulSet" + }, + { + "custom": true, + "id": "", + "inputs": { + "length": 16, + "overrideSpecial": "_%@", + "special": true + }, + "name": "validator-postgres-passwd", + "provider": "", + "type": "random:index/randomPassword:RandomPassword" }, { "custom": true, diff --git a/cluster/pulumi/common-sv/src/physicalSynchronizerConfig.ts b/cluster/pulumi/common-sv/src/physicalSynchronizerConfig.ts index fd89fe8721..e885d75dd6 100644 --- a/cluster/pulumi/common-sv/src/physicalSynchronizerConfig.ts +++ b/cluster/pulumi/common-sv/src/physicalSynchronizerConfig.ts @@ -7,6 +7,7 @@ import { EnvVarConfigSchema, K8sResourceSchema, spliceConfig, + SplicePostgresSchema, } from '@canton-network/splice-pulumi-common'; import { z } from 'zod'; @@ -18,6 +19,11 @@ export const SvMediatorConfigSchema = z additionalEnvVars: z.array(EnvVarConfigSchema).default([]), additionalJvmOptions: z.string().optional(), cloudSql: CloudSqlWithOverrideConfigSchema, + // Mediator is either deployed on cloudSQL or is reset frequently, so we can skip migration + splicePostgres: SplicePostgresSchema.default({ + postgresImage: 'postgres:18', + deployment: 'docker-image', + }), resources: K8sResourceSchema, }) .strict(); @@ -27,6 +33,11 @@ export const SvSequencerConfigSchema = z additionalEnvVars: z.array(EnvVarConfigSchema).default([]), additionalJvmOptions: z.string().optional(), cloudSql: CloudSqlWithOverrideConfigSchema, + // Sequencer is either deployed on cloudSQL or is reset frequently, so we can skip migration + splicePostgres: SplicePostgresSchema.default({ + postgresImage: 'postgres:18', + deployment: 'docker-image', + }), resources: K8sResourceSchema, enableAntiAffinity: z.boolean().default(true), }) diff --git a/cluster/pulumi/common-sv/src/sv.ts b/cluster/pulumi/common-sv/src/sv.ts index fac7b413d4..658e9bda69 100644 --- a/cluster/pulumi/common-sv/src/sv.ts +++ b/cluster/pulumi/common-sv/src/sv.ts @@ -36,6 +36,7 @@ import { persistentHeapDumpsPvc, sanitizedForPostgres, spliceInstanceNames, + SplicePostgresConfig, svCometBftGovernanceKeyFromSecret, svCometBftGovernanceKeySecret, SvIdKey, @@ -358,6 +359,7 @@ export async function installSvNode( 'postgres', config.version, spliceConfig.pulumiProjectConfig.cloudSql, + spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, false, { logicalDecoding: !!baseConfig.scanApp?.bigQuery, @@ -372,6 +374,7 @@ export async function installSvNode( `cn-apps-pg`, config.version, svConfig.appsPg?.cloudSql ?? spliceConfig.pulumiProjectConfig.cloudSql, + spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, true, { logicalDecoding: !!baseConfig.scanApp?.bigQuery, diff --git a/cluster/pulumi/common-validator/src/participant.ts b/cluster/pulumi/common-validator/src/participant.ts index 23bbd36d32..10221ac7de 100644 --- a/cluster/pulumi/common-validator/src/participant.ts +++ b/cluster/pulumi/common-validator/src/participant.ts @@ -45,6 +45,7 @@ export async function installParticipant( `participant-pg`, activeVersion, spliceConfig.pulumiProjectConfig.cloudSql, + spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, true )); const participantValues: ChartValues = { diff --git a/cluster/pulumi/common/src/config/cloudSql.ts b/cluster/pulumi/common/src/config/cloudSql.ts deleted file mode 100644 index f9fc0c2ca7..0000000000 --- a/cluster/pulumi/common/src/config/cloudSql.ts +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 -import { merge } from 'lodash'; -import { z } from 'zod'; - -import { spliceConfig } from './config'; - -export const CloudSqlConfigSchema = z.object({ - enabled: z.boolean(), - // Docs on cloudsql maintenance windows: https://cloud.google.com/sql/docs/postgres/set-maintenance-window - maintenanceWindow: z - .object({ - day: z.number().min(1).max(7).default(2), // 1 (Monday) to 7 (Sunday) - hour: z.number().min(0).max(23).default(8), // 24-hour format UTC - }) - .default({ day: 2, hour: 8 }), - protected: z.boolean(), - tier: z.string(), - enterprisePlus: z.boolean(), - flags: z.record(z.string(), z.string()).default({}), - // https://cloud.google.com/sql/docs/mysql/backup-recovery/backups#retained-backups - // controls the number of automated gcp sql backups to retain - backupsToRetain: z.number().optional(), - databaseVersion: z.string().default('POSTGRES_14'), -}); -export type CloudSqlConfig = z.infer; diff --git a/cluster/pulumi/common/src/config/configSchema.ts b/cluster/pulumi/common/src/config/configSchema.ts index a170e817bd..f3bd9020ce 100644 --- a/cluster/pulumi/common/src/config/configSchema.ts +++ b/cluster/pulumi/common/src/config/configSchema.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { z } from 'zod'; -import { CloudSqlConfigSchema } from './cloudSql'; +import { CloudSqlConfigSchema, SplicePostgresSchema } from './database'; import { defaultActiveMigration, SynchronizerMigrationSchema } from './migrationSchema'; // This is a config that's relevant for all (most) pulumi projects. For project-specific configuration, @@ -13,6 +13,10 @@ const PulumiProjectConfigSchema = z.object({ hasPublicInfo: z.boolean(), interAppsDependencies: z.boolean(), cloudSql: CloudSqlConfigSchema, + defaultSplicePostgresConfig: SplicePostgresSchema.default({ + deployment: 'docker-image', + postgresImage: 'postgres:18', + }), allowDowngrade: z.boolean(), replacePostgresStatefulSetOnChanges: z.boolean().default(false), }); diff --git a/cluster/pulumi/common/src/config/database.ts b/cluster/pulumi/common/src/config/database.ts new file mode 100644 index 0000000000..9ff8bc8ac5 --- /dev/null +++ b/cluster/pulumi/common/src/config/database.ts @@ -0,0 +1,52 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { merge } from 'lodash'; +import { z } from 'zod'; + +import { spliceConfig } from './config'; + +export const CloudSqlConfigSchema = z.object({ + enabled: z.boolean(), + // Docs on cloudsql maintenance windows: https://cloud.google.com/sql/docs/postgres/set-maintenance-window + maintenanceWindow: z + .object({ + day: z.number().min(1).max(7).default(2), // 1 (Monday) to 7 (Sunday) + hour: z.number().min(0).max(23).default(8), // 24-hour format UTC + }) + .default({ day: 2, hour: 8 }), + protected: z.boolean(), + tier: z.string(), + enterprisePlus: z.boolean(), + flags: z.record(z.string(), z.string()).default({}), + // https://cloud.google.com/sql/docs/mysql/backup-recovery/backups#retained-backups + // controls the number of automated gcp sql backups to retain + backupsToRetain: z.number().optional(), + databaseVersion: z.string().default('POSTGRES_14'), +}); +export type CloudSqlConfig = z.infer; + +// Deployment strategy: +// - If no migration is necessary, just default docker-image will deploy the latest version +// - If you want to migrate data, you need to, in this order: +// 1) deployment = 'legacy-helm-chart' (this is the original state, which uses pg14, unconfigurable) +// 2) When the time to migrate comes, scale down all pods that use the database and set deployment = 'migrate' +// 3) Once the migration is complete (i.e., the DB pod is up and running and apps can connect to it), set deployment = 'docker-image' +// Once everything has been migrated we can drop this, as everything will be using docker-image. +export const SplicePostgresMigrateSchema = z.object({ + deployment: z.literal('migrate'), + migrationVolumeSize: z.string(), + postgresImage: z.string(), +}); +export type SplicePostgresMigrateConfig = z.infer; +export const SplicePostgresDockerImageSchema = z.object({ + deployment: z.literal('docker-image'), + postgresImage: z.string(), +}); +export type SplicePostgresDockerImageConfig = z.infer; +export const SplicePostgresSchema = z.union([ + z.object({ deployment: z.literal('legacy-helm-chart') }), + SplicePostgresMigrateSchema, + SplicePostgresDockerImageSchema, +]); + +export type SplicePostgresConfig = z.infer; diff --git a/cluster/pulumi/common/src/config/index.ts b/cluster/pulumi/common/src/config/index.ts index 33faa48511..cd82f1c099 100644 --- a/cluster/pulumi/common/src/config/index.ts +++ b/cluster/pulumi/common/src/config/index.ts @@ -6,7 +6,7 @@ import { spliceEnvConfig } from './envConfig'; export * from './configSchema'; export * from './kms'; -export * from './cloudSql'; +export * from './database'; export { spliceEnvConfig as config } from './envConfig'; export const DeploySvRunbook = spliceEnvConfig.envFlag('SPLICE_DEPLOY_SV_RUNBOOK', false); diff --git a/cluster/pulumi/common/src/postgres.ts b/cluster/pulumi/common/src/postgres.ts index f2141e0df8..5e63603a9f 100644 --- a/cluster/pulumi/common/src/postgres.ts +++ b/cluster/pulumi/common/src/postgres.ts @@ -1,24 +1,33 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import * as gcp from '@pulumi/gcp'; +import * as k8s from '@pulumi/kubernetes'; import * as pulumi from '@pulumi/pulumi'; import * as random from '@pulumi/random'; import * as _ from 'lodash'; import { Resource } from '@pulumi/pulumi'; import { CnChartVersion } from './artifacts'; -import { clusterSmallDisk, CloudSqlConfig, config } from './config'; +import { + clusterSmallDisk, + CloudSqlConfig, + config, + SplicePostgresConfig, + SplicePostgresMigrateConfig, + SplicePostgresDockerImageConfig, +} from './config'; import { spliceConfig } from './config/config'; import { GcpProject } from './config/gcpConfig'; import { appsAffinityAndTolerations, + CnInput, infraAffinityAndTolerations, installSpliceHelmChart, + SpliceCustomResourceOptions, } from './helm'; import { installPostgresPasswordSecret } from './secrets'; import { standardStorageClassName } from './storage/storageClass'; -import { createVolumeSnapshot } from './storage/volumeSnapshot'; -import { ChartValues, CLUSTER_BASENAME, ExactNamespace, GCP_ZONE } from './utils'; +import { CLUSTER_BASENAME, ExactNamespace, GCP_ZONE } from './utils'; const project = gcp.organizations.getProjectOutput({}); @@ -54,6 +63,7 @@ export interface Postgres extends pulumi.Resource { readonly databaseId?: pulumi.Output; readonly userName: string; + readonly database: Resource; addUser(userName: string): PostgresUser; } @@ -71,6 +81,7 @@ export class CloudPostgres user!: gcp.sql.User; userName!: string; zone!: string; + database!: Resource; private name!: string; private args!: CloudPostgresResolvedArgs; @@ -189,7 +200,7 @@ export class CloudPostgres ? await gcp.sql.getDatabase({ instance: existingInstanceName, name: 'cantonnet' }) : undefined; - new gcp.sql.Database( + const database = new gcp.sql.Database( `${namespace.logicalName}-db-${instanceName}-cantonnet`, { instance: databaseInstance.name, @@ -216,6 +227,7 @@ export class CloudPostgres this.user = defaultUser.sqlUser; this.userName = defaultUser.userName; this.zone = zone; + this.database = database; return { address: this.address, @@ -368,30 +380,35 @@ type CloudPostgresOutput = { secretName: pulumi.Output; }; -export class SplicePostgres extends pulumi.ComponentResource implements Postgres { +/** + * Legacy Helm-backed postgres declaration kept for migration windows where the + * old splice-postgres release must stay declared to avoid Pulumi deleting it. + */ +export class LegacyHelmSplicePostgres extends pulumi.ComponentResource implements Postgres { instanceName: string; namespace: ExactNamespace; address: pulumi.Output; pg: Resource; secretName: pulumi.Output; userName: string; + database: Resource; constructor( xns: ExactNamespace, instanceName: string, - alias: string, - secretName: string, - values?: ChartValues, + getOrInstallPassword: (parent: Resource) => k8s.core.v1.Secret, + values?: LegacyChartValues, overrideDbSizeFromValues?: boolean, disableProtection?: boolean, version?: CnChartVersion, - useInfraAffinityAndTolerations: boolean = false + useInfraAffinityAndTolerations: boolean = false, + resourceOpts?: SpliceCustomResourceOptions ) { const logicalName = xns.logicalName + '-' + instanceName; - const logicalNameAlias = xns.logicalName + '-' + alias; // pulumi name before #12391 super('canton:network:postgres', logicalName, [], { + ...resourceOpts, protect: disableProtection ? false : spliceConfig.pulumiProjectConfig.cloudSql.protected, - aliases: [{ name: logicalNameAlias, type: 'canton:network:postgres' }], + aliases: [], }); this.instanceName = instanceName; @@ -400,11 +417,7 @@ export class SplicePostgres extends pulumi.ComponentResource implements Postgres this.address = pulumi.output( `${this.instanceName}.${this.namespace.logicalName}.svc.cluster.local` ); - const password = generatePassword(`${logicalName}-passwd`, { - parent: this, - aliases: [{ name: `${logicalNameAlias}-passwd` }], - }).result; - const passwordSecret = installPostgresPasswordSecret(xns, password, secretName); + const passwordSecret = getOrInstallPassword(this); this.secretName = passwordSecret.metadata.name; // an initial database named cantonnet is created automatically (configured in the Helm chart). @@ -428,7 +441,7 @@ export class SplicePostgres extends pulumi.ComponentResource implements Postgres }), version, { - aliases: [{ name: logicalNameAlias, type: 'kubernetes:helm.sh/v3:Release' }], + aliases: [{ name: instanceName, type: 'kubernetes:helm.sh/v3:Release' }], dependsOn: [passwordSecret], ...(spliceConfig.pulumiProjectConfig.replacePostgresStatefulSetOnChanges ? { @@ -441,6 +454,7 @@ export class SplicePostgres extends pulumi.ComponentResource implements Postgres useInfraAffinityAndTolerations ? infraAffinityAndTolerations : appsAffinityAndTolerations ); this.pg = pg; + this.database = pg; this.registerOutputs({ address: pg.id.apply(() => `${instanceName}.${xns.logicalName}.svc.cluster.local`), @@ -456,59 +470,491 @@ export class SplicePostgres extends pulumi.ComponentResource implements Postgres } } +/** + * Configuration for migrating data from a pre-existing PostgreSQL instance + * (one previously deployed via the splice-postgres Helm chart) into a + * freshly-created StatefulSet volume. + * + * The migration runs once, inside an init container, only when PGDATA is + * empty (i.e. on the very first pod start against a blank PVC). It dumps + * all databases into a dedicated migration PVC that is mounted into + * `/docker-entrypoint-initdb.d` for one-time restore during postgres init. + */ +export interface PostgresMigrationSource { + host: string; + port?: number; + userName?: string; + pvcSize: string; + pvcName: string; +} + +type LegacyChartValues = Partial<{ + resources: k8s.types.input.core.v1.ResourceRequirements; + db: Partial<{ + volumeSize: string; + maxConnections: number; + volumeStorageClass: string; + pvcTemplateName: string; + maxWalSize: string; + dataSource: pulumi.Input; + }>; + appsAffinityAndTolerations: unknown; +}>; + +export class SplicePostgres extends pulumi.ComponentResource implements Postgres { + instanceName: string; + namespace: ExactNamespace; + address: pulumi.Output; + pg: Resource; + secretName: pulumi.Output; + userName: string; + database: Resource; + + constructor( + xns: ExactNamespace, + instanceName: string, + installPassword: (parent: Resource) => k8s.core.v1.Secret, + splicePostgresHelmMigrationConfig: + | SplicePostgresMigrateConfig + | SplicePostgresDockerImageConfig, + values?: LegacyChartValues, + overrideDbSizeFromValues?: boolean, + disableProtection?: boolean, + version?: CnChartVersion, + useInfraAffinityAndTolerations: boolean = false, + resourceOpts?: SpliceCustomResourceOptions + ) { + // Avoiding collisions with the name in LegacyHelmSplicePostgres + const deployedInstanceName = `${instanceName}-helmless`; + const logicalName = xns.logicalName + '-' + deployedInstanceName; + super('canton:network:postgres', logicalName, [], { + ...resourceOpts, + protect: disableProtection ? false : spliceConfig.pulumiProjectConfig.cloudSql.protected, + aliases: [], + }); + + const passwordSecret = installPassword(this); + this.secretName = passwordSecret.metadata.name; + + let migrationSource: PostgresMigrationSource | undefined = undefined; + if (splicePostgresHelmMigrationConfig.deployment == 'migrate') { + new LegacyHelmSplicePostgres( + xns, + instanceName, + () => passwordSecret, // reuse the same secret + values, + overrideDbSizeFromValues, + disableProtection, + version, + useInfraAffinityAndTolerations + ); + + migrationSource = { + host: `${instanceName}.${xns.logicalName}.svc.cluster.local`, + port: 5432, + userName: 'cnadmin', + pvcSize: splicePostgresHelmMigrationConfig.migrationVolumeSize, + pvcName: 'migration-data', + }; + } + + this.instanceName = deployedInstanceName; + this.namespace = xns; + const postgresUser: string = 'cnadmin'; + const postgresDb: string = 'cantonnet'; + this.userName = postgresUser; + this.address = pulumi.output( + `${this.instanceName}.${this.namespace.logicalName}.svc.cluster.local` + ); + + const smallDiskSize = clusterSmallDisk ? '240Gi' : undefined; + + const volumeSize = overrideDbSizeFromValues + ? values?.db?.volumeSize || smallDiskSize || '2800Gi' + : smallDiskSize || '2800Gi'; + const pvcTemplateName = 'pg-data-hd'; + const volumeStorageClass = standardStorageClassName; + const maxConnections: number = values?.db?.maxConnections ?? 300; + const maxWalSize: string = values?.db?.maxWalSize ?? '2GB'; + const imageName: string = splicePostgresHelmMigrationConfig.postgresImage; + const resources = _.merge( + { limits: { memory: '12Gi' }, requests: { cpu: '0.5', memory: '1Gi' } }, + values?.resources || {} + ); + const affinityAndTolerations = useInfraAffinityAndTolerations + ? infraAffinityAndTolerations + : appsAffinityAndTolerations; + + // Optional init container that migrates data from a pre-existing postgres instance. + // It runs pg_dumpall against the source and writes migration.dump into a dedicated + // migration PVC that the main container mounts at /docker-entrypoint-initdb.d. + const initContainers: k8s.types.input.core.v1.Container[] = []; + // Extra volumeMounts added to the main postgres container + const migrationVolumeMounts: k8s.types.input.core.v1.VolumeMount[] = []; + const migrationVolumes: k8s.types.input.core.v1.Volume[] = []; + + if (migrationSource) { + const srcPort = String(migrationSource.port ?? 5432); + const srcUser = migrationSource.userName ?? postgresUser; + const migrationPvc = new k8s.core.v1.PersistentVolumeClaim( + `${logicalName}-migration-pvc`, + { + metadata: { + name: `${deployedInstanceName}-migration-pvc`, + namespace: xns.logicalName, + }, + spec: { + accessModes: ['ReadWriteOnce'], + resources: { requests: { storage: migrationSource.pvcSize ?? volumeSize } }, + storageClassName: volumeStorageClass, + volumeMode: 'Filesystem', + }, + }, + { parent: this, dependsOn: [xns.ns] } + ); + + // Shell script executed by the init container. + // Only runs when PGDATA is empty (first-ever pod start). The generated SQL + // file is persisted on a dedicated migration PVC and then consumed by the + // postgres entrypoint from /docker-entrypoint-initdb.d. + const migrationScript = [ + 'set -eou pipefail', + 'if [ -n "$(ls -A "$PGDATA" 2>/dev/null)" ]; then', + ' echo "PGDATA already contains data, skipping migration."', + ' exit 0', + 'fi', + 'echo "PGDATA is empty. Dumping all databases from $SOURCE_HOST:$SOURCE_PORT ..."', + 'pg_dumpall \\', + ' -h "$SOURCE_HOST" \\', + ' -p "$SOURCE_PORT" \\', + ' -U "$SOURCE_USER" \\', + ' --no-role-passwords \\', + ' -f /migration/migration.dump', + "cat > /migration/00-restore.sh << 'RESTORE_EOF'", + '#!/bin/bash', + 'set -euo pipefail', + '[ -f /docker-entrypoint-initdb.d/migration.dump ] || exit 0', + 'echo "Restoring all databases from migration.dump ..."', + 'psql --username "$POSTGRES_USER" --dbname postgres -f /docker-entrypoint-initdb.d/migration.dump', + 'echo "Restore complete."', + 'RESTORE_EOF', + 'chmod +x /migration/00-restore.sh', + 'echo "Migration dump ready at /migration/migration.dump"', + ].join('\n'); + + // Mount migration PVC into /docker-entrypoint-initdb.d so postgres restores it on first init. + migrationVolumes.push({ + name: migrationSource.pvcName, + persistentVolumeClaim: { claimName: migrationPvc.metadata.name }, + }); + migrationVolumeMounts.push({ + name: migrationSource.pvcName, + mountPath: '/docker-entrypoint-initdb.d', + }); + + initContainers.push({ + name: 'pg-migrate', + image: imageName, + imagePullPolicy: 'IfNotPresent', + securityContext: { + runAsNonRoot: true, + runAsUser: 999, + runAsGroup: 999, + allowPrivilegeEscalation: false, + privileged: false, + capabilities: { drop: ['ALL'] }, + }, + command: ['bash', '-c'], + args: [migrationScript], + env: [ + { name: 'PGDATA', value: '/var/lib/postgresql/data/pgdata' }, + { name: 'SOURCE_HOST', value: migrationSource.host }, + { name: 'SOURCE_PORT', value: srcPort }, + { name: 'SOURCE_USER', value: srcUser }, + { + name: 'PGPASSWORD', + valueFrom: { + secretKeyRef: { + name: passwordSecret.metadata.name, + key: 'postgresPassword', + }, + }, + }, + ], + volumeMounts: [ + // Mount PGDATA read-only – we only inspect it to decide whether to dump + { name: pvcTemplateName, mountPath: '/var/lib/postgresql/data', readOnly: true }, + { name: migrationSource.pvcName, mountPath: '/migration' }, + ], + }); + } + + // ConfigMap for non-secret environment variables + const configMap = new k8s.core.v1.ConfigMap( + `${logicalName}-configuration`, + { + metadata: { + name: `${deployedInstanceName}-configuration`, + namespace: xns.logicalName, + }, + data: { + PGDATA: '/var/lib/postgresql/data/pgdata', + POSTGRES_DB: postgresDb, + POSTGRES_USER: postgresUser, + POSTGRES_INITDB_ARGS: '--data-checksums', + }, + }, + { parent: this, dependsOn: [xns.ns] } + ); + + // StatefulSet using the official postgres image + const statefulSet = new k8s.apps.v1.StatefulSet( + logicalName, + { + metadata: { + name: deployedInstanceName, + namespace: xns.logicalName, + }, + spec: { + serviceName: deployedInstanceName, + replicas: 1, + selector: { matchLabels: { app: deployedInstanceName } }, + template: { + metadata: { + labels: { app: deployedInstanceName, namespace: xns.logicalName }, + }, + spec: { + securityContext: { + seccompProfile: { type: 'RuntimeDefault' }, + fsGroup: 999, + fsGroupChangePolicy: 'OnRootMismatch', + }, + ...(initContainers.length > 0 ? { initContainers } : {}), + containers: [ + { + name: deployedInstanceName, + image: imageName, + imagePullPolicy: 'IfNotPresent', + securityContext: { + runAsNonRoot: true, + runAsUser: 999, + runAsGroup: 999, + allowPrivilegeEscalation: false, + privileged: false, + capabilities: { drop: ['ALL'] }, + }, + args: [ + '-c', + `max_connections=${maxConnections}`, + '-c', + `max_wal_size=${maxWalSize}`, + ], + env: [ + { + name: 'POSTGRES_PASSWORD', + valueFrom: { + secretKeyRef: { + name: this.secretName, + key: 'postgresPassword', + }, + }, + }, + ], + envFrom: [{ configMapRef: { name: `${deployedInstanceName}-configuration` } }], + livenessProbe: { + exec: { + command: ['psql', '-U', postgresUser, '-d', 'template1', '-c', 'SELECT 1'], + }, + failureThreshold: 3, + periodSeconds: 10, + successThreshold: 1, + timeoutSeconds: 1, + }, + ports: [{ containerPort: 5432, name: 'postgresdb', protocol: 'TCP' }], + resources, + volumeMounts: [ + { mountPath: '/var/lib/postgresql/data', name: pvcTemplateName }, + ...migrationVolumeMounts, + ], + }, + ], + restartPolicy: 'Always', + affinity: affinityAndTolerations.affinity, + tolerations: affinityAndTolerations.tolerations, + ...(migrationVolumes.length > 0 ? { volumes: migrationVolumes } : {}), + }, + }, + volumeClaimTemplates: [ + { + metadata: { name: pvcTemplateName }, + spec: { + accessModes: ['ReadWriteOnce'], + resources: { requests: { storage: volumeSize } }, + storageClassName: volumeStorageClass, + volumeMode: 'Filesystem', + ...(values?.db?.dataSource ? { dataSource: values.db.dataSource } : {}), + }, + }, + ], + }, + }, + { + parent: this, + dependsOn: [passwordSecret, configMap], + ...(spliceConfig.pulumiProjectConfig.replacePostgresStatefulSetOnChanges + ? { replaceOnChanges: ['*'], deleteBeforeReplace: true } + : {}), + } + ); + + // Headless service for the StatefulSet + new k8s.core.v1.Service( + `${logicalName}-svc`, + { + metadata: { + name: deployedInstanceName, + namespace: xns.logicalName, + }, + spec: { + ports: [{ name: 'postgresdb', port: 5432, protocol: 'TCP' }], + selector: { app: deployedInstanceName }, + }, + }, + { parent: this, dependsOn: [xns.ns] } + ); + + this.pg = statefulSet; + this.database = statefulSet; + + this.registerOutputs({ + address: statefulSet.id.apply( + () => `${deployedInstanceName}.${xns.logicalName}.svc.cluster.local` + ), + secretName: this.secretName, + }); + } + + addUser(_userName: string): PostgresUser { + return { + userName: 'cnadmin', + secretName: this.secretName, + }; + } +} + // toplevel +type SplicePostgresInstallOptions = { + isActive?: boolean; + migrationId?: number; + disableProtection?: boolean; + logicalDecoding?: boolean; + userName?: string; + existingInstanceName?: string; + existingSecretName?: string; + retainDbResourcesOnDelete?: boolean; +}; + export async function installPostgres( xns: ExactNamespace, instanceName: string, alias: string, version: CnChartVersion, cloudSqlConfig: CloudSqlConfig, + splicePostgresHelmMigrationConfig: SplicePostgresConfig, uniqueSecretName = false, - opts: { - isActive?: boolean; - migrationId?: number; - disableProtection?: boolean; - logicalDecoding?: boolean; - userName?: string; - existingInstanceName?: string; - existingSecretName?: string; - retainDbResourcesOnDelete?: boolean; - databaseVersion?: string; - } = {} + opts: SplicePostgresInstallOptions = {} ): Promise { const o = { isActive: true, ...opts }; const secretName = uniqueSecretName ? instanceName + '-secrets' : 'postgres-secrets'; - return cloudSqlConfig.enabled - ? await CloudPostgres.install( - `${xns.logicalName}-${instanceName}`, - { - active: o.isActive, - alias, - cloudSqlConfig, - disableProtection: o.disableProtection, - existingInstanceName: o.existingInstanceName, - existingSecretName: o.existingSecretName, - instanceName, - logicalDecoding: o.logicalDecoding, - migrationId: o.migrationId, - namespace: xns, - secretName, - userName: o.userName, - retainDbResourcesOnDelete: o.retainDbResourcesOnDelete, - }, - { - aliases: [{ name: `${xns.logicalName}-${alias}` }], - } - ) - : new SplicePostgres( - xns, - instanceName, + if (cloudSqlConfig.enabled) { + return await CloudPostgres.install( + `${xns.logicalName}-${instanceName}`, + { + active: o.isActive, alias, + cloudSqlConfig, + disableProtection: o.disableProtection, + existingInstanceName: o.existingInstanceName, + existingSecretName: o.existingSecretName, + instanceName, + logicalDecoding: o.logicalDecoding, + migrationId: o.migrationId, + namespace: xns, secretName, - undefined, - undefined, - undefined, - version - ); + userName: o.userName, + retainDbResourcesOnDelete: o.retainDbResourcesOnDelete, + }, + { + aliases: [{ name: `${xns.logicalName}-${alias}` }], + } + ); + } else { + return installSplicePostgres( + xns, + instanceName, + secretName, + splicePostgresHelmMigrationConfig, + version, + opts + ); + } +} + +export function installSplicePostgres( + xns: ExactNamespace, + instanceName: string, + secretName: string, + splicePostgresHelmMigrationConfig: SplicePostgresConfig, + version?: CnChartVersion, + opts: SplicePostgresInstallOptions = {}, + chartValues?: LegacyChartValues, + overrideDbSizeFromValues?: boolean, + useInfraAffinityAndTolerations: boolean = false, + resourceOpts?: SpliceCustomResourceOptions +): Postgres { + if (splicePostgresHelmMigrationConfig.deployment == 'legacy-helm-chart') { + return new LegacyHelmSplicePostgres( + xns, + instanceName, + parent => installPasswordWithParent(parent, xns, instanceName, secretName), + chartValues, + overrideDbSizeFromValues, + opts.disableProtection, + version, + useInfraAffinityAndTolerations, + resourceOpts + ); + } else { + // If deployment == 'migrate', it will also create the LegacyHelmSplicePostgres + return new SplicePostgres( + xns, + instanceName, + parent => installPasswordWithParent(parent, xns, instanceName, secretName), + splicePostgresHelmMigrationConfig as + | SplicePostgresMigrateConfig + | SplicePostgresDockerImageConfig, + chartValues, + overrideDbSizeFromValues, + opts.disableProtection, + version, + useInfraAffinityAndTolerations, + resourceOpts + ); + } +} + +export function installPasswordWithParent( + parent: Resource, + xns: ExactNamespace, + instanceName: string, + secretName: string +): k8s.core.v1.Secret { + // Password keeps the same historical name to avoid re-creating it unnecessarily + const password = generatePassword(`${xns.logicalName}-${instanceName}-passwd`, { + parent, + // same name, no parent, because multi-validators were creating their own without a parent + aliases: [{ parent: undefined, name: `${xns.logicalName}-${instanceName}-passwd` }], + }).result; + return installPostgresPasswordSecret(xns, password, secretName); } diff --git a/cluster/pulumi/multi-validator/src/config.ts b/cluster/pulumi/multi-validator/src/config.ts index 0362d1ae20..1df541717e 100644 --- a/cluster/pulumi/multi-validator/src/config.ts +++ b/cluster/pulumi/multi-validator/src/config.ts @@ -3,7 +3,7 @@ import { clusterYamlConfig } from '@canton-network/splice-pulumi-common/src/config/config'; import { z } from 'zod'; -import { LogLevelSchema } from '../../common'; +import { LogLevelSchema, SplicePostgresSchema } from '../../common'; import { K8sResourceSchema } from '../../common/src/config/configSchema'; export const EnvironmentVariableSchema = z.object({ @@ -17,6 +17,10 @@ export const MultiValidatorConfigSchema = z.object({ multiValidator: z .object({ postgresPvcSize: z.string().optional(), + // Multi-validator needs to be migrated + postgres: SplicePostgresSchema.default({ + deployment: 'legacy-helm-chart', + }), requiresOnboardingSecret: z.boolean().default(false), extraValidatorEnvVars: z.array(EnvironmentVariableSchema).default([]), extraParticipantEnvVars: z.array(EnvironmentVariableSchema).default([]), diff --git a/cluster/pulumi/multi-validator/src/installNode.ts b/cluster/pulumi/multi-validator/src/installNode.ts index 760bf7d76b..df4eeba47c 100644 --- a/cluster/pulumi/multi-validator/src/installNode.ts +++ b/cluster/pulumi/multi-validator/src/installNode.ts @@ -21,11 +21,13 @@ export async function installNode(): Promise { for (let i = 0; i < numInstances; i++) { const postgres = installPostgres(namespace, `postgres-${i}`, imagePullDeps); const postgresConf = { - host: `postgres-${i}`, + // being just the init container it doesn't matter too much so as long as it has psql + initImageName: 'postgres:18', + host: postgres.address, port: '5432', schema: 'cantonnet', secret: { - name: `postgres-${i}-secret`, + name: postgres.secretName, key: 'postgresPassword', }, }; diff --git a/cluster/pulumi/multi-validator/src/multiNodeDeployment.ts b/cluster/pulumi/multi-validator/src/multiNodeDeployment.ts index 25f5e72e99..43a8c3d633 100644 --- a/cluster/pulumi/multi-validator/src/multiNodeDeployment.ts +++ b/cluster/pulumi/multi-validator/src/multiNodeDeployment.ts @@ -18,11 +18,12 @@ import { EnvironmentVariable, multiValidatorConfig } from './config'; export interface BaseMultiNodeArgs { namespace: k8s.core.v1.Namespace; postgres: { - host: string; + initImageName: string; + host: pulumi.Output; schema: string; port: string; db: string; - secret: { name: string; key: string }; + secret: { name: pulumi.Output; key: string }; }; } @@ -140,7 +141,7 @@ export class MultiNodeDeployment extends pulumi.ComponentResource { initContainers: [ { name: 'pg-init', - image: 'postgres:14', + image: args.postgres.initImageName, env: [ { name: 'PGPASSWORD', @@ -152,13 +153,12 @@ export class MultiNodeDeployment extends pulumi.ComponentResource { command: [ 'bash', '-c', - ` + args.postgres.host.apply( + host => ` function createDb() { local dbname="$1" - until errmsg=$(psql -h ${ - args.postgres.host - } --username=cnadmin --dbname=cantonnet -c "create database $dbname" 2>&1); do + until errmsg=$(psql -h ${host} --username=cnadmin --dbname=cantonnet -c "create database $dbname" 2>&1); do if [[ $errmsg == *"already exists"* ]]; then echo "Database $dbname already exists. Done." break @@ -173,7 +173,8 @@ export class MultiNodeDeployment extends pulumi.ComponentResource { { length: numNodesPerInstance }, (_, i) => `createDb ${args.postgres.db}_${zeroPad(i, 2)}` ).join('\n')} - `, + ` + ), ], }, ], diff --git a/cluster/pulumi/multi-validator/src/postgres.ts b/cluster/pulumi/multi-validator/src/postgres.ts index a8280160a9..ffc9d600fc 100644 --- a/cluster/pulumi/multi-validator/src/postgres.ts +++ b/cluster/pulumi/multi-validator/src/postgres.ts @@ -1,19 +1,15 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import * as pulumi from '@pulumi/pulumi'; -import * as random from '@pulumi/random'; import { activeVersion, appsAffinityAndTolerations, CnInput, ExactNamespace, - InstalledHelmChart, - installPostgresPasswordSecret, - installSpliceRunbookHelmChart, spliceConfig, standardStorageClassName, - createVolumeSnapshot, } from '@canton-network/splice-pulumi-common'; +import { installSplicePostgres, Postgres } from '@canton-network/splice-pulumi-common/src/postgres'; import { multiValidatorConfig } from './config'; @@ -21,26 +17,22 @@ export function installPostgres( xns: ExactNamespace, name: string, dependsOn: CnInput[] -): InstalledHelmChart { - const password = new random.RandomPassword(`${xns.logicalName}-${name}-passwd`, { - length: 16, - overrideSpecial: '_%@', - special: true, - }).result; +): Postgres { const secretName = `${name}-secret`; - const passwordSecret = installPostgresPasswordSecret(xns, password, secretName); if (!multiValidatorConfig) { throw new Error('multiValidator config must be set when they are enabled'); } const config = multiValidatorConfig!; - return installSpliceRunbookHelmChart( + return installSplicePostgres( xns, name, - 'splice-postgres', + secretName, + config.postgres, + activeVersion, + {}, { - persistence: { secretName }, db: { volumeSize: config.postgresPvcSize, maxConnections: 1000, @@ -50,9 +42,10 @@ export function installPostgres( resources: config.resources?.postgres, appsAffinityAndTolerations, }, - activeVersion, + true, // overrideDbSizeFromValues + false, // useInfraAffinityAndTolerations { - dependsOn: [passwordSecret, ...dependsOn], + dependsOn, ...(spliceConfig.pulumiProjectConfig.replacePostgresStatefulSetOnChanges ? { replaceOnChanges: ['*'], diff --git a/cluster/pulumi/observability/src/config.ts b/cluster/pulumi/observability/src/config.ts index e902707d22..df8baf1602 100644 --- a/cluster/pulumi/observability/src/config.ts +++ b/cluster/pulumi/observability/src/config.ts @@ -1,6 +1,10 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { clusterSubConfig } from '@canton-network/splice-pulumi-common'; +import { + clusterSubConfig, + SplicePostgresConfig, + SplicePostgresSchema, +} from '@canton-network/splice-pulumi-common'; import { z } from 'zod'; const quotaMetricNameSchema = z @@ -53,9 +57,14 @@ const MuteTimeIntervalSchema = z.array( ); export type MuteTimeInterval = z.infer[number]; +// Observability needs to be migrated +const defaultObservabilityPostgresConfig: SplicePostgresConfig = { + deployment: 'legacy-helm-chart', +}; const MonitoringConfigSchema = z .object({ enableGrafanaServiceAccountToken: z.boolean(), + grafanaPostgres: SplicePostgresSchema.default({ deployment: 'legacy-helm-chart' }), alerting: z.object({ enableNoDataAlerts: z.boolean(), alerts: z.object({ diff --git a/cluster/pulumi/observability/src/observability.ts b/cluster/pulumi/observability/src/observability.ts index 4dec7009c5..8e97d128ba 100644 --- a/cluster/pulumi/observability/src/observability.ts +++ b/cluster/pulumi/observability/src/observability.ts @@ -29,7 +29,7 @@ import { standardSvConfigsBasic, } from '@canton-network/splice-pulumi-common-sv/src/svConfigsBasic'; import { SweepConfig } from '@canton-network/splice-pulumi-common-validator'; -import { SplicePostgres } from '@canton-network/splice-pulumi-common/src/postgres'; +import { installSplicePostgres, Postgres } from '@canton-network/splice-pulumi-common/src/postgres'; import { infraStack } from '@canton-network/splice-pulumi-common/src/stackReferences'; import { local } from '@pulumi/command'; import { getSecretVersionOutput } from '@pulumi/gcp/secretmanager/getSecretVersion'; @@ -547,7 +547,7 @@ export function configureObservability(namespace: ExactNamespace): pulumi.Resour ); createGrafanaAlerting(namespaceName); if (monitoringConfig.enableGrafanaServiceAccountToken) { - createGrafanaServiceAccount(namespaceName, adminPassword, [prometheusStack, postgres.pg]); + createGrafanaServiceAccount(namespaceName, adminPassword, [prometheusStack, postgres.database]); } createGrafanaEnvoyFilter(namespaceName, [prometheusStack]); @@ -1184,16 +1184,17 @@ function grafanaKeysFromSecret(): pulumi.Output { }); } -function installPostgres(namespace: ExactNamespace): SplicePostgres { - return new SplicePostgres( +function installPostgres(namespace: ExactNamespace): Postgres { + const instanceName = 'grafana-postgres'; + return installSplicePostgres( namespace, - 'grafana-postgres', - 'grafana-postgres', + instanceName, 'grafana-postgres-secret', + monitoringConfig.grafanaPostgres, + undefined, // chart version + { disableProtection: true }, { db: { volumeSize: '20Gi' } }, // A tiny pvc should be enough for grafana true, // overrideDbSizeFromValues - true, // disableProtection - undefined, // chart version true // useInfraAffinityAndTolerations ); } diff --git a/cluster/pulumi/splitwell/src/splitwell.ts b/cluster/pulumi/splitwell/src/splitwell.ts index d3a20de854..361d714e00 100644 --- a/cluster/pulumi/splitwell/src/splitwell.ts +++ b/cluster/pulumi/splitwell/src/splitwell.ts @@ -51,6 +51,7 @@ export async function installSplitwell( 'splitwell-pg', activeVersion, spliceConfig.pulumiProjectConfig.cloudSql, + spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, splitPostgresInstances ); @@ -81,6 +82,7 @@ export async function installSplitwell( 'sw-pg', activeVersion, spliceConfig.pulumiProjectConfig.cloudSql, + spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, true )); const splitwellDbName = 'app_splitwell'; @@ -127,6 +129,7 @@ export async function installSplitwell( 'validator-pg', activeVersion, spliceConfig.pulumiProjectConfig.cloudSql, + spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, true )); const validatorDbName = 'val_splitwell'; diff --git a/cluster/pulumi/sv-canton/src/canton.ts b/cluster/pulumi/sv-canton/src/canton.ts index 245b086ffe..56852a5230 100644 --- a/cluster/pulumi/sv-canton/src/canton.ts +++ b/cluster/pulumi/sv-canton/src/canton.ts @@ -83,6 +83,7 @@ export async function installCantonComponents( `mediator-pg`, version, physicalSynchronizerConfig.mediator.cloudSql, + physicalSynchronizerConfig.mediator.splicePostgres, true, { isActive: migrationStillRunning, @@ -98,6 +99,7 @@ export async function installCantonComponents( `sequencer-pg`, version, physicalSynchronizerConfig.sequencer.cloudSql, + physicalSynchronizerConfig.sequencer.splicePostgres, true, { isActive: migrationStillRunning, migrationId, disableProtection } )); @@ -109,6 +111,7 @@ export async function installCantonComponents( `sequencer-bft-pg`, version, physicalSynchronizerConfig.sequencer.cloudSql, + physicalSynchronizerConfig.sequencer.splicePostgres, true, { isActive: migrationStillRunning, migrationId, disableProtection } ) diff --git a/cluster/pulumi/sv-runbook/src/postgres.ts b/cluster/pulumi/sv-runbook/src/postgres.ts index 83952ccd9e..32f0c34a39 100644 --- a/cluster/pulumi/sv-runbook/src/postgres.ts +++ b/cluster/pulumi/sv-runbook/src/postgres.ts @@ -10,7 +10,11 @@ import { supportsSvRunbookReset, } from '@canton-network/splice-pulumi-common'; import { spliceConfig } from '@canton-network/splice-pulumi-common/src/config/config'; -import { CloudPostgres, SplicePostgres } from '@canton-network/splice-pulumi-common/src/postgres'; +import { + CloudPostgres, + installPasswordWithParent, + SplicePostgres, +} from '@canton-network/splice-pulumi-common/src/postgres'; export async function installPostgres( xns: ExactNamespace, @@ -40,8 +44,7 @@ export async function installPostgres( return new SplicePostgres( xns, name, - name, - secretName, + parent => installPasswordWithParent(parent, xns, name, secretName), values, undefined, supportsSvRunbookReset diff --git a/cluster/pulumi/sv/src/participant.ts b/cluster/pulumi/sv/src/participant.ts index 83117eb9c2..ac372bd715 100644 --- a/cluster/pulumi/sv/src/participant.ts +++ b/cluster/pulumi/sv/src/participant.ts @@ -61,6 +61,7 @@ async function installParticipantPostgres({ 'participant-pg', version, participant?.cloudSql ?? spliceConfig.pulumiProjectConfig.cloudSql, + spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, true, { disableProtection, diff --git a/cluster/pulumi/validator-runbook/src/installNode.ts b/cluster/pulumi/validator-runbook/src/installNode.ts index c8e075f36a..f25ce11383 100644 --- a/cluster/pulumi/validator-runbook/src/installNode.ts +++ b/cluster/pulumi/validator-runbook/src/installNode.ts @@ -45,7 +45,10 @@ import { } from '@canton-network/splice-pulumi-common'; import { installLoopback } from '@canton-network/splice-pulumi-common-sv'; import { installParticipant } from '@canton-network/splice-pulumi-common-validator'; -import { SplicePostgres } from '@canton-network/splice-pulumi-common/src/postgres'; +import { + installPasswordWithParent, + SplicePostgres, +} from '@canton-network/splice-pulumi-common/src/postgres'; import { installPartyAllocator } from './partyAllocator'; import { validatorConfig, validatorName } from './validatorConfig'; @@ -168,12 +171,16 @@ async function installValidator( db: { ...postgresValuesFromFile.db, volumeSize: validatorConfig.postgresPvcSize }, } : postgresValuesFromFile; + const postgresInstanceName = 'postgres'; const postgres = new SplicePostgres( xns, - 'postgres', - // can be removed once base version > 0.2.1 - `postgres`, - 'postgres-secrets', + postgresInstanceName, + parent => installPasswordWithParent(parent, xns, postgresInstanceName, 'postgres-secrets'), + // No need to support legacy chart + { + deployment: 'docker-image', + postgresImage: postgresValuesFromFile.db.postgresImage || 'postgres:18', + }, postgresValues, true, supportsValidatorRunbookReset, diff --git a/cluster/pulumi/validator1/src/validator1.ts b/cluster/pulumi/validator1/src/validator1.ts index 39007b0cd3..cd82a160af 100644 --- a/cluster/pulumi/validator1/src/validator1.ts +++ b/cluster/pulumi/validator1/src/validator1.ts @@ -60,6 +60,7 @@ export async function installValidator1( 'postgres', activeVersion, spliceConfig.pulumiProjectConfig.cloudSql, + spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, false ) : undefined; @@ -72,6 +73,7 @@ export async function installValidator1( `validator-pg`, activeVersion, spliceConfig.pulumiProjectConfig.cloudSql, + spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, true )); const validatorDbName = `validator1`; From 0dfa468bd961bc59a15dcb2a704dfad98b9f3fa0 Mon Sep 17 00:00:00 2001 From: Puneet Bharti Date: Tue, 4 Aug 2026 16:16:17 +0200 Subject: [PATCH 174/329] SV UI: add Submitted By column to governance tables (#3691) (#6460) Signed-off-by: Puneet Bharti --- .../action-required-section.test.tsx | 50 +++-- .../governance/governance-sorting.test.tsx | 9 +- .../governance/proposal-listing.test.tsx | 61 +++++- .../utils/getRequesterPartyId.test.ts | 31 +++ .../governance/ActionRequiredSection.tsx | 192 +++++++++--------- .../governance/ProposalListingSection.tsx | 165 ++++++++++++--- apps/sv/frontend/src/routes/governance.tsx | 11 +- apps/sv/frontend/src/utils/governance.ts | 14 ++ apps/sv/frontend/src/utils/types.ts | 1 + 9 files changed, 374 insertions(+), 160 deletions(-) create mode 100644 apps/sv/frontend/src/__tests__/utils/getRequesterPartyId.test.ts diff --git a/apps/sv/frontend/src/__tests__/governance/action-required-section.test.tsx b/apps/sv/frontend/src/__tests__/governance/action-required-section.test.tsx index 8a72cf3096..b5bd2e3e19 100644 --- a/apps/sv/frontend/src/__tests__/governance/action-required-section.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/action-required-section.test.tsx @@ -11,24 +11,27 @@ import { VoteRequest } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules' import { MemoryRouter } from 'react-router'; import dayjs from 'dayjs'; import { dateTimeFormatISO } from '@canton-network/splice-common-frontend-utils'; +import { svPartyId, voteRequests } from '../mocks/constants'; + +const sampleContractId = voteRequests.dso_rules_vote_requests[0] + .contract_id as ContractId; const requests: ActionRequiredData[] = [ { actionName: 'Feature Application', description: 'Test description for feature application', - contractId: '2abcde123456' as ContractId, + contractId: sampleContractId, votingCloses: '2024-09-25 11:00', createdAt: '2024-09-25 11:00', - requester: 'sv1', + requester: svPartyId, }, { actionName: 'Set DSO Rules Configuration', description: 'Test description for DSO rules configuration', - contractId: '2bcde123456' as ContractId, + contractId: voteRequests.dso_rules_vote_requests[1].contract_id as ContractId, votingCloses: '2024-09-25 11:00', createdAt: '2024-09-25 11:00', - requester: 'sv2', - isYou: true, + requester: svPartyId, }, ]; @@ -76,10 +79,10 @@ describe('Action Required', () => { const actionRequired = { actionName: 'Feature Application', description: 'Test description', - contractId: '2abcde123456' as ContractId, + contractId: sampleContractId, votingCloses: closesDate, createdAt: createdDate, - requester: 'sv1', + requester: svPartyId, }; render( @@ -104,23 +107,22 @@ describe('Action Required', () => { expect(votingCloses).toBeInTheDocument(); expect(votingCloses.textContent).toBe('10 days'); - const requester = screen.getByTestId('action-required-requester-identifier-value'); - expect(requester).toBeInTheDocument(); - expect(requester.textContent).toBe(actionRequired.requester); + const submittedBy = screen.getByTestId('action-required-submitted-by-identifier-value'); + expect(submittedBy).toBeInTheDocument(); + expect(submittedBy.textContent).toBe(svPartyId); const viewDetails = screen.getByTestId('action-required-view-details'); expect(viewDetails).toBeInTheDocument(); }); - test('should render isYou badge for requests created by viewing sv', () => { + test('should render submitted by with copy button and no You badge', () => { const actionRequired = { actionName: 'Feature Application', description: 'Test description', - contractId: '2abcde123456' as ContractId, + contractId: sampleContractId, votingCloses: '2029-09-25 11:00', createdAt: '2029-09-25 11:00', - requester: 'sv1', - isYou: true, + requester: svPartyId, }; render( @@ -129,18 +131,22 @@ describe('Action Required', () => { ); - const isYou = screen.getByTestId('action-required-requester-identifier-badge'); - expect(isYou).toBeInTheDocument(); + expect( + screen.getByTestId('action-required-submitted-by-identifier-copy-button') + ).toBeInTheDocument(); + expect( + screen.queryByTestId('action-required-submitted-by-identifier-badge') + ).not.toBeInTheDocument(); }); - test('should not render isYou badge for requests created by other svs', () => { + test('should render vote proposal contract id with full value', () => { const actionRequired = { actionName: 'Feature Application', description: 'Test description', - contractId: '2abcde123456' as ContractId, + contractId: sampleContractId, votingCloses: '2029-09-25 11:00', createdAt: '2029-09-25 11:00', - requester: 'sv1', + requester: svPartyId, }; render( @@ -149,8 +155,8 @@ describe('Action Required', () => { ); - const isYou = screen.queryByTestId('action-required-requester-identifier-badge'); - - expect(isYou).not.toBeInTheDocument(); + expect(screen.getByTestId('action-required-contract-id-value').textContent).toBe( + sampleContractId + ); }); }); diff --git a/apps/sv/frontend/src/__tests__/governance/governance-sorting.test.tsx b/apps/sv/frontend/src/__tests__/governance/governance-sorting.test.tsx index 9ff8bb2236..fe1307b030 100644 --- a/apps/sv/frontend/src/__tests__/governance/governance-sorting.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/governance-sorting.test.tsx @@ -11,6 +11,7 @@ import { } from '../../components/governance/ActionRequiredSection'; import { ProposalListingSection } from '../../components/governance/ProposalListingSection'; import { ProposalListingData } from '../../utils/types'; +import { svPartyId } from '../mocks/constants'; describe('Governance Page Sorting', () => { describe('Action Required Section', () => { @@ -22,7 +23,7 @@ describe('Governance Page Sorting', () => { contractId: 'c' as ContractId, votingCloses: '2025-01-25 12:00', createdAt: '2025-01-10 12:00', - requester: 'sv1', + requester: svPartyId, }, { actionName: 'Action A - Earliest', @@ -30,7 +31,7 @@ describe('Governance Page Sorting', () => { contractId: 'a' as ContractId, votingCloses: '2025-01-15 10:00', createdAt: '2025-01-10 12:00', - requester: 'sv1', + requester: svPartyId, }, { actionName: 'Action B - Middle', @@ -38,7 +39,7 @@ describe('Governance Page Sorting', () => { contractId: 'b' as ContractId, votingCloses: '2025-01-15 18:00', createdAt: '2025-01-10 12:00', - requester: 'sv1', + requester: svPartyId, }, ]; @@ -69,6 +70,7 @@ describe('Governance Page Sorting', () => { yourVote: 'accepted', status: 'In Progress', acceptanceThreshold: BigInt(11), + requester: svPartyId, }; test('should sort with Threshold items first (by votes desc, then deadline asc), then dated items by effective date asc', () => { @@ -153,6 +155,7 @@ describe('Governance Page Sorting', () => { status: 'Implemented', voteStats: { accepted: 8, rejected: 2, 'no-vote': 1 }, acceptanceThreshold: BigInt(11), + requester: svPartyId, }; test('renders in backend order without client re-sorting', () => { diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-listing.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-listing.test.tsx index 98eaac5705..21fe59d81b 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-listing.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-listing.test.tsx @@ -7,11 +7,16 @@ import { VoteRequest } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules' import { ContractId } from '@daml/types'; import { ProposalListingData } from '../../utils/types'; import { MemoryRouter } from 'react-router'; +import { svPartyId, voteRequests } from '../mocks/constants'; + +const sampleContractId = voteRequests.dso_rules_vote_requests[0] + .contract_id as ContractId; const inflightVoteRequests: ProposalListingData[] = [ { actionName: 'Feature Application', - contractId: '2abcde123456' as ContractId, + contractId: sampleContractId, + requester: svPartyId, votingThresholdDeadline: '2025-09-25 11:00', voteTakesEffect: '2025-09-26 11:00', yourVote: 'no-vote', @@ -21,7 +26,8 @@ const inflightVoteRequests: ProposalListingData[] = [ }, { actionName: 'Set DSO Rules Configuration', - contractId: 'bcde123456' as ContractId, + contractId: voteRequests.dso_rules_vote_requests[1].contract_id as ContractId, + requester: svPartyId, votingThresholdDeadline: '2025-09-25 11:00', voteTakesEffect: '2025-09-26 11:00', yourVote: 'accepted', @@ -34,7 +40,8 @@ const inflightVoteRequests: ProposalListingData[] = [ const voteHistory: ProposalListingData[] = [ { actionName: 'Feature Application', - contractId: '2abcde123456' as ContractId, + contractId: sampleContractId, + requester: svPartyId, votingThresholdDeadline: '2025-09-25 11:00', voteTakesEffect: '2025-09-26 11:00', yourVote: 'no-vote', @@ -44,7 +51,8 @@ const voteHistory: ProposalListingData[] = [ }, { actionName: 'Set DSO Rules Configuration', - contractId: '2bcde123456' as ContractId, + contractId: voteRequests.dso_rules_vote_requests[1].contract_id as ContractId, + requester: svPartyId, votingThresholdDeadline: '2025-09-25 11:00', voteTakesEffect: '2025-09-26 11:00', yourVote: 'accepted', @@ -93,6 +101,8 @@ describe('Inflight Vote Requests', () => { const uniqueId = 'proposals-request'; const data = { actionName: 'Feature Application', + contractId: sampleContractId, + requester: svPartyId, votingThresholdDeadline: '2025-09-25 11:00', voteTakesEffect: '2025-09-26 11:00', yourVote: 'no-vote', @@ -136,10 +146,49 @@ describe('Inflight Vote Requests', () => { expect(yourVote.textContent).toMatch(/No Vote/); }); + test('should render submitted by column with full party id and copy button', () => { + const uniqueId = 'proposals-request'; + const data = { + actionName: 'Feature Application', + contractId: sampleContractId, + requester: svPartyId, + votingThresholdDeadline: '2025-09-25 11:00', + voteTakesEffect: '2025-09-26 11:00', + yourVote: 'accepted', + status: 'In Progress', + voteStats: { accepted: 0, rejected: 0, 'no-vote': 0 }, + acceptanceThreshold: BigInt(11), + } as ProposalListingData; + + render( + + + + ); + + expect(screen.getByText('SUBMITTED BY')).toBeInTheDocument(); + expect(screen.getByTestId(`${uniqueId}-row-submitted-by-identifier-value`).textContent).toBe( + svPartyId + ); + expect( + screen.getByTestId(`${uniqueId}-row-submitted-by-identifier-copy-button`) + ).toBeInTheDocument(); + expect( + screen.queryByTestId(`${uniqueId}-row-submitted-by-identifier-badge`) + ).not.toBeInTheDocument(); + }); + test('should render Accepted inflight vote request', () => { const uniqueId = 'proposals-request'; const data = { actionName: 'Feature Application', + contractId: sampleContractId, + requester: svPartyId, votingThresholdDeadline: '2025-09-25 11:00', voteTakesEffect: '2025-09-26 11:00', yourVote: 'accepted', @@ -171,6 +220,8 @@ describe('Inflight Vote Requests', () => { const uniqueId = 'proposals-request'; const data = { actionName: 'Feature Application', + contractId: sampleContractId, + requester: svPartyId, votingThresholdDeadline: '2025-09-25 11:00', voteTakesEffect: '2025-09-26 11:00', yourVote: 'rejected', @@ -255,6 +306,8 @@ describe('Vote history', () => { const uniqueId = 'vote-history'; const data = { actionName: 'Feature Application', + contractId: sampleContractId, + requester: svPartyId, votingThresholdDeadline: '2024-09-25 11:00', voteTakesEffect: '2024-09-26 11:00', yourVote: 'accepted', diff --git a/apps/sv/frontend/src/__tests__/utils/getRequesterPartyId.test.ts b/apps/sv/frontend/src/__tests__/utils/getRequesterPartyId.test.ts new file mode 100644 index 0000000000..aed68de29d --- /dev/null +++ b/apps/sv/frontend/src/__tests__/utils/getRequesterPartyId.test.ts @@ -0,0 +1,31 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, test } from 'vitest'; +import type { SvInfo } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules'; +import { getRequesterPartyId } from '../../utils/governance'; + +const partyId = 'digital-asset-2::1220abc'; +const svName = 'Digital-Asset-2'; + +const svs = { + entriesArray: () => [[partyId, { name: svName } as SvInfo] as [string, SvInfo]], +}; + +describe('getRequesterPartyId', () => { + test('returns requester unchanged when it is already a party id', () => { + expect(getRequesterPartyId(partyId, svs)).toBe(partyId); + }); + + test('resolves sv name to party id', () => { + expect(getRequesterPartyId(svName, svs)).toBe(partyId); + }); + + test('returns requester when svs is undefined', () => { + expect(getRequesterPartyId(svName, undefined)).toBe(svName); + }); + + test('returns requester when sv is not in svs (e.g. offboarded)', () => { + expect(getRequesterPartyId('Offboarded-SV', svs)).toBe('Offboarded-SV'); + }); +}); diff --git a/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx b/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx index 36e338bf49..167c46c932 100644 --- a/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx +++ b/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx @@ -5,7 +5,7 @@ import { ContractId } from '@daml/types'; import { East } from '@mui/icons-material'; import { Alert, Box, Stack, Typography } from '@mui/material'; import { Link as RouterLink } from 'react-router'; -import { CopyableIdentifier, MemberIdentifier, PageSectionHeader } from '../../components/beta'; +import { CopyableIdentifier, PageSectionHeader } from '../../components/beta'; import React from 'react'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; @@ -19,7 +19,6 @@ export interface ActionRequiredData { votingCloses: string; createdAt: string; requester: string; - isYou?: boolean; } export interface ActionRequiredProps { @@ -60,7 +59,6 @@ export const ActionRequiredSection: React.FC = ( contractId={ar.contractId} votingEnds={ar.votingCloses} requester={ar.requester} - isYou={ar.isYou} /> )) )} @@ -76,11 +74,13 @@ interface ActionCardProps { contractId: ContractId; votingEnds: string; requester: string; - isYou?: boolean; } +const actionRequiredGridTemplate = + 'minmax(0, 0.85fr) minmax(0, 1fr) minmax(0, 1.15fr) minmax(0, 0.75fr) minmax(0, 0.85fr) 270px auto'; + const ActionCard = (props: ActionCardProps) => { - const { action, description, createdAt, contractId, votingEnds, requester, isYou } = props; + const { action, description, createdAt, contractId, votingEnds, requester } = props; const remainingTime = dayjs(votingEnds).fromNow(true); return ( @@ -92,99 +92,87 @@ const ActionCard = (props: ActionCardProps) => { - - - - - - - {description} - - } - data-testid="action-required-description" - /> - - - - } - data-testid="action-required-contract-id-segment" - /> - - - - - - + + {description} + + } + data-testid="action-required-description" + /> + - - - - } - data-testid="action-required-requester" + } + data-testid="action-required-contract-id-segment" + /> + + + - - - - View Details - - - + } + data-testid="action-required-submitted-by" + /> + + + View Details + + @@ -202,14 +190,24 @@ const ActionCardSegment: React.FC = ({ content, 'data-testid': testId, }) => ( - + {title} @@ -220,13 +218,13 @@ const ActionCardSegment: React.FC = ({ color="text.light" fontWeight="medium" fontSize={14} - lineHeight={2} + lineHeight="26px" data-testid={`${testId}-content`} > {content} ) : ( - content + {content} )} ); diff --git a/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx b/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx index 8961a717df..c09694794d 100644 --- a/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx @@ -67,11 +67,85 @@ const sortProposals = ( .toSorted((a, b) => getEffectiveDate(a).diff(getEffectiveDate(b))); }; -const getColumnsCount = (...shown: (boolean | undefined)[]) => 4 + shown.filter(Boolean).length; +const getColumnsCount = (...shown: (boolean | undefined)[]) => 5 + shown.filter(Boolean).length; const getGridTemplate = (columnsCount: number) => `minmax(0, 1fr) minmax(0, 0.7fr) ${'1fr '.repeat(columnsCount - 2).trim()}`; +const governanceTableHeadCellSx = { + py: '10px', + px: '16px', + fontSize: 12, + fontWeight: 600, + textTransform: 'uppercase' as const, + color: 'colors.neutral.80', + borderBottom: 'none', + display: 'flex', + alignItems: 'center', +}; + +const governanceTableBodyCellSx = { + py: '15px', + px: '16px', + borderBottom: 'none', + display: 'flex', + alignItems: 'center', + alignSelf: 'stretch', + minWidth: 0, +}; + +interface SubmittedByCellProps { + requester: string; + uniqueId: string; +} + +const identifierCellSx = { + ...governanceTableBodyCellSx, + overflow: 'visible', +}; + +const SubmittedByCell: React.FC = ({ requester, uniqueId }) => ( + + + +); + +interface TableHeaderProps { + showThresholdDeadline?: boolean; + showStatus?: boolean; + showVoteStats?: boolean; +} + +const TableHeader: React.FC = ({ + showThresholdDeadline, + showStatus, + showVoteStats, +}) => ( + <> + PROPOSAL TYPE + VOTE PROPOSAL CONTRACT ID + {showThresholdDeadline ? ( + <> + THRESHOLD DEADLINE + SUBMITTED BY + EFFECTIVE AT + + ) : ( + <> + EFFECTIVE AT + SUBMITTED BY + {showStatus && STATUS} + + )} + {showVoteStats && VOTES} + YOUR VOTE + +); + export const ProposalListingSection: React.FC = props => { const { sectionTitle, @@ -121,13 +195,11 @@ export const ProposalListingSection: React.FC = pro - ACTION - VOTE PROPOSAL CONTRACT ID - {showThresholdDeadline && THRESHOLD DEADLINE} - EFFECTIVE AT - {showStatus && STATUS} - {showVoteStats && VOTES} - YOUR VOTE + @@ -137,6 +209,7 @@ export const ProposalListingSection: React.FC = pro actionName={vote.actionName} description={vote.description} contractId={vote.contractId} + requester={vote.requester} uniqueId={uniqueId} votingThresholdDeadline={vote.votingThresholdDeadline} voteTakesEffect={vote.voteTakesEffect} @@ -226,6 +299,7 @@ interface VoteRowProps { actionName: string; description?: string; contractId: ContractId; + requester: string; status: ProposalListingStatus; uniqueId: string; voteStats: Record; @@ -243,6 +317,7 @@ const VoteRow: React.FC = React.memo(props => { actionName, description, contractId, + requester, status, uniqueId, voteStats, @@ -272,10 +347,24 @@ const VoteRow: React.FC = React.memo(props => { }} data-testid={`${uniqueId}-row`} > - + {actionName} @@ -283,6 +372,7 @@ const VoteRow: React.FC = React.memo(props => { = React.memo(props => { WebkitBoxOrient: 'vertical', overflow: 'hidden', textOverflow: 'ellipsis', - lineHeight: 1.4, + lineHeight: '20px', }} > {description} )} - + - {showThresholdDeadline && ( - - {votingThresholdDeadline} - - )} - - {voteTakesEffect} - - - {showStatus && ( - - {status} - + {showThresholdDeadline ? ( + <> + + {votingThresholdDeadline} + + + + {voteTakesEffect} + + + ) : ( + <> + + {voteTakesEffect} + + + {showStatus && ( + + {status} + + )} + )} {showVoteStats && ( - + = React.memo(props => { /> )} - + { const svPartyId = dsoInfosQuery.data?.svPartyId; const votingThreshold = dsoInfosQuery.data?.votingThreshold; + const svs = dsoInfosQuery.data?.dsoRules.payload.svs; const alreadyVotedRequestIds: Set> = useMemo(() => { return svPartyId && votesQuery.data ? new Set(votesQuery.data.filter(v => v.voter === svPartyId).map(v => v.requestCid)) @@ -81,7 +83,7 @@ export const Governance: React.FC = () => { const votes = vr.request.votes.entriesArray().map(e => e[1]); return { - contractId: vr.request.trackingCid, + contractId: (vr.request.trackingCid ?? '') as ContractId, actionName: actionTagToTitle(amuletName)[getAction(vr.request.action) as SupportedActionTag], description: vr.request.reason.body, @@ -94,9 +96,10 @@ export const Governance: React.FC = () => { status: getVoteResultStatus(vr.outcome), voteStats: computeVoteStats(votes), acceptanceThreshold: votingThreshold, + requester: getRequesterPartyId(vr.request.requester, svs), } as ProposalListingData; }); - }, [voteResultsInfiniteQuery.data?.pages, amuletName, svPartyId, votingThreshold]); + }, [voteResultsInfiniteQuery.data?.pages, amuletName, svPartyId, votingThreshold, svs]); if ( dsoInfosQuery.isPending || @@ -128,8 +131,7 @@ export const Governance: React.FC = () => { description: vr.payload.reason.body, votingCloses: dayjs(vr.payload.voteBefore).format(dateTimeFormatISO), createdAt: dayjs(vr.createdAt).format(dateTimeFormatISO), - requester: vr.payload.requester, - isYou: vr.payload.requester === svPartyId, + requester: getRequesterPartyId(vr.payload.requester, svs), } as ActionRequiredData; }); @@ -152,6 +154,7 @@ export const Governance: React.FC = () => { status: 'In Progress', voteStats: computeVoteStats(votes), acceptanceThreshold: dsoInfosQuery.data.votingThreshold, + requester: getRequesterPartyId(v.payload.requester, svs), } as ProposalListingData; }); diff --git a/apps/sv/frontend/src/utils/governance.ts b/apps/sv/frontend/src/utils/governance.ts index c00936a76a..3e9c27a282 100644 --- a/apps/sv/frontend/src/utils/governance.ts +++ b/apps/sv/frontend/src/utils/governance.ts @@ -104,6 +104,20 @@ export function computeVoteStats(votes: Vote[]): { ); } +export function getRequesterPartyId( + requester: string, + svs: { entriesArray(): [string, SvInfo][] } | undefined +): string { + if (requester.includes('::')) { + return requester; + } + if (!svs) { + return requester; + } + const match = svs.entriesArray().find(([, info]) => info.name === requester); + return match?.[0] ?? requester; +} + export function computeYourVote(votes: Vote[], svPartyId: string | undefined): YourVoteStatus { if (svPartyId === undefined) { return 'no-vote'; diff --git a/apps/sv/frontend/src/utils/types.ts b/apps/sv/frontend/src/utils/types.ts index 2bf6428dbd..6329201013 100644 --- a/apps/sv/frontend/src/utils/types.ts +++ b/apps/sv/frontend/src/utils/types.ts @@ -157,6 +157,7 @@ export interface ProposalListingData { contractId: ContractId; actionName: string; description?: string; + requester: string; votingThresholdDeadline: string; voteTakesEffect: string; yourVote: YourVoteStatus; From f381a810b8bbd7df3c62a61a9bfafcd7ed1c2838 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Tue, 4 Aug 2026 18:21:40 +0200 Subject: [PATCH 175/329] Fix runbooks' deployments (#6662) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix validator runbook Signed-off-by: Oriol Muñoz * fix sv runbook Signed-off-by: Oriol Muñoz * doc parameters Signed-off-by: Oriol Muñoz * update expected [static] Signed-off-by: Oriol Muñoz --------- Signed-off-by: Oriol Muñoz --- cluster/expected/validator-runbook/expected.json | 2 +- cluster/pulumi/sv-runbook/src/postgres.ts | 9 +++++++-- cluster/pulumi/validator-runbook/src/installNode.ts | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/cluster/expected/validator-runbook/expected.json b/cluster/expected/validator-runbook/expected.json index a2be062bb4..cd9a5bf39b 100644 --- a/cluster/expected/validator-runbook/expected.json +++ b/cluster/expected/validator-runbook/expected.json @@ -915,7 +915,7 @@ }, "persistence": { "host": "postgres", - "postgresName": "postgres", + "postgresName": "postgres-helmless", "secretName": "postgres-secrets" }, "pvc": { diff --git a/cluster/pulumi/sv-runbook/src/postgres.ts b/cluster/pulumi/sv-runbook/src/postgres.ts index 32f0c34a39..9336973aa4 100644 --- a/cluster/pulumi/sv-runbook/src/postgres.ts +++ b/cluster/pulumi/sv-runbook/src/postgres.ts @@ -45,9 +45,14 @@ export async function installPostgres( xns, name, parent => installPasswordWithParent(parent, xns, name, secretName), + // No need to support legacy chart + { + deployment: 'docker-image', + postgresImage: valuesFromFile.db.postgresImage || 'postgres:18', + }, values, - undefined, - supportsSvRunbookReset + undefined, // overrideDbSizeFromValues + supportsSvRunbookReset // disableProtection ); } } diff --git a/cluster/pulumi/validator-runbook/src/installNode.ts b/cluster/pulumi/validator-runbook/src/installNode.ts index f25ce11383..066bc6f30f 100644 --- a/cluster/pulumi/validator-runbook/src/installNode.ts +++ b/cluster/pulumi/validator-runbook/src/installNode.ts @@ -254,7 +254,7 @@ async function installValidator( ...(participantBootstrapDumpSecret ? { nodeIdentifier: newParticipantIdentifier } : {}), persistence: { ...validatorValuesFromYamlFiles.persistence, - postgresName: 'postgres', + postgresName: postgres.instanceName, }, pvc: { volumeStorageClass: standardStorageClassName, From 6ec1db1b4c77007ff1b53fbb7fb092979c99ac70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Tue, 4 Aug 2026 20:10:16 +0200 Subject: [PATCH 176/329] Fix validator runbook using a default postgres host (#6664) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Oriol Muñoz --- cluster/expected/validator-runbook/expected.json | 2 +- cluster/pulumi/validator-runbook/src/installNode.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cluster/expected/validator-runbook/expected.json b/cluster/expected/validator-runbook/expected.json index cd9a5bf39b..ef6d6662fd 100644 --- a/cluster/expected/validator-runbook/expected.json +++ b/cluster/expected/validator-runbook/expected.json @@ -914,7 +914,7 @@ "retention": "30d" }, "persistence": { - "host": "postgres", + "host": "postgres-helmless.validator.svc.cluster.local", "postgresName": "postgres-helmless", "secretName": "postgres-secrets" }, diff --git a/cluster/pulumi/validator-runbook/src/installNode.ts b/cluster/pulumi/validator-runbook/src/installNode.ts index 066bc6f30f..15c8238c91 100644 --- a/cluster/pulumi/validator-runbook/src/installNode.ts +++ b/cluster/pulumi/validator-runbook/src/installNode.ts @@ -255,6 +255,7 @@ async function installValidator( persistence: { ...validatorValuesFromYamlFiles.persistence, postgresName: postgres.instanceName, + host: postgres.address, }, pvc: { volumeStorageClass: standardStorageClassName, From efc9af496f5c0614694d16811655faea72f0da43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 5 Aug 2026 10:06:42 +0200 Subject: [PATCH 177/329] Fix backup & restore on helmless postgres (#6668) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Signed-off-by: Oriol Muñoz --- cluster/scripts/node-backup.sh | 8 ++------ cluster/scripts/node-restore.sh | 7 ++----- cluster/scripts/utils.source | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/cluster/scripts/node-backup.sh b/cluster/scripts/node-backup.sh index f84bcac39d..ebc0e869fe 100755 --- a/cluster/scripts/node-backup.sh +++ b/cluster/scripts/node-backup.sh @@ -74,10 +74,8 @@ function backup_pvc_postgres() { _info "** Backup up pvc-based postgres $description **" - # Since we only have one replica, it's always 0. - replica_index="0" local pvc_name - pvc_name="pg-data-hd-$instance-$replica_index" + pvc_name=$(get_postgres_pvc_name "$namespace" "$instance") backup_pvc "$description" "$namespace" "$pvc_name" "$migration_id" } @@ -197,10 +195,8 @@ function wait_for_postgres_backup() { type=$(get_postgres_type "$full_instance" "$stack") if [ "$type" == "canton:network:postgres" ]; then - # Since we only have one replica, it's always 0. - replica_index="0" local pvc_name - pvc_name="pg-data-hd-$instance-$replica_index" + pvc_name=$(get_postgres_pvc_name "$namespace" "$instance") wait_for_pvc_backup "$description" "$namespace" "$pvc_name" elif [ "$type" == "canton:cloud:postgres" ]; then wait_for_cloudsql_backup "$description" "$full_instance" "$stack" diff --git a/cluster/scripts/node-restore.sh b/cluster/scripts/node-restore.sh index a8237442fb..8417504cac 100755 --- a/cluster/scripts/node-restore.sh +++ b/cluster/scripts/node-restore.sh @@ -159,14 +159,11 @@ function restore_pvc_postgres() { local -r component=$2 local -r run_id=$3 - local template_name local storage_class - template_name="pg-data-hd" storage_class="hyperdisk-standard-rwo" - local -r ss_name="$component-pg" - local -r pg_pod_name="$ss_name-0" - local -r pvc_name="$template_name-$pg_pod_name" + local -r ss_name=$(get_postgres_statefulset_name "$namespace" "$component-pg") + local -r pvc_name=$(get_postgres_pvc_name "$namespace" "$component-pg") local -r snapshot_name="$pvc_name-$run_id" _info "Scaling down postgres StatefulSet" diff --git a/cluster/scripts/utils.source b/cluster/scripts/utils.source index df02c20b72..da6f79a103 100755 --- a/cluster/scripts/utils.source +++ b/cluster/scripts/utils.source @@ -30,6 +30,38 @@ function get_cloudsql_id() { fi } +function get_postgres_statefulset_name() { + local namespace=$1 + local instance=$2 + + local candidate + for candidate in "${instance}-helmless" "$instance"; do + if kubectl get statefulset -n "$namespace" "$candidate" >/dev/null 2>&1; then + echo "$candidate" + return 0 + fi + done + + _error "No postgres StatefulSet found for base name $instance in namespace $namespace" +} + +function get_postgres_pvc_name() { + local namespace=$1 + local instance=$2 + + local statefulset_name + statefulset_name=$(get_postgres_statefulset_name "$namespace" "$instance") + + local pvc_template_name + pvc_template_name=$(kubectl get statefulset -n "$namespace" "$statefulset_name" -o jsonpath='{.spec.volumeClaimTemplates[0].metadata.name}') + + if [ -z "$pvc_template_name" ]; then + _error "Unable to determine PVC template name for StatefulSet $statefulset_name in namespace $namespace" + fi + + echo "${pvc_template_name}-${statefulset_name}-0" +} + function get_stack_for_namespace_component() { local namespace=$1 local component=$2 From 09014439a5d0b079a1e6e3a6cef2e9bd8c4eb162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 5 Aug 2026 10:53:56 +0200 Subject: [PATCH 178/329] Fix uid of Sequencer Traffic dashboard (#6666) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Oriol Muñoz --- .../grafana/dashboards/canton-network/sequencer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/canton/community/app/src/pack/examples/13-observability/grafana/dashboards/canton-network/sequencer.json b/canton/community/app/src/pack/examples/13-observability/grafana/dashboards/canton-network/sequencer.json index e8cbf3b4eb..0839ea2ead 100644 --- a/canton/community/app/src/pack/examples/13-observability/grafana/dashboards/canton-network/sequencer.json +++ b/canton/community/app/src/pack/examples/13-observability/grafana/dashboards/canton-network/sequencer.json @@ -1321,7 +1321,7 @@ "timepicker": {}, "timezone": "", "title": "Sequencer Traffic", - "uid": "fdjrxql2alblsd", + "uid": "fdjrxql3alclsd", "version": 9, "weekStart": "" } From a01f08fea0a09807cb369cf62d9bc9f8d66e2719 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Wed, 5 Aug 2026 14:07:16 +0200 Subject: [PATCH 179/329] Fix cilr pod-reaper getting stuck due to pull image (#6672) [ci] Signed-off-by: Pasindu Tennage Signed-off-by: pasindutennage-da --- cluster/pulumi/infra/src/maintenance.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cluster/pulumi/infra/src/maintenance.ts b/cluster/pulumi/infra/src/maintenance.ts index 47753d355c..54cb748803 100644 --- a/cluster/pulumi/infra/src/maintenance.ts +++ b/cluster/pulumi/infra/src/maintenance.ts @@ -139,7 +139,7 @@ export function deployGCPodReaper( }, spec: { schedule: schedule, - concurrencyPolicy: 'Forbid', + concurrencyPolicy: 'Replace', // Replace stuck jobs instead of freezing successfulJobsHistoryLimit: 2, failedJobsHistoryLimit: 2, jobTemplate: { @@ -149,6 +149,7 @@ export function deployGCPodReaper( }, }, spec: { + activeDeadlineSeconds: 1200, // Kill the job if it hangs for 20 minutes template: { spec: { serviceAccountName: serviceAccountName, @@ -158,7 +159,7 @@ export function deployGCPodReaper( { name: cronJobName, image: `${DOCKER_REPO}/splice-debug:${versionFromDefault()}`, - imagePullPolicy: 'Always', + imagePullPolicy: 'IfNotPresent', // Stop forcing pulls if image is cached command: deleteBadPodsCommand, env: [ { From 496a0e401e0b594f2b04a2e2615ec362cda5c664 Mon Sep 17 00:00:00 2001 From: Martin Florian Date: Wed, 5 Aug 2026 14:58:48 +0200 Subject: [PATCH 180/329] Add alert for repeated `/api/scan/v0/sv-bft-sequencers` fails (#6675) Part of https://github.com/DACH-NY/cn-test-failures/issues/9485 ; next step will be to demote the log omitted there to info (or at least avoid it being warn already on the first fail). [static] Signed-off-by: Martin Florian --- cluster/expected/observability/expected.json | 1 + .../scan_bft_sequencers_alerts.yaml | 79 +++++++++++++++++++ .../pulumi/observability/src/observability.ts | 3 + 3 files changed, 83 insertions(+) create mode 100644 cluster/pulumi/observability/grafana-alerting/scan_bft_sequencers_alerts.yaml diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 1cb396836c..ed0dffdc2c 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -88,6 +88,7 @@ "mute_time_intervals.yaml": "apiVersion: 1\nmuteTimes:\n - orgId: 1\n name: sv1-mute\n time_intervals:\n - times:\n - start_time: '18:00'\n end_time: '24:00'\n weekdays:\n - wednesday\n location: UTC\n - orgId: 1\n name: sv-runbook-mute\n time_intervals:\n - times:\n - start_time: '18:00'\n end_time: '24:00'\n weekdays:\n - wednesday\n location: UTC\n - times:\n - start_time: '00:00'\n end_time: '06:00'\n - start_time: '12:00'\n end_time: '13:00'\n weekdays:\n - thursday\n location: UTC\n", "notification_policies.yaml": "apiVersion: 1\npolicies:\n - orgId: 1\n receiver: cn-ci-channel-notification\n routes:\n - receiver: cn-ci-channel-notification\n object_matchers:\n - - namespace\n - '='\n - sv1-test\n mute_time_intervals:\n - sv1-mute\n - receiver: cn-ci-channel-notification\n object_matchers:\n - - namespace\n - '='\n - sv-test\n mute_time_intervals:\n - sv-runbook-mute\n - receiver: cn-ci-channel-high-prio-notification\n object_matchers:\n - - priority\n - '='\n - high\n group_wait: 30s\n group_interval: 1m\n repeat_interval: 4h\n continue: true\n - receiver: cn-ci-channel-notification\n object_matchers:\n - - team\n - '!='\n - support\n group_wait: 30s\n group_interval: 10m\n repeat_interval: 4h\n", "pruning_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: pruning\n folder: canton-network\n interval: 10m\n rules:\n - uid: ff3sfady2o6bke\n title: Participant pruning\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: daml_pruning_max_event_age\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 720\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 10m\n annotations:\n description: The participant in {{ $labels.namespace }} on migration ID {{ $labels.migration_id }} has not been pruned {{ 30 }} days.\n summary: Participant has not pruned ACS.\n labels:\n \"\": \"\"\n isPaused: false\n - uid: df3sfgne49bswb\n title: Sequencer Pruning\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: daml_sequencer_max_event_age\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 720\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 10m\n annotations:\n description: The sequencer in {{ $labels.namespace }} on migration ID {{ $labels.migration_id }} has not been pruned {{ 30 }} days.\n summary: Sequencer has not been pruned.\n labels:\n \"\": \"\"\n isPaused: false\n - uid: ef3sfj563dq0we\n title: Mediator Pruning\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: daml_mediator_max_event_age\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 720\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 10m\n annotations:\n description: The mediator in {{ $labels.namespace }} on migration ID {{ $labels.migration_id }} has not been pruned {{ 30 }} days.\n summary: Mediator has not been pruned.\n labels:\n \"\": \"\"\n isPaused: false", + "scan_bft_sequencers_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: scan bft sequencer reads\n folder: canton-network\n interval: 10m\n rules:\n - uid: aesvbftscanreadfail1\n title: SV failing to read BFT sequencer list from a scan\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n sum by (namespace, job, target_host) (\n histogram_count(increase(daml_http_client_requests_duration_seconds{http_client=\"HttpScanAppClient\", operation=\"ListBftSequencers\", status_code!~\"2..\"}[30m]))\n )\n /\n sum by (namespace, job, target_host) (\n histogram_count(increase(daml_http_client_requests_duration_seconds{http_client=\"HttpScanAppClient\", operation=\"ListBftSequencers\"}[30m]))\n )\n instant: true\n intervalMs: 1000\n legendFormat: '{{namespace}}:{{job}}:{{target_host}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.5\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.5\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: a8a3113a-bccf-4728-b752-dd7a5d6f9bda\n panelId: 2\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: a8a3113a-bccf-4728-b752-dd7a5d6f9bda\n __panelId__: \"2\"\n description: More than 50% of this SV's reads of the BFT sequencer list (/v0/sv-bft-sequencers) from this scan failed (non-2xx response, timeout, or connection error) over the last 30 minutes. The BFT peer reconciler tolerates individual failures safely, but while this scan is unreachable the SV cannot pick up BFT sequencer endpoint changes published by that scan's SV.\n severity: warning\n summary: SV {{ $labels.job }} in namespace {{ $labels.namespace }} failed {{ index $values \"A\" }} of its BFT sequencer list reads from scan {{ $labels.target_host }} in the last 30m.\n labels: {}\n isPaused: false\n", "scan_connection_disagreement_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: scan connection consensus\n folder: canton-network\n interval: 10m\n rules:\n - uid: aescandisagreeok1\n title: Scan connection returning successful responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n max by (scan_connection, request) (\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"true\", request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\"}[30m]))\n )\n instant: true\n intervalMs: 1000\n legendFormat: '{{scan_connection}}:{{request}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons for this request on this scan connection returned a successful (2xx) response that disagreed with the BFT consensus result over the last 30 minutes.\n severity: critical\n summary: Scan connection {{ $labels.scan_connection }} returned successful responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons for request {{ $labels.request }} in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n - uid: aescandisagreefail1\n title: Scan connection returning failed responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n max by (scan_connection) (\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"false\", http_status!~\"404\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{http_status!~\"404\"}[30m]))\n )\n instant: true\n intervalMs: 1000\n legendFormat: '{{scan_connection}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons on this scan connection returned a failed (non-2xx) response that disagreed with the BFT consensus result over the last 30 minutes. This can indicate the scan node is down.\n severity: warning\n summary: Scan connection {{ $labels.scan_connection }} returned failed responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n", "sequencer_client_delay_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer client delay\n folder: canton\n interval: 1m\n rules:\n - uid: efe3v7bed6134e\n title: Sequencer Client Delay\n condition: client delay threshold exceeded\n data:\n - refId: client delay\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by (namespace, job) (timestamp(daml_sequencer_client_handler_last_sequencing_time_micros) - (daml_sequencer_client_handler_last_sequencing_time_micros>0)/1e6)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: client delay\n - refId: client delay threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 90\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: client delay\n intervalMs: 1000\n maxDataPoints: 43200\n refId: client delay threshold exceeded\n type: threshold\n dashboardUid: ca9df344-c699-4efe-83c2-5fb2639d96d9\n panelId: 3\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: ca9df344-c699-4efe-83c2-5fb2639d96d9\n __panelId__: \"3\"\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting an increased sequencer client delay.\n severity: critical\n labels: {}\n isPaused: false\n", "sequencer_connection_pool_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer connection pool\n folder: canton\n interval: 1m\n rules:\n - uid: dfeb9wma1dk3kc\n title: Health of a connection\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n adhocFilters: []\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (connection, namespace, job) (daml_sequencer_client_sequencer_connection_pool_connection_health{namespace=~\".*\", job=~\".*\"})\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}}:{{job}}:{{connection}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 2\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n panelId: 6\n noDataState: NoData\n execErrState: Error\n for: 1m\n annotations:\n __dashboardUid__: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n __panelId__: \"6\"\n description: A failed connection is periodically retried for availability. A fatal subscription is considered invalid and will never be retried.\n summary: The sequencer connection {{ $labels.connection }} of job {{ $labels.job }} in namespace {{ $labels.namespace }} failed validation.\n isPaused: false\n - uid: dfebau7w2r7cwa\n title: Number of active subscriptions in the subscription pool\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n adhocFilters: []\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (namespace, job) (daml_sequencer_client_sequencer_connection_pool_active_subscriptions{namespace=~\".*\",job=~\".*\"})\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}}:{{job}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: B\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (namespace, job) (daml_sequencer_client_sequencer_connection_pool_subscription_threshold{namespace=~\".*\",job=~\".*\"})\n hide: false\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: $A-$B\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n panelId: 5\n noDataState: NoData\n execErrState: Error\n for: 5m\n annotations:\n __dashboardUid__: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n __panelId__: \"5\"\n description: The liveness margin determines how many subscriptions on different sequencers are continuously maintained, beyond the minimum number defined by the trust threshold. In other words, the subscription pool will strive to maintain at all times (trust threshold + liveness margin)-many subscriptions active. This provides tolerance to subscriptions falling, enabling the node to continue operating while some sequencers are down.\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} has {{ index $values \"C\" }} less active subscriptions than the sum of trust threshold in the subscription pool\n severity: critical\n labels: {}\n isPaused: false\n", diff --git a/cluster/pulumi/observability/grafana-alerting/scan_bft_sequencers_alerts.yaml b/cluster/pulumi/observability/grafana-alerting/scan_bft_sequencers_alerts.yaml new file mode 100644 index 0000000000..618e452e39 --- /dev/null +++ b/cluster/pulumi/observability/grafana-alerting/scan_bft_sequencers_alerts.yaml @@ -0,0 +1,79 @@ +apiVersion: 1 +groups: + - orgId: 1 + name: scan bft sequencer reads + folder: canton-network + interval: 10m + rules: + - uid: aesvbftscanreadfail1 + title: SV failing to read BFT sequencer list from a scan + condition: C + data: + - refId: A + relativeTimeRange: + from: 1800 + to: 0 + datasourceUid: prometheus + model: + datasource: + type: prometheus + uid: prometheus + editorMode: code + expr: |- + sum by (namespace, job, target_host) ( + histogram_count(increase(daml_http_client_requests_duration_seconds{http_client="HttpScanAppClient", operation="ListBftSequencers", status_code!~"2.."}[30m])) + ) + / + sum by (namespace, job, target_host) ( + histogram_count(increase(daml_http_client_requests_duration_seconds{http_client="HttpScanAppClient", operation="ListBftSequencers"}[30m])) + ) + instant: true + intervalMs: 1000 + legendFormat: '{{namespace}}:{{job}}:{{target_host}}' + maxDataPoints: 43200 + range: false + refId: A + - refId: C + datasourceUid: __expr__ + model: + conditions: + - evaluator: + params: + - 0.5 + - 0 + type: gt + operator: + type: and + query: + params: [] + reducer: + params: [] + type: last + type: query + unloadEvaluator: + params: + - 0.5 + - 0 + type: lte + datasource: + name: Expression + type: __expr__ + uid: __expr__ + expression: A + intervalMs: 1000 + maxDataPoints: 43200 + refId: C + type: threshold + dashboardUid: a8a3113a-bccf-4728-b752-dd7a5d6f9bda + panelId: 2 + noDataState: OK + execErrState: OK + for: 0m + annotations: + __dashboardUid__: a8a3113a-bccf-4728-b752-dd7a5d6f9bda + __panelId__: "2" + description: More than 50% of this SV's reads of the BFT sequencer list (/v0/sv-bft-sequencers) from this scan failed (non-2xx response, timeout, or connection error) over the last 30 minutes. The BFT peer reconciler tolerates individual failures safely, but while this scan is unreachable the SV cannot pick up BFT sequencer endpoint changes published by that scan's SV. + severity: warning + summary: SV {{ $labels.job }} in namespace {{ $labels.namespace }} failed {{ index $values "A" }} of its BFT sequencer list reads from scan {{ $labels.target_host }} in the last 30m. + labels: {} + isPaused: false diff --git a/cluster/pulumi/observability/src/observability.ts b/cluster/pulumi/observability/src/observability.ts index 8e97d128ba..10b302233e 100644 --- a/cluster/pulumi/observability/src/observability.ts +++ b/cluster/pulumi/observability/src/observability.ts @@ -975,6 +975,9 @@ function createGrafanaAlerting(namespace: Input) { 'scan_connection_disagreement_alerts.yaml': substituteScanConnectionDisagreementAlerts( readGrafanaAlertingFile('scan_connection_disagreement_alerts.yaml') ), + 'scan_bft_sequencers_alerts.yaml': readGrafanaAlertingFile( + 'scan_bft_sequencers_alerts.yaml' + ), 'extra_k8s_alerts.yaml': readGrafanaAlertingFile('extra_k8s_alerts.yaml'), 'sequencer_rate_limit_alerts.yaml': readGrafanaAlertingFile( 'sequencer_rate_limit_alerts.yaml' From 31024be6f187ae6494d955a7e7f7b23e7cf34bb0 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:57:54 +0200 Subject: [PATCH 181/329] Increase default cantonbft segment length (#6671) * Increase default cantonbft segment length --------- Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- .../splice/sv/config/SvAppConfig.scala | 7 +++++-- docs/src/release_notes_upcoming.rst | 4 ++++ project/ignore-patterns/canton_log.ignore.txt | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala index 8dedaf4757..ae629e488b 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala @@ -461,13 +461,16 @@ case class SvAppBackendConfig( cantonBftSequencingParameters: Option[BftSequencingParameters] = Some( BftSequencingParameters( pbftViewChangeTimeout = PositiveFiniteDuration.ofSeconds(5), - segmentLength = SequencingParameters.DefaultSegmentLength.length, + // increased from default as epoch changes are synchronization points which can slow things down. + segmentLength = + PositiveLong.tryCreate(SequencingParameters.DefaultSegmentLength.length.value * 4), blacklistLeaderSelectionPolicyConfig = SequencingParameters.DefaultLeaderSelectionPolicyConfig.copy( howLongToBlacklist = BlacklistLeaderSelectionPolicyConfig.HowLongToBlacklist.Exponential( initialValue = 1L, - maximumEpochBlacklisted = Some(250L), + // Reduced by 4 to compensate for increased segmentLength. + maximumEpochBlacklisted = Some(250L / 4L), ) ), ) diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index c8975afa6e..235c261a90 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -14,3 +14,7 @@ This aligns with standard idempotency-key semantics: a second request with a previously accepted command id receives a 200 response with the same result as the first. Concurrent duplicates, where no submission has completed yet, are still rejected. + + - CantonBft + + - Increase the default segment length by 4x to reduce performance impact from epoch switches. diff --git a/project/ignore-patterns/canton_log.ignore.txt b/project/ignore-patterns/canton_log.ignore.txt index 02203eb1fe..8dd35b65f6 100644 --- a/project/ignore-patterns/canton_log.ignore.txt +++ b/project/ignore-patterns/canton_log.ignore.txt @@ -211,3 +211,6 @@ LOCAL_VERDICT_FAILED_MODEL_CONFORMANCE_CHECK.*Rejected transaction due to a fail # TODO(DACH-NY/cn-test-failures#9136) remove once Canton fixes this race condition from not storing packages in dependency order INTERNAL/Missing package-id.*in package metadata view + +# We can get temporarily blacklisted during onboarding triggering a retry for this. +Now retrying operation 'request current time' From 00b619ac58b5d3a88656d45e4d1c48b80c1aeac1 Mon Sep 17 00:00:00 2001 From: Martin Florian Date: Wed, 5 Aug 2026 16:26:31 +0200 Subject: [PATCH 182/329] Demote `Failed to read bft sequencers list from scan` to INFO (#6677) Fixes https://github.com/DACH-NY/cn-test-failures/issues/9485 Alerting now covered via https://github.com/canton-network/splice/pull/6675 I contemplated making the logging smarter (only warn if stays failed for N minutes), but somehow I'm not convinced that this is worth the effort and added complexity. Signed-off-by: Martin Florian --- .../integration/tests/BftScanConnectionIntegrationTest.scala | 2 -- .../splice/sv/onboarding/SequencerBftPeerReconciler.scala | 3 ++- project/ignore-patterns/canton_network_test_log.ignore.txt | 4 ---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BftScanConnectionIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BftScanConnectionIntegrationTest.scala index 6e60e173a5..e67b6943af 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BftScanConnectionIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BftScanConnectionIntegrationTest.scala @@ -90,8 +90,6 @@ class BftScanConnectionIntegrationTest ) or include("Encountered 4 consecutive transient failures") or include( "Failed to connect to scan of FAILED Seed URL #0 (http://localhost:5112)." - ) or include( - "Failed to read bft sequencers list from scan http://localhost:5112" )) ), ) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconciler.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconciler.scala index 161bde3d70..beb8cd4be7 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconciler.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SequencerBftPeerReconciler.scala @@ -138,7 +138,8 @@ abstract class SequencerBftPeerReconciler( scan .listSvBftSequencers() .recover { case NonFatal(ex) => - logger.warn(s"Failed to read bft sequencers list from scan ${scan.url}", ex) + // not a warn because short-term failures are benign and longer-term outages should be covered by other monitoring + logger.info(s"Failed to read bft sequencers list from scan ${scan.url}", ex) Seq.empty } } diff --git a/project/ignore-patterns/canton_network_test_log.ignore.txt b/project/ignore-patterns/canton_network_test_log.ignore.txt index ab044cad67..927af211f4 100644 --- a/project/ignore-patterns/canton_network_test_log.ignore.txt +++ b/project/ignore-patterns/canton_network_test_log.ignore.txt @@ -107,10 +107,6 @@ Splice unsafe shutdown future # SV UI polls cometbft status endpoint even when there's no cometBFT (we have this in the full network docker compose test) .*CometBFT is not configured for this app.* -# Expected during SV onboarding as we start scan and the sv app concurrently -# TODO(#893) maybe remove this again -Failed to read bft sequencers list from scan - Failed to connect to scan of Digital-Asset-Eng-3.*AppUpgradeIntegrationTest # Ignore scan issues in disaster recovery tests as we don't start scan there From ff14142f20246d751474864251ee3c00a567c0ff Mon Sep 17 00:00:00 2001 From: Matteo Limberto Date: Wed, 5 Aug 2026 22:07:08 +0200 Subject: [PATCH 183/329] localnet: make TARGET_TRAFFIC_THROUGHPUT configurable (#6676) Signed-off-by: Matteo Limberto --- cluster/compose/localnet/env/splice.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster/compose/localnet/env/splice.env b/cluster/compose/localnet/env/splice.env index 7a23f4bcd7..41edb60178 100644 --- a/cluster/compose/localnet/env/splice.env +++ b/cluster/compose/localnet/env/splice.env @@ -1,5 +1,5 @@ # Traffic topups -TARGET_TRAFFIC_THROUGHPUT=20000 +TARGET_TRAFFIC_THROUGHPUT=${TARGET_TRAFFIC_THROUGHPUT:-20000} MIN_TRAFFIC_TOPUP_INTERVAL=1m SPLICE_APP_VALIDATOR_SV_SPONSOR_ADDRESS=http://localhost:5014 From 257137c00b879c633a6852190bfed6329bb23fe1 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Wed, 5 Aug 2026 22:42:30 +0200 Subject: [PATCH 184/329] Update canton to 3.5.12-snapshot.20260804.19139.0.vbd8b06e0 (#6684) [ci] Signed-off-by: pasindutennage-da Signed-off-by: Pasindu Tennage --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index 5c0d3a759d..fe8557521c 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.11", - "oss_sha256": "sha256:19nrzwix6pkqg7ah3jbb4nx2g4772f9m966dklzfya545rqinx6h", - "canton_base_image_sha256": "sha256:6cdfea8af002ac46bcd1c4f6e2355cc99f20635bc87cb02df999f9b52738ba71", - "canton_participant_image_sha256": "sha256:b0b50bf86560d66ee382d9eebdc4e6829341564c03327d90699de478f1950324", - "canton_mediator_image_sha256": "sha256:a6f2bf118e1e293850fae4a29c01b33d30cb806d5e104dfe8e10c081fb20b0d5", - "canton_sequencer_image_sha256": "sha256:9af09befb80623cf16d70d38b112c65aa311ce744811711f5fc5b93e4548d5da" + "version": "3.5.12-snapshot.20260804.19139.0.vbd8b06e0", + "oss_sha256": "sha256:18vymy7ph3lddabaxxv0l8fq470kvvf6mmd6bnhklrlyc9ypddx2", + "canton_base_image_sha256": "sha256:beb89710fc11d302fe0bcc3af8262b72d1c2f9d5ea7484c7c471abe3048a8377", + "canton_participant_image_sha256": "sha256:3861e1c8bafd3cc3b9df466da256a01d86c8fd9f579e014d6fa717d2b3f1110f", + "canton_mediator_image_sha256": "sha256:2c3147ca302838d539f48b4b5d13af510ed097ad9d77c297dac6de3f0410ce50", + "canton_sequencer_image_sha256": "sha256:9f9039668d60c115f9385f012a8a033badf7dbbd7dda596f7d30d53d3a72e175" } From 9d8d0dc820151f19ca2cba9f956d1c8e4fbb91db Mon Sep 17 00:00:00 2001 From: Jaeyoon Cho Date: Thu, 6 Aug 2026 14:27:36 +0900 Subject: [PATCH 185/329] Support external sharing configuration (#6353) Signed-off-by: JYC11 Signed-off-by: Jaeyoon Cho Co-authored-by: Simon Meier Co-authored-by: Divam <681060+dfordivam@users.noreply.github.com> --- .../splice/config/SpliceConfig.scala | 82 +++++++++---- .../splice/config/SpliceConfigTest.scala | 90 ++++++++++++++- ...ngDelegationTimeBasedIntegrationTest.scala | 108 +++++++++++++++++- ...alletRewardsTimeBasedIntegrationTest.scala | 65 ++++++++++- .../wallet/ExternalPartyWalletManager.scala | 2 +- .../splice/wallet/UserWalletManager.scala | 2 +- .../splice/wallet/UserWalletService.scala | 4 +- ...ntingDelegationCollectRewardsTrigger.scala | 64 +++++++---- .../automation/RewardSharingTrigger.scala | 2 +- .../UserWalletAutomationService.scala | 19 +-- .../wallet/config/WalletAppConfig.scala | 71 ++++++++---- docs/src/release_notes_upcoming.rst | 21 ++++ 12 files changed, 443 insertions(+), 87 deletions(-) diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala index fad206d8f9..57f81e1ce7 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala @@ -31,7 +31,7 @@ import org.lfdecentralizedtrust.splice.splitwell.config.{ SplitwellSynchronizerConfig, } import org.lfdecentralizedtrust.splice.sv.config.* -import org.lfdecentralizedtrust.splice.sv.{SvAppClientConfig} +import org.lfdecentralizedtrust.splice.sv.SvAppClientConfig import org.lfdecentralizedtrust.splice.sv.config.SvOnboardingConfig.FoundDso import org.lfdecentralizedtrust.splice.util.{Codec, SpliceRateLimitConfig} import org.lfdecentralizedtrust.splice.validator.config.* @@ -69,8 +69,8 @@ import com.typesafe.config.{Config, ConfigRenderOptions} import com.typesafe.config.ConfigException.UnresolvedSubstitution import org.slf4j.{Logger, LoggerFactory} import pureconfig.configurable.{genericMapReader, genericMapWriter} -import pureconfig.generic.FieldCoproductHint -import pureconfig.{ConfigReader, ConfigWriter} +import pureconfig.generic.{CoproductHint, FieldCoproductHint, ProductHint} +import pureconfig.{ConfigCursor, ConfigReader, ConfigWriter} import pureconfig.error.{CannotConvert, FailureReason} import pureconfig.module.cats.{nonEmptyListReader, nonEmptyListWriter} import io.circe.parser.* @@ -695,8 +695,34 @@ object SpliceConfig { deriveReader[AutoAcceptTransfersConfig] implicit val appRewardBeneficiaryConfigReader: ConfigReader[AppRewardBeneficiaryConfig] = deriveReader[AppRewardBeneficiaryConfig] + + implicit val rewardSharingConfigHint: FieldCoproductHint[RewardSharingConfig] = + new FieldCoproductHint[RewardSharingConfig]("type") { + override def from( + cursor: ConfigCursor, + options: Seq[String], + ): ConfigReader.Result[CoproductHint.Action] = { + cursor.asObjectCursor.flatMap { objCur => + if (objCur.atKeyOrUndefined("type").isUndefined) { + options + .find(fieldValue(_) == "built-in") + .fold(super.from(cursor, options))(opt => Right(CoproductHint.Use(objCur, opt))) + } else { + super.from(cursor, options) + } + } + } + } + + implicit val rewardSharingBuiltInReader: ConfigReader[RewardSharingConfig.BuiltIn] = + deriveReader[RewardSharingConfig.BuiltIn] + implicit val rewardSharingExternalHint: ProductHint[RewardSharingConfig.External] = + ProductHint[RewardSharingConfig.External](allowUnknownKeys = false) + implicit val rewardSharingExternalReader: ConfigReader[RewardSharingConfig.External] = + deriveReader[RewardSharingConfig.External] implicit val rewardSharingConfigReader: ConfigReader[RewardSharingConfig] = deriveReader[RewardSharingConfig] + implicit val validatorDecentralizedSynchronizerConfigReader : ConfigReader[ValidatorDecentralizedSynchronizerConfig] = deriveReader[ValidatorDecentralizedSynchronizerConfig].emap(config => { @@ -827,29 +853,33 @@ object SpliceConfig { case (Left(err), _) => Left(err) case (Right(()), (party, sharingConfig)) => for { - _ <- Either.cond( - sharingConfig.beneficiaries.forall(b => - b.percentage > 0 && b.percentage <= BigDecimal(1.0) - ), - (), - ConfigValidationFailed( - s"Reward sharing percentages for $party must be in (0.0, 1.0]" - ), - ) - _ <- Either.cond( - sharingConfig.beneficiaries.map(_.percentage).sum <= BigDecimal(1.0), - (), - ConfigValidationFailed( - s"Reward sharing percentages for $party must sum to at most 1.0" - ), - ) _ <- Either.cond( sharingConfig.batchSize > 0, (), - ConfigValidationFailed( - s"Reward sharing batchSize for $party must be positive" - ), + ConfigValidationFailed(s"Reward sharing batchSize for $party must be positive"), ) + _ <- sharingConfig match { + case RewardSharingConfig.External(_) => Right(()) + case builtIn: RewardSharingConfig.BuiltIn => + for { + _ <- Either.cond( + builtIn.beneficiaries.forall(b => + b.percentage > 0 && b.percentage <= BigDecimal(1.0) + ), + (), + ConfigValidationFailed( + s"Reward sharing percentages for $party must be in (0.0, 1.0]" + ), + ) + _ <- Either.cond( + builtIn.beneficiaries.map(_.percentage).sum <= BigDecimal(1.0), + (), + ConfigValidationFailed( + s"Reward sharing percentages for $party must sum to at most 1.0" + ), + ) + } yield () + } } yield () } } yield conf @@ -1137,8 +1167,16 @@ object SpliceConfig { deriveWriter[AutoAcceptTransfersConfig] implicit val appRewardBeneficiaryConfigWriter: ConfigWriter[AppRewardBeneficiaryConfig] = deriveWriter[AppRewardBeneficiaryConfig] + + implicit val rewardSharingConfigHint: FieldCoproductHint[RewardSharingConfig] = + new FieldCoproductHint[RewardSharingConfig]("type") + implicit val rewardSharingConfigBuiltInWriter: ConfigWriter[RewardSharingConfig.BuiltIn] = + deriveWriter[RewardSharingConfig.BuiltIn] + implicit val rewardSharingConfigExternalWriter: ConfigWriter[RewardSharingConfig.External] = + deriveWriter[RewardSharingConfig.External] implicit val rewardSharingConfigWriter: ConfigWriter[RewardSharingConfig] = deriveWriter[RewardSharingConfig] + implicit val validatorDecentralizedSynchronizerConfigWriter : ConfigWriter[ValidatorDecentralizedSynchronizerConfig] = deriveWriter[ValidatorDecentralizedSynchronizerConfig] diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/config/SpliceConfigTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/config/SpliceConfigTest.scala index db5150864f..041b6cc1e7 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/config/SpliceConfigTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/config/SpliceConfigTest.scala @@ -87,8 +87,8 @@ class SpliceConfigTest extends AsyncWordSpec with BaseTest { } // Shared helper for RewardSharingConfig tests - private def mkSharingCfg(percentages: BigDecimal*): RewardSharingConfig = - RewardSharingConfig( + private def mkSharingCfg(percentages: BigDecimal*): RewardSharingConfig.BuiltIn = + RewardSharingConfig.BuiltIn( minTtlAfterSharing = NonNegativeFiniteDuration.ofHours(30), beneficiaries = percentages.zipWithIndex.map { case (pct, i) => AppRewardBeneficiaryConfig( @@ -165,6 +165,7 @@ class SpliceConfigTest extends AsyncWordSpec with BaseTest { s""" |canton.validator-apps.aliceValidator.reward-sharing-config-by-party = { | "alice::1220abc" = { + | type = "built-in" | beneficiaries = [$beneficiaries] | min-ttl-after-sharing = 30h | } @@ -222,6 +223,7 @@ class SpliceConfigTest extends AsyncWordSpec with BaseTest { """ |canton.validator-apps.aliceValidator.reward-sharing-config-by-party = { | "alice::1220abc" = { + | type = "built-in" | beneficiaries = [{ beneficiary = "bob::1220", percentage = 0.4 }] | min-ttl-after-sharing = 30h | batch-size = 50 @@ -238,6 +240,7 @@ class SpliceConfigTest extends AsyncWordSpec with BaseTest { """ |canton.validator-apps.aliceValidator.reward-sharing-config-by-party = { | "alice::1220abc" = { + | type = "built-in" | beneficiaries = [{ beneficiary = "bob::1220", percentage = 0.4 }] | min-ttl-after-sharing = 30h | batch-size = 0 @@ -252,5 +255,88 @@ class SpliceConfigTest extends AsyncWordSpec with BaseTest { .value .toString should include("batchSize") } + + def sharingConfigOf(cfg: SpliceConfig): RewardSharingConfig = + cfg.validatorApps.values + .flatMap(_.rewardSharingConfigByParty.get("alice::1220abc")) + .loneElement + + "accept type = external with no beneficiaries and custom batch size" in { + val overwrite = ConfigFactory.parseString( + """ + |canton.validator-apps.aliceValidator.reward-sharing-config-by-party = { + | "alice::1220abc" = { + | type = "external" + | batch-size = 500 + | } + |} + """.stripMargin + ) + val validConfig = CantonConfig.mergeConfigs(config, Seq(overwrite)) + val loaded = SpliceConfig.loadAndValidate(validConfig).value + sharingConfigOf(loaded) shouldBe RewardSharingConfig.External(batchSize = 500) + } + + "accept explicit type = built-in with beneficiaries" in { + val overwrite = ConfigFactory.parseString( + """ + |canton.validator-apps.aliceValidator.reward-sharing-config-by-party = { + | "alice::1220abc" = { + | type = "built-in" + | beneficiaries = [{ beneficiary = "bob::1220", percentage = 0.4 }] + | min-ttl-after-sharing = 30h + | } + |} + """.stripMargin + ) + val validConfig = CantonConfig.mergeConfigs(config, Seq(overwrite)) + val loaded = SpliceConfig.loadAndValidate(validConfig).value + sharingConfigOf(loaded) shouldBe a[RewardSharingConfig.BuiltIn] + } + + "reject type = external, with beneficiaries" in { + val overwrite = ConfigFactory.parseString( + """ + |canton.validator-apps.aliceValidator.reward-sharing-config-by-party = { + | "alice::1220abc" = { + | type = "external" + | beneficiaries = [{ beneficiary = "bob::1220", percentage = 0.4 }] + | } + |} + """.stripMargin + ) + val validConfig = CantonConfig.mergeConfigs(config, Seq(overwrite)) + SpliceConfig.loadAndValidate(validConfig) shouldBe a[Left[?, ?]] + } + + "reject an invalid type value" in { + val overwrite = ConfigFactory.parseString( + """ + |canton.validator-apps.aliceValidator.reward-sharing-config-by-party = { + | "alice::1220abc" = { + | type = "bogus" + | } + |} + """.stripMargin + ) + val buggyConfig = CantonConfig.mergeConfigs(config, Seq(overwrite)) + SpliceConfig.loadAndValidate(buggyConfig) shouldBe a[Left[?, ?]] + } + + "default to built-in when type is omitted (legacy config shape)" in { + val overwrite = ConfigFactory.parseString( + """ + |canton.validator-apps.aliceValidator.reward-sharing-config-by-party = { + | "alice::1220abc" = { + | beneficiaries = [{ beneficiary = "bob::1220", percentage = 0.4 }] + | min-ttl-after-sharing = 30h + | } + |} + """.stripMargin + ) + val loaded = + SpliceConfig.loadAndValidate(CantonConfig.mergeConfigs(config, Seq(overwrite))).value + sharingConfigOf(loaded) shouldBe a[RewardSharingConfig.BuiltIn] + } } } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletMintingDelegationTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletMintingDelegationTimeBasedIntegrationTest.scala index 1e25e56b30..8ea958b5c6 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletMintingDelegationTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletMintingDelegationTimeBasedIntegrationTest.scala @@ -51,6 +51,7 @@ class WalletMintingDelegationTimeBasedIntegrationTest // Pre-generate key pairs so external party IDs are known at config time private val sharingAppProvider = preGenerateExternalParty("sharing_app_provider") private val sharingRecipient = preGenerateExternalParty("sharing_recipient") + private val externalSharingProvider = preGenerateExternalParty("external_sharing_provider") // We create many coupons directly, so avoid running sanity checks override protected def runUpdateHistorySanityCheck: Boolean = false @@ -66,13 +67,14 @@ class WalletMintingDelegationTimeBasedIntegrationTest updateAllValidatorConfigs { case (name, c) => if (name == "aliceValidator") { c.copy( - rewardSharingConfigByParty = Map( - sharingAppProvider.partyId.toProtoPrimitive -> RewardSharingConfig( + rewardSharingConfigByParty = Map[String, RewardSharingConfig]( + sharingAppProvider.partyId.toProtoPrimitive -> RewardSharingConfig.BuiltIn( minTtlAfterSharing = NonNegativeFiniteDuration.ofHours(25), beneficiaries = Seq( AppRewardBeneficiaryConfig(sharingRecipient.partyId, BigDecimal(0.4)) ), - ) + ), + externalSharingProvider.partyId.toProtoPrimitive -> RewardSharingConfig.External(), ) ) } else c @@ -756,6 +758,106 @@ class WalletMintingDelegationTimeBasedIntegrationTest } } } + "mint already-assigned V2 coupons but hold back unassigned ones in external sharing mode" in { + implicit env => + val aliceParty = onboardWalletUser(aliceWalletClient, aliceValidatorBackend) + aliceWalletClient.tap(100.0) + aliceValidatorWalletClient.tap(100.0) + + val externalSharingParty = + onboardExternalParty(aliceValidatorBackend, externalSharingProvider) + createAndAcceptExternalPartySetupProposal(aliceValidatorBackend, externalSharingParty) + + val expiresAt = env.environment.clock.now.plus(Duration.ofDays(30)).toInstant + val (_, proposalContractId) = actAndCheck( + "Create minting delegation proposal", + createMintingDelegationProposal(externalSharingParty, aliceParty, expiresAt), + )( + "Proposal is visible", + _ => { + val proposals = aliceWalletClient.listMintingDelegationProposals() + proposals.proposals should have size 1 withClue "proposals" + proposals.proposals.head.contract.contractId + }, + ) + + actAndCheck( + "Alice accepts the proposal", + aliceWalletClient.acceptMintingDelegationProposal(proposalContractId), + )( + "Delegation is created", + _ => { + val delegations = aliceWalletClient.listMintingDelegations() + delegations.delegations should have size 1 withClue "delegations" + }, + ) + + val unassignedAmount1 = BigDecimal(1000.0) + val unassignedAmount2 = BigDecimal(500.0) + val assignedAmount = BigDecimal(250.0) + + val externalPartyMintingDelegationTrigger = mintingDelegationCollectRewardsTrigger( + aliceValidatorBackend, + externalSharingParty.party, + ) + + val externalPartyWallet = aliceValidatorBackend.appState.walletManager + .valueOrFail("WalletManager is expected to be defined") + .externalPartyWalletManager + .lookupExternalPartyWallet(externalSharingParty.party) + .valueOrFail( + s"Expected ${externalSharingParty.party} to have an external party wallet" + ) + + // Pause the trigger, create two unassigned and one already-assigned V2 + // coupon, then resume. In external sharing mode the off-node automation + // owns beneficiary assignment, so the trigger must leave the unassigned + // coupons untouched while still minting the already-assigned coupon. + setTriggersWithin(triggersToPauseAtStart = Seq(externalPartyMintingDelegationTrigger)) { + actAndCheck( + "Create V2 coupons", + createRewardCouponsV2( + Seq( + (externalSharingParty.party, unassignedAmount1, None), + (externalSharingParty.party, unassignedAmount2, None), + (externalSharingParty.party, assignedAmount, Some(externalSharingParty.party)), + ) + ), + )( + "Coupons are visible in store", + _ => + externalPartyWallet.store.multiDomainAcsStore + .listContracts(RewardCouponV2.COMPANION) + .futureValue should have size 3, + ) + } + + clue("Assigned coupon is minted while unassigned coupons are held back untouched") { + eventually() { + val v2Coupons = externalPartyWallet.store.multiDomainAcsStore + .listContracts(RewardCouponV2.COMPANION) + .futureValue + v2Coupons.filter(_.payload.beneficiary.isEmpty) should have size 2 withClue + "external sharing mode must leave unassigned coupons untouched" + v2Coupons.filter(_.payload.beneficiary.isPresent) shouldBe + empty withClue "the already-assigned coupon must be minted and consumed" + } + } + + // Only the already-assigned coupon is minted; the two unassigned coupons + // are neither shared nor collected, so they do not contribute to the balance. + clue("Balance reflects only the directly minted assigned coupon") { + eventually() { + val balance = BigDecimal( + aliceValidatorBackend + .getExternalPartyBalance(externalSharingParty.party) + .totalUnlockedCoin + ) + balance shouldBe assignedAmount withClue + "external sharing mode mints only the already-assigned coupon, not the held-back unassigned ones" + } + } + } } private def collectRewardsAndMergeAmuletsTrigger( diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletRewardsTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletRewardsTimeBasedIntegrationTest.scala index 39852b9dde..5cabd4b0bf 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletRewardsTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletRewardsTimeBasedIntegrationTest.scala @@ -19,7 +19,10 @@ import org.lfdecentralizedtrust.splice.util.{ WalletTestUtil, } import org.lfdecentralizedtrust.splice.validator.automation.ReceiveFaucetCouponTrigger -import org.lfdecentralizedtrust.splice.wallet.automation.CollectRewardsAndMergeAmuletsTrigger +import org.lfdecentralizedtrust.splice.wallet.automation.{ + CollectRewardsAndMergeAmuletsTrigger, + RewardSharingTrigger, +} import org.lfdecentralizedtrust.splice.wallet.config.{ AppRewardBeneficiaryConfig, RewardSharingConfig, @@ -31,6 +34,11 @@ import scala.concurrent.duration.DurationInt * that the sharing trigger correctly assigns beneficiaries with the right * amounts (batching multiple coupons), that the minting trigger does not * re-assign unshared coupons, and that balances reflect the minted rewards. + * The test also verifies external sharing mode: a party configured with the + * External reward-sharing config gets no built-in sharing trigger, and its + * unassigned reward coupons are left untouched (neither shared nor collected). + * This confirms that built-in and external sharing modes co-exist in one + * environment without interfering. */ @org.lfdecentralizedtrust.splice.util.scalatesttags.SpliceAmulet_0_1_19 class WalletRewardsTimeBasedIntegrationTest @@ -54,12 +62,14 @@ class WalletRewardsTimeBasedIntegrationTest } val aliceValidatorPartyId = validatorPartyId("alice_validator_user", "aliceValidator") val bobValidatorPartyId = validatorPartyId("bob_validator_user", "bobValidator") + val splitwellValidatorPartyId = + validatorPartyId("splitwell_validator_user", "splitwellValidator") updateAllValidatorConfigs { case (name, c) => if (name == "aliceValidator") { // Alice shares 40% with bob; the implicit remainder (60%) goes to alice. c.copy( rewardSharingConfigByParty = Map( - aliceValidatorPartyId.toProtoPrimitive -> RewardSharingConfig( + aliceValidatorPartyId.toProtoPrimitive -> RewardSharingConfig.BuiltIn( minTtlAfterSharing = NonNegativeFiniteDuration.ofHours(30), beneficiaries = Seq( AppRewardBeneficiaryConfig(bobValidatorPartyId, BigDecimal(0.4)) @@ -67,6 +77,12 @@ class WalletRewardsTimeBasedIntegrationTest ) ) ) + } else if (name == "splitwellValidator") { + c.copy( + rewardSharingConfigByParty = Map( + splitwellValidatorPartyId.toProtoPrimitive -> RewardSharingConfig.External() + ) + ) } else c }(config) }) @@ -88,6 +104,24 @@ class WalletRewardsTimeBasedIntegrationTest waitForWalletUser(bobValidatorWalletClient) val aliceValidatorParty = aliceValidatorBackend.getValidatorPartyId() val bobValidatorParty = bobValidatorBackend.getValidatorPartyId() + val splitwellValidatorParty = splitwellValidatorBackend.getValidatorPartyId() + + clue( + "alice (built in) has sharing trigger; splitwell (external sharing automation) does not" + ) { + val aliceAutomation = aliceValidatorBackend + .userWalletAutomation(aliceValidatorWalletClient.config.ledgerApiUser) + .futureValue + aliceAutomation.triggers[RewardSharingTrigger] should not be empty + + eventually() { + val splitwellWallet = splitwellValidatorBackend.appState.walletManager + .valueOrFail("WalletManager is expected to be defined") + .lookupEndUserPartyWallet(splitwellValidatorParty) + .valueOrFail("Expected splitwell validator to have a wallet") + splitwellWallet.automation.triggers[RewardSharingTrigger] shouldBe empty + } + } // Tap amulet and do a transfer from alice to bob aliceWalletClient.tap(walletAmuletToUsd(50)) @@ -111,6 +145,7 @@ class WalletRewardsTimeBasedIntegrationTest val bobV2Amount = BigDecimal(1000.0) val aliceV2Amounts = Seq(BigDecimal(10.0), BigDecimal(5.0)) + val splitwellV2Amount = BigDecimal(7.0) val openRounds = eventually() { import math.Ordering.Implicits.* @@ -159,10 +194,11 @@ class WalletRewardsTimeBasedIntegrationTest // Bob (no sharing config) → his coupon stays unminted (trigger paused). // Alice (has sharing config, 2 coupons) → shared then minted, // exercising batching via additionalCoupons in AssignBeneficiaries. - clue("Create unassigned RewardCouponV2 for both validators") { + clue("Create unassigned RewardCouponV2 for all validators") { createRewardCouponsV2( Seq( - (bobValidatorParty, bobV2Amount, None) + (bobValidatorParty, bobV2Amount, None), + (splitwellValidatorParty, splitwellV2Amount, None), ) ++ aliceV2Amounts.map((aliceValidatorParty, _, None)) ) } @@ -225,6 +261,27 @@ class WalletRewardsTimeBasedIntegrationTest } } + clue("splitwell's external-sharing-mode coupon is neither shared nor collected") { + val splitwellWallet = splitwellValidatorBackend.appState.walletManager + .valueOrFail("WalletManager is expected to be defined") + .lookupEndUserPartyWallet(splitwellValidatorParty) + .valueOrFail("Expected splitwell validator to have a wallet") + eventually() { + val coupons = splitwellWallet.store.multiDomainAcsStore + .listContracts(RewardCouponV2.COMPANION) + .futureValue + .filter(_.payload.provider == splitwellValidatorParty.toProtoPrimitive) + + coupons should have size 1 withClue + "the single unassigned coupon must still be present" + + coupons.filter(_.payload.beneficiary.isPresent) shouldBe + empty withClue "external sharing mode must not assign beneficiaries" + + BigDecimal(coupons.head.payload.amount) shouldBe splitwellV2Amount + } + } + balance } diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletManager.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletManager.scala index bea200160a..d410127388 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletManager.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/ExternalPartyWalletManager.scala @@ -179,7 +179,7 @@ class ExternalPartyWalletManager( packageVersionSupport, rewardSharingConfigByParty.getOrElse( externalParty.toProtoPrimitive, - RewardSharingConfig(), + RewardSharingConfig.BuiltIn(), ), ) } catch { diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletManager.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletManager.scala index 026c5f4c07..5e90a51ba6 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletManager.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletManager.scala @@ -236,7 +236,7 @@ class UserWalletManager( walletSweep.get(endUserParty.toProtoPrimitive), autoAcceptTransfers.get(endUserParty.toProtoPrimitive), rewardSharingConfigByParty - .getOrElse(endUserParty.toProtoPrimitive, RewardSharingConfig()), + .getOrElse(endUserParty.toProtoPrimitive, RewardSharingConfig.BuiltIn()), dedupDuration, params, ) diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletService.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletService.scala index 4c793f4575..3a8b7a316e 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletService.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/UserWalletService.scala @@ -100,7 +100,7 @@ class UserWalletService( walletManager, retryProvider, scanConnection, - mintUnassignedRewardCouponsV2 = rewardSharingConfig.beneficiaries.isEmpty, + mintUnassignedRewardCouponsV2 = rewardSharingConfig.mintUnassignedCoupons, loggerFactory, ) } catch { @@ -110,7 +110,7 @@ class UserWalletService( throw e } - val automation = + val automation: UserWalletAutomationService = try { new UserWalletAutomationService( store, diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/MintingDelegationCollectRewardsTrigger.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/MintingDelegationCollectRewardsTrigger.scala index b2e645547d..925ef6ad08 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/MintingDelegationCollectRewardsTrigger.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/MintingDelegationCollectRewardsTrigger.scala @@ -80,6 +80,8 @@ class MintingDelegationCollectRewardsTrigger( materializer: Materializer, ) extends PollingTrigger { + import MintingDelegationCollectRewardsTrigger.* + private def externalParty = store.key.externalParty override protected def extraMetricLabels = Seq("party" -> externalParty.toString) @@ -149,6 +151,20 @@ class MintingDelegationCollectRewardsTrigger( } } + // Handling of unassigned V2 coupons (no beneficiary yet) depends on the sharing mode: + // - No sharing (no beneficiaries, not external): mint them directly to ourselves. + // - InternalSharing (beneficiaries set): hold them back, assign to the configured + // beneficiaries first, then mint; already-assigned coupons mint directly. + // - ExternalSharing: hold them back and leave them untouched, so the + // off-node automation owns their assignment; only already-assigned coupons mint here. + private val mode: SharingMode = + rewardSharingConfig match { + case RewardSharingConfig.External(_) => ExternalSharing + case builtIn: RewardSharingConfig.BuiltIn if builtIn.automateRewardSharing => + InternalSharing(builtIn) + case _: RewardSharingConfig.BuiltIn => NoSharing + } + private def performMintIfNeeded( mintInputs: MintInputs, couponsData: CouponsData, @@ -162,30 +178,29 @@ class MintingDelegationCollectRewardsTrigger( val amuletsToMerge = selectAmuletsToMerge(amulets, mintInputs.delegation) val shouldMergeAmulets = amuletsToMerge.nonEmpty - // Without sharing config, all V2 coupons are mintable directly. - // With sharing config, only assigned-to-us V2 coupons are mintable; - // unassigned ones need sharing first. - val hasBeneficiaries = rewardSharingConfig.beneficiaries.nonEmpty - val (unassignedV2, mintableV2) = - if (hasBeneficiaries) + val (unassignedV2, mintableV2) = mode match { + case NoSharing => (Seq.empty, filteredCouponsData.rewardCouponsV2) + case InternalSharing(_) | ExternalSharing => filteredCouponsData.rewardCouponsV2.partition(_.payload.beneficiary.isEmpty) - else - (Seq.empty, filteredCouponsData.rewardCouponsV2) + } val couponsToMint = filteredCouponsData.copy(rewardCouponsV2 = mintableV2) + val submission = buildMintSubmissionData(mintInputs, couponsToMint, amuletsToMerge) // Share when the TTL threshold is reached, or batch sharing with // amulet merging to reduce traffic costs by combining both in one transaction. - val shouldAssign = unassignedV2.nonEmpty && - (shouldShareNow(unassignedV2, rewardSharingConfig) || shouldMergeAmulets) - - val submission = buildMintSubmissionData(mintInputs, couponsToMint, amuletsToMerge) - if (shouldAssign) { - performAssignAndMint(submission, unassignedV2.toList, rewardSharingConfig) - } else if (couponsToMint.hasRewards || shouldMergeAmulets) { - performMint(submission) - } else { - // Nothing to do: no rewards to mint, coupons to assign, or amulets to merge - Future.successful(false) + val hasSomethingToMint = couponsToMint.hasRewards || shouldMergeAmulets + mode match { + case InternalSharing(config) => + val shouldAssignAndMint = unassignedV2.nonEmpty && (shouldShareNow( + unassignedV2, + config, + ) || shouldMergeAmulets) + if (shouldAssignAndMint) performAssignAndMint(submission, unassignedV2.toList, config) + else if (hasSomethingToMint) performMint(submission) + else Future.successful(false) + case NoSharing | ExternalSharing => + if (hasSomethingToMint) performMint(submission) + else Future.successful(false) } } @@ -213,7 +228,7 @@ class MintingDelegationCollectRewardsTrigger( private def performAssignAndMint( submission: MintSubmissionData, unassignedV2: List[Contract[RewardCouponV2.ContractId, RewardCouponV2]], - config: RewardSharingConfig, + config: RewardSharingConfig.BuiltIn, )(implicit tc: TraceContext): Future[Boolean] = { unassignedV2 match { case Nil => @@ -436,7 +451,7 @@ class MintingDelegationCollectRewardsTrigger( private def shouldShareNow( coupons: Seq[Contract[RewardCouponV2.ContractId, RewardCouponV2]], - config: RewardSharingConfig, + config: RewardSharingConfig.BuiltIn, ): Boolean = { val now = context.clock.now.toInstant val minTtl = config.minTtlAfterSharing.asJava @@ -508,3 +523,10 @@ class MintingDelegationCollectRewardsTrigger( ) } } + +object MintingDelegationCollectRewardsTrigger { + private sealed trait SharingMode + private case object NoSharing extends SharingMode + private final case class InternalSharing(config: RewardSharingConfig.BuiltIn) extends SharingMode + private case object ExternalSharing extends SharingMode +} diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/RewardSharingTrigger.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/RewardSharingTrigger.scala index c9ed70ca64..40e5dff446 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/RewardSharingTrigger.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/RewardSharingTrigger.scala @@ -32,7 +32,7 @@ import scala.jdk.CollectionConverters.* class RewardSharingTrigger( override protected val context: TriggerContext, store: UserWalletStore, - config: RewardSharingConfig, + config: RewardSharingConfig.BuiltIn, spliceLedgerConnection: SpliceLedgerConnection, )(implicit override val ec: ExecutionContext, diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/UserWalletAutomationService.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/UserWalletAutomationService.scala index 9a597b9da4..23be2f128f 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/UserWalletAutomationService.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/automation/UserWalletAutomationService.scala @@ -177,15 +177,18 @@ class UserWalletAutomationService( ) } - if (rewardSharingConfig.beneficiaries.nonEmpty) { - registerTrigger( - new RewardSharingTrigger( - triggerContext, - store, - rewardSharingConfig, - connection(SpliceLedgerConnectionPriority.Low), + rewardSharingConfig match { + case builtIn: RewardSharingConfig.BuiltIn if builtIn.automateRewardSharing => + registerTrigger( + new RewardSharingTrigger( + triggerContext, + store, + builtIn, + connection(SpliceLedgerConnectionPriority.Low), + ) ) - ) + case _: RewardSharingConfig.BuiltIn => () + case _: RewardSharingConfig.External => () } registerTrigger( diff --git a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/config/WalletAppConfig.scala b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/config/WalletAppConfig.scala index 75d179a8ce..2cb4f9ba35 100644 --- a/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/config/WalletAppConfig.scala +++ b/apps/wallet/src/main/scala/org/lfdecentralizedtrust/splice/wallet/config/WalletAppConfig.scala @@ -55,28 +55,55 @@ final case class AppRewardBeneficiaryConfig( percentage: BigDecimal, ) -/** Configuration for sharing traffic-based app reward coupons with beneficiaries. - * @param minTtlAfterSharing minimum remaining coupon TTL before sharing is triggered; - * e.g., 30h means share when 30h of coupon lifetime remains (6h after creation for 36h coupons) - * @param beneficiaries parties to share rewards with and their percentages; - * the provider keeps the remainder (1.0 - sum of percentages) - * @param batchSize maximum number of coupons to share or assign per trigger run - */ -final case class RewardSharingConfig( - minTtlAfterSharing: NonNegativeFiniteDuration = NonNegativeFiniteDuration.ofHours(30), - beneficiaries: Seq[AppRewardBeneficiaryConfig] = Seq.empty, - batchSize: Int = 100, -) { - def providerRemainder: BigDecimal = BigDecimal(1.0) - beneficiaries.map(_.percentage).sum - - @VisibleForTesting - def allBeneficiaries(provider: PartyId): Seq[AppRewardBeneficiaryConfig] = { - val remainder = providerRemainder - beneficiaries ++ - (if (remainder > 0) Seq(AppRewardBeneficiaryConfig(provider, remainder)) - else Seq.empty) +/** How traffic-based app reward coupons are shared with beneficiaries. */ +sealed trait RewardSharingConfig { + def mintUnassignedCoupons: Boolean + def automateRewardSharing: Boolean + def batchSize: Int +} + +object RewardSharingConfig { + + val DefaultBatchSize: Int = 100 + + /** Beneficiary assignment for RewardCouponV2 contracts is managed by a process + * external to the validator app: the validator app must thus leave unassigned + * coupons untouched rather than assigning or minting them itself. + */ + case class External( + batchSize: Int = DefaultBatchSize + ) extends RewardSharingConfig { + override def mintUnassignedCoupons: Boolean = false + override def automateRewardSharing: Boolean = false } - def allDamlBeneficiaries(provider: PartyId): Seq[(PartyId, java.math.BigDecimal)] = - allBeneficiaries(provider).map(b => (b.beneficiary, SpliceUtil.damlDecimal(b.percentage))) + /** The node performs beneficiary assignment and minting itself. + * @param minTtlAfterSharing minimum remaining coupon TTL before sharing is triggered; + * e.g., 30h means share when 30h of coupon lifetime remains (6h after creation for 36h coupons) + * @param beneficiaries parties to share rewards with and their percentages; + * the provider keeps the remainder (1.0 - sum of percentages) + * @param batchSize maximum number of coupons to share or assign per trigger run + */ + final case class BuiltIn( + minTtlAfterSharing: NonNegativeFiniteDuration = NonNegativeFiniteDuration.ofHours(30), + beneficiaries: Seq[AppRewardBeneficiaryConfig] = Seq.empty, + batchSize: Int = DefaultBatchSize, + ) extends RewardSharingConfig { + def providerRemainder: BigDecimal = BigDecimal(1.0) - beneficiaries.map(_.percentage).sum + + @VisibleForTesting + def allBeneficiaries(provider: PartyId): Seq[AppRewardBeneficiaryConfig] = { + val remainder = providerRemainder + beneficiaries ++ + (if (remainder > 0) Seq(AppRewardBeneficiaryConfig(provider, remainder)) + else Seq.empty) + } + + def allDamlBeneficiaries(provider: PartyId): Seq[(PartyId, java.math.BigDecimal)] = + allBeneficiaries(provider).map(b => (b.beneficiary, SpliceUtil.damlDecimal(b.percentage))) + + override def mintUnassignedCoupons: Boolean = beneficiaries.isEmpty + + override def automateRewardSharing: Boolean = beneficiaries.nonEmpty + } } diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 235c261a90..f86a9877d5 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -18,3 +18,24 @@ - CantonBft - Increase the default segment length by 4x to reduce performance impact from epoch switches. + + - Validator App + + - Added a ``type`` parameter to validator config's ``reward-sharing-config-by-party`` option. + + When this is set to ``external``, it indicates that the assignment of reward coupons to beneficiaries is being managed by a process external to the validator app, and thus the validator app's automation does not assign or mint the unassigned coupons. + + The ``type`` defaults to ``built-in`` preserving the existing behavior where the validator app will either mint the unassigned rewards coupons, or assign them to beneficiaries if configured. + + See the reward-sharing documentation for details: + https://docs.canton.network/global-synchronizer/splice-fundamentals/reward-sharing#reward-sharing + + Example enabling external sharing automation for a party:: + + canton.validator-apps..reward-sharing-config-by-party = { + "" = { + type = "external" + # Optionally batch-size may be specified to configure the maximum number of coupons to mint in a single transaction + batch-size = 80 + } + } From 491d80382023bd1e4e9d1eb830ae73227f349a9d Mon Sep 17 00:00:00 2001 From: canton-network-da Date: Thu, 6 Aug 2026 10:20:05 +0200 Subject: [PATCH 186/329] [ci] bump GHA runner version to the latest (auto-generated) (#6648) Signed-off-by: DA Automation Co-authored-by: DA Automation --- cluster/images/splice-test-docker-runner/Dockerfile | 4 ++-- cluster/images/splice-test-runner-hook/Dockerfile | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cluster/images/splice-test-docker-runner/Dockerfile b/cluster/images/splice-test-docker-runner/Dockerfile index 6c5c8bce9f..587bfec4f2 100644 --- a/cluster/images/splice-test-docker-runner/Dockerfile +++ b/cluster/images/splice-test-docker-runner/Dockerfile @@ -1,5 +1,5 @@ -ARG RUNNER_VERSION=2.335.1 -ARG RUNNER_DIGEST=sha256:08c30b0a7105f64bddfc485d2487a22aa03932a791402393352fdf674bda2c29 +ARG RUNNER_VERSION=2.336.0 +ARG RUNNER_DIGEST=sha256:0cfdcc701ce933c6d243c6b0b2da767366dc9f2e99961d4c3754b0b78084cdda # Note that we don't currently support arm64 runners, so we build this only for amd64 FROM --platform=$BUILDPLATFORM ghcr.io/actions/actions-runner:${RUNNER_VERSION}@${RUNNER_DIGEST} diff --git a/cluster/images/splice-test-runner-hook/Dockerfile b/cluster/images/splice-test-runner-hook/Dockerfile index 5c9c8802a2..7104427fb3 100644 --- a/cluster/images/splice-test-runner-hook/Dockerfile +++ b/cluster/images/splice-test-runner-hook/Dockerfile @@ -1,5 +1,5 @@ -ARG RUNNER_VERSION=2.335.1 -ARG RUNNER_DIGEST=sha256:08c30b0a7105f64bddfc485d2487a22aa03932a791402393352fdf674bda2c29 +ARG RUNNER_VERSION=2.336.0 +ARG RUNNER_DIGEST=sha256:0cfdcc701ce933c6d243c6b0b2da767366dc9f2e99961d4c3754b0b78084cdda # Note that we don't currently support arm64 runners, so we build this only for amd64 FROM --platform=$BUILDPLATFORM ghcr.io/actions/actions-runner:${RUNNER_VERSION}@${RUNNER_DIGEST} From f003204c1d5b2fd843320cb15486a405e5cb5bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Thu, 6 Aug 2026 10:46:34 +0200 Subject: [PATCH 187/329] Bump timeout in archive expired VoteRequest contracts test (#6692) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../tests/SvTimeBasedOnboardingIntegrationTest.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvTimeBasedOnboardingIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvTimeBasedOnboardingIntegrationTest.scala index 6376cd4d42..28dc924e85 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvTimeBasedOnboardingIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvTimeBasedOnboardingIntegrationTest.scala @@ -228,7 +228,10 @@ class SvTimeBasedOnboardingIntegrationTest }, ) - actAndCheck("one week has passed", advanceTime(JavaDuration.ofDays(8)))( + actAndCheck(timeUntilSuccess = 30.seconds)( + "one week has passed", + advanceTime(JavaDuration.ofDays(8)), + )( "the vote request is not displayed anymore", _ => { sv1Backend.listVoteRequests() shouldBe empty withClue "VoteRequests" From 3406e645ee8742f42a510d0e80f707ba8a0d8b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Thu, 6 Aug 2026 11:05:09 +0200 Subject: [PATCH 188/329] remove migration ID from non-SV participant names (#5991) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Błażejewski --- cluster/expected/splitwell/expected.json | 190 +++++++------- .../expected/validator-runbook/expected.json | 8 +- cluster/expected/validator1/expected.json | 244 +++++++++--------- .../templates/required.yaml | 4 - .../templates/splitwell.yaml | 2 +- .../pulumi/canton-network/src/chaosMesh.ts | 2 +- .../common-validator/src/participant.ts | 17 +- cluster/pulumi/splitwell/src/splitwell.ts | 1 - .../validator-runbook/src/installNode.ts | 1 - .../validator-runbook/src/partyAllocator.ts | 3 +- cluster/pulumi/validator1/src/validator1.ts | 13 +- 11 files changed, 236 insertions(+), 249 deletions(-) diff --git a/cluster/expected/splitwell/expected.json b/cluster/expected/splitwell/expected.json index f1dacdc6aa..d6479a67ad 100644 --- a/cluster/expected/splitwell/expected.json +++ b/cluster/expected/splitwell/expected.json @@ -526,99 +526,6 @@ "provider": "", "type": "kubernetes:core/v1:Secret" }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-participant", - "compat": "true", - "maxHistory": 10, - "name": "participant-2", - "namespace": "splitwell", - "timeout": 600, - "values": { - "additionalEnvVars": [ - { - "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", - "value": "# Ignore missing ACS commitment and commitment mismatches\ncanton.participants.participant.parameters.stores.safe-to-prune-commitment-state = \"all\"\n" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1", - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "auth": { - "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json", - "targetAudience": "https://canton.network.global" - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "disableAuth": false, - "enableHealthProbes": true, - "enablePostgresMetrics": true, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "metrics": { - "enable": true - }, - "participantAdminUserNameFrom": { - "secretKeyRef": { - "key": "ledger-api-user", - "name": "splice-app-validator-ledger-api-auth", - "optional": false - } - }, - "persistence": { - "databaseName": "participant_2", - "postgresName": "participant-pg", - "schema": "participant", - "secretName": "participant-pg-secrets" - }, - "resources": { - "limits": { - "memory": "8Gi" - }, - "requests": { - "memory": "4Gi" - } - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] - }, - "version": "0.3.20" - }, - "name": "splitwell-participant-2", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, { "custom": true, "id": "", @@ -748,6 +655,99 @@ "provider": "", "type": "gcp:sql/databaseInstance:DatabaseInstance" }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-participant", + "compat": "true", + "maxHistory": 10, + "name": "participant", + "namespace": "splitwell", + "timeout": 600, + "values": { + "additionalEnvVars": [ + { + "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", + "value": "# Ignore missing ACS commitment and commitment mismatches\ncanton.participants.participant.parameters.stores.safe-to-prune-commitment-state = \"all\"\n" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "auth": { + "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json", + "targetAudience": "https://canton.network.global" + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "disableAuth": false, + "enableHealthProbes": true, + "enablePostgresMetrics": true, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "metrics": { + "enable": true + }, + "participantAdminUserNameFrom": { + "secretKeyRef": { + "key": "ledger-api-user", + "name": "splice-app-validator-ledger-api-auth", + "optional": false + } + }, + "persistence": { + "databaseName": "participant_2", + "postgresName": "participant-pg", + "schema": "participant", + "secretName": "participant-pg-secrets" + }, + "resources": { + "limits": { + "memory": "8Gi" + }, + "requests": { + "memory": "4Gi" + } + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + }, + "version": "0.3.20" + }, + "name": "splitwell-participant", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, { "custom": true, "id": "", @@ -797,7 +797,7 @@ "migration": { "id": 9 }, - "participantHost": "participant-2", + "participantHost": "participant", "persistence": { "databaseName": "app_splitwell", "port": 5432, @@ -1160,7 +1160,7 @@ "optional": false } }, - "participantAddress": "participant-2", + "participantAddress": "participant", "participantIdentitiesDumpPeriodicBackup": { "backupInterval": "10m", "location": { diff --git a/cluster/expected/validator-runbook/expected.json b/cluster/expected/validator-runbook/expected.json index ef6d6662fd..b27b7190d9 100644 --- a/cluster/expected/validator-runbook/expected.json +++ b/cluster/expected/validator-runbook/expected.json @@ -420,7 +420,7 @@ "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-participant", "compat": "true", "maxHistory": 10, - "name": "participant-2", + "name": "participant", "namespace": "validator", "timeout": 600, "values": { @@ -503,7 +503,7 @@ }, "version": "0.3.20" }, - "name": "validator-participant-2", + "name": "validator-participant", "provider": "", "type": "kubernetes:helm.sh/v3:Release" }, @@ -548,7 +548,7 @@ "name": "cn-mocknet" }, "config": { - "jsonLedgerApiUrl": "http://participant-2:7575", + "jsonLedgerApiUrl": "http://participant:7575", "keyDirectory": "/keys", "maxParties": 1234, "parallelism": 321, @@ -895,7 +895,7 @@ }, "migrateValidatorParty": false, "nodeIdentifier": "validator-runbook", - "participantAddress": "participant-2", + "participantAddress": "participant", "participantIdentitiesDumpPeriodicBackup": { "backupInterval": "10m", "location": { diff --git a/cluster/expected/validator1/expected.json b/cluster/expected/validator1/expected.json index ed65f7cc81..cea2b9a7e6 100644 --- a/cluster/expected/validator1/expected.json +++ b/cluster/expected/validator1/expected.json @@ -479,9 +479,7 @@ }, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", "ingress": { - "decentralizedSynchronizer": { - "activeMigrationId": "2" - }, + "decentralizedSynchronizer": {}, "splitwell": true }, "spliceDomainNames": { @@ -564,125 +562,6 @@ "provider": "", "type": "kubernetes:core/v1:Secret" }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-participant", - "compat": "true", - "maxHistory": 10, - "name": "participant-2", - "namespace": "validator1", - "timeout": 600, - "values": { - "additionalEnvVars": [ - { - "name": "GOOGLE_APPLICATION_CREDENTIALS", - "value": "/app/gcp-credentials.json" - }, - { - "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", - "value": "# Ignore missing ACS commitment and commitment mismatches\ncanton.participants.participant.parameters.stores.safe-to-prune-commitment-state = \"all\"\n" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1", - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "auth": { - "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json", - "targetAudience": "https://canton.network.global" - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "disableAuth": false, - "enableHealthProbes": true, - "enablePostgresMetrics": true, - "extraVolumeMounts": [ - { - "mountPath": "/app/gcp-credentials.json", - "name": "gcp-credentials", - "subPath": "googleCredentials" - } - ], - "extraVolumes": [ - { - "name": "gcp-credentials", - "secret": { - "secretName": "gke-credentials" - } - } - ], - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "kms": { - "keyRingId": "validator1_mock", - "locationId": "us-central1", - "projectId": "da-cn-devnet", - "type": "gcp" - }, - "logLevel": "DEBUG", - "metrics": { - "enable": true - }, - "participantAdminUserNameFrom": { - "secretKeyRef": { - "key": "ledger-api-user", - "name": "splice-app-validator-ledger-api-auth", - "optional": false - } - }, - "persistence": { - "databaseName": "participant_2", - "postgresName": "participant-pg", - "schema": "participant", - "secretName": "participant-pg-secrets" - }, - "resources": { - "limits": { - "memory": "8Gi" - }, - "requests": { - "memory": "4Gi" - } - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] - }, - "version": "0.3.20" - }, - "name": "validator1-participant-2", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, { "custom": true, "id": "", @@ -812,6 +691,125 @@ "provider": "", "type": "gcp:sql/databaseInstance:DatabaseInstance" }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-participant", + "compat": "true", + "maxHistory": 10, + "name": "participant", + "namespace": "validator1", + "timeout": 600, + "values": { + "additionalEnvVars": [ + { + "name": "GOOGLE_APPLICATION_CREDENTIALS", + "value": "/app/gcp-credentials.json" + }, + { + "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", + "value": "# Ignore missing ACS commitment and commitment mismatches\ncanton.participants.participant.parameters.stores.safe-to-prune-commitment-state = \"all\"\n" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "auth": { + "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json", + "targetAudience": "https://canton.network.global" + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "disableAuth": false, + "enableHealthProbes": true, + "enablePostgresMetrics": true, + "extraVolumeMounts": [ + { + "mountPath": "/app/gcp-credentials.json", + "name": "gcp-credentials", + "subPath": "googleCredentials" + } + ], + "extraVolumes": [ + { + "name": "gcp-credentials", + "secret": { + "secretName": "gke-credentials" + } + } + ], + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "kms": { + "keyRingId": "validator1_mock", + "locationId": "us-central1", + "projectId": "da-cn-devnet", + "type": "gcp" + }, + "logLevel": "DEBUG", + "metrics": { + "enable": true + }, + "participantAdminUserNameFrom": { + "secretKeyRef": { + "key": "ledger-api-user", + "name": "splice-app-validator-ledger-api-auth", + "optional": false + } + }, + "persistence": { + "databaseName": "participant_2", + "postgresName": "participant-pg", + "schema": "participant", + "secretName": "participant-pg-secrets" + }, + "resources": { + "limits": { + "memory": "8Gi" + }, + "requests": { + "memory": "4Gi" + } + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + }, + "version": "0.3.20" + }, + "name": "validator1-participant", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, { "custom": true, "id": "", @@ -1090,7 +1088,7 @@ "optional": false } }, - "participantAddress": "participant-2", + "participantAddress": "participant", "participantIdentitiesDumpPeriodicBackup": { "backupInterval": "10m", "location": { diff --git a/cluster/helm/splice-cluster-ingress-runbook/templates/required.yaml b/cluster/helm/splice-cluster-ingress-runbook/templates/required.yaml index bf1603450a..14d85ae7fc 100644 --- a/cluster/helm/splice-cluster-ingress-runbook/templates/required.yaml +++ b/cluster/helm/splice-cluster-ingress-runbook/templates/required.yaml @@ -5,7 +5,3 @@ {{ $_ := required ".Values.cluster.hostname is required." (.Values.cluster).hostname }} {{ $_ := required ".Values.cluster.svNamespace is required." (.Values.cluster).svNamespace }} -# if ingress.splitwell is set to true then require the active migration id -{{- if .Values.ingress.splitwell }} -{{ $_ := required ".Values.ingress.decentralizedSynchronizer.activeMigrationId is required." ((.Values.ingress).decentralizedSynchronizer).activeMigrationId }} -{{- end }} diff --git a/cluster/helm/splice-cluster-ingress-runbook/templates/splitwell.yaml b/cluster/helm/splice-cluster-ingress-runbook/templates/splitwell.yaml index 94677abc4f..7a199ef5a2 100644 --- a/cluster/helm/splice-cluster-ingress-runbook/templates/splitwell.yaml +++ b/cluster/helm/splice-cluster-ingress-runbook/templates/splitwell.yaml @@ -22,7 +22,7 @@ spec: - destination: port: number: 7575 - host: participant-{{ ((.Values.ingress).decentralizedSynchronizer).activeMigrationId }}.{{ (.Values.cluster).svNamespace }}.svc.cluster.local + host: participant.{{ (.Values.cluster).svNamespace }}.svc.cluster.local rewrite: uri: "/" - match: diff --git a/cluster/pulumi/canton-network/src/chaosMesh.ts b/cluster/pulumi/canton-network/src/chaosMesh.ts index 9442791948..f901863d3b 100644 --- a/cluster/pulumi/canton-network/src/chaosMesh.ts +++ b/cluster/pulumi/canton-network/src/chaosMesh.ts @@ -251,7 +251,7 @@ export const installChaosMesh = ({ dependsOn }: ChaosMeshArguments): k8s.helm.v3 `global-domain-${DecentralizedSynchronizerUpgradeConfig.active.id}-cometbft`, `global-domain-${DecentralizedSynchronizerUpgradeConfig.active.id}-mediator`, `global-domain-${DecentralizedSynchronizerUpgradeConfig.active.id}-sequencer`, - `participant-${DecentralizedSynchronizerUpgradeConfig.active.id}`, + 'participant', 'scan-app', 'sv-app', 'validator-app', diff --git a/cluster/pulumi/common-validator/src/participant.ts b/cluster/pulumi/common-validator/src/participant.ts index 10221ac7de..f41485b826 100644 --- a/cluster/pulumi/common-validator/src/participant.ts +++ b/cluster/pulumi/common-validator/src/participant.ts @@ -6,17 +6,16 @@ import { Auth0Config, auth0UserNameEnvVarSource, ChartValues, - DomainMigrationIndex, ExactNamespace, getAdditionalJvmOptions, getParticipantKmsHelmResources, installSpliceHelmChart, loadYamlFromFile, - sanitizedForPostgres, SPLICE_ROOT, SpliceCustomResourceOptions, spliceConfig, getLedgerApiAudience, + DecentralizedSynchronizerUpgradeConfig, } from '@canton-network/splice-pulumi-common'; import { ValidatorNodeConfig } from '@canton-network/splice-pulumi-common-validator'; import { CnChartVersion } from '@canton-network/splice-pulumi-common/src/artifacts'; @@ -24,7 +23,6 @@ import { Output } from '@pulumi/pulumi'; export async function installParticipant( validatorConfig: ValidatorNodeConfig, - migrationId: DomainMigrationIndex, xns: ExactNamespace, auth0Config: Auth0Config, disableAuth?: boolean, @@ -56,8 +54,7 @@ export async function installParticipant( } ), ...loadYamlFromFile( - `${SPLICE_ROOT}/apps/app/src/pack/examples/sv-helm/standalone-participant-values.yaml`, - { MIGRATION_ID: migrationId.toString() } + `${SPLICE_ROOT}/apps/app/src/pack/examples/sv-helm/standalone-participant-values.yaml` ), ...kmsValues, metrics: { @@ -73,8 +70,8 @@ export async function installParticipant( }, }; - const name = `participant-${migrationId}`; - const pgName = sanitizedForPostgres(name); + const name = 'participant'; + const pgName = `participant_${DecentralizedSynchronizerUpgradeConfig.frozenMigrationId}`; const release = installSpliceHelmChart( xns, name, @@ -113,6 +110,12 @@ export async function installParticipant( dependsOn: (customOptions?.dependsOn || []) .concat([participantPostgres]) .concat(kmsDependencies), + deleteBeforeReplace: true, + aliases: [ + { + name: `${xns.logicalName}-participant-${DecentralizedSynchronizerUpgradeConfig.frozenMigrationId}`, + }, + ], } ); return { diff --git a/cluster/pulumi/splitwell/src/splitwell.ts b/cluster/pulumi/splitwell/src/splitwell.ts index 361d714e00..233608935a 100644 --- a/cluster/pulumi/splitwell/src/splitwell.ts +++ b/cluster/pulumi/splitwell/src/splitwell.ts @@ -63,7 +63,6 @@ export async function installSplitwell( const participant = await installParticipant( splitwellConfig, - decentralizedSynchronizerMigrationConfig.activeMigrationId, xns, auth0Client.getCfg(), false, diff --git a/cluster/pulumi/validator-runbook/src/installNode.ts b/cluster/pulumi/validator-runbook/src/installNode.ts index 15c8238c91..7ac1faedd5 100644 --- a/cluster/pulumi/validator-runbook/src/installNode.ts +++ b/cluster/pulumi/validator-runbook/src/installNode.ts @@ -189,7 +189,6 @@ async function installValidator( const participantAddress = ( await installParticipant( validatorConfig, - DecentralizedSynchronizerUpgradeConfig.activeMigrationId, xns, auth0Client.getCfg(), false, // We don't currently support non-auth for validator-runbook diff --git a/cluster/pulumi/validator-runbook/src/partyAllocator.ts b/cluster/pulumi/validator-runbook/src/partyAllocator.ts index e97ce99bb9..60006d771e 100644 --- a/cluster/pulumi/validator-runbook/src/partyAllocator.ts +++ b/cluster/pulumi/validator-runbook/src/partyAllocator.ts @@ -5,7 +5,6 @@ import { activeVersion, CnInput, createVolumeSnapshot, - DecentralizedSynchronizerUpgradeConfig, ExactNamespace, InstalledHelmChart, installSpliceHelmChart, @@ -26,7 +25,7 @@ export function installPartyAllocator( config: { token: '${SPLICE_APP_VALIDATOR_LEDGER_API_AUTH_TOKEN}', userId: '${SPLICE_APP_VALIDATOR_LEDGER_API_AUTH_USER_NAME}', - jsonLedgerApiUrl: `http://participant-${DecentralizedSynchronizerUpgradeConfig.activeMigrationId}:7575`, + jsonLedgerApiUrl: `http://participant:7575`, scanApiUrl: 'http://scan-app.sv-1:5012', validatorApiUrl: 'http://validator-app:5003', maxParties: config.maxParties, diff --git a/cluster/pulumi/validator1/src/validator1.ts b/cluster/pulumi/validator1/src/validator1.ts index cd82a160af..4f84de9529 100644 --- a/cluster/pulumi/validator1/src/validator1.ts +++ b/cluster/pulumi/validator1/src/validator1.ts @@ -82,7 +82,6 @@ export async function installValidator1( const participant = await installParticipant( validator1Config, - decentralizedSynchronizerMigrationConfig.activeMigrationId, xns, auth0Client.getCfg(), validator1Config?.disableAuth, @@ -132,7 +131,7 @@ export async function installValidator1( version: activeVersion, additionalEnvVars: validator1Config?.validatorApp?.additionalEnvVars, }); - installIngress(xns, installSplitwell, decentralizedSynchronizerMigrationConfig); + installIngress(xns, installSplitwell); if (installSplitwell) { installSpliceHelmChart( @@ -158,11 +157,7 @@ export async function installValidator1( return validator; } -function installIngress( - xns: ExactNamespace, - splitwell: boolean, - decentralizedSynchronizerMigrationConfig: DecentralizedSynchronizerMigrationConfig -) { +function installIngress(xns: ExactNamespace, splitwell: boolean) { installSpliceHelmChart( xns, `cluster-ingress-${xns.logicalName}`, @@ -178,9 +173,7 @@ function installIngress( }, ingress: { splitwell: splitwell, - decentralizedSynchronizer: { - activeMigrationId: decentralizedSynchronizerMigrationConfig.activeMigrationId.toString(), - }, + decentralizedSynchronizer: {}, }, } ); From 73774ae6569ee5e6c7ca979502bfbe1330a2cc92 Mon Sep 17 00:00:00 2001 From: Divam <681060+dfordivam@users.noreply.github.com> Date: Thu, 6 Aug 2026 18:18:27 +0900 Subject: [PATCH 189/329] Ingest ProcessRewardsV2 in ScanRewardsReferenceStore (#6691) Signed-off-by: Divam --- .../splice/scan/store/ScanRewardsReferenceStore.scala | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanRewardsReferenceStore.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanRewardsReferenceStore.scala index 505fcae739..e98796e306 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanRewardsReferenceStore.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanRewardsReferenceStore.scala @@ -189,6 +189,17 @@ object ScanRewardsReferenceStore { round = Some(contract.payload.round.number), ) }, + mkFilter(splice.amulet.rewardaccountingv2.ProcessRewardsV2.COMPANION)( + co => co.payload.dso == dso, + versionGuard = { case (pkgVersionSupport, now) => + (tc) => pkgVersionSupport.supportsTrafficBasedAppRewards(Seq(key.dsoParty), now)(tc) + }, + ) { contract => + ScanRewardsReferenceStoreRowData( + contract = contract, + round = Some(contract.payload.round.number), + ) + }, ), interfaceFilters = Map.empty, synchronizerFilter = Some(key.synchronizerId), From 3f4ad9e9c19220b20d53cafc79eebe419a8625df Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Thu, 6 Aug 2026 11:35:08 +0200 Subject: [PATCH 190/329] Add bad counterparties auto-ignore mechanism in Ans and Transfer pre-approval expiry triggers (#6680) Signed-off-by: Julien Tinguely --- ...inimalVettedPackagesIntegrationTest.scala} | 182 +++++++++++++----- .../splice}/store/IgnoredPartiesStore.scala | 2 +- .../splice/store/MultiDomainAcsStore.scala | 4 +- .../store/db/DbMultiDomainAcsStore.scala | 21 +- .../DsoDelegateBasedAutomationService.scala | 30 ++- .../ExpireRewardCouponsTrigger.scala | 4 +- .../ExpireTransferPreapprovalsTrigger.scala | 68 +++++-- .../ExpiredAmuletAllocationTrigger.scala | 2 +- .../ExpiredAmuletAllocationV2Trigger.scala | 2 +- ...iredAmuletTransferInstructionTrigger.scala | 2 +- .../delegatebased/ExpiredAmuletTrigger.scala | 2 +- .../ExpiredAnsEntryTrigger.scala | 62 +++++- .../ExpiredAnsSubscriptionTrigger.scala | 59 +++++- .../ExpiredLockedAmuletTrigger.scala | 2 +- .../FeaturedAppActivityMarkerTrigger.scala | 2 +- .../IgnoredAmuletVersionGuard.scala | 2 +- .../splice/sv/store/SvDsoStore.scala | 17 +- .../splice/sv/store/db/DbSvDsoStore.scala | 24 ++- .../splice/store/db/SvDsoStoreTest.scala | 168 ++++++++++++---- test-full-class-names.log | 4 +- 20 files changed, 509 insertions(+), 150 deletions(-) rename apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/{AmuletExpiryWithOldPackageIntegrationTest.scala => ExpiryWithMinimalVettedPackagesIntegrationTest.scala} (65%) rename apps/{sv/src/main/scala/org/lfdecentralizedtrust/splice/sv => common/src/main/scala/org/lfdecentralizedtrust/splice}/store/IgnoredPartiesStore.scala (93%) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AmuletExpiryWithOldPackageIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ExpiryWithMinimalVettedPackagesIntegrationTest.scala similarity index 65% rename from apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AmuletExpiryWithOldPackageIntegrationTest.scala rename to apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ExpiryWithMinimalVettedPackagesIntegrationTest.scala index ceb4cfa41a..b513cf43f0 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AmuletExpiryWithOldPackageIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ExpiryWithMinimalVettedPackagesIntegrationTest.scala @@ -5,14 +5,26 @@ package org.lfdecentralizedtrust.splice.integration.tests import com.digitalasset.canton.config.CantonRequireTypes.InstanceName import com.digitalasset.canton.config.NonNegativeFiniteDuration +import com.digitalasset.canton.discard.Implicits.DiscardOps import com.digitalasset.canton.logging.SuppressionRule import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.topology.transaction.ParticipantPermission import com.digitalasset.daml.lf.data.Ref.{PackageName, PackageVersion} +import org.lfdecentralizedtrust.splice.codegen.java.da.time.types.RelTime import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet.{ AppRewardCoupon, FeaturedAppActivityMarker, } +import org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.TransferPreapproval +import org.lfdecentralizedtrust.splice.codegen.java.splice.ans.{AnsEntry, AnsEntryContext} +import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.payment.{PaymentAmount, Unit} +import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.subscriptions.{ + Subscription, + SubscriptionData, + SubscriptionIdleState, + SubscriptionPayData, + SubscriptionRequest, +} import org.lfdecentralizedtrust.splice.config.ConfigTransforms import org.lfdecentralizedtrust.splice.config.ConfigTransforms.{ ConfigurableApp, @@ -28,18 +40,22 @@ import org.lfdecentralizedtrust.splice.store.db.DbMultiDomainAcsStore import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.{ AdvanceOpenMiningRoundTrigger, ExpireRewardCouponsTrigger, + ExpireTransferPreapprovalsTrigger, ExpiredAmuletTrigger, + ExpiredAnsEntryTrigger, + ExpiredAnsSubscriptionTrigger, ExpiredLockedAmuletTrigger, FeaturedAppActivityMarkerTrigger, UpdateExternalPartyConfigStateTrigger, } import org.lfdecentralizedtrust.splice.util.* +import org.lfdecentralizedtrust.splice.wallet.automation.SubscriptionReadyForPaymentTrigger import org.slf4j.event.Level import scala.concurrent.duration.* import java.time.Duration -abstract class AmuletExpiryWithOldPackageIntegrationTestBase +abstract class ExpiryWithMinimalVettedPackagesIntegrationTestBase extends IntegrationTestWithIsolatedEnvironment with WalletTestUtil with TimeTestUtil @@ -92,11 +108,15 @@ abstract class AmuletExpiryWithOldPackageIntegrationTestBase .withPausedTrigger[UpdateExternalPartyConfigStateTrigger] .withPausedTrigger[ExpireRewardCouponsTrigger] .withPausedTrigger[FeaturedAppActivityMarkerTrigger] + .withPausedTrigger[ExpireTransferPreapprovalsTrigger] + .withPausedTrigger[ExpiredAnsEntryTrigger] + .withPausedTrigger[ExpiredAnsSubscriptionTrigger] )(c) ) .addConfigTransforms((_, c) => updateAutomationConfig(ConfigurableApp.Validator)( _.copy(enableAutomaticRewardsCollectionAndAmuletMerging = false) + .withPausedTrigger[SubscriptionReadyForPaymentTrigger] )(c) ) .addConfigTransforms((_, c) => @@ -110,7 +130,27 @@ abstract class AmuletExpiryWithOldPackageIntegrationTestBase )(c) ) - def setupAliceWithDustAmulets()(implicit env: SpliceTestConsoleEnvironment): PartyId = { + protected val danglingSubscriptionCid = new Subscription.ContractId("00" * 33 + "01") + protected val danglingSubscriptionRequestCid = + new SubscriptionRequest.ContractId("00" * 33 + "02") + + protected def createAsDso[T](signatories: PartyId*)( + update: com.daml.ledger.javaapi.data.codegen.Update[T] + )(implicit env: SpliceTestConsoleEnvironment) = { + sv1Backend.participantClientWithAdminToken.ledger_api_extensions.commands + .submitWithResult( + userId = sv1Backend.config.ledgerApiUser, + actAs = dsoParty +: signatories, + readAs = Seq.empty, + update = update, + ) + .discard + } + + protected def dsoAcs(implicit env: SpliceTestConsoleEnvironment) = + sv1Backend.participantClientWithAdminToken.ledger_api_extensions.acs + + protected def setupAliceWithDustAmulets()(implicit env: SpliceTestConsoleEnvironment): PartyId = { val synchronizerId = decentralizedSynchronizerId clue("aliceValidator has not vetted splice-amulet 0.1.17 and 0.1.18") { @@ -206,8 +246,8 @@ abstract class AmuletExpiryWithOldPackageIntegrationTestBase /** Tests that expiry triggers fall back to V1 choices when alice's validator * has only vetted minimal package versions (not splice-amulet 0.1.17+). */ -class AmuletExpiryWithMinimalPackageIntegrationTest - extends AmuletExpiryWithOldPackageIntegrationTestBase { +class AmuletExpiryV1FallbackIntegrationTest + extends ExpiryWithMinimalVettedPackagesIntegrationTestBase { "Amulet expiry falls back to V1 choices when alice's validator has not vetted splice-amulet 0.1.17" in { implicit env => @@ -235,49 +275,81 @@ class AmuletExpiryWithMinimalPackageIntegrationTest /** Tests that expiry triggers skip batches when the task's amulet preferred package version * is listed in `ignoredAmuletVersions`, adding the party to the ignored-parties store. */ -class AmuletBasedExpiryWithIgnoredPackageIntegrationTest - extends AmuletExpiryWithOldPackageIntegrationTestBase { +class ExpiryWithIgnoredAmuletVersionIntegrationTest + extends ExpiryWithMinimalVettedPackagesIntegrationTestBase { override val ignoredAmuletVersions: Set[String] = Set( DarResources.amulet_0_1_15.metadata.version.toString ) - "Triggers expiring amulet, locked amulet, and reward coupons and featured app markers skip parties when their preferred amulet package version is marked as ignored" in { + private val entryName = "alice.unverified.ans" + private val entryDescription = "expired ans entry" + + "Expiry triggers skip parties whose preferred amulet package version is ignored" in { implicit env => - val aliceParty: PartyId = setupAliceWithDustAmulets() + val alice = setupAliceWithDustAmulets() + val aliceId = alice.toProtoPrimitive + val dsoId = dsoParty.toProtoPrimitive + advanceRoundsByOneTickViaAutomation() advanceRoundsByOneTickViaAutomation() val (openRounds, _) = sv1ScanBackend.getOpenAndIssuingMiningRounds() val currentRound = openRounds.toList.headOption.value.payload.round + val now = env.environment.clock.now.toInstant + val expired = now.minus(Duration.ofSeconds(1)) - sv1Backend.participantClientWithAdminToken.ledger_api_extensions.commands - .submitWithResult( - userId = sv1Backend.config.ledgerApiUser, - actAs = Seq(dsoParty), - readAs = Seq.empty, - update = new AppRewardCoupon( - dsoParty.toProtoPrimitive, - aliceParty.toProtoPrimitive, + clue("Create dust contracts owned or referenced by alice") { + createAsDso()( + new AppRewardCoupon( + dsoId, + aliceId, false, BigDecimal(10.0).bigDecimal, currentRound, java.util.Optional.empty(), - ).create, + ).create ) - - sv1Backend.participantClientWithAdminToken.ledger_api_extensions.commands - .submitWithResult( - userId = sv1Backend.config.ledgerApiUser, - actAs = Seq(dsoParty), - readAs = Seq.empty, - update = new FeaturedAppActivityMarker( - dsoParty.toProtoPrimitive, - aliceParty.toProtoPrimitive, - aliceParty.toProtoPrimitive, - BigDecimal(1.0).bigDecimal, - ).create, + createAsDso()( + new FeaturedAppActivityMarker(dsoId, aliceId, aliceId, BigDecimal(1.0).bigDecimal).create + ) + createAsDso(alice)( + new TransferPreapproval( + dsoId, + aliceId, // receiver + aliceId, // provider + now.minus(Duration.ofHours(1)), // validFrom + now.minus(Duration.ofHours(1)), // lastRenewedAt + expired, + ).create ) + createAsDso(alice)( + new AnsEntry(aliceId, dsoId, entryName, "", entryDescription, expired).create + ) + createAsDso(alice)( + new AnsEntryContext( + dsoId, + aliceId, + entryName, + "", + entryDescription, + danglingSubscriptionRequestCid, + ).create + ) + createAsDso(alice)( + new SubscriptionIdleState( + danglingSubscriptionCid, + new SubscriptionData(aliceId, dsoId, dsoId, dsoId, entryDescription), + new SubscriptionPayData( + new PaymentAmount(BigDecimal(1.0).bigDecimal, Unit.AMULETUNIT), + new RelTime(1_000_000_000L), + new RelTime(1_000_000L), + ), + expired, // nextPaymentDueAt -> overdue + danglingSubscriptionRequestCid, + ).create + ) + } actAndCheck(timeUntilSuccess = 60.seconds)( "Advance 4 rounds and resume expiry triggers", { @@ -289,28 +361,44 @@ class AmuletBasedExpiryWithIgnoredPackageIntegrationTest sv.dsoDelegateBasedAutomation.trigger[ExpiredLockedAmuletTrigger].resume() sv.dsoDelegateBasedAutomation.trigger[ExpireRewardCouponsTrigger].resume() sv.dsoDelegateBasedAutomation.trigger[FeaturedAppActivityMarkerTrigger].resume() + sv.dsoDelegateBasedAutomation.trigger[ExpireTransferPreapprovalsTrigger].resume() + sv.dsoDelegateBasedAutomation.trigger[ExpiredAnsEntryTrigger].resume() + sv.dsoDelegateBasedAutomation.trigger[ExpiredAnsSubscriptionTrigger].resume() } }, )( - "Dust contracts remain because preferred version 0.1.15 is in ignoredAmuletVersions", + s"All dust contracts remain because alice's preferred version is in ignoredAmuletVersions", _ => { - sv1Backend.dsoDelegateBasedAutomation.expiredAmuletIgnoredPartiesStore.getAll should contain( - aliceParty - ) - aliceWalletClient.list().amulets should have length 2L withClue "amulets should remain" - aliceWalletClient - .list() - .lockedAmulets should have length 2L withClue "locked amulets should remain" - sv1Backend.participantClientWithAdminToken.ledger_api_extensions.acs - .filterJava(AppRewardCoupon.COMPANION)( - dsoParty, - co => co.data.provider == aliceParty.toProtoPrimitive, - ) should have size 1L withClue "app reward coupon should remain" - sv1Backend.participantClientWithAdminToken.ledger_api_extensions.acs - .filterJava(FeaturedAppActivityMarker.COMPANION)( - dsoParty, - co => co.data.provider == aliceParty.toProtoPrimitive, - ) should have size 1L withClue "featured app activity marker should remain" + sv1Backend.dsoDelegateBasedAutomation.expiredAmuletIgnoredPartiesStore.getAll should + contain(alice) + + aliceWalletClient.list().amulets should have length 2L withClue "amulets" + aliceWalletClient.list().lockedAmulets should have length 2L withClue "locked amulets" + + dsoAcs.filterJava(AppRewardCoupon.COMPANION)( + dsoParty, + _.data.provider == aliceId, + ) should have size 1L withClue "app reward coupon" + + dsoAcs.filterJava(FeaturedAppActivityMarker.COMPANION)( + dsoParty, + _.data.provider == aliceId, + ) should have size 1L withClue "featured app activity marker" + + dsoAcs.filterJava(TransferPreapproval.COMPANION)( + dsoParty, + _.data.receiver == aliceId, + ) should have size 1L withClue "transfer preapproval" + + dsoAcs.filterJava(AnsEntry.COMPANION)( + dsoParty, + _.data.user == aliceId, + ) should have size 1L withClue "ans entry" + + dsoAcs.filterJava(SubscriptionIdleState.COMPANION)( + dsoParty, + _.data.subscriptionData.sender == aliceId, + ) should have size 1L withClue "ans subscription" }, ) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/IgnoredPartiesStore.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IgnoredPartiesStore.scala similarity index 93% rename from apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/IgnoredPartiesStore.scala rename to apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IgnoredPartiesStore.scala index de73bfed60..b481314cdf 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/IgnoredPartiesStore.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IgnoredPartiesStore.scala @@ -1,7 +1,7 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -package org.lfdecentralizedtrust.splice.sv.store +package org.lfdecentralizedtrust.splice.store import com.digitalasset.canton.topology.PartyId diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/MultiDomainAcsStore.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/MultiDomainAcsStore.scala index 280f64fa57..5961cc6f65 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/MultiDomainAcsStore.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/MultiDomainAcsStore.scala @@ -196,7 +196,9 @@ trait MultiDomainAcsStore extends HasIngestionSink with AutoCloseable with Named ): Future[Seq[Contract[TCid, T]]] private[splice] def listExpiredFromPayloadExpiry[C, TCid <: ContractId[T], T <: Template]( - companion: C + companion: C, + ignoredPartiesStore: Option[IgnoredPartiesStore] = None, + ignoredPartyFields: Seq[String] = Seq.empty, )(implicit companionClass: ContractCompanion[C, TCid, T] ): ListExpiredContracts[TCid, T] diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala index a935ddacd5..c18d71b485 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala @@ -37,7 +37,7 @@ import com.digitalasset.canton.discard.Implicits.DiscardOps import com.digitalasset.canton.lifecycle.{CloseContext, FutureUnlessShutdown} import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.resource.DbStorage -import com.digitalasset.canton.topology.SynchronizerId +import com.digitalasset.canton.topology.{PartyId, SynchronizerId} import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.util.ShowUtil.showPretty @@ -378,9 +378,23 @@ final class DbMultiDomainAcsStore[TXE]( override private[splice] def listExpiredFromPayloadExpiry[C, TCid <: ContractId[ T - ], T <: Template](companion: C)(implicit + ], T <: Template]( + companion: C, + ignoredPartiesStore: Option[IgnoredPartiesStore] = None, + ignoredPartyFields: Seq[String] = Seq.empty, + )(implicit companionClass: ContractCompanion[C, TCid, T] ): ListExpiredContracts[TCid, T] = { (now, limit) => implicit traceContext => + val ignoredParties = ignoredPartiesStore.fold(Set.empty[PartyId])(_.getAll) + val ignoredPartiesFilter: SQLActionBuilder = + if (ignoredParties.isEmpty || ignoredPartyFields.isEmpty) sql"" + else + ignoredPartyFields.foldLeft(sql"") { (acc, field) => + (acc ++ sql" and " ++ notInClause( + s"acs.create_arguments->>'$field'", + ignoredParties, + )).toActionBuilder + } for { _ <- waitUntilAcsIngested() result <- storage @@ -390,7 +404,8 @@ final class DbMultiDomainAcsStore[TXE]( acsStoreId, domainMigrationId, companion, - additionalWhere = sql"""and acs.contract_expires_at < $now""", + additionalWhere = + (sql"""and acs.contract_expires_at < $now""" ++ ignoredPartiesFilter).toActionBuilder, orderLimit = sql"""limit ${sqlLimit(limit)}""", ), "listExpiredFromPayloadExpiry", diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala index 81df31bf99..e161567339 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala @@ -13,11 +13,10 @@ import org.lfdecentralizedtrust.splice.automation.AutomationServiceCompanion.{ } import org.lfdecentralizedtrust.splice.automation.{AutomationService, AutomationServiceCompanion} import org.lfdecentralizedtrust.splice.environment.RetryProvider -import org.lfdecentralizedtrust.splice.store.DomainTimeSynchronization +import org.lfdecentralizedtrust.splice.store.{DomainTimeSynchronization, IgnoredPartiesStore} import org.lfdecentralizedtrust.splice.scan.admin.api.client.{BftScanConnection, ScanConnection} import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.* import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.ExpiredAmuletAllocationTrigger -import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import scala.concurrent.{ExecutionContextExecutor, Future} @@ -125,9 +124,30 @@ class DsoDelegateBasedAutomationService( ) registerTrigger(new AnsSubscriptionRenewalPaymentTrigger(triggerContext, svTaskContext)) - registerTrigger(new ExpiredAnsEntryTrigger(triggerContext, svTaskContext)) - registerTrigger(new ExpireTransferPreapprovalsTrigger(triggerContext, svTaskContext)) - registerTrigger(new ExpiredAnsSubscriptionTrigger(triggerContext, svTaskContext)) + registerTrigger( + new ExpiredAnsEntryTrigger( + triggerContext, + svTaskContext, + config, + expiredAmuletIgnoredPartiesStore, + ) + ) + registerTrigger( + new ExpireTransferPreapprovalsTrigger( + triggerContext, + svTaskContext, + config, + expiredAmuletIgnoredPartiesStore, + ) + ) + registerTrigger( + new ExpiredAnsSubscriptionTrigger( + triggerContext, + svTaskContext, + config, + expiredAmuletIgnoredPartiesStore, + ) + ) registerTrigger(new TerminatedSubscriptionTrigger(triggerContext, svTaskContext)) registerTrigger(new MergeSvRewardStateContractsTrigger(triggerContext, svTaskContext)) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala index 93c303d6f9..51e3f2388d 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala @@ -16,7 +16,7 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.{ } import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.DsoRules import org.lfdecentralizedtrust.splice.environment.PackageIdResolver.Package.SpliceAmulet -import org.lfdecentralizedtrust.splice.sv.store.{ExpiredRewardCouponsBatch, IgnoredPartiesStore} +import org.lfdecentralizedtrust.splice.sv.store.ExpiredRewardCouponsBatch import org.lfdecentralizedtrust.splice.util.{AssignedContract, Contract} import org.lfdecentralizedtrust.splice.util.PrettyInstances.* import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} @@ -26,7 +26,7 @@ import com.digitalasset.canton.util.MonadUtil import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority -import org.lfdecentralizedtrust.splice.store.PageLimit +import org.lfdecentralizedtrust.splice.store.{IgnoredPartiesStore, PageLimit} import org.lfdecentralizedtrust.splice.codegen.java.splice import java.util.Optional diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireTransferPreapprovalsTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireTransferPreapprovalsTrigger.scala index 7071d559e4..d5d6d620c5 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireTransferPreapprovalsTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireTransferPreapprovalsTrigger.scala @@ -10,13 +10,20 @@ import com.digitalasset.canton.tracing.TraceContext import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority +import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import java.util.Optional import scala.concurrent.{ExecutionContext, Future} +import ExpireTransferPreapprovalsTrigger.{Task, getStakeholders} +import org.lfdecentralizedtrust.splice.environment.PackageIdResolver +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore class ExpireTransferPreapprovalsTrigger( override protected val context: TriggerContext, override protected val svTaskContext: SvTaskBasedTrigger.Context, + override protected val svConfig: SvAppBackendConfig, + override protected val ignoredPartiesStore: IgnoredPartiesStore, )(implicit override val ec: ExecutionContext, mat: Materializer, @@ -26,30 +33,52 @@ class ExpireTransferPreapprovalsTrigger( TransferPreapproval, ]( svTaskContext.dsoStore.multiDomainAcsStore, - svTaskContext.dsoStore.listExpiredTransferPreapprovals, + svTaskContext.dsoStore.listExpiredTransferPreapprovals(Some(ignoredPartiesStore)), TransferPreapproval.COMPANION, ) with SvTaskBasedTrigger[ScheduledTaskTrigger.ReadyTask[AssignedContract[ TransferPreapproval.ContractId, TransferPreapproval, - ]]] { - type Task = ScheduledTaskTrigger.ReadyTask[ - AssignedContract[ - TransferPreapproval.ContractId, - TransferPreapproval, - ] - ] + ]]] + with IgnoredAmuletVersionGuard { private val store = svTaskContext.dsoStore - override def completeTaskAsDsoDelegate(co: Task, controller: String)(implicit + override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit + tc: TraceContext + ): Future[TaskOutcome] = { + val stakeholders = getStakeholders(task.work.payload).toSet + svTaskContext.vettingLookupService + .lookupVettingState(stakeholders.toSeq, PackageIdResolver.Package.SpliceAmulet) + .flatMap { + case Some(vettedVersion) => + completeWithIgnoredAmuletVersionCheck( + vettedVersion.toString, + stakeholders, + store.key.dsoParty, + enableUnresponsivePartiesAutoIgnore = true, + )(completeExpiryTaskAsDsoDelegate(task, controller)) + case None => + Future.successful( + TaskSuccess( + s"No vetted SpliceAmulet version for stakeholders $stakeholders of " + + s"TransferPreapproval ${task.work.contractId}, skipping." + ) + ) + } + } + + private def completeExpiryTaskAsDsoDelegate( + task: Task, + controller: String, + )(implicit tc: TraceContext - ): Future[TaskOutcome] = + ): Future[TaskOutcome] = { for { dsoRules <- store.getDsoRules() cmd = dsoRules.exercise( _.exerciseDsoRules_ExpireTransferPreapproval( - co.work.contractId, + task.work.contractId, Optional.of(controller), ) ) @@ -59,6 +88,21 @@ class ExpireTransferPreapprovalsTrigger( .noDedup .yieldUnit() } yield TaskSuccess( - s"Archived expired TransferPreapproval with contractId ${co.work.contractId}" + s"Archived expired TransferPreapproval with contractId ${task.work.contractId}" ) + } +} + +object ExpireTransferPreapprovalsTrigger extends ContractStakeholders[TransferPreapproval] { + type Task = ScheduledTaskTrigger.ReadyTask[ + AssignedContract[ + TransferPreapproval.ContractId, + TransferPreapproval, + ] + ] + + override def informees(payload: TransferPreapproval): Seq[String] = + Seq(payload.provider, payload.receiver) + + override def dso(payload: TransferPreapproval): String = payload.dso } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala index c5bba7e680..216c7878e8 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala @@ -16,8 +16,8 @@ import ExpiredAmuletAllocationTrigger.{Task, getStakeholders} import com.digitalasset.canton.util.MonadUtil import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig -import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import scala.jdk.CollectionConverters.* diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala index af5142be8b..ce54bffef5 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala @@ -12,7 +12,6 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig -import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.util.{ChoiceContextWithDisclosures, TokenStandardMetadata} import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders @@ -21,6 +20,7 @@ import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.* import cats.implicits.* import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.metadatav1.anyvalue.AV_Bool +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore class ExpiredAmuletAllocationV2Trigger( override protected val svConfig: SvAppBackendConfig, diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala index c86f6991f1..89416b70ad 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala @@ -16,8 +16,8 @@ import ExpiredAmuletTransferInstructionTrigger.{Task, getStakeholders} import com.digitalasset.canton.util.MonadUtil import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig -import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import scala.jdk.CollectionConverters.* diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala index 816d82826d..8f849a073c 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala @@ -13,9 +13,9 @@ import scala.concurrent.{ExecutionContext, Future} import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig -import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import ExpiredAmuletTrigger.{Task, getStakeholders} +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore import java.util.Optional import scala.jdk.CollectionConverters.* diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsEntryTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsEntryTrigger.scala index 5b60065613..27b0e00115 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsEntryTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsEntryTrigger.scala @@ -10,14 +10,21 @@ import org.lfdecentralizedtrust.splice.util.AssignedContract import com.digitalasset.canton.tracing.TraceContext import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer +import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority +import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import java.util.Optional import scala.concurrent.{ExecutionContext, Future} +import ExpiredAnsEntryTrigger.{Task, getStakeholders} +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore class ExpiredAnsEntryTrigger( override protected val context: TriggerContext, override protected val svTaskContext: SvTaskBasedTrigger.Context, + override protected val svConfig: SvAppBackendConfig, + override protected val ignoredPartiesStore: IgnoredPartiesStore, )(implicit override val ec: ExecutionContext, mat: Materializer, @@ -27,30 +34,52 @@ class ExpiredAnsEntryTrigger( splice.ans.AnsEntry, ]( svTaskContext.dsoStore.multiDomainAcsStore, - svTaskContext.dsoStore.listExpiredAnsEntries, + svTaskContext.dsoStore.listExpiredAnsEntries(Some(ignoredPartiesStore)), splice.ans.AnsEntry.COMPANION, ) with SvTaskBasedTrigger[ScheduledTaskTrigger.ReadyTask[AssignedContract[ splice.ans.AnsEntry.ContractId, splice.ans.AnsEntry, - ]]] { - type Task = ScheduledTaskTrigger.ReadyTask[ - AssignedContract[ - splice.ans.AnsEntry.ContractId, - splice.ans.AnsEntry, - ] - ] + ]]] + with IgnoredAmuletVersionGuard { private val store = svTaskContext.dsoStore - override def completeTaskAsDsoDelegate(co: Task, controller: String)(implicit + override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit + tc: TraceContext + ): Future[TaskOutcome] = { + val stakeholders = getStakeholders(task.work.payload).toSet + svTaskContext.vettingLookupService + .lookupVettingState(stakeholders.toSeq, PackageIdResolver.Package.SpliceAmulet) + .flatMap { + case Some(vettedVersion) => + completeWithIgnoredAmuletVersionCheck( + vettedVersion.toString, + stakeholders, + store.key.dsoParty, + enableUnresponsivePartiesAutoIgnore = true, + )(completeExpiryTaskAsDsoDelegate(task, controller)) + case None => + Future.successful( + TaskSuccess( + s"No vetted SpliceAmulet version for stakeholders $stakeholders of " + + s"AnsEntry ${task.work.contractId}, skipping." + ) + ) + } + } + + private def completeExpiryTaskAsDsoDelegate( + task: Task, + controller: String, + )(implicit tc: TraceContext ): Future[TaskOutcome] = for { dsoRules <- store.getDsoRules() cmd = dsoRules.exercise( _.exerciseDsoRules_ExpireAnsEntry( - co.work.contractId, + task.work.contractId, new AnsEntry_Expire(store.key.dsoParty.toProtoPrimitive), Optional.of(controller), ) @@ -62,3 +91,16 @@ class ExpiredAnsEntryTrigger( .yieldUnit() } yield TaskSuccess("archived expired ANS entry") } + +object ExpiredAnsEntryTrigger extends ContractStakeholders[splice.ans.AnsEntry] { + type Task = ScheduledTaskTrigger.ReadyTask[ + AssignedContract[ + splice.ans.AnsEntry.ContractId, + splice.ans.AnsEntry, + ] + ] + + override def informees(payload: splice.ans.AnsEntry): Seq[String] = Seq(payload.user) + + override def dso(payload: splice.ans.AnsEntry): String = payload.dso +} diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsSubscriptionTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsSubscriptionTrigger.scala index 8f8b673493..815b3ed3ec 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsSubscriptionTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsSubscriptionTrigger.scala @@ -16,32 +16,64 @@ import io.opentelemetry.api.trace.Tracer import org.lfdecentralizedtrust.splice.codegen.java.splice.ans as ansCodegen import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.subscriptions as subsCodegen import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.subscriptions.SubscriptionIdleState_ExpireSubscription -import org.lfdecentralizedtrust.splice.store.PageLimit +import org.lfdecentralizedtrust.splice.environment.PackageIdResolver +import org.lfdecentralizedtrust.splice.store.{IgnoredPartiesStore, PageLimit} +import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.store.SvDsoStore +import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import org.apache.pekko.stream.Materializer import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import java.util.Optional import scala.concurrent.{ExecutionContext, Future} +import ExpiredAnsSubscriptionTrigger.{Task, getStakeholders} class ExpiredAnsSubscriptionTrigger( override protected val context: TriggerContext, override protected val svTaskContext: SvTaskBasedTrigger.Context, + override protected val svConfig: SvAppBackendConfig, + override protected val ignoredPartiesStore: IgnoredPartiesStore, )(implicit - ec: ExecutionContext, + override val ec: ExecutionContext, mat: Materializer, tracer: Tracer, ) extends ScheduledTaskTrigger[SvDsoStore.IdleAnsSubscription] - with SvTaskBasedTrigger[ScheduledTaskTrigger.ReadyTask[SvDsoStore.IdleAnsSubscription]] { + with SvTaskBasedTrigger[ScheduledTaskTrigger.ReadyTask[SvDsoStore.IdleAnsSubscription]] + with IgnoredAmuletVersionGuard { private val store = svTaskContext.dsoStore override protected def listReadyTasks(now: CantonTimestamp, limit: Int)(implicit tc: TraceContext ): Future[Seq[SvDsoStore.IdleAnsSubscription]] = - store.listExpiredAnsSubscriptions(now, PageLimit.tryCreate(limit)) + store.listExpiredAnsSubscriptions(now, PageLimit.tryCreate(limit), Some(ignoredPartiesStore)) override protected def completeTaskAsDsoDelegate( - task: ScheduledTaskTrigger.ReadyTask[SvDsoStore.IdleAnsSubscription], + task: Task, + controller: String, + )(implicit tc: TraceContext): Future[TaskOutcome] = { + val stakeholders = getStakeholders(task.work.state.payload).toSet + svTaskContext.vettingLookupService + .lookupVettingState(stakeholders.toSeq, PackageIdResolver.Package.SpliceAmulet) + .flatMap { + case Some(vettedVersion) => + completeWithIgnoredAmuletVersionCheck( + vettedVersion.toString, + stakeholders, + store.key.dsoParty, + enableUnresponsivePartiesAutoIgnore = true, + )(completeExpiryTaskAsDsoDelegate(task, controller)) + case None => + Future.successful( + TaskSuccess( + s"No vetted SpliceAmulet version for stakeholders $stakeholders of " + + s"ANS subscription ${task.work.state.contractId}, skipping." + ) + ) + } + } + + private def completeExpiryTaskAsDsoDelegate( + task: Task, controller: String, )(implicit tc: TraceContext): Future[TaskOutcome] = for { dsoRules <- store.getDsoRules() @@ -67,7 +99,7 @@ class ExpiredAnsSubscriptionTrigger( } yield result override protected def isStaleTask( - task: ScheduledTaskTrigger.ReadyTask[SvDsoStore.IdleAnsSubscription] + task: Task )(implicit tc: TraceContext): Future[Boolean] = (for { _ <- OptionT( @@ -86,3 +118,18 @@ class ExpiredAnsSubscriptionTrigger( ) } yield ()).isEmpty } + +object ExpiredAnsSubscriptionTrigger + extends ContractStakeholders[subsCodegen.SubscriptionIdleState] { + type Task = ScheduledTaskTrigger.ReadyTask[SvDsoStore.IdleAnsSubscription] + + override def informees(payload: subsCodegen.SubscriptionIdleState): Seq[String] = + Seq( + payload.subscriptionData.sender, + payload.subscriptionData.receiver, + payload.subscriptionData.provider, + ) + + override def dso(payload: subsCodegen.SubscriptionIdleState): String = + payload.subscriptionData.dso +} diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala index 9df0611977..0ea2f6b28a 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala @@ -13,8 +13,8 @@ import org.apache.pekko.stream.Materializer import scala.concurrent.{ExecutionContext, Future} import ExpiredLockedAmuletTrigger.{Task, getStakeholders} import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig -import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import java.util.Optional diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala index dc404c48c8..8502130f8d 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala @@ -32,8 +32,8 @@ import FeaturedAppActivityMarkerTrigger.{ getInformeesFromContracts, } import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig -import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import java.util.Optional diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala index 9d15092d0a..eb99c31ed8 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala @@ -8,8 +8,8 @@ import com.digitalasset.canton.topology.PartyId import io.grpc.StatusRuntimeException import io.grpc.protobuf.StatusProto import org.lfdecentralizedtrust.splice.automation.{TaskOutcome, TaskSuccess} +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig -import org.lfdecentralizedtrust.splice.sv.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.util.UnresponsiveParties import scala.concurrent.{ExecutionContext, Future} diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala index 14250dbba4..1de78c5c15 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/SvDsoStore.scala @@ -753,15 +753,20 @@ trait SvDsoStore : ListExpiredContracts[so.SvOnboardingConfirmed.ContractId, so.SvOnboardingConfirmed] = multiDomainAcsStore.listExpiredFromPayloadExpiry(so.SvOnboardingConfirmed.COMPANION) - def listExpiredAnsEntries: ListExpiredContracts[ + def listExpiredAnsEntries(ignoredPartiesStore: Option[IgnoredPartiesStore]): ListExpiredContracts[ splice.ans.AnsEntry.ContractId, splice.ans.AnsEntry, ] = - multiDomainAcsStore.listExpiredFromPayloadExpiry(splice.ans.AnsEntry.COMPANION) + multiDomainAcsStore.listExpiredFromPayloadExpiry( + splice.ans.AnsEntry.COMPANION, + ignoredPartiesStore, + ignoredPartyFields = Seq("user"), + ) def listExpiredAnsSubscriptions( now: CantonTimestamp, limit: Limit = defaultLimit, + ignoredPartiesStore: Option[IgnoredPartiesStore], )(implicit tc: TraceContext): Future[Seq[SvDsoStore.IdleAnsSubscription]] def listExpiredUnallocatedUnclaimedActivityRecord: ListExpiredContracts[ @@ -1102,12 +1107,16 @@ trait SvDsoStore Seq[Contract[splice.dsorules.Confirmation.ContractId, splice.dsorules.Confirmation]] ] - def listExpiredTransferPreapprovals: ListExpiredContracts[ + def listExpiredTransferPreapprovals( + ignoredPartiesStore: Option[IgnoredPartiesStore] + ): ListExpiredContracts[ splice.amuletrules.TransferPreapproval.ContractId, splice.amuletrules.TransferPreapproval, ] = multiDomainAcsStore.listExpiredFromPayloadExpiry( - splice.amuletrules.TransferPreapproval.COMPANION + splice.amuletrules.TransferPreapproval.COMPANION, + ignoredPartiesStore, + ignoredPartyFields = Seq("receiver", "provider"), ) def getExternalPartyAmuletRules()(implicit diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvDsoStore.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvDsoStore.scala index 06d7133d6a..301d1d387f 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvDsoStore.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/store/db/DbSvDsoStore.scala @@ -46,17 +46,13 @@ import org.lfdecentralizedtrust.splice.store.db.{ } import org.lfdecentralizedtrust.splice.store.{ DbVotesAcsStoreQueryBuilder, + IgnoredPartiesStore, IngestionSummary, Limit, LimitHelpers, MultiDomainAcsStore, } -import org.lfdecentralizedtrust.splice.sv.store.{ - AppRewardCouponsSum, - IgnoredPartiesStore, - SvDsoStore, - SvStore, -} +import org.lfdecentralizedtrust.splice.sv.store.{AppRewardCouponsSum, SvDsoStore, SvStore} import SvDsoStore.RoundBatch import com.digitalasset.canton.config.CantonRequireTypes.String2066 import org.lfdecentralizedtrust.splice.util.* @@ -146,11 +142,22 @@ class DbSvDsoStore( override def listExpiredAnsSubscriptions( now: CantonTimestamp, limit: Limit = defaultLimit, + ignoredPartiesStore: Option[IgnoredPartiesStore] = None, )(implicit tc: TraceContext): Future[Seq[SvDsoStore.IdleAnsSubscription]] = waitUntilAcsIngested { + val ignoredParties = ignoredPartiesStore.fold(Set.empty[PartyId])(_.getAll) + val ignoredPartiesFilter: SQLActionBuilder = + if (ignoredParties.nonEmpty) { + (sql" and " ++ notInClause( + "idle.create_arguments->'subscriptionData'->>'sender'", + ignoredParties, + )).toActionBuilder + } else { + sql"" + } for { joinedRows <- storage .query( - sql""" + (sql""" select idle.store_id, idle.migration_id, @@ -188,9 +195,10 @@ class DbSvDsoStore( AnsEntryContext.TEMPLATE_ID_WITH_PACKAGE_ID )} and idle.subscription_next_payment_due_at < $now + """ ++ ignoredPartiesFilter ++ sql""" order by idle.subscription_next_payment_due_at limit ${sqlLimit(limit)} - """.as[(SelectFromAcsTableResult, SelectFromAcsTableResult)], + """).toActionBuilder.as[(SelectFromAcsTableResult, SelectFromAcsTableResult)], "listExpiredAnsSubscriptions", ) } yield applyLimit("listExpiredAnsSubscriptions", limit, joinedRows).map { diff --git a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvDsoStoreTest.scala b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvDsoStoreTest.scala index ba343fdae2..aeb5808050 100644 --- a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvDsoStoreTest.scala +++ b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SvDsoStoreTest.scala @@ -54,6 +54,7 @@ import org.lfdecentralizedtrust.splice.environment.{DarResources, RetryProvider} import org.lfdecentralizedtrust.splice.store.MultiDomainAcsStore.QueryResult import org.lfdecentralizedtrust.splice.store.{ HardLimit, + IgnoredPartiesStore, Limit, MiningRoundsStore, PageLimit, @@ -61,7 +62,7 @@ import org.lfdecentralizedtrust.splice.store.{ } import org.lfdecentralizedtrust.splice.sv.store.SvDsoStore.{IdleAnsSubscription, RoundBatch} import org.lfdecentralizedtrust.splice.sv.store.db.DbSvDsoStore -import org.lfdecentralizedtrust.splice.sv.store.{IgnoredPartiesStore, SvDsoStore, SvStore} +import org.lfdecentralizedtrust.splice.sv.store.{SvDsoStore, SvStore} import org.lfdecentralizedtrust.splice.sv.util.SvUtil import org.lfdecentralizedtrust.splice.util.{ AssignedContract, @@ -1775,28 +1776,64 @@ abstract class SvDsoStoreTest extends StoreTestBase with HasExecutionContext { } - "listExpiredAnsSubscriptions" should { + "listExpiredAnsEntries" should { - "return all entries where subscription_next_payment_due_at < now" in { + // 1 to 3 expire at time(1..3), 4 to 6 at time(4..6); queries run at time(4). + def mkAnsEntries(range: Range) = + range.map(i => ansEntry(userParty(i), s"entry$i", expiresAt = time(i.toLong).toInstant)) + + def setupAnsEntries(store: SvDsoStore) = { + val expired = mkAnsEntries(1 to 3) + val notExpired = mkAnsEntries(4 to 6) + MonadUtil + .sequentialTraverse(expired ++ notExpired)( + dummyDomain.create(_)(store.multiDomainAcsStore) + ) + .map(_ => expired) + } + + "return all expired ans entries" in { for { store <- mkStore() - // 1 to 3 are expired, 4 to 6 are not - data = ((1 to 3).map(n => - n -> Instant.now().truncatedTo(ChronoUnit.MICROS).minusSeconds(n * 1000L) - ) ++ (4 to 6) - .map(n => n -> Instant.now().truncatedTo(ChronoUnit.MICROS).plusSeconds(n * 1000L))) - .map { case (n, nextPaymentDueAt) => - val contextContract = - ansEntryContext(n, n.toString) - val idleStateContract = - subscriptionIdleState( - n, - nextPaymentDueAt, - ) + expired <- setupAnsEntries(store) + result <- store.listExpiredAnsEntries(None)( + time(4), + PageLimit.tryCreate(100), + )(traceContext) + } yield { + result.map(_.contract) should contain theSameElementsAs expired + } + } - (contextContract, idleStateContract) - } - _ <- MonadUtil.sequentialTraverse(data) { case (contextContract, idleContract) => + "filter out ans entries whose user is ignored" in { + for { + store <- mkStore() + expired <- setupAnsEntries(store) + result <- store.listExpiredAnsEntries( + Some(new IgnoredPartiesStore(Set(userParty(1), userParty(2)))) + )( + time(4), + PageLimit.tryCreate(100), + )(traceContext) + } yield { + result.map(_.contract) should contain theSameElementsAs Seq(expired(2)) + } + } + } + + "listExpiredAnsSubscriptions" should { + + def setupExpiredSubscriptions(store: SvDsoStore) = { + // 1 to 3 are expired, 4 to 6 are not + val data = ((1 to 3).map(n => + n -> Instant.now().truncatedTo(ChronoUnit.MICROS).minusSeconds(n * 1000L) + ) ++ (4 to 6) + .map(n => n -> Instant.now().truncatedTo(ChronoUnit.MICROS).plusSeconds(n * 1000L))) + .map { case (n, nextPaymentDueAt) => + (ansEntryContext(n, n.toString), subscriptionIdleState(n, nextPaymentDueAt)) + } + MonadUtil + .sequentialTraverse(data) { case (contextContract, idleContract) => for { _ <- dummyDomain.create(contextContract, createdEventSignatories = Seq(dsoParty))( store.multiDomainAcsStore @@ -1806,6 +1843,13 @@ abstract class SvDsoStoreTest extends StoreTestBase with HasExecutionContext { ) } yield () } + .map(_ => data) + } + + "return all entries where subscription_next_payment_due_at < now" in { + for { + store <- mkStore() + data <- setupExpiredSubscriptions(store) } yield { val expected = data .take(3) @@ -1814,7 +1858,34 @@ abstract class SvDsoStoreTest extends StoreTestBase with HasExecutionContext { } .reverse store - .listExpiredAnsSubscriptions(CantonTimestamp.now(), limit = PageLimit.tryCreate(3)) + .listExpiredAnsSubscriptions( + CantonTimestamp.now(), + limit = PageLimit.tryCreate(3), + None, + ) + .futureValue should be(expected) + } + } + + "filter out subscriptions whose sender is in the ignored parties store" in { + for { + store <- mkStore() + data <- setupExpiredSubscriptions(store) + } yield { + // n=1 and n=2 are expired but their senders are ignored, only n=3 remains + val expected = data + .slice(2, 3) + .map { case (ctxContract, idleContract) => + IdleAnsSubscription(idleContract, ctxContract) + } + store + .listExpiredAnsSubscriptions( + CantonTimestamp.now(), + limit = PageLimit.tryCreate(3), + ignoredPartiesStore = Some( + new IgnoredPartiesStore(Set(userParty(1), userParty(2))) + ), + ) .futureValue should be(expected) } } @@ -1935,41 +2006,54 @@ abstract class SvDsoStoreTest extends StoreTestBase with HasExecutionContext { "listExpiredTransferPreapprovals" should { - "return all expired transfer pre-approvals" in { - val expired = (1 to 3).map(n => + def mkTransferPreapprovals(n: Range) = + n.map(i => transferPreapproval( - userParty(n), - providerParty(n), + userParty(i), + providerParty(i), time(0), - expiresAt = time(n.toLong), + expiresAt = time(i.toLong), ) ) - val notExpired = - (4 to 6).map(n => - transferPreapproval( - userParty(n), - providerParty(n), - time(0), - expiresAt = time(n.toLong), - ) + + def setupTransferPreapprovals(store: SvDsoStore) = { + val expired = mkTransferPreapprovals(1 to 3) + val notExpired = mkTransferPreapprovals(4 to 6) + MonadUtil + .sequentialTraverse(expired ++ notExpired)( + dummyDomain.create(_)(store.multiDomainAcsStore) ) + .map(_ => expired) + } + + "return all expired transfer pre-approvals" in { for { store <- mkStore() - _ <- MonadUtil.sequentialTraverse(expired ++ notExpired)( - dummyDomain.create(_)(store.multiDomainAcsStore) - ) - result <- store.listExpiredTransferPreapprovals( + expired <- setupTransferPreapprovals(store) + result <- store.listExpiredTransferPreapprovals(None)( time(4), PageLimit.tryCreate(100), - )( - traceContext - ) + )(traceContext) } yield { - val contracts = result.map(_.contract) - contracts should contain theSameElementsAs expired + result.map(_.contract) should contain theSameElementsAs expired } } + "filter out pre-approvals whose receiver or provider is ignored" in { + for { + store <- mkStore() + expired <- setupTransferPreapprovals(store) + result <- store.listExpiredTransferPreapprovals( + // n=1 is dropped via its receiver, n=2 via its provider + Some(new IgnoredPartiesStore(Set(userParty(1), providerParty(2)))) + )( + time(4), + PageLimit.tryCreate(100), + )(traceContext) + } yield { + result.map(_.contract) should contain theSameElementsAs Seq(expired(2)) + } + } } } diff --git a/test-full-class-names.log b/test-full-class-names.log index 46807bd46b..c4ad4ee597 100644 --- a/test-full-class-names.log +++ b/test-full-class-names.log @@ -1,7 +1,6 @@ org.lfdecentralizedtrust.splice.integration.tests.AmuletAllocationsIntegrationTest -org.lfdecentralizedtrust.splice.integration.tests.AmuletBasedExpiryWithIgnoredPackageIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.AmuletExpiryIntegrationTest -org.lfdecentralizedtrust.splice.integration.tests.AmuletExpiryWithMinimalPackageIntegrationTest +org.lfdecentralizedtrust.splice.integration.tests.AmuletExpiryV1FallbackIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.Ans4SvsIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.AnsIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.AutoIgnoreUnresponsivePartiesIntegrationTest @@ -20,6 +19,7 @@ org.lfdecentralizedtrust.splice.integration.tests.DevelopmentFundCouponIntegrati org.lfdecentralizedtrust.splice.integration.tests.DirectoryPeriodicBackupIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.DistributedDomainIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.DowngradeSvPackagesIntegrationTest +org.lfdecentralizedtrust.splice.integration.tests.ExpiryWithIgnoredAmuletVersionIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.ExternalPartySetupProposalIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.ExternallySignedPartyOnboardingTest org.lfdecentralizedtrust.splice.integration.tests.FeaturedAppActivityMarkerIntegrationTest From 25041c55dbc39d59810e65fcb765987048334a91 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:02:15 +0200 Subject: [PATCH 191/329] Fix cncluster psql for participants (#6695) Tested on CILR that it works for sv participant, sequencer and sv-app and for validator1 participant. Fixes #6693 [static] Signed-off-by: Moritz Kiefer Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: Moritz Kiefer --- build-tools/cncluster | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/build-tools/cncluster b/build-tools/cncluster index 1a25cb4e80..0e7a5e0114 100755 --- a/build-tools/cncluster +++ b/build-tools/cncluster @@ -2336,23 +2336,36 @@ function subcmd_psql() { local APPLICATION="$2" _info "Retrieving DB connection info from pod description..." - local DB_INIT_COMMAND - DB_INIT_COMMAND=$( + local DB_INIT_SCRIPT + DB_INIT_SCRIPT=$( kubectl get deployment \ --namespace "${NAMESPACE}" \ --selector "app=${APPLICATION}" \ -o 'jsonpath={..spec.template.spec.initContainers[0].command}' \ - | jq 'join(" ")' \ - | grep -i psql \ - | sed -nE 's/^.*(psql.?*) 2>&1.*$/\1/p' + | jq -r 'join("\n")' ) + # There can be multiple psql invocations, pick the one that contains create database. + local DB_INIT_COMMAND + DB_INIT_COMMAND=$( + grep -F 'create database' <<< "${DB_INIT_SCRIPT}" \ + | sed -nE 's/^.*(psql .*) 2>&1.*$/\1/p' \ + | tail -n 1 + ) || true + if [ -z "${DB_INIT_COMMAND}" ]; then _error "Application ${APPLICATION} in namespace ${NAMESPACE} does not have an associated database." exit 1 fi - eval "set -- $DB_INIT_COMMAND" + # The participant does not directly inline the db name so we resolve things here. + local DB_INIT_VARS + DB_INIT_VARS=$( + sed -nE "s/^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*='[^']*')[[:space:]]*$/local \1/p" <<< "${DB_INIT_SCRIPT}" + ) || true + + eval "${DB_INIT_VARS} + set -- ${DB_INIT_COMMAND}" while [ $# -gt 0 ]; do case "$1" in -h|--host) From e48db39b616490d2de34bc11e76fefe4b9450b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Thu, 6 Aug 2026 12:19:26 +0200 Subject: [PATCH 192/329] Prevent page_token=123 false alert (#6697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Oriol Muñoz --- cluster/configs/shared/base.yaml | 3 ++- cluster/deployment/scratchneta/config.resolved.yaml | 2 +- cluster/deployment/scratchnetb/config.resolved.yaml | 2 +- cluster/deployment/scratchnetc/config.resolved.yaml | 2 +- cluster/deployment/scratchnetd/config.resolved.yaml | 2 +- cluster/deployment/scratchnete/config.resolved.yaml | 2 +- cluster/expected/observability/expected.json | 2 +- 7 files changed, 8 insertions(+), 7 deletions(-) diff --git a/cluster/configs/shared/base.yaml b/cluster/configs/shared/base.yaml index 583c4a9620..4e605de8e2 100644 --- a/cluster/configs/shared/base.yaml +++ b/cluster/configs/shared/base.yaml @@ -209,7 +209,8 @@ monitoring: ( ( jsonPayload.message=~"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+" AND - -jsonPayload.message=~"(?i)(secret|token|(private|secret)(-)?key|password)=(\"\*\*\*\*\"|hidden)" + -jsonPayload.message=~"(?i)(secret|token|(private|secret)(-)?key|password)=(\"\*\*\*\*\"|hidden)" AND + -jsonPayload.message=~"(?i)page(_|-)?token=[^, ]+" ) OR jsonPayload.message=~"eyJhbGc[A-Za-z0-9_-]{2,}\.[A-Za-z0-9_-]{2,}\.[A-Za-z0-9_-]{2,}" OR jsonPayload.message=~"Bearer\s+eyJ[A-Za-z0-9_-]{2,}" diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index b1a109d6c3..3734a99ebe 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -122,7 +122,7 @@ monitoring: walletSweep: tolerance: 1.5 enableNoDataAlerts: false - loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" + loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\" AND\n -jsonPayload.message=~\"(?i)page(_|-)?token=[^, ]+\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" muteTimeIntervals: [] enableGrafanaServiceAccountToken: true multiValidator: diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index b1a109d6c3..3734a99ebe 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -122,7 +122,7 @@ monitoring: walletSweep: tolerance: 1.5 enableNoDataAlerts: false - loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" + loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\" AND\n -jsonPayload.message=~\"(?i)page(_|-)?token=[^, ]+\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" muteTimeIntervals: [] enableGrafanaServiceAccountToken: true multiValidator: diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index b1a109d6c3..3734a99ebe 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -122,7 +122,7 @@ monitoring: walletSweep: tolerance: 1.5 enableNoDataAlerts: false - loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" + loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\" AND\n -jsonPayload.message=~\"(?i)page(_|-)?token=[^, ]+\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" muteTimeIntervals: [] enableGrafanaServiceAccountToken: true multiValidator: diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index b1a109d6c3..3734a99ebe 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -122,7 +122,7 @@ monitoring: walletSweep: tolerance: 1.5 enableNoDataAlerts: false - loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" + loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\" AND\n -jsonPayload.message=~\"(?i)page(_|-)?token=[^, ]+\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" muteTimeIntervals: [] enableGrafanaServiceAccountToken: true multiValidator: diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index b1a109d6c3..3734a99ebe 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -122,7 +122,7 @@ monitoring: walletSweep: tolerance: 1.5 enableNoDataAlerts: false - loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" + loggedSecretsFilter: "(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\" AND\n -jsonPayload.message=~\"(?i)page(_|-)?token=[^, ]+\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n" muteTimeIntervals: [] enableGrafanaServiceAccountToken: true multiValidator: diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index ed0dffdc2c..f042086d6b 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -800,7 +800,7 @@ "id": "", "inputs": { "description": "Logs containing secrets (JWTs, Bearer tokens, passwords, etc.)", - "filter": "resource.labels.cluster_name=\"cn-mocknet\"\n(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n", + "filter": "resource.labels.cluster_name=\"cn-mocknet\"\n(\n (\n jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=[^, ]+\" AND\n -jsonPayload.message=~\"(?i)(secret|token|(private|secret)(-)?key|password)=(\\\"\\*\\*\\*\\*\\\"|hidden)\" AND\n -jsonPayload.message=~\"(?i)page(_|-)?token=[^, ]+\"\n ) OR\n jsonPayload.message=~\"eyJhbGc[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\\.[A-Za-z0-9_-]{2,}\" OR\n jsonPayload.message=~\"Bearer\\s+eyJ[A-Za-z0-9_-]{2,}\"\n)\n", "labelExtractors": { "cluster": "EXTRACT(resource.labels.cluster_name)", "namespace": "EXTRACT(resource.labels.namespace_name)" From df76027a6bdc149184e9ec7911a9978b20c5729e Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 6 Aug 2026 12:43:27 +0200 Subject: [PATCH 193/329] Ignore expired preapprovals in AcceptTransferPreapprovalProposalTrigger (#6673) [ci] fixes #6610 Signed-off-by: Moritz Kiefer --- .../WalletTimeBasedIntegrationTest.scala | 70 ++++++++++++++++++- ...ptTransferPreapprovalProposalTrigger.scala | 13 +++- docs/src/release_notes_upcoming.rst | 2 + 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletTimeBasedIntegrationTest.scala index ec75840912..d9bee318f0 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/WalletTimeBasedIntegrationTest.scala @@ -1,5 +1,7 @@ package org.lfdecentralizedtrust.splice.integration.tests +import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.transferpreapproval.TransferPreapprovalProposal +import org.lfdecentralizedtrust.splice.config.ConfigTransforms import org.lfdecentralizedtrust.splice.config.ConfigTransforms.{ ConfigurableApp, updateAutomationConfig, @@ -10,6 +12,7 @@ import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.Integration import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.{ AnsSubscriptionRenewalPaymentTrigger, ExpiredLockedAmuletTrigger, + ExpireTransferPreapprovalsTrigger, } import org.lfdecentralizedtrust.splice.sv.automation.singlesv.ReceiveSvRewardCouponTrigger import org.lfdecentralizedtrust.splice.util.{ @@ -18,8 +21,13 @@ import org.lfdecentralizedtrust.splice.util.{ TriggerTestUtil, WalletTestUtil, } -import org.lfdecentralizedtrust.splice.validator.automation.ReceiveFaucetCouponTrigger +import org.lfdecentralizedtrust.splice.validator.automation.{ + ReceiveFaucetCouponTrigger, + RenewTransferPreapprovalTrigger, +} import org.lfdecentralizedtrust.splice.wallet.admin.api.client.commands.HttpWalletAppClient +import com.digitalasset.canton.config.NonNegativeFiniteDuration +import monocle.macros.syntax.lens.* import java.time.Duration @@ -30,6 +38,9 @@ class WalletTimeBasedIntegrationTest with SplitwellTestUtil with TriggerTestUtil { + // reduce for expiry test + private val preapprovalLifetime = NonNegativeFiniteDuration.ofMinutes(1) + override def environmentDefinition: SpliceEnvironmentDefinition = EnvironmentDefinition .simpleTopology1SvWithSimTime(this.getClass.getSimpleName) @@ -50,6 +61,11 @@ class WalletTimeBasedIntegrationTest _.withPausedTrigger[ReceiveSvRewardCouponTrigger] )(config) ) + .addConfigTransforms((_, config) => + ConfigTransforms.updateAllValidatorConfigs_( + _.focus(_.transferPreapproval.preapprovalLifetime).replace(preapprovalLifetime) + )(config) + ) "A wallet" should { @@ -220,6 +236,58 @@ class WalletTimeBasedIntegrationTest } } } + + "create a new TransferPreapproval if the existing one has expired" in { implicit env => + val aliceUserParty = onboardWalletUser(aliceWalletClient, aliceValidatorBackend) + val aliceValidatorParty = aliceValidatorBackend.getValidatorPartyId() + + def alicePreapprovals = + aliceValidatorBackend + .listTransferPreapprovals() + .filter(_.payload.receiver == aliceUserParty.toProtoPrimitive) + + // disable renew and expiry automation so the expired one does not get archived. + setTriggersWithin( + triggersToPauseAtStart = Seq( + aliceValidatorBackend.validatorAutomation.trigger[RenewTransferPreapprovalTrigger] + ) ++ activeSvs.map(_.dsoDelegateBasedAutomation.trigger[ExpireTransferPreapprovalsTrigger]) + ) { + val initial = clue("Alice creates a TransferPreapproval") { + createTransferPreapprovalEnsuringItExists(aliceWalletClient, aliceValidatorBackend) + alicePreapprovals.loneElement + } + + clue("The TransferPreapproval expires without being renewed or archived") { + advanceTime(preapprovalLifetime.asJava.plusSeconds(1)) + val expired = alicePreapprovals.loneElement + expired.contract.contractId shouldBe initial.contract.contractId + expired.payload.expiresAt should be < getLedgerTime.toInstant + } + + actAndCheck( + "Alice creates another TransferPreapprovalProposal", + aliceValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.commands + .submitWithResult( + userId = aliceValidatorBackend.config.ledgerApiUser, + actAs = Seq(aliceUserParty), + readAs = Seq(aliceUserParty), + update = TransferPreapprovalProposal.create( + aliceUserParty.toProtoPrimitive, + aliceValidatorParty.toProtoPrimitive, + java.util.Optional.of(dsoParty.toProtoPrimitive), + ), + ), + )( + "Validator automation creates a new TransferPreapproval", + _ => { + val fresh = alicePreapprovals + .filterNot(_.contract.contractId == initial.contract.contractId) + .loneElement + fresh.payload.expiresAt should be > getLedgerTime.toInstant + }, + ) + } + } } } diff --git a/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/AcceptTransferPreapprovalProposalTrigger.scala b/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/AcceptTransferPreapprovalProposalTrigger.scala index 21d5e4edc9..c60f31b25a 100644 --- a/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/AcceptTransferPreapprovalProposalTrigger.scala +++ b/apps/validator/src/main/scala/org/lfdecentralizedtrust/splice/validator/automation/AcceptTransferPreapprovalProposalTrigger.scala @@ -110,11 +110,20 @@ class AcceptTransferPreapprovalProposalTrigger( for { validatorWallet <- ValidatorUtil.getValidatorWallet(store, walletManager) result <- store.lookupTransferPreapprovalByReceiverPartyWithOffset(receiverParty) flatMap { - case QueryResult(_, Some(_)) => + // Expired pre-approvals are ignored: the receiver cannot be paid through them anymore + // and they may stick around for a while until the SV automation archives them. + case QueryResult(_, Some(existing)) + if existing.payload.expiresAt.isAfter(clock.now.toInstant) => Future.successful( TaskSuccess(show"TransferPreapproval for receiver $receiverParty already exists") ) - case QueryResult(offset, None) => + case QueryResult(offset, existing) => + existing.foreach(expired => + logger.info( + s"Accepting proposal for receiver $receiverParty as its existing TransferPreapproval " + + s"${expired.contractId.contractId} expired at ${expired.payload.expiresAt}" + ) + ) validatorWallet.treasury .enqueueAmuletOperation( operation, diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index f86a9877d5..15c3bc9926 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -15,6 +15,8 @@ accepted command id receives a 200 response with the same result as the first. Concurrent duplicates, where no submission has completed yet, are still rejected. + - ``TransferPreapprovalProposal`` s are now accepted if there is an existing one but it has expired. + - CantonBft - Increase the default segment length by 4x to reduce performance impact from epoch switches. From 51f6c4199a16a4e908addb33550e5e64a430b437 Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Thu, 6 Aug 2026 07:45:40 -0400 Subject: [PATCH 194/329] Show diff of `dars.lock` if it's out of date (#6657) * Show diff of `dars.lock` if it's out of date. Fixes #5899 This updates `DarLockChecker.scala` to shell out to `diff` to get the difference between the current lock file and its expected contents, and includes the output in the error message. Signed-off-by: Matt Dziuban * Change `garbage collection` to `GC`. Signed-off-by: Matt Dziuban --------- Signed-off-by: Matt Dziuban --- .../splice/build_tools/DarLockChecker.scala | 37 +++++++++++---- .../build_tools/DarLockCheckerTest.scala | 47 +++++++++++++++++++ project/ignore-patterns/sbt-output.ignore.txt | 2 +- 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/build-tools/dar-lock-checker/src/main/scala/org/lfdecentralizedtrust/splice/build_tools/DarLockChecker.scala b/build-tools/dar-lock-checker/src/main/scala/org/lfdecentralizedtrust/splice/build_tools/DarLockChecker.scala index b8720005b3..26c2a2ba26 100644 --- a/build-tools/dar-lock-checker/src/main/scala/org/lfdecentralizedtrust/splice/build_tools/DarLockChecker.scala +++ b/build-tools/dar-lock-checker/src/main/scala/org/lfdecentralizedtrust/splice/build_tools/DarLockChecker.scala @@ -121,15 +121,7 @@ object DarLockChecker { val currentHashes = File(outputFilename).contentAsString val lockStr = getLockStr(checkedInDarMap ++ darMap) if (currentHashes != lockStr) - sys.error( - Seq( - "Error: daml lockfile is not up-to-date", - "Expected:", - lockStr, - "Actual:", - currentHashes, - ).mkString(System.lineSeparator()) - ) + sys.error(lockOutOfDateMessage(outputFilename, lockStr)) case "update" => // Check that the freshly built packages either match the // last release or have a different version number. @@ -263,6 +255,33 @@ object DarLockChecker { .sorted .mkString(System.lineSeparator()) + private[build_tools] def lockOutOfDateMessage( + currentLockFile: String, + expectedLockStr: String, + diffColor: String = if (sys.env.contains("CI")) "never" else "always", + ): String = + File.temporaryFile(prefix = "expected-dars", suffix = ".lock") { expectedFile => + val _ = expectedFile.write(expectedLockStr) + + val out = new StringBuilder("") + def appendToOut(s: String): Unit = out ++= s + System.lineSeparator() + val _ = Seq( + "diff", + s"--color=$diffColor", + "--unified=0", + s"--label=$currentLockFile", + "--label=expected-dars.lock", + currentLockFile, + expectedFile.toString, + ).!(ProcessLogger(appendToOut, appendToOut)) + + Seq( + "Error: daml lockfile is not up-to-date", + "", + out.toString, + ).mkString(System.lineSeparator()) + } + private def getCheckedInDarMap(): Map[(PackageName, PackageVersion), String] = { val checkedInDars = File("daml/dars").list(_.extension == Some(".dar")).toSeq toDarMap(readDars(checkedInDars.map(_.toString()))) diff --git a/build-tools/dar-lock-checker/src/test/scala/org/lfdecentralizedtrust/splice/build_tools/DarLockCheckerTest.scala b/build-tools/dar-lock-checker/src/test/scala/org/lfdecentralizedtrust/splice/build_tools/DarLockCheckerTest.scala index 85efab7cb2..fa503d98e0 100644 --- a/build-tools/dar-lock-checker/src/test/scala/org/lfdecentralizedtrust/splice/build_tools/DarLockCheckerTest.scala +++ b/build-tools/dar-lock-checker/src/test/scala/org/lfdecentralizedtrust/splice/build_tools/DarLockCheckerTest.scala @@ -3,6 +3,7 @@ package org.lfdecentralizedtrust.splice.build_tools +import better.files.* import com.digitalasset.daml.lf.data.Ref.{PackageName, PackageVersion} import org.scalatest.matchers.should.Matchers import org.scalatest.wordspec.AnyWordSpec @@ -13,6 +14,52 @@ class DarLockCheckerTest extends AnyWordSpec with Matchers { private def ver(v: String): PackageVersion = PackageVersion.assertFromString(v) private def key(name: String, v: String) = (pkg(name), ver(v)) + "lockOutOfDateMessage" should { + def lock(entries: String*): String = entries.mkString(System.lineSeparator()) + + def messageFor(currentLockContent: String, expectedLockStr: String): String = + File.temporaryFile(prefix = "current-dars", suffix = ".lock").apply { currentFile => + val _ = currentFile.write(currentLockContent) + DarLockChecker.lockOutOfDateMessage(currentFile.toString, expectedLockStr, "never") + } + + // The `-`/`+` lines of the diff embedded in the message + def changedLines(message: String): Seq[String] = + message.linesIterator + .filterNot(line => line.startsWith("---") || line.startsWith("+++")) + .filter(line => line.startsWith("-") || line.startsWith("+")) + .toSeq + + "report only the entries whose package id changed" in { + val current = lock( + "splice-amulet 0.1.0 hash0", + "splice-amulet 0.1.1 hash1", + "splice-amulet 0.1.2 hash2", + ) + val expected = lock( + "splice-amulet 0.1.0 hash0", + "splice-amulet 0.1.1 rebuiltHash1", + "splice-amulet 0.1.2 hash2", + ) + changedLines(messageFor(current, expected)) shouldBe Seq( + "-splice-amulet 0.1.1 hash1", + "+splice-amulet 0.1.1 rebuiltHash1", + ) + } + + "report entries missing from the lock file" in { + val current = lock() + val expected = lock("splice-amulet 0.1.0 hash0") + changedLines(messageFor(current, expected)) shouldBe Seq("+splice-amulet 0.1.0 hash0") + } + + "report entries that are no longer expected" in { + val current = lock("splice-amulet 0.1.0 hash0") + val expected = lock() + changedLines(messageFor(current, expected)) shouldBe Seq("-splice-amulet 0.1.0 hash0") + } + } + "detectBumps" should { "return empty when branch and compare base match exactly" in { val branch = Map(key("splice-amulet", "0.1.18") -> "hashA") diff --git a/project/ignore-patterns/sbt-output.ignore.txt b/project/ignore-patterns/sbt-output.ignore.txt index 6dde7f039e..3d40dd449a 100644 --- a/project/ignore-patterns/sbt-output.ignore.txt +++ b/project/ignore-patterns/sbt-output.ignore.txt @@ -43,7 +43,7 @@ protoc-jar: caught exception, retrying: java\.io\.IOException: Cannot run progra .*no longer exists at.* # sbt 1.4 onwards logs GC warnings -.*\[.*warn.*\].* of the last .* were spent in garbage collection\. +.*\[.*warn.*\].* of the last .* were spent in GC\. # During release bundling, the copy of the Canton OS repo emits a bunch of warnings .*\[.*warn.*\].*Negative time From 044ec802c3f47530d0b484972e19c695ecded73d Mon Sep 17 00:00:00 2001 From: Tim Date: Thu, 6 Aug 2026 15:40:46 +0200 Subject: [PATCH 195/329] Unified URLs (#6455) Signed-off-by: Tim Pelzer --- .../proposal-details-content.test.tsx | 7 ++++ .../governance/proposal-summary.test.tsx | 14 ++++---- ...UnallocatedUnclaimedActivityRecordForm.tsx | 4 +-- .../forms/GrantRevokeFeaturedAppForm.tsx | 4 +-- .../src/components/forms/OffboardSvForm.tsx | 4 +-- .../forms/SetAmuletConfigRulesForm.tsx | 6 ++-- .../forms/SetDsoConfigRulesForm.tsx | 6 ++-- .../forms/UpdateSvRewardWeightForm.tsx | 6 ++-- .../governance/ActionRequiredSection.tsx | 3 +- .../governance/ProposalDetailsContent.tsx | 32 +++++++++++++++---- .../governance/ProposalListingSection.tsx | 3 +- .../components/governance/ProposalSummary.tsx | 4 +-- .../governance/ProposalVoteForm.tsx | 5 +-- apps/sv/frontend/src/utils/constants.ts | 5 +++ 14 files changed, 71 insertions(+), 32 deletions(-) diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx index 5a3df3f1c0..ba7aedaf51 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx @@ -24,6 +24,7 @@ import { ProposalVoteForm } from '../../components/governance/ProposalVoteForm'; import App from '../../App'; import { svPartyId } from '../mocks/constants'; import { Wrapper } from '../helpers'; +import { SUPPORTING_URL_LABEL, VOTE_PROPOSAL_CONTRACT_ID_LABEL } from '../../utils/constants'; const voteRequest = { contractId: 'abc123' as ContractId, @@ -165,6 +166,10 @@ describe('Proposal Details Content', () => { const action = screen.getByTestId('proposal-details-action-value'); expect(action.textContent).toMatch(/Offboard Member/); + expect(screen.getByTestId('proposal-details-contractid-label').textContent).toBe( + VOTE_PROPOSAL_CONTRACT_ID_LABEL + ); + const offboardSection = screen.getByTestId('proposal-details-offboard-member-section'); expect(offboardSection).toBeInTheDocument(); @@ -177,6 +182,8 @@ describe('Proposal Details Content', () => { const summary = screen.getByTestId('proposal-details-summary-value'); expect(summary.textContent).toMatch(/Summary of the proposal/); + expect(screen.getByTestId('proposal-details-url-label').textContent).toBe(SUPPORTING_URL_LABEL); + const url = screen.getByTestId('proposal-details-url'); expect(url.textContent).toMatch(/https:\/\/example.com/); diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx index 5bbd6213a1..66a23b706b 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx @@ -32,7 +32,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('action-title').textContent).toBe('Action'); expect(screen.getByTestId('action-field').textContent).toBe(actionName); - expect(screen.getByTestId('url-title').textContent).toBe('URL'); + expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); expect(screen.getByTestId('url-field').textContent).toBe(url); expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); @@ -96,7 +96,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('action-title').textContent).toBe('Action'); expect(screen.getByTestId('action-field').textContent).toBe(actionName); - expect(screen.getByTestId('url-title').textContent).toBe('URL'); + expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); expect(screen.getByTestId('url-field').textContent).toBe(url); expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); @@ -136,7 +136,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('action-title').textContent).toBe('Action'); expect(screen.getByTestId('action-field').textContent).toBe(actionName); - expect(screen.getByTestId('url-title').textContent).toBe('URL'); + expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); expect(screen.getByTestId('url-field').textContent).toBe(url); expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); @@ -180,7 +180,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('action-title').textContent).toBe('Action'); expect(screen.getByTestId('action-field').textContent).toBe(actionName); - expect(screen.getByTestId('url-title').textContent).toBe('URL'); + expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); expect(screen.getByTestId('url-field').textContent).toBe(url); expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); @@ -230,7 +230,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('action-title').textContent).toBe('Action'); expect(screen.getByTestId('action-field').textContent).toBe(actionName); - expect(screen.getByTestId('url-title').textContent).toBe('URL'); + expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); expect(screen.getByTestId('url-field').textContent).toBe(url); expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); @@ -296,7 +296,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('action-title').textContent).toBe('Action'); expect(screen.getByTestId('action-field').textContent).toBe(actionName); - expect(screen.getByTestId('url-title').textContent).toBe('URL'); + expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); expect(screen.getByTestId('url-field').textContent).toBe(url); expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); @@ -368,7 +368,7 @@ describe('Review Proposal Component', () => { expect(screen.getByTestId('action-title').textContent).toBe('Action'); expect(screen.getByTestId('action-field').textContent).toBe(actionName); - expect(screen.getByTestId('url-title').textContent).toBe('URL'); + expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); expect(screen.getByTestId('url-field').textContent).toBe(url); expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); diff --git a/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx b/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx index 9ff397a7e5..16f3ea75d1 100644 --- a/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx +++ b/apps/sv/frontend/src/components/forms/CreateUnallocatedUnclaimedActivityRecordForm.tsx @@ -8,7 +8,7 @@ import { useState } from 'react'; import { useDsoInfos } from '../../contexts/SvContext'; import { useAppForm } from '../../hooks/form'; import { useProposalMutation } from '../../hooks/useProposalMutation'; -import { THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; +import { SUPPORTING_URL_LABEL, THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; import { createProposalActions, getInitialExpiration } from '../../utils/governance'; import type { CommonProposalFormData } from '../../utils/types'; import { EffectiveDateField } from '../form-components/EffectiveDateField'; @@ -227,7 +227,7 @@ export const CreateUnallocatedUnclaimedActivityRecordForm: React.FC = _ => { > {field => ( )} diff --git a/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx b/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx index 898fb0001d..3ce9825fd1 100644 --- a/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx +++ b/apps/sv/frontend/src/components/forms/GrantRevokeFeaturedAppForm.tsx @@ -13,7 +13,7 @@ import { import { dateTimeFormatISO } from '@canton-network/splice-common-frontend-utils'; import { useAppForm } from '../../hooks/form'; import { useStore } from '@tanstack/react-form'; -import { THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; +import { SUPPORTING_URL_LABEL, THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; import { CommonProposalFormData } from '../../utils/types'; import { ContractId } from '@daml/types'; import { FeaturedAppRight } from '@daml.js/splice-amulet/lib/Splice/Amulet'; @@ -346,7 +346,7 @@ export const GrantRevokeFeaturedAppForm: React.FC validateUrl(value), }} > - {field => } + {field => } )} diff --git a/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx b/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx index ce35a04548..4bfccbfd99 100644 --- a/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx +++ b/apps/sv/frontend/src/components/forms/OffboardSvForm.tsx @@ -22,7 +22,7 @@ import { EffectiveDateField } from '../form-components/EffectiveDateField'; import { ProposalSummary } from '../governance/ProposalSummary'; import { ProposalSubmissionError } from '../form-components/ProposalSubmissionError'; import { useProposalMutation } from '../../hooks/useProposalMutation'; -import { THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; +import { SUPPORTING_URL_LABEL, THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; interface ExtraFormFields { sv: string; @@ -175,7 +175,7 @@ export const OffboardSvForm: React.FC = _ => { onChange: ({ value }) => validateUrl(value), }} > - {field => } + {field => } )} diff --git a/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx b/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx index bfd977dd36..3175b38765 100644 --- a/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx +++ b/apps/sv/frontend/src/components/forms/SetAmuletConfigRulesForm.tsx @@ -5,7 +5,7 @@ import { ActionRequiringConfirmation, AmuletRules_ActionRequiringConfirmation, } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules'; -import { THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; +import { SUPPORTING_URL_LABEL, THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; import { buildAmuletRulesPendingConfigFields, configFormDataToConfigChanges, @@ -288,7 +288,9 @@ export const SetAmuletConfigRulesForm: () => JSX.Element = () => { onChange: ({ value }) => validateUrl(value), }} > - {field => } + {field => ( + + )} )} diff --git a/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx b/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx index 8bb802af89..9530d4b5d2 100644 --- a/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx +++ b/apps/sv/frontend/src/components/forms/SetDsoConfigRulesForm.tsx @@ -20,7 +20,7 @@ import { useAppForm } from '../../hooks/form'; import { useProposalMutation } from '../../hooks/useProposalMutation'; import { buildDsoConfigChanges } from '../../utils/buildDsoConfigChanges'; import { buildDsoRulesConfigFromChanges } from '../../utils/buildDsoRulesConfigFromChanges'; -import { THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; +import { SUPPORTING_URL_LABEL, THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; import { buildPendingConfigFields, configFormDataToConfigChanges, @@ -304,7 +304,9 @@ export const SetDsoConfigRulesForm: () => JSX.Element = () => { onChange: ({ value }) => validateUrl(value), }} > - {field => } + {field => ( + + )} )} diff --git a/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx b/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx index 07028252f9..5b8bb357a3 100644 --- a/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx +++ b/apps/sv/frontend/src/components/forms/UpdateSvRewardWeightForm.tsx @@ -17,7 +17,7 @@ import { validateUrl, validateWeight, } from './formValidators'; -import { THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; +import { SUPPORTING_URL_LABEL, THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; import { createProposalActions, formatBasisPoints, @@ -220,7 +220,9 @@ export const UpdateSvRewardWeightForm: React.FC = _ => { onChange: ({ value }) => validateUrl(value), }} > - {field => } + {field => ( + + )} )} diff --git a/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx b/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx index 167c46c932..eeca50f4a4 100644 --- a/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx +++ b/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx @@ -6,6 +6,7 @@ import { East } from '@mui/icons-material'; import { Alert, Box, Stack, Typography } from '@mui/material'; import { Link as RouterLink } from 'react-router'; import { CopyableIdentifier, PageSectionHeader } from '../../components/beta'; +import { VOTE_PROPOSAL_CONTRACT_ID_LABEL } from '../../utils/constants'; import React from 'react'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; @@ -131,7 +132,7 @@ const ActionCard = (props: ActionCardProps) => { data-testid="action-required-description" /> = pro /> = pro /> = ({ /> {comment && ( - - {comment} - + + + {VOTE_REASON_SUMMARY_LABEL} + + + {comment} + + + )} + {url && ( + + + {VOTE_REASON_URL_LABEL} + + + )} - {url && } = ({ }) => ( <> PROPOSAL TYPE - VOTE PROPOSAL CONTRACT ID + {VOTE_PROPOSAL_CONTRACT_ID_LABEL} {showThresholdDeadline ? ( <> THRESHOLD DEADLINE diff --git a/apps/sv/frontend/src/components/governance/ProposalSummary.tsx b/apps/sv/frontend/src/components/governance/ProposalSummary.tsx index 508ef39599..c563c4c216 100644 --- a/apps/sv/frontend/src/components/governance/ProposalSummary.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalSummary.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { Box, Typography } from '@mui/material'; -import { THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; +import { SUPPORTING_URL_LABEL, THRESHOLD_DEADLINE_SUBTITLE } from '../../utils/constants'; import type { ConfigChange } from '../../utils/types'; import { scrollContainerSx, scrollableIdentifierFieldSx } from '../beta/identifierStyles'; import { ConfigValuesChanges } from './ConfigValuesChanges'; @@ -71,7 +71,7 @@ export const ProposalSummary: React.FC = props => { - + diff --git a/apps/sv/frontend/src/components/governance/ProposalVoteForm.tsx b/apps/sv/frontend/src/components/governance/ProposalVoteForm.tsx index 46790d07bf..4a736b8a0d 100644 --- a/apps/sv/frontend/src/components/governance/ProposalVoteForm.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalVoteForm.tsx @@ -10,6 +10,7 @@ import { ContractId } from '@daml/types'; import { VoteRequest } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules'; import { ProposalVote } from '../../utils/types'; import { Alert, Box, Button, Stack, TextField, Typography } from '@mui/material'; +import { VOTE_REASON_SUMMARY_LABEL, VOTE_REASON_URL_LABEL } from '../../utils/constants'; interface CastVoteArgs { accepted: boolean; url: string; @@ -109,7 +110,7 @@ export const ProposalVoteForm: React.FC = props => { fontSize={18} lineHeight={1} > - Reason + {VOTE_REASON_SUMMARY_LABEL} = props => { fontSize={18} lineHeight={1} > - Vote Reason URL + {VOTE_REASON_URL_LABEL} Date: Thu, 6 Aug 2026 16:12:11 +0200 Subject: [PATCH 196/329] Disable circuit breakers for all sv4 apps in LSU test (#6704) fixes DACH-NY/cn-test-failures#9487 Previous fix (#5587) only covered the sv app; DACH-NY/cn-test-failures#9487 shows the same trip (sequencer backpressure during sv4 catchup) from the sv4 validator app [ci] Signed-off-by: Martin Florian --- .../tests/LsuIntegrationTest.scala | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LsuIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LsuIntegrationTest.scala index 5191fc8be3..59e3baa1f1 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LsuIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LsuIntegrationTest.scala @@ -109,10 +109,11 @@ class LsuIntegrationTest physicalSynchronizerExpiration = NonNegativeFiniteDuration.ofSeconds(1) ), // sv-4 is intentionally a late-joining node in this test, which means the - // sequencer spends some time catching up. This can cause the sv-app's + // sequencer spends some time catching up. This can cause the sv-4 apps' // circuit breakers to trip, which makes annoying logs and delays init. // The circuit breakers tripping a bit during catchup would be just fine - // IRL, so as a simple fix to this test we disable them for sv-4. + // IRL, so as a simple fix to this test we disable them for all sv-4 apps + // (see also the scan and validator app transforms below). circuitBreakers = if (name == "sv4") CircuitBreakersConfig.never else config.parameters.circuitBreakers, @@ -121,7 +122,7 @@ class LsuIntegrationTest } .andThen( ConfigTransforms - .updateAllScanAppConfigs { (_, config) => + .updateAllScanAppConfigs { (name, config) => config.copy( synchronizerNodes = config.synchronizerNodes.copy( successor = Some(config.synchronizerNodes.current) @@ -129,10 +130,25 @@ class LsuIntegrationTest parameters = config.parameters.copy( spliceCachingConfigs = config.parameters.spliceCachingConfigs.copy( physicalSynchronizerExpiration = NonNegativeFiniteDuration.ofSeconds(1) - ) + ), + circuitBreakers = + if (name == "sv4Scan") CircuitBreakersConfig.never + else config.parameters.circuitBreakers, ), ) } + ) + .andThen( + ConfigTransforms + .updateAllValidatorAppConfigs { (name, config) => + config.copy( + parameters = config.parameters.copy( + circuitBreakers = + if (name == "sv4Validator") CircuitBreakersConfig.never + else config.parameters.circuitBreakers + ) + ) + } )(config) }) .withBftSequencersSuccessor From 0dd7425c2d7cf8a72799a3742d5b7df264d9eb0b Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Thu, 6 Aug 2026 10:43:20 -0400 Subject: [PATCH 197/329] use binary slick-fork instead of forked source (#6687) * move asUpdateReturning to a syntax extension --------- Assisted-by: Copilot:claude-5-opus Signed-off-by: Stephen Compall --- .../splice/store/UpdateHistory.scala | 1 + .../splice/store/db/AsUpdateReturning.scala | 39 +++++ .../store/db/DbMultiDomainAcsStore.scala | 1 + build.sbt | 2 - canton/community/lib/slick/LICENSE.txt | 25 ---- .../scala/slick/jdbc/canton/StaticQuery.scala | 133 ------------------ project/BuildCommon.scala | 23 +-- project/CantonDependencies.scala | 1 + scripts/copy-canton.sh | 1 + 9 files changed, 46 insertions(+), 180 deletions(-) create mode 100644 apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AsUpdateReturning.scala delete mode 100644 canton/community/lib/slick/LICENSE.txt delete mode 100644 canton/community/lib/slick/src/main/scala/slick/jdbc/canton/StaticQuery.scala diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/UpdateHistory.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/UpdateHistory.scala index 11e1e3b111..db3161ae2e 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/UpdateHistory.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/UpdateHistory.scala @@ -28,6 +28,7 @@ import org.lfdecentralizedtrust.splice.store.HistoryBackfilling.{ } import org.lfdecentralizedtrust.splice.store.MultiDomainAcsStore.{HasIngestionSink, IngestionFilter} import org.lfdecentralizedtrust.splice.store.db.{AcsJdbcTypes, AcsQueries} +import db.AsUpdateReturning.* import org.lfdecentralizedtrust.splice.util.{ Contract, DomainRecordTimeRange, diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AsUpdateReturning.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AsUpdateReturning.scala new file mode 100644 index 0000000000..95bf108fc3 --- /dev/null +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AsUpdateReturning.scala @@ -0,0 +1,39 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.store.db + +import slick.dbio.Effect +import slick.jdbc.GetResult +import slick.jdbc.canton.SQLActionBuilder +import slick.sql.SqlStreamingAction + +/** Syntax for running data-modifying statements that also return rows, such as + * PostgreSQL's `insert ... returning`, `insert ... on conflict do nothing returning`, + * and `delete ... returning`. + */ +private[store] object AsUpdateReturning { + + implicit class `SQLActionBuilder asUpdateReturning`(private val builder: SQLActionBuilder) + extends AnyVal { + + /** Run this statement as one that both writes and reads back rows. + * + * Neither of the combinators that come with [[slick.jdbc.canton.SQLActionBuilder]] + * fits `... returning ...` statements: + * + * - `asUpdate` yields the JDBC update count as a single `Int` and throws + * away the result set. + * - `as[R]` does decode the result set, but types the action as + * `Effect.Read` alone, which would allow its usage with unsafe + * combinators. + * + * So this simply relabels the read action as also writing by widening the + * result of `as[R]`. + */ + def asUpdateReturning[R](implicit + rconv: GetResult[R] + ): SqlStreamingAction[Vector[R], R, Effect.Read & Effect.Write] = + builder.as[R] + } +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala index c18d71b485..470d5e445b 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbMultiDomainAcsStore.scala @@ -54,6 +54,7 @@ import org.lfdecentralizedtrust.splice.store.db.AcsQueries.{ SelectFromAcsTableWithStateResult, } import org.lfdecentralizedtrust.splice.store.db.AcsTables.ContractStateRowData +import AsUpdateReturning.* import com.daml.nonempty.NonEmpty import com.digitalasset.canton.data.CantonTimestamp import com.daml.metrics.api.MetricHandle.LabeledMetricsFactory diff --git a/build.sbt b/build.sbt index 246d7e85c3..c38db07cc1 100644 --- a/build.sbt +++ b/build.sbt @@ -25,7 +25,6 @@ lazy val `canton-community-participant` = BuildCommon.`canton-community-particip lazy val `canton-community-admin-api` = BuildCommon.`canton-community-admin-api` lazy val `canton-community-integration-testing` = BuildCommon.`canton-community-integration-testing` lazy val `canton-community-testing` = BuildCommon.`canton-community-testing` -lazy val `canton-slick-fork` = BuildCommon.`canton-slick-fork` lazy val `canton-wartremover-extension` = BuildCommon.`canton-wartremover-extension` lazy val `canton-util-observability` = BuildCommon.`canton-util-observability` lazy val `canton-ledger-api-value` = BuildCommon.`canton-ledger-api-value` @@ -130,7 +129,6 @@ lazy val root: Project = (project in file(".")) `canton-community-common`, `canton-community-integration-testing`, `canton-community-testing`, - `canton-slick-fork`, `canton-wartremover-extension`, `canton-community-app`, `canton-community-app-base`, diff --git a/canton/community/lib/slick/LICENSE.txt b/canton/community/lib/slick/LICENSE.txt deleted file mode 100644 index 0fb1082e73..0000000000 --- a/canton/community/lib/slick/LICENSE.txt +++ /dev/null @@ -1,25 +0,0 @@ -Copyright 2011-2021 Lightbend, Inc. - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. diff --git a/canton/community/lib/slick/src/main/scala/slick/jdbc/canton/StaticQuery.scala b/canton/community/lib/slick/src/main/scala/slick/jdbc/canton/StaticQuery.scala deleted file mode 100644 index e78c8b850b..0000000000 --- a/canton/community/lib/slick/src/main/scala/slick/jdbc/canton/StaticQuery.scala +++ /dev/null @@ -1,133 +0,0 @@ -package slick.jdbc.canton - -import slick.dbio.{Effect, NoStream} -import slick.jdbc.{ - GetResult, - Invoker, - PositionedParameters, - PositionedResult, - SetParameter, - StatementInvoker, - StreamingInvokerAction, -} -import slick.sql.SqlStreamingAction - -import java.sql.PreparedStatement -import scala.collection.mutable -import scala.collection.mutable.ArrayBuffer -import scala.language.implicitConversions - -/** Fork of slick's sql interpolation. - * - * `sqlu` now returns an action indicating a write effect. - * `sql` now returns a forked SQL action builder where `as` results in an action with read effect and - * `asUpdate` in one with a write effect. - */ -class ActionBasedSQLInterpolation(val s: StringContext) extends AnyVal { - - /** Build a SQLActionBuilder via string interpolation */ - def sql(params: TypedParameter[?]*): SQLActionBuilder = SQLActionBuilder.parse(s.parts, params) - - /** Build an Action for an UPDATE statement via string interpolation */ - def sqlu( - params: TypedParameter[?]* - ): SqlStreamingAction[Vector[Int], Int, Effect.Write]#ResultAction[Int, NoStream, Effect.Write] = - sql(params *).asUpdate -} - -object ActionBasedSQLInterpolation { - - object Implicits { - implicit def actionBasedSQLInterpolationCanton(s: StringContext): ActionBasedSQLInterpolation = - new ActionBasedSQLInterpolation(s) - } -} - -class TypedParameter[T](val param: T, val setParameter: SetParameter[T]) { - def applied: SetParameter[Unit] = setParameter.applied(param) -} - -object TypedParameter { - implicit def typedParameter[T](param: T)(implicit - setParameter: SetParameter[T] - ): TypedParameter[T] = - new TypedParameter[T](param, setParameter) -} - -object SQLActionBuilder { - def parse(strings: Seq[String], typedParams: Seq[TypedParameter[?]]): SQLActionBuilder = - if (strings.sizeIs == 1) - SQLActionBuilder(strings.head, SetParameter.SetUnit) - else { - val b = new mutable.StringBuilder - val remaining = new ArrayBuffer[SetParameter[Unit]] - typedParams.zip(strings.iterator.to(Iterable)).foreach { zipped => - val p = zipped._1.param - var literal = false - - def decode(s: String): String = - if (s.endsWith("##")) decode(s.substring(0, s.length - 2)) + "#" - else if (s.endsWith("#")) { - literal = true - s.substring(0, s.length - 1) - } else - s - - b.append(decode(zipped._2)) - if (literal) b.append(p.toString) - else { - b.append('?') - remaining += zipped._1.applied - } - } - b.append(strings.last) - SQLActionBuilder(b.toString, (u, pp) => remaining.foreach(_(u, pp))) - } -} - -case class SQLActionBuilder(sql: String, setParameter: SetParameter[Unit]) { - - private def asInternal[R, E <: Effect](implicit - getResult: GetResult[R] - ): SqlStreamingAction[Vector[R], R, E] = - new StreamingInvokerAction[Vector[R], R, Effect] { - def statements: Iterable[String] = List(sql) - - protected[this] def createInvoker(statements: Iterable[String]): Invoker[R] = - new StatementInvoker[R] { - val getStatement = statements.head - - protected def setParam(st: PreparedStatement): Unit = - setParameter((), new PositionedParameters(st)) - - protected def extractValue(rs: PositionedResult): R = getResult(rs) - } - - protected[this] def createBuilder: collection.mutable.Builder[R, Vector[R]] = - Vector.newBuilder[R] - } - - def as[R](implicit getResult: GetResult[R]): SqlStreamingAction[Vector[R], R, Effect.Read] = - asInternal[R, Effect.Read] - - def asUpdate: SqlStreamingAction[Vector[Int], Int, Effect.Write]#ResultAction[ - Int, - NoStream, - Effect.Write, - ] = - asInternal[Int, Effect.Write](GetResult.GetUpdateValue).head - - def asUpdateReturning[R](implicit - rconv: GetResult[R] - ): SqlStreamingAction[Vector[R], R, Effect.Read & Effect.Write] = - asInternal[R, Effect.Read & Effect.Write] - - def concat(b: SQLActionBuilder): SQLActionBuilder = - SQLActionBuilder( - sql + b.sql, - (p, pp) => { - setParameter(p, pp) - b.setParameter(p, pp) - }, - ) -} diff --git a/project/BuildCommon.scala b/project/BuildCommon.scala index 3af0d703e9..4e04762d8c 100644 --- a/project/BuildCommon.scala +++ b/project/BuildCommon.scala @@ -212,7 +212,6 @@ object BuildCommon { Global / excludeLintKeys += `canton-community-synchronizer` / autoAPIMappings, Global / excludeLintKeys += `canton-community-participant` / autoAPIMappings, // Global / excludeLintKeys += `demo` / autoAPIMappings, - Global / excludeLintKeys += `canton-slick-fork` / autoAPIMappings, Global / excludeLintKeys += Global / damlCodeGeneration, Global / googleCredentialsDisable := true, Global / resolvers += ("Canton snapshots" at "artifactregistry://europe-maven.pkg.dev/da-images/public-maven-unstable"), @@ -511,8 +510,7 @@ object BuildCommon { .apply("canton-community-base", file("canton/community/base")) .enablePlugins(BuildInfoPlugin) .dependsOn( - `canton-slick-fork`, - `canton-community-admin-api`, + `canton-community-admin-api` // Canton depends on the Daml code via a git submodule and the two // projects below. We instead depend on the artifacts released // from the Daml repo listed in libraryDependencies below. @@ -531,6 +529,7 @@ object BuildCommon { bouncycastle_bcpkix_jdk15on, bouncycastle_bcprov_jdk15on, canton_kms_driver_api, + canton_slick_fork, canton_util_external, cats, chimney, @@ -920,27 +919,10 @@ object BuildCommon { ) } - lazy val `canton-slick-fork` = { - import CantonDependencies._ - sbt.Project - .apply("canton-slick-fork", file("canton/community/lib/slick")) - .disablePlugins(ScalafmtPlugin, WartRemover) - .settings( - sharedCantonSettings, - removeTestSources, - sharedSettings, - libraryDependencies ++= Seq( - scala_reflect, - slick, - ), - ) - } - lazy val `canton-wartremover-extension` = { import CantonDependencies._ sbt.Project .apply("canton-wartremover-extension", file("canton/community/lib/wartremover")) - .dependsOn(`canton-slick-fork`) .settings( Test / scalacOptions ++= Seq( "-Wconf:msg=synchronized not selected from this instance:silent" @@ -948,6 +930,7 @@ object BuildCommon { disableTests, sharedSettings, libraryDependencies ++= Seq( + canton_slick_fork, canton_wartremover_annotations, cats, grpc_stub, diff --git a/project/CantonDependencies.scala b/project/CantonDependencies.scala index 9fea4e15e5..c15228e47b 100644 --- a/project/CantonDependencies.scala +++ b/project/CantonDependencies.scala @@ -105,6 +105,7 @@ object CantonDependencies { "com.daml" %% "observability-metrics" % canton_library_version lazy val canton_contextualized_logging = "com.daml" %% "contextualized-logging" % canton_library_version + lazy val canton_slick_fork = "com.daml" %% "slick-fork" % canton_library_version lazy val canton_traffic_enforcement_api = "com.daml" %% "traffic-enforcement-api" % canton_library_version lazy val canton_util_external = "com.daml" %% "util-external" % canton_library_version diff --git a/scripts/copy-canton.sh b/scripts/copy-canton.sh index a2ae78176b..76327a3198 100755 --- a/scripts/copy-canton.sh +++ b/scripts/copy-canton.sh @@ -24,6 +24,7 @@ rsync -av --delete --exclude version.sbt --exclude community-build.sbt --exclude --exclude '*/community/lib/google-common-protos-scala' \ --exclude '*/community/lib/magnolify' \ --exclude '*/community/lib/scalatest' \ + --exclude '*/community/lib/slick' \ --exclude '*/community/lib/wartremover-annotations' \ --exclude 'community/bindings-java' --exclude "*/community/transcode" \ --exclude '*/community/kms-driver-api' \ From 451d3d5e8d1591d8b730eeff983e0da40714c095 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Fri, 7 Aug 2026 09:48:02 +0200 Subject: [PATCH 198/329] Do not write metadata file in PeriodicTopologySnapshotTrigger (#6706) Signed-off-by: Julien Tinguely --- .../SequencerAdminConnection.scala | 23 +------------------ .../PeriodicTopologySnapshotTrigger.scala | 20 ++++------------ 2 files changed, 6 insertions(+), 37 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SequencerAdminConnection.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SequencerAdminConnection.scala index 8fa05b59ae..70d7bd6715 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SequencerAdminConnection.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/SequencerAdminConnection.scala @@ -33,15 +33,13 @@ import com.digitalasset.canton.synchronizer.sequencer.SequencerPruningStatus import com.digitalasset.canton.synchronizer.sequencer.admin.grpc.InitializeSequencerResponse import com.digitalasset.canton.time.Clock import com.digitalasset.canton.topology.{Member, NodeIdentity, PhysicalSynchronizerId, SequencerId} -import com.digitalasset.canton.topology.admin.grpc.{BaseQuery, TopologyStoreId} import com.digitalasset.canton.topology.admin.v30.{ GenesisStateV2Response, SequencerLsuStateResponse, } import com.digitalasset.canton.topology.MediatorGroup.MediatorGroupIndex import com.digitalasset.canton.topology.store.StoredTopologyTransactions.GenericStoredTopologyTransactions -import com.digitalasset.canton.topology.store.TimeQuery.Snapshot -import com.digitalasset.canton.topology.transaction.{SequencerSynchronizerState, TopologyMapping} +import com.digitalasset.canton.topology.transaction.SequencerSynchronizerState import com.digitalasset.canton.tracing.TraceContext import com.google.protobuf.ByteString import com.typesafe.config.ConfigFactory @@ -187,25 +185,6 @@ class SequencerAdminConnection( ) } - def getTopologyTransactionsSummary(store: TopologyStoreId, now: CantonTimestamp)(implicit - traceContext: TraceContext - ): Future[Map[TopologyMapping.Code, Int]] = { - runCmd( - TopologyAdminCommands.Read.ListAllV2( - query = BaseQuery( - store = store, - proposals = false, - timeQuery = Snapshot(now), - ops = None, - filterSigningKey = "", - protocolVersion = None, - ), - filterNamespace = "", - includeMappings = Seq.empty, - ) - ).map(_.result.groupMapReduce(_.mapping.code)(_ => 1)(_ + _)) - } - def getOnboardingState(sequencerIdOrTimestamp: Either[SequencerId, CantonTimestamp])(implicit traceContext: TraceContext ): Future[ByteString] = { diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/PeriodicTopologySnapshotTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/PeriodicTopologySnapshotTrigger.scala index 038683cf44..91a336982c 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/PeriodicTopologySnapshotTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/PeriodicTopologySnapshotTrigger.scala @@ -8,7 +8,6 @@ import com.digitalasset.canton.SynchronizerAlias import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.time.Clock import com.digitalasset.canton.topology.PhysicalSynchronizerId -import com.digitalasset.canton.topology.admin.grpc.TopologyStoreId import com.digitalasset.canton.tracing.TraceContext import io.circe.Json import io.grpc.{Status, StatusRuntimeException} @@ -153,21 +152,12 @@ class PeriodicTopologySnapshotTrigger( }, logger, ) - // list a summary of the transactions state at the time of the snapshot to validate further imports - summary <- triggerContext.retryProvider.retry( - RetryFor.Automation, - "getTopologyTransactionsSummary", - "Get topology transactions summary", - sequencerAdminConnection.getTopologyTransactionsSummary( - TopologyStoreId.Synchronizer(physicalSynchronizerId.logical), - clock.now, - ), - logger, - ) // we create a single metadata file to store the amounts of the different transactions along the sequencerId - metadataMap = summary.map(e => (e._1.code, e._2.toString)) + - ("sequencerId" -> sequencerId.toProtoPrimitive) + - ("physicalSynchronizerId" -> physicalSynchronizerId.toProtoPrimitive) + metadataMap = + Map( + "sequencerId" -> sequencerId.toProtoPrimitive, + "physicalSynchronizerId" -> physicalSynchronizerId.toProtoPrimitive, + ) metadataJson = Json .obj(metadataMap.map { case (k, v) => k -> Json.fromString(v) }.toSeq*) .spaces2 From b26ee6dc2467c916f864c286f532878363402fd0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:35:53 +0200 Subject: [PATCH 199/329] Bump the development-dependencies group across 1 directory with 5 updates (#6475) Bumps the development-dependencies group with 4 updates in the /load-tester directory: [@eslint/eslintrc](https://github.com/eslint/eslintrc), [@types/k6](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/k6), [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) and [prettier](https://github.com/prettier/prettier). Updates `@eslint/eslintrc` from 3.3.5 to 3.3.6 - [Release notes](https://github.com/eslint/eslintrc/releases) - [Changelog](https://github.com/eslint/eslintrc/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslintrc/compare/eslintrc-v3.3.5...eslintrc-v3.3.6) Updates `@types/k6` from 2.0.0 to 2.0.1 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/k6) Updates `@typescript-eslint/eslint-plugin` from 8.61.1 to 8.65.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.61.1 to 8.66.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.66.0/packages/parser) Updates `prettier` from 3.8.4 to 3.9.6 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.8.4...3.9.6) --- updated-dependencies: - dependency-name: "@eslint/eslintrc" dependency-version: 3.3.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: "@types/k6" dependency-version: 2.0.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.64.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: "@typescript-eslint/parser" dependency-version: 8.65.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: prettier dependency-version: 3.9.5 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- load-tester/package-lock.json | 504 ++++++++++++++++++++++++++++------ 1 file changed, 426 insertions(+), 78 deletions(-) diff --git a/load-tester/package-lock.json b/load-tester/package-lock.json index 47716d11ed..c1e8c151ec 100644 --- a/load-tester/package-lock.json +++ b/load-tester/package-lock.json @@ -719,9 +719,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -731,7 +731,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -988,9 +988,9 @@ "peer": true }, "node_modules/@types/k6": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/k6/-/k6-2.0.0.tgz", - "integrity": "sha512-ztO2fVOAQxtCpF6VWTn8O6FW6Z4DpjhY+XvD2GpCe/+fAfekD45bQ+vrM0B0C0c5ajLpVgWTMlLu9dKZ1nI+wg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/k6/-/k6-2.0.1.tgz", + "integrity": "sha512-3lAXtNx1qMwnn+fbE65yP1h9ADJyG6xQaIfGrhasZI2uck8S+gLuCbQl38YkbR5lGeE/NyCiF0FznmElpUgBsA==", "dev": true, "license": "MIT" }, @@ -1005,17 +1005,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", - "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/type-utils": "8.61.1", - "@typescript-eslint/utils": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1028,7 +1028,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.61.1", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -1044,16 +1044,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", - "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", + "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "engines": { @@ -1068,15 +1068,64 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-eslint/project-service": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", - "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.61.1", - "@typescript-eslint/types": "^8.61.1", + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "engines": { @@ -1091,14 +1140,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", - "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1108,10 +1157,24 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", - "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", "dev": true, "license": "MIT", "engines": { @@ -1126,15 +1189,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", - "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1", - "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -1150,10 +1213,130 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/types": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", - "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", "dev": true, "license": "MIT", "engines": { @@ -1165,16 +1348,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", - "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.61.1", - "@typescript-eslint/tsconfig-utils": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -1192,6 +1375,24 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -1203,26 +1404,39 @@ } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -1232,16 +1446,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", - "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1255,14 +1469,134 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", - "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -1273,6 +1607,20 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", @@ -1901,9 +2249,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", - "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "funding": [ { @@ -2140,9 +2488,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -2163,9 +2511,9 @@ } }, "node_modules/prettier": { - "version": "3.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", - "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, "license": "MIT", "bin": { From 2b0b31e38bef77acf0f66ce7a68c260e680be3b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:36:48 +0200 Subject: [PATCH 200/329] Bump @types/node from 25.9.4 to 26.1.0 in /gha-scripts (#6683) Bumps [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) from 25.9.4 to 26.1.0. - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/node" dependency-version: 26.1.0 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- gha-scripts/package-lock.json | 16 ++++++++-------- gha-scripts/package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gha-scripts/package-lock.json b/gha-scripts/package-lock.json index 2c43862448..49f6f0c078 100644 --- a/gha-scripts/package-lock.json +++ b/gha-scripts/package-lock.json @@ -12,7 +12,7 @@ "@actions/core": "^3.0.1", "@actions/github": "^9.1.1", "@types/js-yaml": "^4.0.5", - "@types/node": "^25.9.4", + "@types/node": "^26.1.0", "esbuild": "^0.28.1", "typescript": "^6.0.3" } @@ -671,13 +671,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.9.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.4.tgz", - "integrity": "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g==", + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.1.0.tgz", + "integrity": "sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": ">=7.24.0 <7.24.7" + "undici-types": "~8.3.0" } }, "node_modules/argparse": { @@ -816,9 +816,9 @@ } }, "node_modules/undici-types": { - "version": "7.24.6", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", - "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT" }, diff --git a/gha-scripts/package.json b/gha-scripts/package.json index 3a3c32efa5..7ef5e81922 100644 --- a/gha-scripts/package.json +++ b/gha-scripts/package.json @@ -13,7 +13,7 @@ "@actions/core": "^3.0.1", "@actions/github": "^9.1.1", "@types/js-yaml": "^4.0.5", - "@types/node": "^25.9.4", + "@types/node": "^26.1.0", "esbuild": "^0.28.1", "typescript": "^6.0.3" } From bea9928ef5c5e3aa23da69fa7fa0616ca392d991 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:41:47 +0200 Subject: [PATCH 201/329] Bump bignumber.js (#6348) Bumps the production-dependencies group with 1 update in the /load-tester directory: [bignumber.js](https://github.com/MikeMcl/bignumber.js). Updates `bignumber.js` from 11.1.4 to 11.1.5 - [Release notes](https://github.com/MikeMcl/bignumber.js/releases) - [Changelog](https://github.com/MikeMcl/bignumber.js/blob/main/CHANGELOG.md) - [Commits](https://github.com/MikeMcl/bignumber.js/compare/v11.1.4...v11.1.5) --- updated-dependencies: - dependency-name: bignumber.js dependency-version: 11.1.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- load-tester/package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/load-tester/package-lock.json b/load-tester/package-lock.json index c1e8c151ec..d552d057c4 100644 --- a/load-tester/package-lock.json +++ b/load-tester/package-lock.json @@ -1689,9 +1689,9 @@ "license": "MIT" }, "node_modules/bignumber.js": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.4.tgz", - "integrity": "sha512-AJ9dSeaUGj2xu7tEwmdqb51dqdb633xo4njI9K8ZFfcLrNr0XN8/EPkkZUNaF9fkCblGt2zVwZymesUdGynEkQ==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.5.tgz", + "integrity": "sha512-6WmzCNtUnfKpbozq+hOgWaZMMzORmYBwF1xZScyoIX3QRYWeKTtxxwDOW5tIz7C9BdjkIYHGTcelCLkXg0mndw==", "license": "MIT" }, "node_modules/brace-expansion": { From 8fc4fbd9a44286f95e9279752d619d62dcd53833 Mon Sep 17 00:00:00 2001 From: Puneet Bharti Date: Fri, 7 Aug 2026 13:20:06 +0200 Subject: [PATCH 202/329] SV UI: warn when disabled config fields change in proposals (#5650) (#6471) Signed-off-by: Puneet Bharti --- .../proposal-details-content.test.tsx | 85 +++++++++++++++++++ .../form-components/ConfigField.tsx | 69 ++++++++++----- .../governance/ConfigValuesChanges.tsx | 37 ++++++-- .../governance/ProposalDetailsContent.tsx | 26 +++++- 4 files changed, 188 insertions(+), 29 deletions(-) diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx index ba7aedaf51..cbeea63329 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx @@ -518,6 +518,91 @@ describe('Proposal Details Content', () => { expect(jsonDiffsToggle).toHaveAttribute('aria-expanded', 'false'); expect(screen.queryByText('JSON')).not.toBeInTheDocument(); expect(screen.getByTestId('json-diffs-details')).not.toBeVisible(); + + expect( + screen.queryByTestId('proposal-details-disabled-fields-warning') + ).not.toBeInTheDocument(); + }); + + test('should warn when disabled fields were altered in an amulet rules config proposal', () => { + const amuletRulesConfigDetails = { + actionName: 'Set Amulet Rules Config', + action: 'CRARC_SetConfig', + proposal: { + configChanges: [ + { + fieldName: 'transferConfigCreateFee', + label: 'Transfer (Create Fee)', + currentValue: '0.03', + newValue: '0.04', + }, + { + fieldName: 'decentralizedSynchronizerActiveSynchronizer', + label: 'The currently active synchronizer', + currentValue: 'global-domain::12', + newValue: 'global-domain::13', + isId: true, + disabled: true, + }, + ], + }, + } as ProposalDetails; + + render( + + + + ); + + const warning = screen.getByTestId('proposal-details-disabled-fields-warning'); + expect(warning).toBeInTheDocument(); + expect(warning.textContent).toMatch(/Disabled fields have been altered in this vote proposal/); + + const changes = screen.getAllByTestId('config-change'); + expect(changes[1]).toHaveAttribute('data-disabled', 'true'); + expect(within(changes[1]).getByTestId('config-change-disabled-label')).toHaveTextContent( + 'Disabled field' + ); + }); + + test('should warn when disabled fields were altered in a dso rules config proposal', () => { + const dsoRulesConfigDetails = { + actionName: 'Set DSO Rules Configuration', + action: 'SRARC_SetConfig', + proposal: { + configChanges: [ + { + fieldName: 'decentralizedSynchronizerActiveSynchronizerId', + label: 'Decentralized synchronizer: Active synchronizer identifier', + currentValue: 'global-domain::12', + newValue: 'global-domain::13', + isId: true, + disabled: true, + }, + ], + }, + } as ProposalDetails; + + render( + + + + ); + + expect(screen.getByTestId('proposal-details-disabled-fields-warning')).toBeInTheDocument(); + expect(screen.getByTestId('config-change-disabled-label')).toHaveTextContent('Disabled field'); }); test('should render dso rules config changes', () => { diff --git a/apps/sv/frontend/src/components/form-components/ConfigField.tsx b/apps/sv/frontend/src/components/form-components/ConfigField.tsx index 689d88c619..fc9b09b44d 100644 --- a/apps/sv/frontend/src/components/form-components/ConfigField.tsx +++ b/apps/sv/frontend/src/components/form-components/ConfigField.tsx @@ -77,10 +77,12 @@ export const ConfigField: React.FC = props => { sx={{ display: 'flex', justifyContent: 'space-between', - alignItems: 'center', + alignItems: 'flex-start', + gap: 2, + minWidth: 0, }} > - + {configChange.label} @@ -94,9 +96,10 @@ export const ConfigField: React.FC = props => { - + = props => { Current Configuration: {configChange.currentValue} @@ -145,24 +154,46 @@ export const PendingConfigDisplay: React.FC = ({ pend effectiveDate === 'Threshold' ? 'at Threshold' : dayjs(effectiveDate).fromNow(); return ( - - Pending Configuration: {pendingValue}
- This{' '} - - pending configuration - {' '} - will go into effect {effectiveText} -
+ Pending Configuration:{' '} + + {pendingValue} + +
+ + This{' '} + + pending configuration + {' '} + will go into effect {effectiveText} + +
); }; diff --git a/apps/sv/frontend/src/components/governance/ConfigValuesChanges.tsx b/apps/sv/frontend/src/components/governance/ConfigValuesChanges.tsx index 530e0d4b1c..5151f5705a 100644 --- a/apps/sv/frontend/src/components/governance/ConfigValuesChanges.tsx +++ b/apps/sv/frontend/src/components/governance/ConfigValuesChanges.tsx @@ -31,17 +31,36 @@ export const ConfigValuesChanges: React.FC = props => {changes.map((change, index) => ( - - {change.label} - + + + {change.label} + + {change.disabled && ( + + Disabled field + + )} + {change.currentValue && ( <> diff --git a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx index 454de649b6..e27ac0d6f4 100644 --- a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx @@ -8,7 +8,7 @@ import { } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules'; import { ContractId } from '@daml/types'; import { ChevronLeft, Edit } from '@mui/icons-material'; -import { Box, Button, Divider, Stack, Tab, Tabs, Typography } from '@mui/material'; +import { Alert, Box, Button, Divider, Stack, Tab, Tabs, Typography } from '@mui/material'; import React, { PropsWithChildren, useEffect, useMemo, useRef, useState } from 'react'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; @@ -20,6 +20,7 @@ import { } from '@canton-network/splice-common-frontend'; import { Link as RouterLink } from 'react-router'; import { + ConfigChange, ProposalDetails, ProposalVote, ProposalVotingInformation, @@ -42,6 +43,11 @@ import { VOTE_REASON_URL_LABEL, } from '../../utils/constants'; +/** True when a proposal changed fields that are locked/disabled in the create UI (e.g. emergency API). */ +export function hasAlteredDisabledFields(changes: ConfigChange[]): boolean { + return changes.some(c => c.disabled && c.currentValue !== c.newValue); +} + dayjs.extend(relativeTime); export interface ProposalDetailsContentProps { @@ -261,6 +267,15 @@ export const ProposalDetailsContent: React.FC = pro {proposalDetails.action === 'CRARC_SetConfig' && ( <> + {hasAlteredDisabledFields(proposalDetails.proposal.configChanges) && ( + + Disabled fields have been altered in this vote proposal. + + )} } @@ -282,6 +297,15 @@ export const ProposalDetailsContent: React.FC = pro {proposalDetails.action === 'SRARC_SetConfig' && ( <> + {hasAlteredDisabledFields(proposalDetails.proposal.configChanges) && ( + + Disabled fields have been altered in this vote proposal. + + )} } From 48911a80c33f10f96e25e156597ed01e393a1abc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:22:07 +0200 Subject: [PATCH 203/329] [ci] Bump the development-dependencies group across 1 directory with 8 updates (#6667) * [ci] Bump the development-dependencies group across 1 directory with 8 updates Bumps the development-dependencies group with 7 updates in the /cluster/pulumi directory: | Package | From | To | | --- | --- | --- | | [@eslint/eslintrc](https://github.com/eslint/eslintrc) | `3.3.5` | `3.3.6` | | [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.61.1` | `8.65.0` | | [@eslint/js](https://github.com/eslint/eslint/tree/HEAD/packages/js) | `9.39.4` | `9.39.5` | | [minimatch](https://github.com/isaacs/minimatch) | `10.2.5` | `10.2.6` | | [prettier](https://github.com/prettier/prettier) | `3.8.4` | `3.9.6` | | [ts-jest](https://github.com/kulshekhar/ts-jest) | `29.4.11` | `29.4.12` | | [@types/lodash](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/lodash) | `4.17.24` | `4.17.25` | Updates `@eslint/eslintrc` from 3.3.5 to 3.3.6 - [Release notes](https://github.com/eslint/eslintrc/releases) - [Changelog](https://github.com/eslint/eslintrc/blob/main/CHANGELOG.md) - [Commits](https://github.com/eslint/eslintrc/compare/eslintrc-v3.3.5...eslintrc-v3.3.6) Updates `@typescript-eslint/eslint-plugin` from 8.61.1 to 8.65.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.65.0/packages/eslint-plugin) Updates `@typescript-eslint/parser` from 8.61.1 to 8.66.0 - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.66.0/packages/parser) Updates `@eslint/js` from 9.39.4 to 9.39.5 - [Release notes](https://github.com/eslint/eslint/releases) - [Commits](https://github.com/eslint/eslint/commits/v9.39.5/packages/js) Updates `minimatch` from 10.2.5 to 10.2.6 - [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md) - [Commits](https://github.com/isaacs/minimatch/compare/v10.2.5...v10.2.6) Updates `prettier` from 3.8.4 to 3.9.6 - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/3.8.4...3.9.6) Updates `ts-jest` from 29.4.11 to 29.4.12 - [Release notes](https://github.com/kulshekhar/ts-jest/releases) - [Changelog](https://github.com/kulshekhar/ts-jest/blob/main/CHANGELOG.md) - [Commits](https://github.com/kulshekhar/ts-jest/compare/v29.4.11...v29.4.12) Updates `@types/lodash` from 4.17.24 to 4.17.25 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/lodash) --- updated-dependencies: - dependency-name: "@eslint/eslintrc" dependency-version: 3.3.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: "@eslint/js" dependency-version: 9.39.5 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: "@types/lodash" dependency-version: 4.17.25 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: "@typescript-eslint/eslint-plugin" dependency-version: 8.65.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: "@typescript-eslint/parser" dependency-version: 8.66.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: minimatch dependency-version: 10.2.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: prettier dependency-version: 3.9.6 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: ts-jest dependency-version: 29.4.12 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies ... Signed-off-by: dependabot[bot] * fix: fix prettier issues Signed-off-by: krzysztofczyz-da * [ci] bump Signed-off-by: krzysztofczyz-da * fix: add prettier changes Signed-off-by: krzysztofczyz-da --------- Signed-off-by: dependabot[bot] Signed-off-by: krzysztofczyz-da Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: krzysztofczyz-da --- .../canton-network/tsconfig.eslint.json | 1 + cluster/pulumi/circleci/tsconfig.eslint.json | 1 + cluster/pulumi/cluster/tsconfig.eslint.json | 1 + cluster/pulumi/common-sv/tsconfig.eslint.json | 1 + .../common-validator/tsconfig.eslint.json | 1 + cluster/pulumi/common/package.json | 2 +- cluster/pulumi/common/src/postgres.ts | 6 +- cluster/pulumi/common/src/serviceAccount.ts | 3 +- cluster/pulumi/common/tsconfig.eslint.json | 1 + .../pulumi/deployment/tsconfig.eslint.json | 1 + cluster/pulumi/eslint.config.mjs | 2 +- cluster/pulumi/gcp/tsconfig.eslint.json | 1 + cluster/pulumi/gha/tsconfig.eslint.json | 1 + cluster/pulumi/infra/src/cloudArmor.ts | 4 +- cluster/pulumi/infra/tsconfig.eslint.json | 1 + .../multi-validator/tsconfig.eslint.json | 1 + .../pulumi/observability/tsconfig.eslint.json | 1 + cluster/pulumi/operator/tsconfig.eslint.json | 1 + cluster/pulumi/package-lock.json | 470 ++++++++++++++---- cluster/pulumi/package.json | 12 +- cluster/pulumi/policies/tsconfig.eslint.json | 1 + cluster/pulumi/splitwell/tsconfig.eslint.json | 1 + cluster/pulumi/sv-canton/tsconfig.eslint.json | 1 + .../pulumi/sv-runbook/tsconfig.eslint.json | 1 + cluster/pulumi/sv/tsconfig.eslint.json | 1 + cluster/pulumi/tsconfig.eslint.json | 5 + .../validator-runbook/tsconfig.eslint.json | 1 + .../pulumi/validator1/tsconfig.eslint.json | 1 + 28 files changed, 421 insertions(+), 103 deletions(-) create mode 100644 cluster/pulumi/canton-network/tsconfig.eslint.json create mode 100644 cluster/pulumi/circleci/tsconfig.eslint.json create mode 100644 cluster/pulumi/cluster/tsconfig.eslint.json create mode 100644 cluster/pulumi/common-sv/tsconfig.eslint.json create mode 100644 cluster/pulumi/common-validator/tsconfig.eslint.json create mode 100644 cluster/pulumi/common/tsconfig.eslint.json create mode 100644 cluster/pulumi/deployment/tsconfig.eslint.json create mode 100644 cluster/pulumi/gcp/tsconfig.eslint.json create mode 100644 cluster/pulumi/gha/tsconfig.eslint.json create mode 100644 cluster/pulumi/infra/tsconfig.eslint.json create mode 100644 cluster/pulumi/multi-validator/tsconfig.eslint.json create mode 100644 cluster/pulumi/observability/tsconfig.eslint.json create mode 100644 cluster/pulumi/operator/tsconfig.eslint.json create mode 100644 cluster/pulumi/policies/tsconfig.eslint.json create mode 100644 cluster/pulumi/splitwell/tsconfig.eslint.json create mode 100644 cluster/pulumi/sv-canton/tsconfig.eslint.json create mode 100644 cluster/pulumi/sv-runbook/tsconfig.eslint.json create mode 100644 cluster/pulumi/sv/tsconfig.eslint.json create mode 100644 cluster/pulumi/tsconfig.eslint.json create mode 100644 cluster/pulumi/validator-runbook/tsconfig.eslint.json create mode 100644 cluster/pulumi/validator1/tsconfig.eslint.json diff --git a/cluster/pulumi/canton-network/tsconfig.eslint.json b/cluster/pulumi/canton-network/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/canton-network/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/circleci/tsconfig.eslint.json b/cluster/pulumi/circleci/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/circleci/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/cluster/tsconfig.eslint.json b/cluster/pulumi/cluster/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/cluster/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/common-sv/tsconfig.eslint.json b/cluster/pulumi/common-sv/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/common-sv/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/common-validator/tsconfig.eslint.json b/cluster/pulumi/common-validator/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/common-validator/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/common/package.json b/cluster/pulumi/common/package.json index c8891effcc..3caf421b9f 100644 --- a/cluster/pulumi/common/package.json +++ b/cluster/pulumi/common/package.json @@ -35,7 +35,7 @@ "devDependencies": { "@jest/globals": "^30.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.17.24", + "@types/lodash": "^4.17.25", "@types/ws": "^8.18.1", "dedent": "^1.7.2" } diff --git a/cluster/pulumi/common/src/postgres.ts b/cluster/pulumi/common/src/postgres.ts index 5e63603a9f..7c8e3a7bd1 100644 --- a/cluster/pulumi/common/src/postgres.ts +++ b/cluster/pulumi/common/src/postgres.ts @@ -515,8 +515,7 @@ export class SplicePostgres extends pulumi.ComponentResource implements Postgres instanceName: string, installPassword: (parent: Resource) => k8s.core.v1.Secret, splicePostgresHelmMigrationConfig: - | SplicePostgresMigrateConfig - | SplicePostgresDockerImageConfig, + SplicePostgresMigrateConfig | SplicePostgresDockerImageConfig, values?: LegacyChartValues, overrideDbSizeFromValues?: boolean, disableProtection?: boolean, @@ -932,8 +931,7 @@ export function installSplicePostgres( instanceName, parent => installPasswordWithParent(parent, xns, instanceName, secretName), splicePostgresHelmMigrationConfig as - | SplicePostgresMigrateConfig - | SplicePostgresDockerImageConfig, + SplicePostgresMigrateConfig | SplicePostgresDockerImageConfig, chartValues, overrideDbSizeFromValues, opts.disableProtection, diff --git a/cluster/pulumi/common/src/serviceAccount.ts b/cluster/pulumi/common/src/serviceAccount.ts index 62803263c8..d23c386728 100644 --- a/cluster/pulumi/common/src/serviceAccount.ts +++ b/cluster/pulumi/common/src/serviceAccount.ts @@ -4,8 +4,7 @@ import * as gcp from '@pulumi/gcp'; import * as pulumi from '@pulumi/pulumi'; type Role = - | string - | { id: string; condition: { title: string; description: string; expression: string } }; + string | { id: string; condition: { title: string; description: string; expression: string } }; const roleToPulumiName = (role: Role): string => { if (typeof role === 'string') { diff --git a/cluster/pulumi/common/tsconfig.eslint.json b/cluster/pulumi/common/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/common/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/deployment/tsconfig.eslint.json b/cluster/pulumi/deployment/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/deployment/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/eslint.config.mjs b/cluster/pulumi/eslint.config.mjs index d5a0756535..8717187764 100644 --- a/cluster/pulumi/eslint.config.mjs +++ b/cluster/pulumi/eslint.config.mjs @@ -32,7 +32,7 @@ export default defineConfig([{ sourceType: "script", parserOptions: { - project: ["./tsconfig.json"], + project: ["./tsconfig.eslint.json"], }, }, diff --git a/cluster/pulumi/gcp/tsconfig.eslint.json b/cluster/pulumi/gcp/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/gcp/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/gha/tsconfig.eslint.json b/cluster/pulumi/gha/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/gha/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/infra/src/cloudArmor.ts b/cluster/pulumi/infra/src/cloudArmor.ts index 2ab7f7e286..82fda8ef65 100644 --- a/cluster/pulumi/infra/src/cloudArmor.ts +++ b/cluster/pulumi/infra/src/cloudArmor.ts @@ -76,7 +76,9 @@ export function configureCloudArmorPolicy( // Step 2: Add predefined WAF rules if (cac.predefinedWafRules && cac.predefinedWafRules.length > 0) { - addPredefinedWafRules(/*securityPolicy, args.predefinedWafRules, cac.allRulesPreviewOnly, ruleOpts*/); + addPredefinedWafRules( + /*securityPolicy, args.predefinedWafRules, cac.allRulesPreviewOnly, ruleOpts*/ + ); } // Step 3: Add IP whitelisting rules diff --git a/cluster/pulumi/infra/tsconfig.eslint.json b/cluster/pulumi/infra/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/infra/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/multi-validator/tsconfig.eslint.json b/cluster/pulumi/multi-validator/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/multi-validator/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/observability/tsconfig.eslint.json b/cluster/pulumi/observability/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/observability/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/operator/tsconfig.eslint.json b/cluster/pulumi/operator/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/operator/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/package-lock.json b/cluster/pulumi/package-lock.json index 829177c65a..8f668a304e 100644 --- a/cluster/pulumi/package-lock.json +++ b/cluster/pulumi/package-lock.json @@ -35,20 +35,20 @@ "commander": "^14.0.3" }, "devDependencies": { - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", "@jest/globals": "^30.4.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/request": "^2.48.13", - "@typescript-eslint/eslint-plugin": "^8.61.1", + "@typescript-eslint/eslint-plugin": "^8.65.0", "@typescript-eslint/parser": "^8.57.2", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-promise": "^7.3.0", "jest": "^30.4.2", - "minimatch": "10.2.5", - "prettier": "^3.8.4", - "ts-jest": "^29.4.11", + "minimatch": "10.2.6", + "prettier": "^3.9.6", + "ts-jest": "^29.4.12", "typescript": "^5.9.3" } }, @@ -108,7 +108,7 @@ "devDependencies": { "@jest/globals": "^30.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.17.24", + "@types/lodash": "^4.17.25", "@types/ws": "^8.18.1", "dedent": "^1.7.2" } @@ -1013,9 +1013,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -1025,7 +1025,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -1061,9 +1061,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { @@ -3581,9 +3581,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", + "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", "dev": true, "license": "MIT" }, @@ -3736,17 +3736,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", - "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/type-utils": "8.61.1", - "@typescript-eslint/utils": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3759,7 +3759,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.61.1", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -3775,16 +3775,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", - "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", + "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "engines": { @@ -3799,15 +3799,64 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-eslint/project-service": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", - "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.61.1", - "@typescript-eslint/types": "^8.61.1", + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "engines": { @@ -3822,14 +3871,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", - "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3839,10 +3888,24 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", - "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", "dev": true, "license": "MIT", "engines": { @@ -3857,15 +3920,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", - "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1", - "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3881,10 +3944,91 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/types": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", - "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", "dev": true, "license": "MIT", "engines": { @@ -3896,16 +4040,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", - "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.61.1", - "@typescript-eslint/tsconfig-utils": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3923,17 +4067,48 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", - "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3947,14 +4122,95 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", - "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3965,6 +4221,20 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", @@ -6146,6 +6416,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", @@ -8616,9 +8900,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8969,12 +9263,12 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -9974,9 +10268,9 @@ } }, "node_modules/prettier": { - "version": "3.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", - "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, "license": "MIT", "bin": { @@ -10425,9 +10719,9 @@ } }, "node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11230,9 +11524,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.11", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", - "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", + "version": "29.4.12", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.12.tgz", + "integrity": "sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==", "dev": true, "license": "MIT", "dependencies": { @@ -11242,7 +11536,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.8.0", + "semver": "^7.8.5", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, diff --git a/cluster/pulumi/package.json b/cluster/pulumi/package.json index f6e4c34262..1ac82f9ff8 100644 --- a/cluster/pulumi/package.json +++ b/cluster/pulumi/package.json @@ -3,20 +3,20 @@ "version": "1.0.0", "main": "src/index.ts", "devDependencies": { - "@eslint/eslintrc": "^3.3.5", + "@eslint/eslintrc": "^3.3.6", "@jest/globals": "^30.4.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/request": "^2.48.13", - "@typescript-eslint/eslint-plugin": "^8.61.1", + "@typescript-eslint/eslint-plugin": "^8.65.0", "@typescript-eslint/parser": "^8.57.2", - "@eslint/js": "9.39.4", + "@eslint/js": "9.39.5", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-promise": "^7.3.0", "jest": "^30.4.2", - "minimatch": "10.2.5", - "prettier": "^3.8.4", - "ts-jest": "^29.4.11", + "minimatch": "10.2.6", + "prettier": "^3.9.6", + "ts-jest": "^29.4.12", "typescript": "^5.9.3" }, "scripts": { diff --git a/cluster/pulumi/policies/tsconfig.eslint.json b/cluster/pulumi/policies/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/policies/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/splitwell/tsconfig.eslint.json b/cluster/pulumi/splitwell/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/splitwell/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/sv-canton/tsconfig.eslint.json b/cluster/pulumi/sv-canton/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/sv-canton/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/sv-runbook/tsconfig.eslint.json b/cluster/pulumi/sv-runbook/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/sv-runbook/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/sv/tsconfig.eslint.json b/cluster/pulumi/sv/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/sv/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/tsconfig.eslint.json b/cluster/pulumi/tsconfig.eslint.json new file mode 100644 index 0000000000..843ee98bc1 --- /dev/null +++ b/cluster/pulumi/tsconfig.eslint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["*.ts"], + "exclude": [] +} diff --git a/cluster/pulumi/validator-runbook/tsconfig.eslint.json b/cluster/pulumi/validator-runbook/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/validator-runbook/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/validator1/tsconfig.eslint.json b/cluster/pulumi/validator1/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/validator1/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} From 5e03485ececb8ac7d7b06ac6a858c5842c8720f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Fri, 7 Aug 2026 14:31:30 +0200 Subject: [PATCH 204/329] Close stream in TemplateJsonDecoder (#6705) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci] Signed-off-by: Oriol Muñoz --- .../splice/util/TemplateJsonDecoder.scala | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/TemplateJsonDecoder.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/TemplateJsonDecoder.scala index 19246976ec..71d6e2f8c0 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/TemplateJsonDecoder.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/TemplateJsonDecoder.scala @@ -18,7 +18,7 @@ import com.digitalasset.canton.ledger.api.util.LfEngineToApi import com.digitalasset.canton.logging.{ErrorLoggingContext, NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.util.ErrorUtil -import com.digitalasset.daml.lf.archive.{ArchivePayload, Dar, DarReader} +import com.digitalasset.daml.lf.archive.DarReader import com.digitalasset.daml.lf.data.Ref import com.digitalasset.daml.lf.data.Ref.{PackageId, QualifiedName} import com.digitalasset.daml.lf.typesig @@ -27,6 +27,7 @@ import io.circe.Json import org.lfdecentralizedtrust.splice.environment.DarResource import java.util.zip.ZipInputStream +import scala.util.Using abstract class TemplateJsonDecoder { def decodeTemplate[TCid <: ContractId[T], T]( @@ -148,12 +149,16 @@ object ResourceTemplateDecoder { if (inputStream == null) { throw new IllegalArgumentException("Resource not found: " + path) } - val dar: Dar[ArchivePayload] = DarReader - .readArchive(resource.path, new ZipInputStream(inputStream)) - .valueOr(e => - throw new IllegalArgumentException(s"Failed to read DAR at path ${resource.path}: $e") - ) - dar.all.map(a => a.pkgId -> typesig.reader.SignatureReader.readPackageSignature(a)._2).toMap + Using(new ZipInputStream(inputStream)) { zip => + DarReader + .readArchive(resource.path, zip) + .valueOr(e => + throw new IllegalArgumentException(s"Failed to read DAR at path ${resource.path}: $e") + ) + }.fold( + e => throw e, + _.all.map(a => a.pkgId -> typesig.reader.SignatureReader.readPackageSignature(a)._2).toMap, + ) }, ) } From ed1beff168c5aaaffc74f60553a690828f4a3c39 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Fri, 7 Aug 2026 15:29:43 +0200 Subject: [PATCH 205/329] Upgrade Canton to 3.5.12 (#6712) [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index fe8557521c..6532a840ed 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.12-snapshot.20260804.19139.0.vbd8b06e0", - "oss_sha256": "sha256:18vymy7ph3lddabaxxv0l8fq470kvvf6mmd6bnhklrlyc9ypddx2", - "canton_base_image_sha256": "sha256:beb89710fc11d302fe0bcc3af8262b72d1c2f9d5ea7484c7c471abe3048a8377", - "canton_participant_image_sha256": "sha256:3861e1c8bafd3cc3b9df466da256a01d86c8fd9f579e014d6fa717d2b3f1110f", - "canton_mediator_image_sha256": "sha256:2c3147ca302838d539f48b4b5d13af510ed097ad9d77c297dac6de3f0410ce50", - "canton_sequencer_image_sha256": "sha256:9f9039668d60c115f9385f012a8a033badf7dbbd7dda596f7d30d53d3a72e175" + "version": "3.5.12", + "oss_sha256": "sha256:03cz2bprd43g721q6j324lfhrkm7v10cslwnwcvm34zm9jqfk2ds", + "canton_base_image_sha256": "sha256:218c4e6ea4a38f4ade82772197f6901d37169af6c2cb1a4c8e47277bbbbe114c", + "canton_participant_image_sha256": "sha256:1950cf9c465b08f52952febcb87cb0393afa7fcb9ea8eab2d9356e3f83028840", + "canton_mediator_image_sha256": "sha256:67504bed9cf6ecbbf50180024a24e6252479c65649ce84962a7afec24f7ce607", + "canton_sequencer_image_sha256": "sha256:359fd1728f8cc174f2c53132a982f4118a4c8aee1d3625c10447091258bce1b9" } From 7461c3974cd90037f6762a9e6ca07e1d46da9d26 Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Fri, 7 Aug 2026 16:46:30 +0200 Subject: [PATCH 206/329] Clear Release Notes (#6721) [static] Signed-off-by: Pasindu Tennage Signed-off-by: pasindutennage-da --- docs/src/release_notes_upcoming.rst | 34 ----------------------------- 1 file changed, 34 deletions(-) diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 15c3bc9926..7488c245e7 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -7,37 +7,3 @@ .. release-notes:: Upcoming - - Wallet app - - - Duplicate wallet operations submitted with the same command id (e.g. tap, transfer, - token standard transfers) now return the original result idempotently instead of HTTP 409. - This aligns with standard idempotency-key semantics: a second request with a previously - accepted command id receives a 200 response with the same result as the first. - Concurrent duplicates, where no submission has completed yet, are still rejected. - - - ``TransferPreapprovalProposal`` s are now accepted if there is an existing one but it has expired. - - - CantonBft - - - Increase the default segment length by 4x to reduce performance impact from epoch switches. - - - Validator App - - - Added a ``type`` parameter to validator config's ``reward-sharing-config-by-party`` option. - - When this is set to ``external``, it indicates that the assignment of reward coupons to beneficiaries is being managed by a process external to the validator app, and thus the validator app's automation does not assign or mint the unassigned coupons. - - The ``type`` defaults to ``built-in`` preserving the existing behavior where the validator app will either mint the unassigned rewards coupons, or assign them to beneficiaries if configured. - - See the reward-sharing documentation for details: - https://docs.canton.network/global-synchronizer/splice-fundamentals/reward-sharing#reward-sharing - - Example enabling external sharing automation for a party:: - - canton.validator-apps..reward-sharing-config-by-party = { - "" = { - type = "external" - # Optionally batch-size may be specified to configure the maximum number of coupons to mint in a single transaction - batch-size = 80 - } - } From 4e9d4af555ec63343e642f1dafdcb7c6662e790b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Fri, 7 Aug 2026 16:59:01 +0200 Subject: [PATCH 207/329] implement split SV deployment migration procedure (#6655) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mateusz Błażejewski --- cluster/deployment/mock/config.yaml | 1 + cluster/expected/canton-network/expected.json | 6568 +--------------- cluster/expected/gcp/expected.json | 168 +- cluster/expected/infra/expected.json | 123 +- .../expected/multi-validator/expected.json | 2 +- cluster/expected/operator/expected.json | 2 +- cluster/expected/splitwell/expected.json | 2 +- cluster/expected/sv-canton/expected.json | 36 +- cluster/expected/sv-runbook/expected.json | 4 +- cluster/expected/sv/expected.json | 6762 ++++++++++++++++- .../expected/validator-runbook/expected.json | 4 +- cluster/expected/validator1/expected.json | 2 +- cluster/pulumi/canton-network/src/dso.ts | 11 +- cluster/pulumi/canton-network/src/index.ts | 20 +- .../canton-network/src/installCluster.ts | 75 +- cluster/pulumi/common-sv/src/bigQuery.ts | 205 +- cluster/pulumi/common-sv/src/sv.ts | 372 +- .../common/src/config/migrationSchema.ts | 2 + .../pulumi/common/src/dump-config-common.ts | 40 +- cluster/pulumi/common/src/postgres.ts | 22 +- cluster/pulumi/common/src/stackReferences.ts | 8 + cluster/pulumi/sv/src/installNode.ts | 36 +- 22 files changed, 7227 insertions(+), 7238 deletions(-) diff --git a/cluster/deployment/mock/config.yaml b/cluster/deployment/mock/config.yaml index ca04106a6d..048f18524d 100644 --- a/cluster/deployment/mock/config.yaml +++ b/cluster/deployment/mock/config.yaml @@ -53,6 +53,7 @@ splitwell: maxDuration: "5m" retention: "30d" synchronizerMigration: + splitSvDeploymentEnabled: true frozenMigrationId: 2 archived: - id: 5 diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index 35f2629ae5..a08db70466 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -1,70 +1,4 @@ [ - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "postgresPassword": "" - } - }, - "kind": "Secret", - "metadata": { - "name": "cn-apps-pg-secrets", - "namespace": "sv-1" - }, - "type": "Opaque" - }, - "name": "cn-app-sv-1-cn-apps-pg-secrets", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "json-credentials": "eyJidWNrZXROYW1lIjoiZGF0YS1leHBvcnQtYnVja2V0LW5hbWUiLCJzZWNyZXROYW1lIjoiZGF0YS1leHBvcnQtYnVja2V0LXNhLWtleS1zZWNyZXQiLCJqc29uQ3JlZGVudGlhbHMiOiJkYXRhLWV4cG9ydC1idWNrZXQtc2Eta2V5LXNlY3JldC1jcmVkcyJ9" - } - }, - "kind": "Secret", - "metadata": { - "name": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps", - "namespace": "sv-1" - }, - "type": "Opaque" - }, - "name": "cn-app-sv-1-cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "json-credentials": "eyJwcm9qZWN0SWQiOiJkYS1jbi1zaGFyZWQiLCJidWNrZXROYW1lIjoidG9wb2xvZ3ktc25hcHNob3QtYnVja2V0LW5hbWUiLCJzZWNyZXROYW1lIjoiZ2NwLXRvcG9sb2d5LXNuYXBzaG90LWJ1Y2tldC1zYS1rZXktc2VjcmV0IiwianNvbkNyZWRlbnRpYWxzIjoidG9wb2xvZ3ktc25hcHNob3QtYnVja2V0LXNhLWtleS1zZWNyZXQtY3JlZHMiLCJidWNrZXRTYUtleVNlY3JldCI6ImdjcC10b3BvbG9neS1zbmFwc2hvdC1idWNrZXQtc2Eta2V5LWV4YW1wbGUiLCJidWNrZXRTYUlhbUFjY291bnQiOiJkYS1jbi1leGFtcGxldEBkYS1jbi1zaGFyZWQuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20ifQ==" - } - }, - "kind": "Secret", - "metadata": { - "name": "cn-gcp-bucket-da-cn-shared-cn-topology-snapshots", - "namespace": "sv-1" - }, - "type": "Opaque" - }, - "name": "cn-app-sv-1-cn-gcp-bucket-da-cn-shared-cn-topology-snapshots", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, { "custom": true, "id": "", @@ -87,73 +21,6 @@ "provider": "", "type": "kubernetes:core/v1:Secret" }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "postgresPassword": "" - } - }, - "kind": "Secret", - "metadata": { - "name": "cn-apps-pg-secrets", - "namespace": "sv-da-1" - }, - "type": "Opaque" - }, - "name": "cn-app-sv-da-1-cn-apps-pg-secrets", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "json-credentials": "eyJidWNrZXROYW1lIjoiZGF0YS1leHBvcnQtYnVja2V0LW5hbWUiLCJzZWNyZXROYW1lIjoiZGF0YS1leHBvcnQtYnVja2V0LXNhLWtleS1zZWNyZXQiLCJqc29uQ3JlZGVudGlhbHMiOiJkYXRhLWV4cG9ydC1idWNrZXQtc2Eta2V5LXNlY3JldC1jcmVkcyJ9" - } - }, - "kind": "Secret", - "metadata": { - "name": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps", - "namespace": "sv-da-1" - }, - "type": "Opaque" - }, - "name": "cn-app-sv-da-1-cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "private": "c3ZkYTEtbW9jay1pZC1wcml2YXRlLWtleQ==", - "public": "c3ZkYTEtbW9jay1pZC1wdWJsaWMta2V5" - } - }, - "kind": "Secret", - "metadata": { - "name": "cn-app-sv-key", - "namespace": "sv-da-1" - }, - "type": "Opaque" - }, - "name": "cn-app-sv-da-1-key", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, { "custom": true, "id": "", @@ -183,7 +50,7 @@ } }, "name": "docs-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-docs-default::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-docs-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -281,6328 +148,243 @@ "type": "kubernetes:core/v1:Namespace" }, { - "custom": false, + "custom": true, "id": "", "inputs": { - "auth0Client": {}, - "decentralizedSynchronizerUpgradeConfig": { - "active": { - "id": 9, - "releaseReference": { - "deploymentDir": "cluster/deployment", - "gitReference": "refs/heads/release-line-0.3.20", - "privateConfigsDir": "cluster/configs/configs-private", - "publicConfigsDir": "cluster/configs/configs", - "pulumiBaseDir": "splice/cluster/pulumi", - "pulumiStacksDir": "cluster/stacks/prod", - "repoUrl": "https://github.com/canton-network/splice", - "spliceRoot": "splice" - }, - "sequencer": { - "dedicatedBftSequencerDb": true, - "enableBftSequencer": true - }, - "version": { - "type": "remote", - "version": "0.3.20" - } - }, - "additionalLegacy": [ - { - "id": 7, - "releaseReference": { - "deploymentDir": "cluster/deployment", - "gitReference": "refs/heads/release-line-0.3.20", - "privateConfigsDir": "cluster/configs/configs-private", - "publicConfigsDir": "cluster/configs/configs", - "pulumiBaseDir": "splice/cluster/pulumi", - "pulumiStacksDir": "cluster/stacks/prod", - "repoUrl": "https://github.com/canton-network/splice", - "spliceRoot": "splice" - }, - "sequencer": { - "dedicatedBftSequencerDb": true, - "enableBftSequencer": false - }, - "version": { - "type": "remote", - "version": "0.3.20" - } - } - ], - "archived": [ - { - "id": 5, - "releaseReference": { - "deploymentDir": "cluster/deployment", - "gitReference": "981227e62011e386cca3cdb5f00b472cf05a12c4", - "pulumiBaseDir": "splice/cluster/pulumi", - "pulumiStacksDir": "cluster/stacks/prod", - "repoUrl": "https://github.com/canton-network/splice", - "spliceRoot": "splice" - }, - "sequencer": { - "dedicatedBftSequencerDb": true, - "enableBftSequencer": false - }, - "version": { - "type": "remote", - "version": "0.3.18" - } - }, - { - "id": 6, - "releaseReference": { - "deploymentDir": "cluster/deployment", - "gitReference": "refs/heads/release-line-0.3.19", - "pulumiBaseDir": "splice/cluster/pulumi", - "pulumiStacksDir": "cluster/stacks/prod", - "repoUrl": "https://github.com/canton-network/splice", - "spliceRoot": "splice" - }, - "sequencer": { - "dedicatedBftSequencerDb": true, - "enableBftSequencer": false - }, - "version": { - "type": "remote", - "version": "0.3.19" - } - } - ], - "frozenMigrationId": 2, - "legacy": { - "id": 8, - "releaseReference": { - "deploymentDir": "cluster/deployment", - "gitReference": "refs/heads/release-line-0.3.20", - "privateConfigsDir": "cluster/configs/configs-private", - "publicConfigsDir": "cluster/configs/configs", - "pulumiBaseDir": "splice/cluster/pulumi", - "pulumiStacksDir": "cluster/stacks/prod", - "repoUrl": "https://github.com/canton-network/splice", - "spliceRoot": "splice" - }, - "sequencer": { - "dedicatedBftSequencerDb": true, - "enableBftSequencer": false - }, - "version": { - "type": "remote", - "version": "0.3.20" - } - }, - "upgrade": { - "id": 10, - "releaseReference": { - "deploymentDir": "cluster/deployment", - "gitReference": "refs/heads/release-line-0.3.21", - "privateConfigsDir": "cluster/configs/configs-private", - "publicConfigsDir": "cluster/configs/configs", - "pulumiBaseDir": "splice/cluster/pulumi", - "pulumiStacksDir": "cluster/stacks/prod", - "repoUrl": "https://github.com/canton-network/splice", - "spliceRoot": "splice" - }, - "sequencer": { - "dedicatedBftSequencerDb": true, - "enableBftSequencer": true - }, - "version": { - "type": "remote", - "version": "0.3.21" - } - } - } + "enableServerSideApply": "true" }, - "name": "dso", + "name": "k8s-imgpull-docs-default", "provider": "", - "type": "canton:network:dso" + "type": "pulumi:providers:kubernetes" }, { "custom": true, "id": "", "inputs": { - "enableServerSideApply": "true" + "datasetId": "mock_da2_scan", + "deleteContentsOnDestroy": true, + "friendlyName": "mock_da2_scan Dataset", + "labels": { + "cluster": "mock" + }, + "location": "europe-west6" }, - "name": "k8s-imgpull-docs-default", + "name": "mock_da2_scan", "provider": "", - "type": "pulumi:providers:kubernetes" + "type": "gcp:bigquery/dataset:Dataset" + }, + { + "custom": true, + "id": "organization/infra/infra.mock", + "inputs": { + "name": "organization/infra/infra.mock" + }, + "name": "organization/infra/infra.mock", + "provider": "", + "type": "pulumi:pulumi:StackReference" }, { "custom": true, "id": "", "inputs": { - "enableServerSideApply": "true" + "create": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' create-pub-rep-slot \\\n --private-network-project=\"test-project\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n ", + "delete": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' delete-pub-rep-slot \\\n --private-network-project=\"test-project\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n " }, - "name": "k8s-imgpull-sv-1-default", + "name": "sv-1-bqdatastream-pub-replicate-slots", "provider": "", - "type": "pulumi:providers:kubernetes" + "type": "command:local:Command" + }, + { + "custom": true, + "id": "sv-1-cn-apps-pg-7ca4614", + "inputs": {}, + "name": "sv-1-cn-apps-pg", + "provider": "", + "type": "gcp:sql/databaseInstance:DatabaseInstance" }, { "custom": true, "id": "", "inputs": { - "enableServerSideApply": "true" + "allows": [ + { + "ports": [ + "5432" + ], + "protocol": "tcp" + } + ], + "destinationRanges": [ + null + ], + "direction": "INGRESS", + "name": "sv-1-datastream-to-nat", + "network": "default", + "priority": 42, + "sourceRanges": [ + "1.2.3.16/29" + ] }, - "name": "k8s-imgpull-sv-da-1-default", + "name": "sv-1-datastream-to-nat", "provider": "", - "type": "pulumi:providers:kubernetes" + "type": "gcp:compute/firewall:Firewall" }, { "custom": true, "id": "", "inputs": { - "apiVersion": "networking.istio.io/v1alpha3", - "kind": "VirtualService", + "bootDisk": { + "initializeParams": { + "image": "debian-cloud/debian-12" + } + }, + "labels": { + "cluster": "mock" + }, + "machineType": "e2-micro", "metadata": { - "name": "cometbft-loopback", - "namespace": "sv-1" + "enable-osconfig": "TRUE", + "enable-oslogin": "true", + "startup-script": "#! /bin/bash\n\nexport DB_ADDR=undefined\nexport DB_PORT=5432\n\n# Enable the VM to receive packets whose destinations do\n# not match any running process local to the VM\necho 1 > /proc/sys/net/ipv4/ip_forward\n\n# Ask the Metadata server for the IP address of the VM nic0\n# network interface:\nmd_url_prefix=\"http://169.254.169.254/computeMetadata/v1/instance\"\nvm_nic_ip=\"$(curl -H \"Metadata-Flavor: Google\" $md_url_prefix/network-interfaces/0/ip)\"\n\n# Clear any existing iptables NAT table entries (all chains):\niptables -t nat -F\n\n# Create a NAT table entry in the prerouting chain, matching\n# any packets with destination database port, changing the destination\n# IP address of the packet to the SQL instance IP address:\niptables -t nat -A PREROUTING \\\n -p tcp --dport $DB_PORT \\\n -j DNAT \\\n --to-destination $DB_ADDR\n\n# Create a NAT table entry in the postrouting chain, matching\n# any packets with destination database port, changing the source IP\n# address of the packet to the NAT VM's primary internal IPv4 address:\niptables -t nat -A POSTROUTING \\\n -p tcp --dport $DB_PORT \\\n -j SNAT \\\n --to-source $vm_nic_ip\n\n# Save iptables configuration:\niptables-save\n" }, - "spec": { - "exportTo": [ - "." - ], - "gateways": [ - "mesh" - ], - "hosts": [ - "mock.global.canton.network.digitalasset.com" - ], - "tcp": [ - { - "match": [ - { - "gateways": [ - "mesh" - ] - } - ], - "route": [ - { - "destination": { - "host": "istio-ingress-cometbft.cluster-ingress.svc.cluster.local" - } - } - ] - } - ] - } + "networkInterfaces": [ + { + "accessConfigs": [ + {} + ], + "network": "default" + } + ], + "zone": "europe-west6-a" }, - "name": "loopback-cometbft-sv-1", + "name": "sv-1-nat-vm", "provider": "", - "type": "kubernetes:networking.istio.io/v1alpha3:VirtualService" + "type": "gcp:compute/instance:Instance" }, { "custom": true, "id": "", "inputs": { - "apiVersion": "networking.istio.io/v1alpha3", - "kind": "VirtualService", - "metadata": { - "name": "cometbft-loopback", - "namespace": "sv-da-1" + "bigqueryProfile": {}, + "connectionProfileId": "sv-1-scan-bq-cxn", + "displayName": "sv-1-scan-bq-cxn", + "labels": { + "cluster": "mock" }, - "spec": { - "exportTo": [ - "." - ], - "gateways": [ - "mesh" - ], - "hosts": [ - "mock.global.canton.network.digitalasset.com" - ], - "tcp": [ - { - "match": [ - { - "gateways": [ - "mesh" - ] - } - ], - "route": [ - { - "destination": { - "host": "istio-ingress-cometbft.cluster-ingress.svc.cluster.local" - } - } - ] - } - ] - } + "location": "europe-west6" }, - "name": "loopback-cometbft-sv-da-1", + "name": "sv-1-scan-bq-cxn", "provider": "", - "type": "kubernetes:networking.istio.io/v1alpha3:VirtualService" + "type": "gcp:datastream/connectionProfile:ConnectionProfile" }, { "custom": true, "id": "", "inputs": { - "apiVersion": "networking.istio.io/v1alpha3", - "kind": "ServiceEntry", - "metadata": { - "name": "loopback", - "namespace": "sv-1" + "connectionProfileId": "sv-1-scan-update-history-cxn", + "displayName": "sv-1-scan-update-history-cxn", + "labels": { + "cluster": "mock" }, - "spec": { - "exportTo": [ - "." - ], - "hosts": [ - "mock.global.canton.network.digitalasset.com" - ], - "ports": [ - { - "name": "http-port", - "number": 80, - "protocol": "HTTP" - }, - { - "name": "tls", - "number": 443, - "protocol": "TLS" - }, - { - "name": "grpc-domain", - "number": 5008, - "protocol": "GRPC" - }, - { - "name": "cometbft-0-0-p2p", - "number": 26006, - "protocol": "TCP" - }, - { - "name": "cometbft-0-1-p2p", - "number": 26016, - "protocol": "TCP" - }, - { - "name": "cometbft-0-2-p2p", - "number": 26026, - "protocol": "TCP" - }, - { - "name": "cometbft-1-0-p2p", - "number": 26106, - "protocol": "TCP" - }, - { - "name": "cometbft-1-1-p2p", - "number": 26116, - "protocol": "TCP" - }, - { - "name": "cometbft-1-2-p2p", - "number": 26126, - "protocol": "TCP" - }, - { - "name": "cometbft-2-0-p2p", - "number": 26206, - "protocol": "TCP" - }, - { - "name": "cometbft-2-1-p2p", - "number": 26216, - "protocol": "TCP" - }, - { - "name": "cometbft-2-2-p2p", - "number": 26226, - "protocol": "TCP" - }, - { - "name": "cometbft-3-0-p2p", - "number": 26306, - "protocol": "TCP" - }, - { - "name": "cometbft-3-1-p2p", - "number": 26316, - "protocol": "TCP" - }, - { - "name": "cometbft-3-2-p2p", - "number": 26326, - "protocol": "TCP" - }, - { - "name": "cometbft-4-0-p2p", - "number": 26406, - "protocol": "TCP" - }, - { - "name": "cometbft-4-1-p2p", - "number": 26416, - "protocol": "TCP" - }, - { - "name": "cometbft-4-2-p2p", - "number": 26426, - "protocol": "TCP" - }, - { - "name": "cometbft-5-0-p2p", - "number": 26506, - "protocol": "TCP" - }, - { - "name": "cometbft-5-1-p2p", - "number": 26516, - "protocol": "TCP" - }, - { - "name": "cometbft-5-2-p2p", - "number": 26526, - "protocol": "TCP" - }, - { - "name": "cometbft-6-0-p2p", - "number": 26606, - "protocol": "TCP" - }, - { - "name": "cometbft-6-1-p2p", - "number": 26616, - "protocol": "TCP" - }, - { - "name": "cometbft-6-2-p2p", - "number": 26626, - "protocol": "TCP" - }, - { - "name": "cometbft-7-0-p2p", - "number": 26706, - "protocol": "TCP" - }, - { - "name": "cometbft-7-1-p2p", - "number": 26716, - "protocol": "TCP" - }, - { - "name": "cometbft-7-2-p2p", - "number": 26726, - "protocol": "TCP" - }, - { - "name": "cometbft-8-0-p2p", - "number": 26806, - "protocol": "TCP" - }, - { - "name": "cometbft-8-1-p2p", - "number": 26816, - "protocol": "TCP" - }, - { - "name": "cometbft-8-2-p2p", - "number": 26826, - "protocol": "TCP" - }, - { - "name": "cometbft-9-0-p2p", - "number": 26906, - "protocol": "TCP" - }, - { - "name": "cometbft-9-1-p2p", - "number": 26916, - "protocol": "TCP" - }, - { - "name": "cometbft-9-2-p2p", - "number": 26926, - "protocol": "TCP" - } - ], - "resolution": "DNS" - } - }, - "name": "loopback-service-entry-sv-1", - "provider": "", - "type": "kubernetes:networking.istio.io/v1alpha3:ServiceEntry" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "networking.istio.io/v1alpha3", - "kind": "ServiceEntry", - "metadata": { - "name": "loopback", - "namespace": "sv-da-1" - }, - "spec": { - "exportTo": [ - "." - ], - "hosts": [ - "mock.global.canton.network.digitalasset.com" - ], - "ports": [ - { - "name": "http-port", - "number": 80, - "protocol": "HTTP" - }, - { - "name": "tls", - "number": 443, - "protocol": "TLS" - }, - { - "name": "grpc-domain", - "number": 5008, - "protocol": "GRPC" - }, - { - "name": "cometbft-0-0-p2p", - "number": 26006, - "protocol": "TCP" - }, - { - "name": "cometbft-0-1-p2p", - "number": 26016, - "protocol": "TCP" - }, - { - "name": "cometbft-0-2-p2p", - "number": 26026, - "protocol": "TCP" - }, - { - "name": "cometbft-1-0-p2p", - "number": 26106, - "protocol": "TCP" - }, - { - "name": "cometbft-1-1-p2p", - "number": 26116, - "protocol": "TCP" - }, - { - "name": "cometbft-1-2-p2p", - "number": 26126, - "protocol": "TCP" - }, - { - "name": "cometbft-2-0-p2p", - "number": 26206, - "protocol": "TCP" - }, - { - "name": "cometbft-2-1-p2p", - "number": 26216, - "protocol": "TCP" - }, - { - "name": "cometbft-2-2-p2p", - "number": 26226, - "protocol": "TCP" - }, - { - "name": "cometbft-3-0-p2p", - "number": 26306, - "protocol": "TCP" - }, - { - "name": "cometbft-3-1-p2p", - "number": 26316, - "protocol": "TCP" - }, - { - "name": "cometbft-3-2-p2p", - "number": 26326, - "protocol": "TCP" - }, - { - "name": "cometbft-4-0-p2p", - "number": 26406, - "protocol": "TCP" - }, - { - "name": "cometbft-4-1-p2p", - "number": 26416, - "protocol": "TCP" - }, - { - "name": "cometbft-4-2-p2p", - "number": 26426, - "protocol": "TCP" - }, - { - "name": "cometbft-5-0-p2p", - "number": 26506, - "protocol": "TCP" - }, - { - "name": "cometbft-5-1-p2p", - "number": 26516, - "protocol": "TCP" - }, - { - "name": "cometbft-5-2-p2p", - "number": 26526, - "protocol": "TCP" - }, - { - "name": "cometbft-6-0-p2p", - "number": 26606, - "protocol": "TCP" - }, - { - "name": "cometbft-6-1-p2p", - "number": 26616, - "protocol": "TCP" - }, - { - "name": "cometbft-6-2-p2p", - "number": 26626, - "protocol": "TCP" - }, - { - "name": "cometbft-7-0-p2p", - "number": 26706, - "protocol": "TCP" - }, - { - "name": "cometbft-7-1-p2p", - "number": 26716, - "protocol": "TCP" - }, - { - "name": "cometbft-7-2-p2p", - "number": 26726, - "protocol": "TCP" - }, - { - "name": "cometbft-8-0-p2p", - "number": 26806, - "protocol": "TCP" - }, - { - "name": "cometbft-8-1-p2p", - "number": 26816, - "protocol": "TCP" - }, - { - "name": "cometbft-8-2-p2p", - "number": 26826, - "protocol": "TCP" - }, - { - "name": "cometbft-9-0-p2p", - "number": 26906, - "protocol": "TCP" - }, - { - "name": "cometbft-9-1-p2p", - "number": 26916, - "protocol": "TCP" - }, - { - "name": "cometbft-9-2-p2p", - "number": 26926, - "protocol": "TCP" - } - ], - "resolution": "DNS" - } - }, - "name": "loopback-service-entry-sv-da-1", - "provider": "", - "type": "kubernetes:networking.istio.io/v1alpha3:ServiceEntry" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "networking.istio.io/v1alpha3", - "kind": "VirtualService", - "metadata": { - "name": "direct-loopback-through-ingress-gateway", - "namespace": "sv-1" - }, - "spec": { - "exportTo": [ - "." - ], - "gateways": [ - "mesh" - ], - "hosts": [ - "mock.global.canton.network.digitalasset.com", - "validator.mock.global.canton.network.digitalasset.com", - "*.validator.mock.global.canton.network.digitalasset.com", - "validator1.mock.global.canton.network.digitalasset.com", - "*.validator1.mock.global.canton.network.digitalasset.com", - "splitwell.mock.global.canton.network.digitalasset.com", - "*.splitwell.mock.global.canton.network.digitalasset.com", - "sv-2.mock.global.canton.network.digitalasset.com", - "*.sv-2.mock.global.canton.network.digitalasset.com", - "sv-1.mock.global.canton.network.digitalasset.com", - "*.sv-1.mock.global.canton.network.digitalasset.com", - "sv.mock.global.canton.network.digitalasset.com", - "*.sv.mock.global.canton.network.digitalasset.com" - ], - "http": [ - { - "match": [ - { - "gateways": [ - "mesh" - ] - } - ], - "route": [ - { - "destination": { - "host": "istio-ingress.cluster-ingress.svc.cluster.local" - } - } - ] - } - ], - "tls": [ - { - "match": [ - { - "gateways": [ - "mesh" - ], - "sniHosts": [ - "mock.global.canton.network.digitalasset.com", - "validator.mock.global.canton.network.digitalasset.com", - "*.validator.mock.global.canton.network.digitalasset.com", - "validator1.mock.global.canton.network.digitalasset.com", - "*.validator1.mock.global.canton.network.digitalasset.com", - "splitwell.mock.global.canton.network.digitalasset.com", - "*.splitwell.mock.global.canton.network.digitalasset.com", - "sv-2.mock.global.canton.network.digitalasset.com", - "*.sv-2.mock.global.canton.network.digitalasset.com", - "sv-1.mock.global.canton.network.digitalasset.com", - "*.sv-1.mock.global.canton.network.digitalasset.com", - "sv.mock.global.canton.network.digitalasset.com", - "*.sv.mock.global.canton.network.digitalasset.com" - ] - } - ], - "route": [ - { - "destination": { - "host": "istio-ingress.cluster-ingress.svc.cluster.local" - } - } - ] - } - ] - } - }, - "name": "loopback-virtual-service-sv-1", - "provider": "", - "type": "kubernetes:networking.istio.io/v1alpha3:VirtualService" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "networking.istio.io/v1alpha3", - "kind": "VirtualService", - "metadata": { - "name": "direct-loopback-through-ingress-gateway", - "namespace": "sv-da-1" - }, - "spec": { - "exportTo": [ - "." - ], - "gateways": [ - "mesh" - ], - "hosts": [ - "mock.global.canton.network.digitalasset.com", - "validator.mock.global.canton.network.digitalasset.com", - "*.validator.mock.global.canton.network.digitalasset.com", - "validator1.mock.global.canton.network.digitalasset.com", - "*.validator1.mock.global.canton.network.digitalasset.com", - "splitwell.mock.global.canton.network.digitalasset.com", - "*.splitwell.mock.global.canton.network.digitalasset.com", - "sv-2.mock.global.canton.network.digitalasset.com", - "*.sv-2.mock.global.canton.network.digitalasset.com", - "sv-1.mock.global.canton.network.digitalasset.com", - "*.sv-1.mock.global.canton.network.digitalasset.com", - "sv.mock.global.canton.network.digitalasset.com", - "*.sv.mock.global.canton.network.digitalasset.com" - ], - "http": [ - { - "match": [ - { - "gateways": [ - "mesh" - ] - } - ], - "route": [ - { - "destination": { - "host": "istio-ingress.cluster-ingress.svc.cluster.local" - } - } - ] - } - ], - "tls": [ - { - "match": [ - { - "gateways": [ - "mesh" - ], - "sniHosts": [ - "mock.global.canton.network.digitalasset.com", - "validator.mock.global.canton.network.digitalasset.com", - "*.validator.mock.global.canton.network.digitalasset.com", - "validator1.mock.global.canton.network.digitalasset.com", - "*.validator1.mock.global.canton.network.digitalasset.com", - "splitwell.mock.global.canton.network.digitalasset.com", - "*.splitwell.mock.global.canton.network.digitalasset.com", - "sv-2.mock.global.canton.network.digitalasset.com", - "*.sv-2.mock.global.canton.network.digitalasset.com", - "sv-1.mock.global.canton.network.digitalasset.com", - "*.sv-1.mock.global.canton.network.digitalasset.com", - "sv.mock.global.canton.network.digitalasset.com", - "*.sv.mock.global.canton.network.digitalasset.com" - ] - } - ], - "route": [ - { - "destination": { - "host": "istio-ingress.cluster-ingress.svc.cluster.local" - } - } - ] - } - ] - } - }, - "name": "loopback-virtual-service-sv-da-1", - "provider": "", - "type": "kubernetes:networking.istio.io/v1alpha3:VirtualService" - }, - { - "custom": true, - "id": "", - "inputs": { - "bucket": "mock-sv-1-bulk-committed", - "member": "serviceAccount:undefined", - "role": "roles/storage.objectCreator" - }, - "name": "mock-sv-1-bulk-committed-sa-role-creator", - "provider": "", - "type": "gcp:storage/bucketIAMMember:BucketIAMMember" - }, - { - "custom": true, - "id": "", - "inputs": { - "bucket": "mock-sv-1-bulk-committed", - "member": "serviceAccount:undefined", - "role": "roles/storage.objectViewer" - }, - "name": "mock-sv-1-bulk-committed-sa-role-reader", - "provider": "", - "type": "gcp:storage/bucketIAMMember:BucketIAMMember" - }, - { - "custom": true, - "id": "", - "inputs": { - "location": "europe-west6", - "name": "mock-sv-1-bulk-committed" - }, - "name": "mock-sv-1-bulk-committed", - "provider": "", - "type": "gcp:storage/bucket:Bucket" - }, - { - "custom": true, - "id": "", - "inputs": {}, - "name": "mock-sv-1-bulk-sa-hmac", - "provider": "", - "type": "gcp:storage/hmacKey:HmacKey" - }, - { - "custom": true, - "id": "", - "inputs": { - "accountId": "mock-sv-1-bulk-sa", - "displayName": "Service Account for Bulk-Storage Bucket Read/Write Access" - }, - "name": "mock-sv-1-bulk-sa", - "provider": "", - "type": "gcp:serviceaccount/account:Account" - }, - { - "custom": true, - "id": "", - "inputs": { - "bucket": "mock-sv-1-bulk-staging", - "member": "serviceAccount:undefined", - "role": "roles/storage.objectUser" - }, - "name": "mock-sv-1-bulk-staging-sa-role", - "provider": "", - "type": "gcp:storage/bucketIAMMember:BucketIAMMember" - }, - { - "custom": true, - "id": "", - "inputs": { - "location": "europe-west6", - "name": "mock-sv-1-bulk-staging" - }, - "name": "mock-sv-1-bulk-staging", - "provider": "", - "type": "gcp:storage/bucket:Bucket" - }, - { - "custom": true, - "id": "", - "inputs": { - "bucket": "mock-sv-da-1-bulk-committed", - "member": "serviceAccount:undefined", - "role": "roles/storage.objectCreator" - }, - "name": "mock-sv-da-1-bulk-committed-sa-role-creator", - "provider": "", - "type": "gcp:storage/bucketIAMMember:BucketIAMMember" - }, - { - "custom": true, - "id": "", - "inputs": { - "bucket": "mock-sv-da-1-bulk-committed", - "member": "serviceAccount:undefined", - "role": "roles/storage.objectViewer" - }, - "name": "mock-sv-da-1-bulk-committed-sa-role-reader", - "provider": "", - "type": "gcp:storage/bucketIAMMember:BucketIAMMember" - }, - { - "custom": true, - "id": "", - "inputs": { - "location": "europe-west6", - "name": "mock-sv-da-1-bulk-committed" - }, - "name": "mock-sv-da-1-bulk-committed", - "provider": "", - "type": "gcp:storage/bucket:Bucket" - }, - { - "custom": true, - "id": "", - "inputs": {}, - "name": "mock-sv-da-1-bulk-sa-hmac", - "provider": "", - "type": "gcp:storage/hmacKey:HmacKey" - }, - { - "custom": true, - "id": "", - "inputs": { - "accountId": "mock-sv-da-1-bulk-sa", - "displayName": "Service Account for Bulk-Storage Bucket Read/Write Access" - }, - "name": "mock-sv-da-1-bulk-sa", - "provider": "", - "type": "gcp:serviceaccount/account:Account" - }, - { - "custom": true, - "id": "", - "inputs": { - "bucket": "mock-sv-da-1-bulk-staging", - "member": "serviceAccount:undefined", - "role": "roles/storage.objectUser" - }, - "name": "mock-sv-da-1-bulk-staging-sa-role", - "provider": "", - "type": "gcp:storage/bucketIAMMember:BucketIAMMember" - }, - { - "custom": true, - "id": "", - "inputs": { - "location": "europe-west6", - "name": "mock-sv-da-1-bulk-staging" - }, - "name": "mock-sv-da-1-bulk-staging", - "provider": "", - "type": "gcp:storage/bucket:Bucket" - }, - { - "custom": true, - "id": "", - "inputs": { - "datasetId": "mock_da2_scan", - "deleteContentsOnDestroy": true, - "friendlyName": "mock_da2_scan Dataset", - "labels": { - "cluster": "mock" - }, - "location": "europe-west6" - }, - "name": "mock_da2_scan", - "provider": "", - "type": "gcp:bigquery/dataset:Dataset" - }, - { - "custom": true, - "id": "organization/infra/infra.mock", - "inputs": { - "name": "organization/infra/infra.mock" - }, - "name": "organization/infra/infra.mock", - "provider": "", - "type": "pulumi:pulumi:StackReference" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "secret": "c3BsaXR3ZWxsc2VjcmV0Mg==" - } - }, - "kind": "Secret", - "metadata": { - "name": "splice-app-validator-onboarding-splitwell2", - "namespace": "sv-1" - }, - "type": "Opaque" - }, - "name": "splice-app-sv-1-validator-onboarding-splitwell2", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "secret": "dmFsaWRhdG9yMXNlY3JldDI=" - } - }, - "kind": "Secret", - "metadata": { - "name": "splice-app-validator-onboarding-validator12", - "namespace": "sv-1" - }, - "type": "Opaque" - }, - "name": "splice-app-sv-1-validator-onboarding-validator12", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "private": "c3ZkYTEtbW9jay1jb21ldGJmdC1nb3Zlcm5hbmNlLWtleS1wcml2YXRlLWtleQ==", - "public": "c3ZkYTEtbW9jay1jb21ldGJmdC1nb3Zlcm5hbmNlLWtleS1wdWJsaWMta2V5" - } - }, - "kind": "Secret", - "metadata": { - "name": "splice-app-sv-cometbft-governance-key", - "namespace": "sv-da-1" - }, - "type": "Opaque" - }, - "name": "splice-app-sv-da-1-cometbft-governance-key", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "private": "c3ZkYTEtbW9jay1pZC1wcml2YXRlLWtleQ==", - "public": "c3ZkYTEtbW9jay1pZC1wdWJsaWMta2V5" - } - }, - "kind": "Secret", - "metadata": { - "name": "splice-app-sv-key", - "namespace": "sv-da-1" - }, - "type": "Opaque" - }, - "name": "splice-app-sv-da-1-key", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-sv-ledger-api-auth", - "namespace": "sv-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "audience": "https://canton.network.global", - "client-id": "sv1-sv-client-id", - "client-secret": "***", - "ledger-api-user": "sv1-sv-client-id@clients", - "url": "https://canton-network-dev.us.auth0.com/.well-known/openid-configuration" - } - } - }, - "name": "splice-auth0-secret-sv-1-sv", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-validator-ledger-api-auth", - "namespace": "sv-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "audience": "https://canton.network.global", - "client-id": "sv1-validator-client-id", - "client-secret": "***", - "ledger-api-user": "sv1-validator-client-id@clients", - "url": "https://canton-network-dev.us.auth0.com/.well-known/openid-configuration" - } - } - }, - "name": "splice-auth0-secret-sv-1-validator", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-sv-ledger-api-auth", - "namespace": "sv-da-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "audience": "https://canton.network.global", - "client-id": "sv-da-1-sv-client-id", - "client-secret": "***", - "ledger-api-user": "sv-da-1-sv-client-id@clients", - "url": "https://canton-network-dev.us.auth0.com/.well-known/openid-configuration" - } - } - }, - "name": "splice-auth0-secret-sv-da-1-sv", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-validator-ledger-api-auth", - "namespace": "sv-da-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "audience": "https://canton.network.global", - "client-id": "sv-da-1-validator-client-id", - "client-secret": "***", - "ledger-api-user": "sv-da-1-validator-client-id@clients", - "url": "https://canton-network-dev.us.auth0.com/.well-known/openid-configuration" - } - } - }, - "name": "splice-auth0-secret-sv-da-1-validator", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-cns-ui-auth", - "namespace": "sv-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "client-id": "sv-1-cns-ui-client-id", - "url": "https://canton-network-dev.us.auth0.com" - } - } - }, - "name": "splice-auth0-ui-secret-sv-1-cns", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-sv-ui-auth", - "namespace": "sv-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "client-id": "sv-1-sv-ui-client-id", - "url": "https://canton-network-dev.us.auth0.com" - } - } - }, - "name": "splice-auth0-ui-secret-sv-1-sv", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-wallet-ui-auth", - "namespace": "sv-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "client-id": "sv-1-wallet-ui-client-id", - "url": "https://canton-network-dev.us.auth0.com" - } - } - }, - "name": "splice-auth0-ui-secret-sv-1-wallet", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-cns-ui-auth", - "namespace": "sv-da-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "client-id": "sv-da-1-cns-ui-client-id", - "url": "https://canton-network-dev.us.auth0.com" - } - } - }, - "name": "splice-auth0-ui-secret-sv-da-1-cns", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-sv-ui-auth", - "namespace": "sv-da-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "client-id": "sv-da-1-sv-ui-client-id", - "url": "https://canton-network-dev.us.auth0.com" - } - } - }, - "name": "splice-auth0-ui-secret-sv-da-1-sv", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "splice-app-wallet-ui-auth", - "namespace": "sv-da-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "client-id": "sv-da-1-wallet-ui-client-id", - "url": "https://canton-network-dev.us.auth0.com" - } - } - }, - "name": "splice-auth0-ui-secret-sv-da-1-wallet", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": false, - "id": "", - "inputs": { - "appLabel": "scan-app", - "globalLimits": { - "fillInterval": "60s", - "maxTokens": 2147483647, - "tokensPerFill": 2147483647 - }, - "inboundPort": 5012, - "namespace": "sv-1", - "rateLimits": { - "/api/scan/livez": { - "name": "livez", - "type": "unlimited" - }, - "/api/scan/readyz": { - "name": "readyz", - "type": "unlimited" - }, - "/api/scan/status": { - "name": "status", - "type": "unlimited" - }, - "/api/scan/v0/acs": { - "fillInterval": "60s", - "maxTokens": 500, - "name": "acs", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 - }, - "tokensPerFill": 500, - "type": "limited" - }, - "/api/scan/v0/active-synchronizer-serial": { - "name": "activeSynchronizerSerial", - "type": "unlimited" - }, - "/api/scan/v0/admin/sv/previous-sv-reward-weight": { - "name": "sv-previous-sv-reward-weight", - "type": "unlimited" - }, - "/api/scan/v0/admin/sv/voterequests": { - "name": "sv-voterequests", - "type": "unlimited" - }, - "/api/scan/v0/admin/sv/voteresults": { - "name": "sv-voteresults", - "type": "unlimited" - }, - "/api/scan/v0/admin/validator": { - "name": "validator-licenses", - "type": "unlimited" - }, - "/api/scan/v0/amulet-config-for-round": { - "name": "amulet-config-for-round", - "type": "banned" - }, - "/api/scan/v0/amulet-price": { - "name": "amulet-price-votes", - "type": "unlimited" - }, - "/api/scan/v0/amulet-rules": { - "name": "amulet-rules", - "type": "unlimited" - }, - "/api/scan/v0/ans-entries": { - "name": "ans-entries", - "type": "unlimited" - }, - "/api/scan/v0/ans-rules": { - "name": "ans-rules", - "type": "unlimited" - }, - "/api/scan/v0/backfilling": { - "name": "backfilling", - "type": "unlimited" - }, - "/api/scan/v0/closed-rounds": { - "name": "closed-rounds", - "type": "unlimited" - }, - "/api/scan/v0/domains": { - "name": "domains", - "type": "unlimited" - }, - "/api/scan/v0/dso": { - "name": "dso-info", - "type": "unlimited" - }, - "/api/scan/v0/dso-party-id": { - "name": "dso-party-id", - "type": "unlimited" - }, - "/api/scan/v0/dso-sequencers": { - "name": "dso-sequencers", - "type": "unlimited" - }, - "/api/scan/v0/events": { - "name": "events", - "type": "unlimited" - }, - "/api/scan/v0/external-party-amulet-rules": { - "name": "external-party-amulet-rules", - "type": "unlimited" - }, - "/api/scan/v0/feature-support": { - "name": "feature-support", - "type": "unlimited" - }, - "/api/scan/v0/featured-apps": { - "name": "featured-apps", - "type": "unlimited" - }, - "/api/scan/v0/history/bulk/acs": { - "name": "listBulkAcsSnapshotObjects", - "type": "unlimited" - }, - "/api/scan/v0/history/bulk/updates": { - "name": "listBulkUpdateHistoryObjects", - "type": "unlimited" - }, - "/api/scan/v0/holdings": { - "name": "holdings", - "type": "unlimited" - }, - "/api/scan/v0/internal/reward-accounting-process": { - "name": "reward-accounting-process", - "type": "unlimited" - }, - "/api/scan/v0/lsu": { - "name": "lsu", - "type": "unlimited" - }, - "/api/scan/v0/migrations": { - "name": "migrations-schedule", - "type": "unlimited" - }, - "/api/scan/v0/open-and-issuing-mining-rounds": { - "name": "open-and-issuing-mining-rounds", - "type": "unlimited" - }, - "/api/scan/v0/roll-forward-lsu": { - "name": "rollForwardLsu", - "type": "unlimited" - }, - "/api/scan/v0/scans": { - "name": "scans", - "type": "unlimited" - }, - "/api/scan/v0/splice-instance-names": { - "name": "splice-instance-names", - "type": "unlimited" - }, - "/api/scan/v0/state": { - "name": "state", - "type": "unlimited" - }, - "/api/scan/v0/sv-bft-sequencers": { - "name": "bft-sequencers", - "type": "unlimited" - }, - "/api/scan/v0/synchronizer-bootstrapping-transactions": { - "name": "synchronizer-bootstrapping-transactions", - "type": "unlimited" - }, - "/api/scan/v0/synchronizer-identities": { - "name": "synchronizer-identities", - "type": "unlimited" - }, - "/api/scan/v0/transfer-command": { - "name": "transfer-command-status", - "type": "unlimited" - }, - "/api/scan/v0/transfer-command-counter": { - "name": "transfer-command-counter", - "type": "unlimited" - }, - "/api/scan/v0/transfer-preapprovals": { - "name": "transfer-preapprovals", - "type": "unlimited" - }, - "/api/scan/v0/unclaimed-development-fund-coupons": { - "name": "unclaimed-development-fund-coupons", - "type": "unlimited" - }, - "/api/scan/v0/updates": { - "name": "v0-updates", - "type": "unlimited" - }, - "/api/scan/v0/validators": { - "name": "validators", - "type": "unlimited" - }, - "/api/scan/v0/voterequest": { - "name": "voterequest", - "type": "unlimited" - }, - "/api/scan/v0/voterequests": { - "name": "voterequests", - "type": "unlimited" - }, - "/api/scan/v1/domains": { - "name": "domains-v1", - "type": "unlimited" - }, - "/api/scan/v1/holdings": { - "name": "holdings", - "type": "unlimited" - }, - "/api/scan/v1/state": { - "name": "state", - "type": "unlimited" - }, - "/api/scan/v1/updates": { - "name": "v1-updates", - "type": "banned" - }, - "/api/scan/v2/updates": { - "name": "v2-updates", - "type": "unlimited" - }, - "/api/scan/version": { - "name": "version", - "type": "unlimited" - }, - "/registry/allocation-instruction/v1/allocation-factory": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocation-factory", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocation-instruction/v2": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocation-instruction-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocation-instruction/v2/allocation-factory": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocation-factory-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocation/v2/settlement-factory": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-settlement-factory-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocations/v1": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocations", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocations/v2": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocations-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/metadata/v1/info": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-metadata-info", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "overrides": { - "test": { - "fillInterval": "60s", - "ips": [ - "192.68.78.50" - ], - "maxTokens": 250, - "tokensPerFill": 250 - } - }, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/metadata/v1/instruments": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-metadata-instruments", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/transfer-instruction/v1": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-transfer-instruction", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/transfer-instruction/v1/transfer-factory": { - "fillInterval": "60s", - "maxTokens": 730, - "name": "registry-transfer-factory", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 730, - "type": "limited" - }, - "/registry/transfer-instruction/v2": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-transfer-instruction-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/transfer-instruction/v2/transfer-factory": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-transfer-factory-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - } - } - }, - "name": "splice-sv-1-scan-app-rate-limit", - "provider": "", - "type": "splice:RateLimit" - }, - { - "custom": false, - "id": "", - "inputs": { - "appLabel": "scan-app", - "globalLimits": { - "fillInterval": "60s", - "maxTokens": 2147483647, - "tokensPerFill": 2147483647 - }, - "inboundPort": 5012, - "namespace": "sv-da-1", - "rateLimits": { - "/api/scan/livez": { - "name": "livez", - "type": "unlimited" - }, - "/api/scan/readyz": { - "name": "readyz", - "type": "unlimited" - }, - "/api/scan/status": { - "name": "status", - "type": "unlimited" - }, - "/api/scan/v0/acs": { - "fillInterval": "60s", - "maxTokens": 500, - "name": "acs", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 10, - "tokensPerFill": 5 - }, - "tokensPerFill": 500, - "type": "limited" - }, - "/api/scan/v0/active-synchronizer-serial": { - "name": "activeSynchronizerSerial", - "type": "unlimited" - }, - "/api/scan/v0/admin/sv/previous-sv-reward-weight": { - "name": "sv-previous-sv-reward-weight", - "type": "unlimited" - }, - "/api/scan/v0/admin/sv/voterequests": { - "name": "sv-voterequests", - "type": "unlimited" - }, - "/api/scan/v0/admin/sv/voteresults": { - "name": "sv-voteresults", - "type": "unlimited" - }, - "/api/scan/v0/admin/validator": { - "name": "validator-licenses", - "type": "unlimited" - }, - "/api/scan/v0/amulet-config-for-round": { - "name": "amulet-config-for-round", - "type": "banned" - }, - "/api/scan/v0/amulet-price": { - "name": "amulet-price-votes", - "type": "unlimited" - }, - "/api/scan/v0/amulet-rules": { - "name": "amulet-rules", - "type": "unlimited" - }, - "/api/scan/v0/ans-entries": { - "name": "ans-entries", - "type": "unlimited" - }, - "/api/scan/v0/ans-rules": { - "name": "ans-rules", - "type": "unlimited" - }, - "/api/scan/v0/backfilling": { - "name": "backfilling", - "type": "unlimited" - }, - "/api/scan/v0/closed-rounds": { - "name": "closed-rounds", - "type": "unlimited" - }, - "/api/scan/v0/domains": { - "name": "domains", - "type": "unlimited" - }, - "/api/scan/v0/dso": { - "name": "dso-info", - "type": "unlimited" - }, - "/api/scan/v0/dso-party-id": { - "name": "dso-party-id", - "type": "unlimited" - }, - "/api/scan/v0/dso-sequencers": { - "name": "dso-sequencers", - "type": "unlimited" - }, - "/api/scan/v0/events": { - "name": "events", - "type": "unlimited" - }, - "/api/scan/v0/external-party-amulet-rules": { - "name": "external-party-amulet-rules", - "type": "unlimited" - }, - "/api/scan/v0/feature-support": { - "name": "feature-support", - "type": "unlimited" - }, - "/api/scan/v0/featured-apps": { - "name": "featured-apps", - "type": "unlimited" - }, - "/api/scan/v0/history/bulk/acs": { - "name": "listBulkAcsSnapshotObjects", - "type": "unlimited" - }, - "/api/scan/v0/history/bulk/updates": { - "name": "listBulkUpdateHistoryObjects", - "type": "unlimited" - }, - "/api/scan/v0/holdings": { - "name": "holdings", - "type": "unlimited" - }, - "/api/scan/v0/internal/reward-accounting-process": { - "name": "reward-accounting-process", - "type": "unlimited" - }, - "/api/scan/v0/lsu": { - "name": "lsu", - "type": "unlimited" - }, - "/api/scan/v0/migrations": { - "name": "migrations-schedule", - "type": "unlimited" - }, - "/api/scan/v0/open-and-issuing-mining-rounds": { - "name": "open-and-issuing-mining-rounds", - "type": "unlimited" - }, - "/api/scan/v0/roll-forward-lsu": { - "name": "rollForwardLsu", - "type": "unlimited" - }, - "/api/scan/v0/scans": { - "name": "scans", - "type": "unlimited" - }, - "/api/scan/v0/splice-instance-names": { - "name": "splice-instance-names", - "type": "unlimited" - }, - "/api/scan/v0/state": { - "name": "state", - "type": "unlimited" - }, - "/api/scan/v0/sv-bft-sequencers": { - "name": "bft-sequencers", - "type": "unlimited" - }, - "/api/scan/v0/synchronizer-bootstrapping-transactions": { - "name": "synchronizer-bootstrapping-transactions", - "type": "unlimited" - }, - "/api/scan/v0/synchronizer-identities": { - "name": "synchronizer-identities", - "type": "unlimited" - }, - "/api/scan/v0/transfer-command": { - "name": "transfer-command-status", - "type": "unlimited" - }, - "/api/scan/v0/transfer-command-counter": { - "name": "transfer-command-counter", - "type": "unlimited" - }, - "/api/scan/v0/transfer-preapprovals": { - "name": "transfer-preapprovals", - "type": "unlimited" - }, - "/api/scan/v0/unclaimed-development-fund-coupons": { - "name": "unclaimed-development-fund-coupons", - "type": "unlimited" - }, - "/api/scan/v0/updates": { - "name": "v0-updates", - "type": "unlimited" - }, - "/api/scan/v0/validators": { - "name": "validators", - "type": "unlimited" - }, - "/api/scan/v0/voterequest": { - "name": "voterequest", - "type": "unlimited" - }, - "/api/scan/v0/voterequests": { - "name": "voterequests", - "type": "unlimited" - }, - "/api/scan/v1/domains": { - "name": "domains-v1", - "type": "unlimited" - }, - "/api/scan/v1/holdings": { - "name": "holdings", - "type": "unlimited" - }, - "/api/scan/v1/state": { - "name": "state", - "type": "unlimited" - }, - "/api/scan/v1/updates": { - "name": "v1-updates", - "type": "banned" - }, - "/api/scan/v2/updates": { - "name": "v2-updates", - "type": "unlimited" - }, - "/api/scan/version": { - "name": "version", - "type": "unlimited" - }, - "/registry/allocation-instruction/v1/allocation-factory": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocation-factory", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocation-instruction/v2": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocation-instruction-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocation-instruction/v2/allocation-factory": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocation-factory-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocation/v2/settlement-factory": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-settlement-factory-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocations/v1": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocations", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/allocations/v2": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-allocations-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/metadata/v1/info": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-metadata-info", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "overrides": { - "test": { - "fillInterval": "60s", - "ips": [ - "192.68.78.50" - ], - "maxTokens": 250, - "tokensPerFill": 250 - } - }, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/metadata/v1/instruments": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-metadata-instruments", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/transfer-instruction/v1": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-transfer-instruction", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/transfer-instruction/v1/transfer-factory": { - "fillInterval": "60s", - "maxTokens": 730, - "name": "registry-transfer-factory", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 730, - "type": "limited" - }, - "/registry/transfer-instruction/v2": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-transfer-instruction-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - }, - "/registry/transfer-instruction/v2/transfer-factory": { - "fillInterval": "60s", - "maxTokens": 720, - "name": "registry-transfer-factory-v2", - "perIpLimits": { - "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 - }, - "tokensPerFill": 720, - "type": "limited" - } - } - }, - "name": "splice-sv-da-1-scan-app-rate-limit", - "provider": "", - "type": "splice:RateLimit" - }, - { - "custom": true, - "id": "", - "inputs": { - "create": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' create-pub-rep-slot \\\n --private-network-project=\"test-project\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n ", - "delete": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' delete-pub-rep-slot \\\n --private-network-project=\"test-project\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n " - }, - "name": "sv-1-bqdatastream-pub-replicate-slots", - "provider": "", - "type": "command:local:Command" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "accessKey": "", - "secretAccessKey": "" - } - }, - "kind": "Secret", - "metadata": { - "name": "splice-app-bulk-storage-credentials", - "namespace": "sv-1" - }, - "type": "Opaque" - }, - "name": "sv-1-bulk-storage-credentials", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "length": 16, - "overrideSpecial": "_%@", - "special": true - }, - "name": "sv-1-cn-apps-pg-cnadmin-passwd", - "provider": "", - "type": "random:index/randomPassword:RandomPassword" - }, - { - "custom": false, - "id": "", - "inputs": { - "active": true, - "alias": "cn-apps-pg", - "cloudSqlConfig": { - "databaseVersion": "POSTGRES_14", - "enabled": true, - "enterprisePlus": false, - "flags": { - "maintenance_work_mem": "2000000", - "max_wal_size": "20480", - "random_page_cost": "1.1", - "temp_file_limit": "2147483647", - "work_mem": "16384" - }, - "maintenanceWindow": { - "day": 2, - "hour": 8 - }, - "protected": true, - "tier": "apps-pg-override-tier-sv-1" - }, - "defaultUserName": "cnadmin", - "deletionProtection": true, - "instanceName": "cn-apps-pg", - "logicalDecoding": true, - "namespace": { - "logicalName": "sv-1", - "ns": { - "__aliases": [], - "__name": "sv-1", - "__providers": {}, - "__pulumiCustomResource": true, - "__pulumiResource": true, - "__pulumiType": "kubernetes:core/v1:Namespace", - "__transformations": [], - "__version": "4.28.0", - "apiVersion": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "id": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "kind": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "metadata": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "spec": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "status": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "urn": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi." - } - }, - "retainDbResourcesOnDelete": false, - "secretName": "cn-apps-pg-secrets" - }, - "name": "sv-1-cn-apps-pg", - "provider": "", - "type": "canton:cloud:postgres" - }, - { - "custom": true, - "id": "", - "inputs": { - "databaseVersion": "POSTGRES_14", - "deletionProtection": false, - "region": "europe-west6", - "settings": { - "activationPolicy": "ALWAYS", - "backupConfiguration": { - "enabled": true, - "pointInTimeRecoveryEnabled": true - }, - "databaseFlags": [ - { - "name": "random_page_cost", - "value": "1.1" - }, - { - "name": "temp_file_limit", - "value": "2147483647" - }, - { - "name": "max_wal_size", - "value": "20480" - }, - { - "name": "maintenance_work_mem", - "value": "2000000" - }, - { - "name": "work_mem", - "value": "16384" - }, - { - "name": "cloudsql.logical_decoding", - "value": "on" - } - ], - "deletionProtectionEnabled": true, - "edition": "ENTERPRISE", - "insightsConfig": { - "queryInsightsEnabled": true - }, - "ipConfiguration": { - "enablePrivatePathForGoogleCloudServices": true, - "ipv4Enabled": false, - "privateNetwork": "projects/test-project/global/networks/default" - }, - "locationPreference": { - "zone": "europe-west6-a" - }, - "maintenanceWindow": { - "day": 2, - "hour": 8 - }, - "tier": "apps-pg-override-tier-sv-1", - "userLabels": { - "cluster": "mock" - } - } - }, - "name": "sv-1-cn-apps-pg", - "provider": "", - "type": "gcp:sql/databaseInstance:DatabaseInstance" - }, - { - "custom": true, - "id": "", - "inputs": { - "allows": [ - { - "ports": [ - "5432" - ], - "protocol": "tcp" - } - ], - "destinationRanges": [ - null - ], - "direction": "INGRESS", - "name": "sv-1-datastream-to-nat", - "network": "default", - "priority": 42, - "sourceRanges": [ - "1.2.3.16/29" - ] - }, - "name": "sv-1-datastream-to-nat", - "provider": "", - "type": "gcp:compute/firewall:Firewall" - }, - { - "custom": true, - "id": "", - "inputs": { - "name": "cantonnet" - }, - "name": "sv-1-db-cn-apps-pg-cantonnet", - "provider": "", - "type": "gcp:sql/database:Database" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "imagePullSecrets": [ - { - "name": "docker-reg-cred" - } - ], - "kind": "ServiceAccount", - "metadata": { - "name": "default", - "namespace": "sv-1" - } - }, - "name": "sv-1-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-default::undefined_id", - "type": "kubernetes:core/v1:ServiceAccountPatch" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "docker-reg-cred", - "namespace": "sv-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" - } - }, - "type": "kubernetes.io/dockerconfigjson" - }, - "name": "sv-1-docker-reg-cred", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-info", - "compat": "true", - "maxHistory": 10, - "name": "info", - "namespace": "sv-1", - "timeout": 600, - "values": { - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "deploymentDetails": { - "configDigest": { - "allowedIpRanges": { - "type": "md5", - "value": "90eedde4a8599204a45dcb972e212c8b" - }, - "approvedSvIdentities": { - "type": "md5", - "value": "5871224b744b45122540483fddbd550f" - } - }, - "network": "test", - "sv": { - "version": "0.3.20" - }, - "synchronizer": { - "current": { - "chainIdSuffix": "4", - "migrationId": 2, - "synchronizerSerialId": 9, - "version": "0.3.20" - }, - "legacy": { - "chainIdSuffix": "4", - "migrationId": 2, - "synchronizerSerialId": 8, - "version": "0.3.20" - }, - "successor": { - "chainIdSuffix": "4", - "migrationId": 2, - "synchronizerSerialId": 10, - "version": "0.3.21" - } - } - }, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "istioVirtualService": { - "gateway": "cluster-ingress/cn-http-gateway", - "host": "info.sv-2.mock.global.canton.network.digitalasset.com" - }, - "runtimeDetails": { - "scanUrl": "http://scan-app.sv-1:5012", - "synchronizerSerialId": 9 - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] - }, - "version": "0.3.20" - }, - "name": "sv-1-info", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-cluster-ingress-runbook", - "compat": "true", - "maxHistory": 10, - "name": "ingress-sv", - "namespace": "sv-1", - "timeout": 600, - "values": { - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet", - "svIngressName": "sv-2", - "svNamespace": "sv-1" - }, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "ingress": { - "decentralizedSynchronizer": { - "migrationIds": [ - "9", - "8", - "7", - "10" - ] - } - }, - "rateLimit": { - "scan": { - "enable": false - } - }, - "spliceDomainNames": { - "nameServiceDomain": "cns" - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ], - "withSvIngress": true - }, - "version": "0.3.20" - }, - "name": "sv-1-ingress-sv", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "bootDisk": { - "initializeParams": { - "image": "debian-cloud/debian-12" - } - }, - "labels": { - "cluster": "mock" - }, - "machineType": "e2-micro", - "metadata": { - "enable-osconfig": "TRUE", - "enable-oslogin": "true", - "startup-script": "#! /bin/bash\n\nexport DB_ADDR=undefined\nexport DB_PORT=5432\n\n# Enable the VM to receive packets whose destinations do\n# not match any running process local to the VM\necho 1 > /proc/sys/net/ipv4/ip_forward\n\n# Ask the Metadata server for the IP address of the VM nic0\n# network interface:\nmd_url_prefix=\"http://169.254.169.254/computeMetadata/v1/instance\"\nvm_nic_ip=\"$(curl -H \"Metadata-Flavor: Google\" $md_url_prefix/network-interfaces/0/ip)\"\n\n# Clear any existing iptables NAT table entries (all chains):\niptables -t nat -F\n\n# Create a NAT table entry in the prerouting chain, matching\n# any packets with destination database port, changing the destination\n# IP address of the packet to the SQL instance IP address:\niptables -t nat -A PREROUTING \\\n -p tcp --dport $DB_PORT \\\n -j DNAT \\\n --to-destination $DB_ADDR\n\n# Create a NAT table entry in the postrouting chain, matching\n# any packets with destination database port, changing the source IP\n# address of the packet to the NAT VM's primary internal IPv4 address:\niptables -t nat -A POSTROUTING \\\n -p tcp --dport $DB_PORT \\\n -j SNAT \\\n --to-source $vm_nic_ip\n\n# Save iptables configuration:\niptables-save\n" - }, - "networkInterfaces": [ - { - "accessConfigs": [ - {} - ], - "network": "default" - } - ], - "zone": "europe-west6-a" - }, - "name": "sv-1-nat-vm", - "provider": "", - "type": "gcp:compute/instance:Instance" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "networking.istio.io/v1alpha3", - "kind": "EnvoyFilter", - "metadata": { - "annotations": { - "proxy.istio.io/config": "proxyStatsMatcher:\n inclusionRegexps:\n - \".*http_local_rate_limit.*\"" - }, - "name": "scan-app-rate-limit", - "namespace": "sv-1" - }, - "spec": { - "configPatches": [ - { - "applyTo": "HTTP_FILTER", - "match": { - "context": "SIDECAR_INBOUND", - "listener": { - "filterChain": { - "filter": { - "name": "envoy.filters.network.http_connection_manager" - } - } - } - }, - "patch": { - "operation": "INSERT_BEFORE", - "value": { - "name": "envoy.filters.http.local_ratelimit", - "typed_config": { - "@type": "type.googleapis.com/udpa.type.v1.TypedStruct", - "type_url": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", - "value": { - "stat_prefix": "http_local_rate_limiter" - } - } - } - } - }, - { - "applyTo": "HTTP_ROUTE", - "match": { - "context": "SIDECAR_INBOUND", - "routeConfiguration": { - "vhost": { - "name": "inbound|http|5012", - "route": { - "action": "ANY" - } - } - } - }, - "patch": { - "operation": "MERGE", - "value": { - "route": { - "rate_limits": [ - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "acs", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/api/scan/v0/acs" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "acs", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/api/scan/v0/acs" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocations", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocations/v1" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocations", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocations/v1" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-metadata-info", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/metadata/v1/info" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-metadata-info", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/metadata/v1/info" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-metadata-instruments", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/metadata/v1/instruments" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-metadata-instruments", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/metadata/v1/instruments" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-factory", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v1/allocation-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-factory", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v1/allocation-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-instruction", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v1" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-instruction", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v1" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-factory", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v1/transfer-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-factory", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v1/transfer-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-settlement-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation/v2/settlement-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-settlement-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation/v2/settlement-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocations-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocations/v2" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocations-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocations/v2" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v2/allocation-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v2/allocation-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-instruction-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v2" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-instruction-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v2" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v2/transfer-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v2/transfer-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-instruction-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v2" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-instruction-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v2" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - } - ] - }, - "typed_per_filter_config": { - "envoy.filters.http.local_ratelimit": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", - "descriptors": [ - { - "entries": [ - { - "key": "header_match", - "value": "acs" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "acs" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocations" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocations" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-info" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-info" - }, - { - "key": "client_ip", - "value": "192.68.78.50" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 250, - "tokens_per_fill": 250 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-info" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-instruments" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-instruments" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-factory" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-factory" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-instruction" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-instruction" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-factory" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 730, - "tokens_per_fill": 730 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-factory" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-settlement-factory-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-settlement-factory-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocations-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocations-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-factory-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-factory-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-instruction-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-instruction-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-factory-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-factory-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-instruction-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-instruction-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - } - ], - "filter_enabled": { - "default_value": { - "denominator": "HUNDRED", - "numerator": 100 - }, - "runtime_key": "local_rate_limit_enabled" - }, - "filter_enforced": { - "default_value": { - "denominator": "HUNDRED", - "numerator": 100 - }, - "runtime_key": "local_rate_limit_enforced" - }, - "response_headers_to_add": [ - { - "append_action": "OVERWRITE_IF_EXISTS_OR_ADD", - "header": { - "key": "x-local-rate-limit", - "value": "true" - } - } - ], - "stat_prefix": "http_local_rate_limiter", - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 2147483647, - "tokens_per_fill": 2147483647 - } - } - } - } - } - } - ], - "workloadSelector": { - "labels": { - "app": "scan-app" - } - } - } - }, - "name": "sv-1-scan-app-rate-limit", - "provider": "", - "type": "kubernetes:networking.istio.io/v1alpha3:EnvoyFilter" - }, - { - "custom": true, - "id": "", - "inputs": { - "bigqueryProfile": {}, - "connectionProfileId": "sv-1-scan-bq-cxn", - "displayName": "sv-1-scan-bq-cxn", - "labels": { - "cluster": "mock" - }, - "location": "europe-west6" - }, - "name": "sv-1-scan-bq-cxn", - "provider": "", - "type": "gcp:datastream/connectionProfile:ConnectionProfile" - }, - { - "custom": true, - "id": "", - "inputs": { - "connectionProfileId": "sv-1-scan-update-history-cxn", - "displayName": "sv-1-scan-update-history-cxn", - "labels": { - "cluster": "mock" - }, - "location": "europe-west6", - "postgresqlProfile": { - "database": "scan_sv_1", - "port": 5432, - "username": "bqdatastream" - }, - "privateConnectivity": {} - }, - "name": "sv-1-scan-update-history-cxn", - "provider": "", - "type": "gcp:datastream/connectionProfile:ConnectionProfile" - }, - { - "custom": true, - "id": "", - "inputs": { - "displayName": "sv-1-scan-update-history-datastream-vpc", - "labels": { - "cluster": "mock" - }, - "location": "europe-west6", - "privateConnectionId": "sv-1-scan-update-history-datastream-vpc", - "vpcPeeringConfig": { - "subnet": "1.2.3.16/29", - "vpc": "projects/test-project/global/networks/default" - } - }, - "name": "sv-1-scan-update-history-datastream-vpc", - "provider": "", - "type": "gcp:datastream/privateConnection:PrivateConnection" - }, - { - "custom": true, - "id": "", - "inputs": { - "backfillAll": {}, - "desiredState": "RUNNING", - "destinationConfig": { - "bigqueryDestinationConfig": { - "dataFreshness": "14400s", - "singleTargetDataset": { - "datasetId": "projects/undefined/datasets/mock_da2_scan" - } - } - }, - "displayName": "sv-1-scan-update-history", - "labels": { - "cluster": "mock" - }, - "location": "europe-west6", - "sourceConfig": { - "postgresqlSourceConfig": { - "includeObjects": { - "postgresqlSchemas": [ - { - "postgresqlTables": [ - { - "table": "update_history_creates" - }, - { - "table": "update_history_exercises" - }, - { - "table": "scan_verdict_store" - }, - { - "table": "scan_verdict_transaction_view_store" - }, - { - "table": "app_activity_record_store" - } - ], - "schema": "scan_sv_1" - } - ] - }, - "publication": "update_history_datastream_pub", - "replicationSlot": "update_history_datastream_r_slot" - } - }, - "streamId": "sv-1-scan-update-history" - }, - "name": "sv-1-scan-update-history", - "provider": "", - "type": "gcp:datastream/stream:Stream" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-scan", - "compat": "true", - "maxHistory": 10, - "name": "scan", - "namespace": "sv-1", - "timeout": 600, - "values": { - "additionalEnvVars": [ - { - "name": "CUSTOM_MOCK_ENV_VAR_NAME", - "value": "CUSTOM_MOCK_ENV_VAR_VALUE" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomScanAppJvmFlag", - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "apiRequestLogLevel": "DEBUG", - "bulkStorage": { - "committed": { - "bucketName": "mock-sv-1-bulk-committed", - "endpoint": "https://storage.googleapis.com", - "region": "europe-west6", - "secretName": "splice-app-bulk-storage-credentials" - }, - "staging": { - "bucketName": "mock-sv-1-bulk-staging", - "endpoint": "https://storage.googleapis.com", - "region": "europe-west6", - "secretName": "splice-app-bulk-storage-credentials" - } - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "enablePostgresMetrics": true, - "failOnAppVersionMismatch": true, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "isFirstSv": true, - "logAsyncFlush": false, - "logLevel": "WARN", - "metrics": { - "enable": true - }, - "participantAddress": "participant", - "persistence": { - "databaseName": "scan_sv_1", - "port": 5432, - "postgresName": "cn-apps-pg", - "schema": "scan_sv_1", - "secretName": "cn-apps-pg-secrets", - "user": "cnadmin" - }, - "publicUrl": "https://scan.sv-2.mock.global.canton.network.digitalasset.com", - "resources": { - "limits": { - "memory": "2048Mi" - }, - "requests": { - "cpu": "0.5", - "memory": "1536Mi" - } - }, - "spliceInstanceNames": { - "amuletName": "Amulet", - "amuletNameAcronym": "AMT", - "nameServiceName": "Amulet Name Service", - "nameServiceNameAcronym": "ANS", - "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", - "networkName": "Splice" - }, - "synchronizers": { - "current": { - "cantonBft": { - "p2pUrl": "https://sequencer-p2p-9.sv-2.mock.global.canton.network.digitalasset.com" - }, - "mediator": "global-domain-9-mediator", - "sequencer": "global-domain-9-sequencer" - }, - "legacy": { - "mediator": "global-domain-8-mediator", - "sequencer": "global-domain-8-sequencer" - }, - "successor": { - "cantonBft": { - "p2pUrl": "https://sequencer-p2p-10.sv-2.mock.global.canton.network.digitalasset.com" - }, - "mediator": "global-domain-10-mediator", - "sequencer": "global-domain-10-sequencer" - } - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] - }, - "version": "0.3.20" - }, - "name": "sv-1-scan", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-sv-node", - "compat": "true", - "maxHistory": 10, - "name": "sv-app", - "namespace": "sv-1", - "timeout": 600, - "values": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "additionalEnvVars": [ - { - "name": "CUSTOM_MOCK_ENV_VAR_NAME", - "value": "CUSTOM_MOCK_ENV_VAR_VALUE" - }, - { - "name": "ADDITIONAL_CONFIG_MEDIATOR_PRUNING", - "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.mediator.pruning {\n cron = \"0 0 * * *\"\n max-duration = \"30m\"\n retention = \"7d\"\n }" - }, - { - "name": "ADDITIONAL_CONFIG_CANTON_BFT_PRUNING", - "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.sequencer.canton-bft-pruning {\n cron = \"0 /10 * * * ?\"\n max-duration = \"5m\"\n retention = \"15 days\"\n }" - }, - { - "name": "ADDITIONAL_CONFIG_ADDITIONAL_PACKAGES_TO_UNVET", - "value": "canton.sv-apps.sv.additional-packages-to-unvet.\"splice-wallet-payments\" = [\"0.1.15\", \"0.1.16\"]\ncanton.sv-apps.sv.additional-packages-to-unvet.\"splice-amulet\" = [\"0.1.15\"]" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomSvAppJvmFlag", - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "apiRequestLogLevel": "DEBUG", - "approvedSvIdentities": [ - { - "name": "Digital-Asset-2", - "publicKey": "sv1-id-public-key", - "rewardWeightBps": 100000 - }, - { - "name": "SV1", - "publicKey": "PUBLIC_KEY_1==", - "rewardWeightBps": 1000000 - }, - { - "extraBeneficiaries": [ - { - "beneficiary": "mock-validator-1::123456789012345678901234567890123456789012234567890123456789012345678", - "weight": 100000 - }, - { - "beneficiary": "Broadridge-validator-1::1220c89a73e7b47f16dfb48a521eeedfe2a8620ea1b19b815ab427388d9f5427faa5", - "weight": 50000 - } - ], - "name": "SV2", - "publicKey": "PUBLIC_KEY_2==", - "rewardWeightBps": 150000 - }, - { - "name": "Digital-Asset-1", - "publicKey": "svda1-mock-id-public-key", - "rewardWeightBps": 150000 - }, - { - "name": "DA-Helm-Test-Node", - "publicKey": "sv-id-public-key", - "rewardWeightBps": 10000 - } - ], - "auth": { - "audience": "https://canton.network.global", - "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json" - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "contactPoint": "sv-support@digitalasset.com", - "delegatelessAutomationExpectedTaskDuration": 5000, - "delegatelessAutomationExpiredRewardCouponBatchSize": 100, - "delegatelessAutomationExpiredRewardCouponNumBatches": 100, - "disableOnboardingParticipantPromotionDelay": false, - "enablePostgresMetrics": true, - "expectedValidatorOnboardings": [ - { - "expiresIn": "24h", - "secretFrom": { - "secretKeyRef": { - "key": "secret", - "name": "splice-app-validator-onboarding-splitwell2", - "optional": false - } - } - }, - { - "expiresIn": "24h", - "secretFrom": { - "secretKeyRef": { - "key": "secret", - "name": "splice-app-validator-onboarding-validator12", - "optional": false - } - } - } - ], - "failOnAppVersionMismatch": true, - "identitiesExport": { - "bucket": { - "bucketName": "da-cn-data-dumps", - "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", - "projectId": "da-cn-devnet", - "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" - }, - "prefix": "mock/sv-1" - }, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "initialAmuletPrice": "0.0517", - "initialPackageConfigJson": "{\"amuletVersion\": \"0.1.4\",\"amuletNameServiceVersion\": \"0.1.4\",\"dsoGovernanceVersion\": \"0.1.7\",\"validatorLifecycleVersion\": \"0.1.1\",\"walletVersion\": \"0.1.4\",\"walletPaymentsVersion\": \"0.1.4\"}", - "initialRound": "0", - "initialSynchronizerFeesConfig": { - "baseRateBurstAmount": 200000, - "baseRateBurstWindowMins": 20, - "extraTrafficPrice": 16.67, - "minTopupAmount": 200000, - "readVsWriteScalingFactor": 4 - }, - "isDevNet": false, - "logAsyncFlush": false, - "logLevel": "WARN", - "maxVettingDelay": "1m", - "metrics": { - "enable": true - }, - "nodeIdentifier": "Digital-Asset-2", - "onboardingFoundingSvRewardWeightBps": 100000, - "onboardingName": "Digital-Asset-2", - "onboardingRoundZeroDuration": "2 h", - "onboardingType": "found-dso", - "participantAddress": "participant", - "periodicTopologySnapshotConfig": { - "backupInterval": "12h", - "location": { - "bucket": { - "bucketName": "cn-topology-snapshots", - "jsonCredentials": "{\"projectId\":\"da-cn-shared\",\"bucketName\":\"topology-snapshot-bucket-name\",\"secretName\":\"gcp-topology-snapshot-bucket-sa-key-secret\",\"jsonCredentials\":\"topology-snapshot-bucket-sa-key-secret-creds\",\"bucketSaKeySecret\":\"gcp-topology-snapshot-bucket-sa-key-example\",\"bucketSaIamAccount\":\"da-cn-examplet@da-cn-shared.iam.gserviceaccount.com\"}", - "projectId": "da-cn-shared", - "secretName": "cn-gcp-bucket-da-cn-shared-cn-topology-snapshots" - }, - "prefix": "mock/sv-1" - } - }, - "permissionedSynchronizer": false, - "persistence": { - "databaseName": "sv_sv_1", - "port": 5432, - "postgresName": "cn-apps-pg", - "schema": "sv_sv_1", - "secretName": "cn-apps-pg-secrets", - "user": "cnadmin" - }, - "pvc": { - "volumeName": "sv-app-global-domain-migration-hd-pvc", - "volumeStorageClass": "hyperdisk-standard-rwo" - }, - "resources": { - "limits": { - "memory": "2Gi" - }, - "requests": { - "cpu": "1", - "memory": "1Gi" - } - }, - "scan": { - "internalUrl": "http://scan-app.sv-1:5012", - "publicUrl": "https://scan.sv-2.mock.global.canton.network.digitalasset.com" - }, - "spliceInstanceNames": { - "amuletName": "Amulet", - "amuletNameAcronym": "AMT", - "nameServiceName": "Amulet Name Service", - "nameServiceNameAcronym": "ANS", - "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", - "networkName": "Splice" - }, - "synchronizers": { - "additionalLegacy": [ - { - "cometBFT": { - "connectionUri": "http://global-domain-7-cometbft-cometbft-rpc:26657", - "enabled": true - }, - "mediatorAddress": "global-domain-7-mediator", - "sequencerAddress": "global-domain-7-sequencer", - "sequencerPruningConfig": { - "enabled": true, - "pruningInterval": "1 hour", - "retentionPeriod": "30 days" - }, - "sequencerPublicUrl": "https://sequencer-7.sv-2.mock.global.canton.network.digitalasset.com" - } - ], - "current": { - "enableBftSequencer": true, - "mediatorAddress": "global-domain-9-mediator", - "sequencerAddress": "global-domain-9-sequencer", - "sequencerPruningConfig": { - "enabled": true, - "pruningInterval": "1 hour", - "retentionPeriod": "30 days" - }, - "sequencerPublicUrl": "https://sequencer-9.sv-2.mock.global.canton.network.digitalasset.com" - }, - "legacy": { - "cometBFT": { - "connectionUri": "http://global-domain-8-cometbft-cometbft-rpc:26657", - "enabled": true - }, - "mediatorAddress": "global-domain-8-mediator", - "sequencerAddress": "global-domain-8-sequencer", - "sequencerPruningConfig": { - "enabled": true, - "pruningInterval": "1 hour", - "retentionPeriod": "30 days" - }, - "sequencerPublicUrl": "https://sequencer-8.sv-2.mock.global.canton.network.digitalasset.com" - }, - "skipInitialization": true, - "successor": { - "enableBftSequencer": true, - "mediatorAddress": "global-domain-10-mediator", - "sequencerAddress": "global-domain-10-sequencer", - "sequencerPruningConfig": { - "enabled": true, - "pruningInterval": "1 hour", - "retentionPeriod": "30 days" - }, - "sequencerPublicUrl": "https://sequencer-10.sv-2.mock.global.canton.network.digitalasset.com" - } - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] - } - }, - "version": "0.3.20" - }, - "name": "sv-1-sv-app", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "name": "bqdatastream", - "password": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": null - } - }, - "name": "sv-1-user-bqdatastream", - "provider": "", - "type": "gcp:sql/user:User" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-validator", - "compat": "true", - "maxHistory": 10, - "name": "validator-sv-1", - "namespace": "sv-1", - "timeout": 600, - "values": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "additionalEnvVars": [ - { - "name": "ADDITIONAL_CONFIG_TOPOLOGY_METRICS_EXPORT", - "value": "canton.validator-apps.validator_backend.automation.topology-metrics-polling-interval = 5m\n" - }, - { - "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", - "value": "canton.validator-apps.validator_backend.participant-pruning-schedule {\n cron = \"0 /10 * * * ?\"\n max-duration = \"5m\"\n retention = \"30d\"\n }" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1", - "additionalUsers": [], - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "apiRequestLogLevel": "DEBUG", - "appDars": [], - "auth": { - "audience": "https://canton.network.global", - "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json" - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "contactPoint": "sv-support@digitalasset.com", - "disableAllocateLedgerApiUserParty": true, - "disableAuth": false, - "enablePostgresMetrics": true, - "failOnAppVersionMismatch": true, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "logAsyncFlush": false, - "logLevel": "WARN", - "maxVettingDelay": "1m", - "metrics": { - "enable": true - }, - "nodeIdentifier": "Digital-Asset-2", - "participantAddress": "participant", - "participantIdentitiesDumpPeriodicBackup": { - "backupInterval": "10m", - "location": { - "bucket": { - "bucketName": "da-cn-data-dumps", - "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", - "projectId": "da-cn-devnet", - "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" - }, - "prefix": "mock/sv-1" - } - }, - "persistence": { - "databaseName": "validator_sv_1", - "port": 5432, - "postgresName": "cn-apps-pg", - "schema": "validator_sv_1", - "secretName": "cn-apps-pg-secrets", - "user": "cnadmin" - }, - "pvc": { - "volumeName": "domain-migration-validator-hd-pvc", - "volumeStorageClass": "hyperdisk-standard-rwo" - }, - "resources": { - "limits": { - "memory": "4Gi" - }, - "requests": { - "memory": "2Gi" - } - }, - "scanAddress": "http://scan-app.sv-1:5012", - "spliceInstanceNames": { - "amuletName": "Amulet", - "amuletNameAcronym": "AMT", - "nameServiceName": "Amulet Name Service", - "nameServiceNameAcronym": "ANS", - "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", - "networkName": "Splice" - }, - "svValidator": true, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ], - "topup": { - "enabled": true, - "minTopupInterval": "1m", - "targetThroughput": 0 - }, - "useSequencerConnectionsFromScan": true, - "validatorWalletUsers": [ - "google-oauth2|1234567890", - "auth0|64529b128448ded6aa68048f" - ], - "walletSweep": { - "mock::11111111111111111111111111111111111111111111111111111111111111111111": { - "maxBalanceUSD": 12345, - "minBalanceUSD": 17, - "receiver": "mock-2::222222222222222222222222222222222222222222222222222222222222222222222" - } - } - } - }, - "version": "0.3.20" - }, - "name": "sv-1-validator-sv-1", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Namespace", - "metadata": { - "labels": { - "istio-injection": "enabled" - }, - "name": "sv-1" - } - }, - "name": "sv-1", - "provider": "", - "type": "kubernetes:core/v1:Namespace" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "data": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "accessKey": "", - "secretAccessKey": "" - } - }, - "kind": "Secret", - "metadata": { - "name": "splice-app-bulk-storage-credentials", - "namespace": "sv-da-1" - }, - "type": "Opaque" - }, - "name": "sv-da-1-bulk-storage-credentials", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "length": 16, - "overrideSpecial": "_%@", - "special": true - }, - "name": "sv-da-1-cn-apps-pg-cnadmin-passwd", - "provider": "", - "type": "random:index/randomPassword:RandomPassword" - }, - { - "custom": false, - "id": "", - "inputs": { - "active": true, - "alias": "cn-apps-pg", - "cloudSqlConfig": { - "databaseVersion": "POSTGRES_14", - "enabled": true, - "enterprisePlus": false, - "flags": { - "maintenance_work_mem": "2000000", - "max_wal_size": "20480", - "random_page_cost": "1.1", - "temp_file_limit": "2147483647", - "work_mem": "16384" - }, - "maintenanceWindow": { - "day": 2, - "hour": 8 - }, - "protected": true, - "tier": "apps-pg-override-tier" - }, - "defaultUserName": "cnadmin", - "deletionProtection": true, - "instanceName": "cn-apps-pg", - "logicalDecoding": false, - "namespace": { - "logicalName": "sv-da-1", - "ns": { - "__aliases": [], - "__name": "sv-da-1", - "__providers": {}, - "__pulumiCustomResource": true, - "__pulumiResource": true, - "__pulumiType": "kubernetes:core/v1:Namespace", - "__transformations": [], - "__version": "4.28.0", - "apiVersion": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "id": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "kind": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "metadata": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "spec": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "status": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "urn": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi." - } - }, - "retainDbResourcesOnDelete": false, - "secretName": "cn-apps-pg-secrets" - }, - "name": "sv-da-1-cn-apps-pg", - "provider": "", - "type": "canton:cloud:postgres" - }, - { - "custom": true, - "id": "", - "inputs": { - "databaseVersion": "POSTGRES_14", - "deletionProtection": false, - "region": "europe-west6", - "settings": { - "activationPolicy": "ALWAYS", - "backupConfiguration": { - "enabled": true, - "pointInTimeRecoveryEnabled": true - }, - "databaseFlags": [ - { - "name": "random_page_cost", - "value": "1.1" - }, - { - "name": "temp_file_limit", - "value": "2147483647" - }, - { - "name": "max_wal_size", - "value": "20480" - }, - { - "name": "maintenance_work_mem", - "value": "2000000" - }, - { - "name": "work_mem", - "value": "16384" - } - ], - "deletionProtectionEnabled": true, - "edition": "ENTERPRISE", - "insightsConfig": { - "queryInsightsEnabled": true - }, - "ipConfiguration": { - "enablePrivatePathForGoogleCloudServices": true, - "ipv4Enabled": false, - "privateNetwork": "projects/test-project/global/networks/default" - }, - "locationPreference": { - "zone": "europe-west6-a" - }, - "maintenanceWindow": { - "day": 2, - "hour": 8 - }, - "tier": "apps-pg-override-tier", - "userLabels": { - "cluster": "mock" - } - } - }, - "name": "sv-da-1-cn-apps-pg", - "provider": "", - "type": "gcp:sql/databaseInstance:DatabaseInstance" - }, - { - "custom": true, - "id": "", - "inputs": { - "name": "cantonnet" - }, - "name": "sv-da-1-db-cn-apps-pg-cantonnet", - "provider": "", - "type": "gcp:sql/database:Database" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "imagePullSecrets": [ - { - "name": "docker-reg-cred" - } - ], - "kind": "ServiceAccount", - "metadata": { - "name": "default", - "namespace": "sv-da-1" - } - }, - "name": "sv-da-1-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-default::undefined_id", - "type": "kubernetes:core/v1:ServiceAccountPatch" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "docker-reg-cred", - "namespace": "sv-da-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" - } - }, - "type": "kubernetes.io/dockerconfigjson" - }, - "name": "sv-da-1-docker-reg-cred", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-info", - "compat": "true", - "maxHistory": 10, - "name": "info", - "namespace": "sv-da-1", - "timeout": 600, - "values": { - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "deploymentDetails": { - "configDigest": { - "allowedIpRanges": { - "type": "md5", - "value": "90eedde4a8599204a45dcb972e212c8b" - }, - "approvedSvIdentities": { - "type": "md5", - "value": "5871224b744b45122540483fddbd550f" - } - }, - "network": "test", - "sv": { - "version": "0.3.20" - }, - "synchronizer": { - "current": { - "chainIdSuffix": "4", - "migrationId": 2, - "synchronizerSerialId": 9, - "version": "0.3.20" - }, - "legacy": { - "chainIdSuffix": "4", - "migrationId": 2, - "synchronizerSerialId": 8, - "version": "0.3.20" - }, - "successor": { - "chainIdSuffix": "4", - "migrationId": 2, - "synchronizerSerialId": 10, - "version": "0.3.21" - } - } - }, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "istioVirtualService": { - "gateway": "cluster-ingress/cn-http-gateway", - "host": "info.sv-1.mock.global.canton.network.digitalasset.com" - }, - "runtimeDetails": { - "scanUrl": "http://scan-app.sv-da-1:5012", - "synchronizerSerialId": 9 - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] - }, - "version": "0.3.20" - }, - "name": "sv-da-1-info", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-cluster-ingress-runbook", - "compat": "true", - "maxHistory": 10, - "name": "ingress-sv", - "namespace": "sv-da-1", - "timeout": 600, - "values": { - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet", - "svIngressName": "sv-1", - "svNamespace": "sv-da-1" - }, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "ingress": { - "decentralizedSynchronizer": { - "migrationIds": [ - "9", - "8", - "7", - "10" - ] - } - }, - "rateLimit": { - "scan": { - "enable": false - } - }, - "spliceDomainNames": { - "nameServiceDomain": "cns" - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ], - "withSvIngress": true - }, - "version": "0.3.20" - }, - "name": "sv-da-1-ingress-sv", - "provider": "", - "type": "kubernetes:helm.sh/v3:Release" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "networking.istio.io/v1alpha3", - "kind": "EnvoyFilter", - "metadata": { - "annotations": { - "proxy.istio.io/config": "proxyStatsMatcher:\n inclusionRegexps:\n - \".*http_local_rate_limit.*\"" - }, - "name": "scan-app-rate-limit", - "namespace": "sv-da-1" - }, - "spec": { - "configPatches": [ - { - "applyTo": "HTTP_FILTER", - "match": { - "context": "SIDECAR_INBOUND", - "listener": { - "filterChain": { - "filter": { - "name": "envoy.filters.network.http_connection_manager" - } - } - } - }, - "patch": { - "operation": "INSERT_BEFORE", - "value": { - "name": "envoy.filters.http.local_ratelimit", - "typed_config": { - "@type": "type.googleapis.com/udpa.type.v1.TypedStruct", - "type_url": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", - "value": { - "stat_prefix": "http_local_rate_limiter" - } - } - } - } - }, - { - "applyTo": "HTTP_ROUTE", - "match": { - "context": "SIDECAR_INBOUND", - "routeConfiguration": { - "vhost": { - "name": "inbound|http|5012", - "route": { - "action": "ANY" - } - } - } - }, - "patch": { - "operation": "MERGE", - "value": { - "route": { - "rate_limits": [ - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "acs", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/api/scan/v0/acs" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "acs", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/api/scan/v0/acs" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocations", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocations/v1" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocations", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocations/v1" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-metadata-info", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/metadata/v1/info" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-metadata-info", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/metadata/v1/info" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-metadata-instruments", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/metadata/v1/instruments" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-metadata-instruments", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/metadata/v1/instruments" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-factory", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v1/allocation-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-factory", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v1/allocation-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-instruction", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v1" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-instruction", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v1" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-factory", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v1/transfer-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-factory", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v1/transfer-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-settlement-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation/v2/settlement-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-settlement-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation/v2/settlement-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocations-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocations/v2" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocations-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocations/v2" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v2/allocation-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v2/allocation-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-instruction-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v2" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-allocation-instruction-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/allocation-instruction/v2" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v2/transfer-factory" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-factory-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v2/transfer-factory" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-instruction-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v2" - } - } - ] - } - } - ] - }, - { - "actions": [ - { - "header_value_match": { - "descriptor_value": "registry-transfer-instruction-v2", - "expect_match": true, - "headers": [ - { - "name": ":path", - "string_match": { - "ignore_case": true, - "prefix": "/registry/transfer-instruction/v2" - } - } - ] - } - }, - { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" - } - } - ] - } - ] - }, - "typed_per_filter_config": { - "envoy.filters.http.local_ratelimit": { - "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", - "descriptors": [ - { - "entries": [ - { - "key": "header_match", - "value": "acs" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 500, - "tokens_per_fill": 500 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "acs" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 10, - "tokens_per_fill": 5 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocations" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocations" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-info" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-info" - }, - { - "key": "client_ip", - "value": "192.68.78.50" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 250, - "tokens_per_fill": 250 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-info" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-instruments" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-metadata-instruments" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-factory" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-factory" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-instruction" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-instruction" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-factory" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 730, - "tokens_per_fill": 730 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-factory" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-settlement-factory-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-settlement-factory-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocations-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocations-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-factory-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-factory-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-instruction-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-allocation-instruction-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-factory-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-factory-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-instruction-v2" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 720, - "tokens_per_fill": 720 - } - }, - { - "entries": [ - { - "key": "header_match", - "value": "registry-transfer-instruction-v2" - }, - { - "key": "client_ip" - } - ], - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 - } - } - ], - "filter_enabled": { - "default_value": { - "denominator": "HUNDRED", - "numerator": 100 - }, - "runtime_key": "local_rate_limit_enabled" - }, - "filter_enforced": { - "default_value": { - "denominator": "HUNDRED", - "numerator": 100 - }, - "runtime_key": "local_rate_limit_enforced" - }, - "response_headers_to_add": [ - { - "append_action": "OVERWRITE_IF_EXISTS_OR_ADD", - "header": { - "key": "x-local-rate-limit", - "value": "true" - } - } - ], - "stat_prefix": "http_local_rate_limiter", - "token_bucket": { - "fill_interval": "60s", - "max_tokens": 2147483647, - "tokens_per_fill": 2147483647 - } - } - } - } - } - } - ], - "workloadSelector": { - "labels": { - "app": "scan-app" - } - } - } + "location": "europe-west6", + "postgresqlProfile": { + "database": "scan_sv_1", + "port": 5432, + "username": "bqdatastream" + }, + "privateConnectivity": {} }, - "name": "sv-da-1-scan-app-rate-limit", + "name": "sv-1-scan-update-history-cxn", "provider": "", - "type": "kubernetes:networking.istio.io/v1alpha3:EnvoyFilter" + "type": "gcp:datastream/connectionProfile:ConnectionProfile" }, { "custom": true, "id": "", "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-scan", - "compat": "true", - "maxHistory": 10, - "name": "scan", - "namespace": "sv-da-1", - "timeout": 600, - "values": { - "additionalEnvVars": [ - { - "name": "CUSTOM_MOCK_ENV_VAR_NAME", - "value": "CUSTOM_MOCK_ENV_VAR_VALUE" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomScanAppJvmFlag", - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "apiRequestLogLevel": "DEBUG", - "bulkStorage": { - "committed": { - "bucketName": "mock-sv-da-1-bulk-committed", - "endpoint": "https://storage.googleapis.com", - "region": "europe-west6", - "secretName": "splice-app-bulk-storage-credentials" - }, - "staging": { - "bucketName": "mock-sv-da-1-bulk-staging", - "endpoint": "https://storage.googleapis.com", - "region": "europe-west6", - "secretName": "splice-app-bulk-storage-credentials" - } - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "enablePostgresMetrics": true, - "failOnAppVersionMismatch": true, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "isFirstSv": false, - "logAsyncFlush": false, - "logLevel": "INFO", - "metrics": { - "enable": true - }, - "participantAddress": "participant", - "persistence": { - "databaseName": "scan_sv_da_1", - "port": 5432, - "postgresName": "cn-apps-pg", - "schema": "scan_sv_da_1", - "secretName": "cn-apps-pg-secrets", - "user": "cnadmin" - }, - "publicUrl": "https://scan.sv-1.mock.global.canton.network.digitalasset.com", - "resources": { - "limits": { - "memory": "2048Mi" - }, - "requests": { - "cpu": "0.5", - "memory": "1536Mi" - } - }, - "spliceInstanceNames": { - "amuletName": "Amulet", - "amuletNameAcronym": "AMT", - "nameServiceName": "Amulet Name Service", - "nameServiceNameAcronym": "ANS", - "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", - "networkName": "Splice" - }, - "sponsorScanUrl": "http://scan-app.sv-1:5012", - "synchronizers": { - "current": { - "cantonBft": { - "p2pUrl": "https://sequencer-p2p-9.sv-1.mock.global.canton.network.digitalasset.com" - }, - "mediator": "global-domain-9-mediator", - "sequencer": "global-domain-9-sequencer" - }, - "legacy": { - "mediator": "global-domain-8-mediator", - "sequencer": "global-domain-8-sequencer" - }, - "successor": { - "cantonBft": { - "p2pUrl": "https://sequencer-p2p-10.sv-1.mock.global.canton.network.digitalasset.com" - }, - "mediator": "global-domain-10-mediator", - "sequencer": "global-domain-10-sequencer" - } - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] + "displayName": "sv-1-scan-update-history-datastream-vpc", + "labels": { + "cluster": "mock" }, - "version": "0.3.20" + "location": "europe-west6", + "privateConnectionId": "sv-1-scan-update-history-datastream-vpc", + "vpcPeeringConfig": { + "subnet": "1.2.3.16/29", + "vpc": "projects/test-project/global/networks/default" + } }, - "name": "sv-da-1-scan", + "name": "sv-1-scan-update-history-datastream-vpc", "provider": "", - "type": "kubernetes:helm.sh/v3:Release" + "type": "gcp:datastream/privateConnection:PrivateConnection" }, { "custom": true, "id": "", "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-sv-node", - "compat": "true", - "maxHistory": 10, - "name": "sv-app", - "namespace": "sv-da-1", - "timeout": 600, - "values": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "additionalEnvVars": [ - { - "name": "CUSTOM_MOCK_ENV_VAR_NAME", - "value": "CUSTOM_MOCK_ENV_VAR_VALUE" - }, - { - "name": "ADDITIONAL_CONFIG_NO_BFT_SEQUENCER_CONNECTION", - "value": "canton.sv-apps.sv.bft-sequencer-connection = false" - }, - { - "name": "ADDITIONAL_CONFIG_MEDIATOR_PRUNING", - "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.mediator.pruning {\n cron = \"0 0 * * *\"\n max-duration = \"30m\"\n retention = \"7d\"\n }" - }, - { - "name": "ADDITIONAL_CONFIG_CANTON_BFT_PRUNING", - "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.sequencer.canton-bft-pruning {\n cron = \"0 /10 * * * ?\"\n max-duration = \"5m\"\n retention = \"15 days\"\n }" - }, - { - "name": "ADDITIONAL_CONFIG_ADDITIONAL_PACKAGES_TO_UNVET", - "value": "canton.sv-apps.sv.additional-packages-to-unvet.\"splice-wallet-payments\" = [\"0.1.15\", \"0.1.16\"]\ncanton.sv-apps.sv.additional-packages-to-unvet.\"splice-amulet\" = [\"0.1.15\"]" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomSvAppJvmFlag", - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ + "backfillAll": {}, + "desiredState": "RUNNING", + "destinationConfig": { + "bigqueryDestinationConfig": { + "dataFreshness": "14400s", + "singleTargetDataset": { + "datasetId": "projects/undefined/datasets/mock_da2_scan" + } + } + }, + "displayName": "sv-1-scan-update-history", + "labels": { + "cluster": "mock" + }, + "location": "europe-west6", + "sourceConfig": { + "postgresqlSourceConfig": { + "includeObjects": { + "postgresqlSchemas": [ + { + "postgresqlTables": [ + { + "table": "update_history_creates" + }, + { + "table": "update_history_exercises" + }, { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] + "table": "scan_verdict_store" + }, + { + "table": "scan_verdict_transaction_view_store" + }, + { + "table": "app_activity_record_store" } - ] - } - } - }, - "apiRequestLogLevel": "DEBUG", - "approvedSvIdentities": [ - { - "name": "Digital-Asset-2", - "publicKey": "sv1-id-public-key", - "rewardWeightBps": 100000 - }, - { - "name": "SV1", - "publicKey": "PUBLIC_KEY_1==", - "rewardWeightBps": 1000000 - }, - { - "extraBeneficiaries": [ - { - "beneficiary": "mock-validator-1::123456789012345678901234567890123456789012234567890123456789012345678", - "weight": 100000 - }, - { - "beneficiary": "Broadridge-validator-1::1220c89a73e7b47f16dfb48a521eeedfe2a8620ea1b19b815ab427388d9f5427faa5", - "weight": 50000 - } - ], - "name": "SV2", - "publicKey": "PUBLIC_KEY_2==", - "rewardWeightBps": 150000 - }, - { - "name": "Digital-Asset-1", - "publicKey": "svda1-mock-id-public-key", - "rewardWeightBps": 150000 - }, - { - "name": "DA-Helm-Test-Node", - "publicKey": "sv-id-public-key", - "rewardWeightBps": 10000 - } - ], - "auth": { - "audience": "https://canton.network.global", - "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json" - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "contactPoint": "sv-support@digitalasset.com", - "decentralizedSynchronizerUrl": "http://global-domain-9-sequencer.sv-1:5008", - "delegatelessAutomationExpectedTaskDuration": 5000, - "delegatelessAutomationExpiredRewardCouponBatchSize": 100, - "delegatelessAutomationExpiredRewardCouponNumBatches": 100, - "disableOnboardingParticipantPromotionDelay": false, - "enablePostgresMetrics": true, - "expectedValidatorOnboardings": [], - "failOnAppVersionMismatch": true, - "identitiesExport": { - "bucket": { - "bucketName": "da-cn-data-dumps", - "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", - "projectId": "da-cn-devnet", - "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" - }, - "prefix": "mock/sv-da-1" - }, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "initialAmuletPrice": "0.0517", - "isDevNet": false, - "joinWithKeyOnboarding": { - "sponsorApiUrl": "http://sv-app.sv-1:5014" - }, - "logAsyncFlush": false, - "logLevel": "INFO", - "maxVettingDelay": "1m", - "metrics": { - "enable": true - }, - "nodeIdentifier": "Digital-Asset-1", - "onboardingName": "Digital-Asset-1", - "onboardingType": "join-with-key", - "participantAddress": "participant", - "permissionedSynchronizer": false, - "persistence": { - "databaseName": "sv_sv_da_1", - "port": 5432, - "postgresName": "cn-apps-pg", - "schema": "sv_sv_da_1", - "secretName": "cn-apps-pg-secrets", - "user": "cnadmin" - }, - "pvc": { - "volumeName": "sv-app-global-domain-migration-hd-pvc", - "volumeStorageClass": "hyperdisk-standard-rwo" - }, - "resources": { - "limits": { - "memory": "2Gi" - }, - "requests": { - "cpu": "1", - "memory": "1Gi" - } - }, - "scan": { - "internalUrl": "http://scan-app.sv-da-1:5012", - "publicUrl": "https://scan.sv-1.mock.global.canton.network.digitalasset.com" - }, - "spliceInstanceNames": { - "amuletName": "Amulet", - "amuletNameAcronym": "AMT", - "nameServiceName": "Amulet Name Service", - "nameServiceNameAcronym": "ANS", - "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", - "networkName": "Splice" - }, - "synchronizers": { - "additionalLegacy": [ - { - "cometBFT": { - "connectionUri": "http://global-domain-7-cometbft-cometbft-rpc:26657", - "enabled": true, - "externalGovernanceKey": true - }, - "mediatorAddress": "global-domain-7-mediator", - "sequencerAddress": "global-domain-7-sequencer", - "sequencerPruningConfig": { - "enabled": true, - "pruningInterval": "1 hour", - "retentionPeriod": "30 days" - }, - "sequencerPublicUrl": "https://sequencer-7.sv-1.mock.global.canton.network.digitalasset.com" + ], + "schema": "scan_sv_1" } - ], - "current": { - "enableBftSequencer": true, - "mediatorAddress": "global-domain-9-mediator", - "sequencerAddress": "global-domain-9-sequencer", - "sequencerPruningConfig": { - "enabled": true, - "pruningInterval": "1 hour", - "retentionPeriod": "30 days" - }, - "sequencerPublicUrl": "https://sequencer-9.sv-1.mock.global.canton.network.digitalasset.com" - }, - "legacy": { - "cometBFT": { - "connectionUri": "http://global-domain-8-cometbft-cometbft-rpc:26657", - "enabled": true, - "externalGovernanceKey": true - }, - "mediatorAddress": "global-domain-8-mediator", - "sequencerAddress": "global-domain-8-sequencer", - "sequencerPruningConfig": { - "enabled": true, - "pruningInterval": "1 hour", - "retentionPeriod": "30 days" - }, - "sequencerPublicUrl": "https://sequencer-8.sv-1.mock.global.canton.network.digitalasset.com" - }, - "skipInitialization": true, - "successor": { - "enableBftSequencer": true, - "mediatorAddress": "global-domain-10-mediator", - "sequencerAddress": "global-domain-10-sequencer", - "sequencerPruningConfig": { - "enabled": true, - "pruningInterval": "1 hour", - "retentionPeriod": "30 days" - }, - "sequencerPublicUrl": "https://sequencer-10.sv-1.mock.global.canton.network.digitalasset.com" - } + ] }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ] + "publication": "update_history_datastream_pub", + "replicationSlot": "update_history_datastream_r_slot" } }, - "version": "0.3.20" + "streamId": "sv-1-scan-update-history" }, - "name": "sv-da-1-sv-app", + "name": "sv-1-scan-update-history", "provider": "", - "type": "kubernetes:helm.sh/v3:Release" + "type": "gcp:datastream/stream:Stream" }, { "custom": true, "id": "", "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-validator", - "compat": "true", - "maxHistory": 10, - "name": "validator-sv-da-1", - "namespace": "sv-da-1", - "timeout": 600, - "values": { + "name": "bqdatastream", + "password": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "additionalEnvVars": [ - { - "name": "ADDITIONAL_CONFIG_NO_BFT_SEQUENCER_CONNECTION", - "value": "canton.validator-apps.validator_backend.disable-sv-validator-bft-sequencer-connection = true" - }, - { - "name": "ADDITIONAL_CONFIG_TOPOLOGY_METRICS_EXPORT", - "value": "canton.validator-apps.validator_backend.automation.topology-metrics-polling-interval = 5m\n" - }, - { - "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", - "value": "canton.validator-apps.validator_backend.participant-pruning-schedule {\n cron = \"0 0 * * *\"\n max-duration = \"30m\"\n retention = \"7d\"\n }" - } - ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1", - "additionalUsers": [], - "affinity": { - "nodeAffinity": { - "requiredDuringSchedulingIgnoredDuringExecution": { - "nodeSelectorTerms": [ - { - "matchExpressions": [ - { - "key": "cn_apps", - "operator": "Exists" - }, - { - "key": "cn_apps", - "operator": "In", - "values": [ - "hyperdisk" - ] - } - ] - } - ] - } - } - }, - "apiRequestLogLevel": "DEBUG", - "appDars": [], - "auth": { - "audience": "https://canton.network.global", - "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json" - }, - "cluster": { - "dnsName": "mock.global.canton.network.digitalasset.com", - "fixedTokens": false, - "hostname": "mock.global.canton.network.digitalasset.com", - "name": "cn-mocknet" - }, - "contactPoint": "sv-support@digitalasset.com", - "disableAllocateLedgerApiUserParty": true, - "disableAuth": false, - "enablePostgresMetrics": true, - "failOnAppVersionMismatch": true, - "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "logAsyncFlush": false, - "logLevel": "INFO", - "maxVettingDelay": "1m", - "metrics": { - "enable": true - }, - "nodeIdentifier": "Digital-Asset-1", - "participantAddress": "participant", - "participantIdentitiesDumpPeriodicBackup": { - "backupInterval": "10m", - "location": { - "bucket": { - "bucketName": "da-cn-data-dumps", - "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", - "projectId": "da-cn-devnet", - "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" - }, - "prefix": "mock/sv-da-1" - } - }, - "persistence": { - "databaseName": "validator_sv_da_1", - "port": 5432, - "postgresName": "cn-apps-pg", - "schema": "validator_sv_da_1", - "secretName": "cn-apps-pg-secrets", - "user": "cnadmin" - }, - "pvc": { - "volumeName": "domain-migration-validator-hd-pvc", - "volumeStorageClass": "hyperdisk-standard-rwo" - }, - "resources": { - "limits": { - "memory": "4Gi" - }, - "requests": { - "memory": "2Gi" - } - }, - "scanAddress": "http://scan-app.sv-da-1:5012", - "spliceInstanceNames": { - "amuletName": "Amulet", - "amuletNameAcronym": "AMT", - "nameServiceName": "Amulet Name Service", - "nameServiceNameAcronym": "ANS", - "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", - "networkName": "Splice" - }, - "svValidator": true, - "synchronizer": { - "connectionType": "trust-single", - "url": "https://sequencer-9.sv-2.mock.global.canton.network.digitalasset.com" - }, - "tolerations": [ - { - "effect": "NoSchedule", - "key": "cn_apps", - "operator": "Exists" - } - ], - "topup": { - "enabled": true, - "minTopupInterval": "1m", - "targetThroughput": 0 - }, - "useSequencerConnectionsFromScan": true, - "validatorWalletUsers": [ - "google-oauth2|1234567890", - "auth0|68c2c41e6470184569e4521e" - ], - "walletSweep": { - "mock-3::33333333333333333333333333333333333333333333333333333333333333333333": { - "maxBalanceUSD": 67890, - "minBalanceUSD": 42, - "receiver": "mock-4::444444444444444444444444444444444444444444444444444444444444444444444" - } - } - } - }, - "version": "0.3.20" + "value": null + } }, - "name": "sv-da-1-validator-sv-da-1", + "name": "sv-1-user-bqdatastream", "provider": "", - "type": "kubernetes:helm.sh/v3:Release" + "type": "gcp:sql/user:User" }, { "custom": true, @@ -6614,39 +396,11 @@ "labels": { "istio-injection": "enabled" }, - "name": "sv-da-1" + "name": "sv-1" } }, - "name": "sv-da-1", + "name": "sv-1", "provider": "", "type": "kubernetes:core/v1:Namespace" - }, - { - "custom": true, - "id": "", - "inputs": { - "name": "cnadmin", - "password": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": null - } - }, - "name": "user-sv-1-cn-apps-pg-cnadmin", - "provider": "", - "type": "gcp:sql/user:User" - }, - { - "custom": true, - "id": "", - "inputs": { - "name": "cnadmin", - "password": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": null - } - }, - "name": "user-sv-da-1-cn-apps-pg-cnadmin", - "provider": "", - "type": "gcp:sql/user:User" } ] diff --git a/cluster/expected/gcp/expected.json b/cluster/expected/gcp/expected.json index 5353532a0c..09dc65895f 100644 --- a/cluster/expected/gcp/expected.json +++ b/cluster/expected/gcp/expected.json @@ -206,13 +206,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"adminUser\": \"grafana-keys-admin-user\"\n , \"adminPassword\": \"grafana-keys-admin-password\"}" - } - }, + "inputs": {}, "name": "grafana-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -273,13 +267,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"publicKey\": \"sv-id-public-key\", \"privateKey\": \"sv-id-private-key\"}" - } - }, + "inputs": {}, "name": "sv-id-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -308,13 +296,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv1-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv1-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv1-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv1-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -343,13 +325,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv10-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv10-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv10-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv10-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -378,13 +354,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv11-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv11-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv11-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv11-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -413,13 +383,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv12-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv12-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv12-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv12-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -448,13 +412,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv13-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv13-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv13-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv13-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -483,13 +441,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv14-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv14-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv14-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv14-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -518,13 +470,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv15-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv15-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv15-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv15-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -553,13 +499,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv16-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv16-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv16-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv16-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -588,13 +528,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv2-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv2-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv2-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv2-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -623,13 +557,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"publicKey\": \"sv2-id-public-key\", \"privateKey\": \"sv2-id-private-key\"}" - } - }, + "inputs": {}, "name": "sv2-id-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -658,13 +586,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv3-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv3-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv3-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv3-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -693,13 +615,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"publicKey\": \"sv3-id-public-key\", \"privateKey\": \"sv3-id-private-key\"}" - } - }, + "inputs": {}, "name": "sv3-id-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -728,13 +644,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv4-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv4-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv4-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv4-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -763,13 +673,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"publicKey\": \"sv4-id-public-key\", \"privateKey\": \"sv4-id-private-key\"}" - } - }, + "inputs": {}, "name": "sv4-id-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -798,13 +702,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv5-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv5-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv5-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv5-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -833,13 +731,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv6-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv6-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv6-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv6-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -868,13 +760,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv7-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv7-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv7-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv7-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -903,13 +789,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv8-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv8-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv8-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv8-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" @@ -938,13 +818,7 @@ { "custom": true, "id": "", - "inputs": { - "secret": "undefined_id", - "secretData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": "{\"nodePrivateKey\": \"sv9-cometbft-keys-node-private-key\", \"validatorPrivateKey\": \"sv9-cometbft-keys-validator-private-key\"\n , \"validatorPublicKey\": \"sv9-cometbft-keys-validator-public-key\"}" - } - }, + "inputs": {}, "name": "sv9-cometbft-keys-secretversion", "provider": "", "type": "gcp:secretmanager/secretVersion:SecretVersion" diff --git a/cluster/expected/infra/expected.json b/cluster/expected/infra/expected.json index cd85d2e63c..1bc189c141 100644 --- a/cluster/expected/infra/expected.json +++ b/cluster/expected/infra/expected.json @@ -12,7 +12,7 @@ ] }, "name": "LedgerApiScopessv1", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/resourceServerScopes:ResourceServerScopes" }, { @@ -28,7 +28,7 @@ ] }, "name": "LedgerApiScopessvda1", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/resourceServerScopes:ResourceServerScopes" }, { @@ -41,7 +41,7 @@ "tokenLifetime": 86400 }, "name": "LedgerApisv1", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/resourceServer:ResourceServer" }, { @@ -54,18 +54,17 @@ "tokenLifetime": 86400 }, "name": "LedgerApisvda1", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/resourceServer:ResourceServer" }, { "custom": true, "id": "", "inputs": { - "authenticationMethod": "none", - "clientId": "Splitwell UI (Pulumi managed, test-stack)_id" + "authenticationMethod": "none" }, "name": "SplitwellUiAppCredentials", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientCredentials:ClientCredentials" }, { @@ -125,7 +124,7 @@ ] }, "name": "SplitwellUiApp", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { @@ -138,7 +137,7 @@ "tokenLifetime": 86400 }, "name": "SvAppApisv1", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/resourceServer:ResourceServer" }, { @@ -151,18 +150,17 @@ "tokenLifetime": 86400 }, "name": "SvAppApisvda1", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/resourceServer:ResourceServer" }, { "custom": true, "id": "", "inputs": { - "authenticationMethod": "none", - "clientId": "ANS UI (Pulumi managed, test-stack)_id" + "authenticationMethod": "none" }, "name": "SvCnsUiCredentials", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientCredentials:ClientCredentials" }, { @@ -206,18 +204,17 @@ ] }, "name": "SvCnsUi", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { "custom": true, "id": "", "inputs": { - "authenticationMethod": "none", - "clientId": "SV UI (Pulumi managed, test-stack)_id" + "authenticationMethod": "none" }, "name": "SvSvUiCredentials", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientCredentials:ClientCredentials" }, { @@ -261,18 +258,17 @@ ] }, "name": "SvSvUi", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { "custom": true, "id": "", "inputs": { - "authenticationMethod": "none", - "clientId": "Wallet UI (Pulumi managed, test-stack)_id" + "authenticationMethod": "none" }, "name": "SvWalletUiCredentials", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientCredentials:ClientCredentials" }, { @@ -316,7 +312,7 @@ ] }, "name": "SvWalletUi", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::sv::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { @@ -329,7 +325,7 @@ "tokenLifetime": 86400 }, "name": "ValidatorAppApisv1", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/resourceServer:ResourceServer" }, { @@ -342,7 +338,7 @@ "tokenLifetime": 86400 }, "name": "ValidatorAppApisvda1", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/resourceServer:ResourceServer" }, { @@ -2806,11 +2802,10 @@ "id": "", "inputs": { "audience": "https://sv.sv-1.test-stack.canton.network/api", - "clientId": "SV1 SV Backend (Pulumi managed, test-stack)_id", "scopes": [] }, "name": "sv1SvBackendAppAppGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -2818,13 +2813,12 @@ "id": "", "inputs": { "audience": "https://ledger_api.sv-1.test-stack.canton.network", - "clientId": "SV1 SV Backend (Pulumi managed, test-stack)_id", "scopes": [ "daml_ledger_api" ] }, "name": "sv1SvBackendAppLedgerGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -2832,13 +2826,12 @@ "id": "", "inputs": { "audience": "https://canton.network.global", - "clientId": "SV1 SV Backend (Pulumi managed, test-stack)_id", "scopes": [ "daml_ledger_api" ] }, "name": "sv1SvBackendAppLegacyGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -2850,18 +2843,17 @@ "name": "SV1 SV Backend (Pulumi managed, test-stack)" }, "name": "sv1SvBackendApp", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { "custom": true, "id": "", "inputs": { - "authenticationMethod": "none", - "clientId": "SV1 UI (Pulumi managed, test-stack)_id" + "authenticationMethod": "none" }, "name": "sv1UiAppCredentials", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientCredentials:ClientCredentials" }, { @@ -2921,7 +2913,7 @@ ] }, "name": "sv1UiApp", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { @@ -2929,11 +2921,10 @@ "id": "", "inputs": { "audience": "https://validator.sv-1.test-stack.canton.network/api", - "clientId": "SV1 Validator Backend (Pulumi managed, test-stack)_id", "scopes": [] }, "name": "sv1ValidatorBackendAppAppGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -2941,13 +2932,12 @@ "id": "", "inputs": { "audience": "https://ledger_api.sv-1.test-stack.canton.network", - "clientId": "SV1 Validator Backend (Pulumi managed, test-stack)_id", "scopes": [ "daml_ledger_api" ] }, "name": "sv1ValidatorBackendAppLedgerGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -2955,13 +2945,12 @@ "id": "", "inputs": { "audience": "https://canton.network.global", - "clientId": "SV1 Validator Backend (Pulumi managed, test-stack)_id", "scopes": [ "daml_ledger_api" ] }, "name": "sv1ValidatorBackendAppLegacyGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -2973,7 +2962,7 @@ "name": "SV1 Validator Backend (Pulumi managed, test-stack)" }, "name": "sv1ValidatorBackendApp", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { @@ -2981,11 +2970,10 @@ "id": "", "inputs": { "audience": "https://sv.sv-da-1.test-stack.canton.network/api", - "clientId": "SVDA1 SV Backend (Pulumi managed, test-stack)_id", "scopes": [] }, "name": "svda1SvBackendAppAppGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -2993,13 +2981,12 @@ "id": "", "inputs": { "audience": "https://ledger_api.sv-da-1.test-stack.canton.network", - "clientId": "SVDA1 SV Backend (Pulumi managed, test-stack)_id", "scopes": [ "daml_ledger_api" ] }, "name": "svda1SvBackendAppLedgerGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -3007,13 +2994,12 @@ "id": "", "inputs": { "audience": "https://canton.network.global", - "clientId": "SVDA1 SV Backend (Pulumi managed, test-stack)_id", "scopes": [ "daml_ledger_api" ] }, "name": "svda1SvBackendAppLegacyGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -3025,18 +3011,17 @@ "name": "SVDA1 SV Backend (Pulumi managed, test-stack)" }, "name": "svda1SvBackendApp", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { "custom": true, "id": "", "inputs": { - "authenticationMethod": "none", - "clientId": "SVDA1 UI (Pulumi managed, test-stack)_id" + "authenticationMethod": "none" }, "name": "svda1UiAppCredentials", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientCredentials:ClientCredentials" }, { @@ -3096,7 +3081,7 @@ ] }, "name": "svda1UiApp", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { @@ -3104,11 +3089,10 @@ "id": "", "inputs": { "audience": "https://validator.sv-da-1.test-stack.canton.network/api", - "clientId": "SVDA1 Validator Backend (Pulumi managed, test-stack)_id", "scopes": [] }, "name": "svda1ValidatorBackendAppAppGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -3116,13 +3100,12 @@ "id": "", "inputs": { "audience": "https://ledger_api.sv-da-1.test-stack.canton.network", - "clientId": "SVDA1 Validator Backend (Pulumi managed, test-stack)_id", "scopes": [ "daml_ledger_api" ] }, "name": "svda1ValidatorBackendAppLedgerGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -3130,13 +3113,12 @@ "id": "", "inputs": { "audience": "https://canton.network.global", - "clientId": "SVDA1 Validator Backend (Pulumi managed, test-stack)_id", "scopes": [ "daml_ledger_api" ] }, "name": "svda1ValidatorBackendAppLegacyGrant", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientGrant:ClientGrant" }, { @@ -3148,7 +3130,7 @@ "name": "SVDA1 Validator Backend (Pulumi managed, test-stack)" }, "name": "svda1ValidatorBackendApp", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { @@ -3175,11 +3157,10 @@ "custom": true, "id": "", "inputs": { - "authenticationMethod": "none", - "clientId": "Validator1 UI (Pulumi managed, test-stack)_id" + "authenticationMethod": "none" }, "name": "validator1UiAppCredentials", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientCredentials:ClientCredentials" }, { @@ -3239,18 +3220,17 @@ ] }, "name": "validator1UiApp", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::dev::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { "custom": true, "id": "", "inputs": { - "authenticationMethod": "none", - "clientId": "ANS UI (Pulumi managed, test-stack)_id" + "authenticationMethod": "none" }, "name": "validatorCnsUiCredentials", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::validator::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::validator::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientCredentials:ClientCredentials" }, { @@ -3302,18 +3282,17 @@ ] }, "name": "validatorCnsUi", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::validator::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::validator::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { "custom": true, "id": "", "inputs": { - "authenticationMethod": "none", - "clientId": "Wallet UI (Pulumi managed, test-stack)_id" + "authenticationMethod": "none" }, "name": "validatorWalletUiCredentials", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::validator::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::validator::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/clientCredentials:ClientCredentials" }, { @@ -3365,7 +3344,7 @@ ] }, "name": "validatorWalletUi", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::validator::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:auth0::validator::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "auth0:index/client:Client" }, { diff --git a/cluster/expected/multi-validator/expected.json b/cluster/expected/multi-validator/expected.json index 0d8ab6e018..73a63b17af 100644 --- a/cluster/expected/multi-validator/expected.json +++ b/cluster/expected/multi-validator/expected.json @@ -4751,7 +4751,7 @@ } }, "name": "multi-validator-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-multi-validator-default::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-multi-validator-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { diff --git a/cluster/expected/operator/expected.json b/cluster/expected/operator/expected.json index 9b1ffb799e..c0b849ffea 100644 --- a/cluster/expected/operator/expected.json +++ b/cluster/expected/operator/expected.json @@ -485,7 +485,7 @@ } }, "name": "operator-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-operator-default::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-operator-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { diff --git a/cluster/expected/splitwell/expected.json b/cluster/expected/splitwell/expected.json index d6479a67ad..d978b51f48 100644 --- a/cluster/expected/splitwell/expected.json +++ b/cluster/expected/splitwell/expected.json @@ -501,7 +501,7 @@ } }, "name": "splitwell-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-splitwell-default::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-splitwell-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { diff --git a/cluster/expected/sv-canton/expected.json b/cluster/expected/sv-canton/expected.json index e3f9c99b94..28ed6af695 100644 --- a/cluster/expected/sv-canton/expected.json +++ b/cluster/expected/sv-canton/expected.json @@ -4304,7 +4304,7 @@ } }, "name": "sv-1-sv-canton-migration-10", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-10::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-10::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -4346,7 +4346,7 @@ } }, "name": "sv-1-sv-canton-migration-5", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-5::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-5::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -4388,7 +4388,7 @@ } }, "name": "sv-1-sv-canton-migration-6", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-6::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-6::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -4430,7 +4430,7 @@ } }, "name": "sv-1-sv-canton-migration-7", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-7::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-7::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -4472,7 +4472,7 @@ } }, "name": "sv-1-sv-canton-migration-8", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-8::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-8::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -4514,7 +4514,7 @@ } }, "name": "sv-1-sv-canton-migration-9", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-9::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv-canton-migration-9::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -7886,7 +7886,7 @@ } }, "name": "sv-da-1-sv-canton-migration-10", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-10::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-10::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -7928,7 +7928,7 @@ } }, "name": "sv-da-1-sv-canton-migration-5", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-5::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-5::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -7970,7 +7970,7 @@ } }, "name": "sv-da-1-sv-canton-migration-6", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-6::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-6::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -8012,7 +8012,7 @@ } }, "name": "sv-da-1-sv-canton-migration-7", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-7::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-7::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -8054,7 +8054,7 @@ } }, "name": "sv-da-1-sv-canton-migration-8", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-8::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-8::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -8096,7 +8096,7 @@ } }, "name": "sv-da-1-sv-canton-migration-9", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-9::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv-canton-migration-9::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -10768,7 +10768,7 @@ } }, "name": "sv-sv-canton-migration-10", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-10::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-10::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -10810,7 +10810,7 @@ } }, "name": "sv-sv-canton-migration-5", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-5::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-5::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -10852,7 +10852,7 @@ } }, "name": "sv-sv-canton-migration-6", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-6::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-6::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -10894,7 +10894,7 @@ } }, "name": "sv-sv-canton-migration-7", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-7::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-7::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -10936,7 +10936,7 @@ } }, "name": "sv-sv-canton-migration-8", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-8::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-8::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -10978,7 +10978,7 @@ } }, "name": "sv-sv-canton-migration-9", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-9::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv-canton-migration-9::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index 17f2d0a2c7..b4b1aceb72 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -16,7 +16,7 @@ } }, "name": "cluster-ingress-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-cluster-ingress-default::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-cluster-ingress-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -1593,7 +1593,7 @@ } }, "name": "sv-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-default::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index 88cb9e079f..90445f6f6a 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -1,4 +1,70 @@ [ + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "postgresPassword": "" + } + }, + "kind": "Secret", + "metadata": { + "name": "cn-apps-pg-secrets", + "namespace": "sv-1" + }, + "type": "Opaque" + }, + "name": "cn-app-sv-1-cn-apps-pg-secrets", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "json-credentials": "eyJidWNrZXROYW1lIjoiZGF0YS1leHBvcnQtYnVja2V0LW5hbWUiLCJzZWNyZXROYW1lIjoiZGF0YS1leHBvcnQtYnVja2V0LXNhLWtleS1zZWNyZXQiLCJqc29uQ3JlZGVudGlhbHMiOiJkYXRhLWV4cG9ydC1idWNrZXQtc2Eta2V5LXNlY3JldC1jcmVkcyJ9" + } + }, + "kind": "Secret", + "metadata": { + "name": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps", + "namespace": "sv-1" + }, + "type": "Opaque" + }, + "name": "cn-app-sv-1-cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "json-credentials": "eyJwcm9qZWN0SWQiOiJkYS1jbi1zaGFyZWQiLCJidWNrZXROYW1lIjoidG9wb2xvZ3ktc25hcHNob3QtYnVja2V0LW5hbWUiLCJzZWNyZXROYW1lIjoiZ2NwLXRvcG9sb2d5LXNuYXBzaG90LWJ1Y2tldC1zYS1rZXktc2VjcmV0IiwianNvbkNyZWRlbnRpYWxzIjoidG9wb2xvZ3ktc25hcHNob3QtYnVja2V0LXNhLWtleS1zZWNyZXQtY3JlZHMiLCJidWNrZXRTYUtleVNlY3JldCI6ImdjcC10b3BvbG9neS1zbmFwc2hvdC1idWNrZXQtc2Eta2V5LWV4YW1wbGUiLCJidWNrZXRTYUlhbUFjY291bnQiOiJkYS1jbi1leGFtcGxldEBkYS1jbi1zaGFyZWQuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20ifQ==" + } + }, + "kind": "Secret", + "metadata": { + "name": "cn-gcp-bucket-da-cn-shared-cn-topology-snapshots", + "namespace": "sv-1" + }, + "type": "Opaque" + }, + "name": "cn-app-sv-1-cn-gcp-bucket-da-cn-shared-cn-topology-snapshots", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, { "custom": true, "id": "", @@ -21,6 +87,73 @@ "provider": "", "type": "kubernetes:core/v1:Secret" }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "postgresPassword": "" + } + }, + "kind": "Secret", + "metadata": { + "name": "cn-apps-pg-secrets", + "namespace": "sv-da-1" + }, + "type": "Opaque" + }, + "name": "cn-app-sv-da-1-cn-apps-pg-secrets", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "json-credentials": "eyJidWNrZXROYW1lIjoiZGF0YS1leHBvcnQtYnVja2V0LW5hbWUiLCJzZWNyZXROYW1lIjoiZGF0YS1leHBvcnQtYnVja2V0LXNhLWtleS1zZWNyZXQiLCJqc29uQ3JlZGVudGlhbHMiOiJkYXRhLWV4cG9ydC1idWNrZXQtc2Eta2V5LXNlY3JldC1jcmVkcyJ9" + } + }, + "kind": "Secret", + "metadata": { + "name": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps", + "namespace": "sv-da-1" + }, + "type": "Opaque" + }, + "name": "cn-app-sv-da-1-cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "private": "c3ZkYTEtbW9jay1pZC1wcml2YXRlLWtleQ==", + "public": "c3ZkYTEtbW9jay1pZC1wdWJsaWMta2V5" + } + }, + "kind": "Secret", + "metadata": { + "name": "cn-app-sv-key", + "namespace": "sv-da-1" + }, + "type": "Opaque" + }, + "name": "cn-app-sv-da-1-key", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, { "custom": true, "id": "", @@ -93,7 +226,7 @@ "inputs": { "enableServerSideApply": "true" }, - "name": "k8s-imgpull-sv-1-sv", + "name": "k8s-imgpull-sv-1-default", "provider": "", "type": "pulumi:providers:kubernetes" }, @@ -103,7 +236,7 @@ "inputs": { "enableServerSideApply": "true" }, - "name": "k8s-imgpull-sv-da-1-sv", + "name": "k8s-imgpull-sv-1-sv", "provider": "", "type": "pulumi:providers:kubernetes" }, @@ -113,7 +246,7 @@ "inputs": { "enableServerSideApply": "true" }, - "name": "k8s-imgpull-sv-sv", + "name": "k8s-imgpull-sv-da-1-default", "provider": "", "type": "pulumi:providers:kubernetes" }, @@ -121,132 +254,4755 @@ "custom": true, "id": "", "inputs": { - "condition": { - "description": "(managed by Pulumi)", - "expression": "resource.name.startsWith(\"projects/da-cn-devnet/locations/us-central1/keyRings/sv-da-1_participant_mock\")", - "title": "\"sv-da-1_participant_mock\" keyring" - }, - "role": "roles/cloudkms.admin" - }, - "name": "mock-sv-da-1-kms-roles/cloudkms.admin-\"sv-da-1_participant_mock\"-keyring-iam", - "provider": "", - "type": "gcp:projects/iAMMember:IAMMember" - }, - { - "custom": true, - "id": "", - "inputs": { - "condition": { - "description": "(managed by Pulumi)", - "expression": "resource.name.startsWith(\"projects/da-cn-devnet/locations/us-central1/keyRings/sv-da-1_participant_mock\")", - "title": "\"sv-da-1_participant_mock\" keyring" - }, - "role": "roles/cloudkms.cryptoOperator" + "enableServerSideApply": "true" }, - "name": "mock-sv-da-1-kms-roles/cloudkms.cryptoOperator-\"sv-da-1_participant_mock\"-keyring-iam", + "name": "k8s-imgpull-sv-da-1-sv", "provider": "", - "type": "gcp:projects/iAMMember:IAMMember" + "type": "pulumi:providers:kubernetes" }, { "custom": true, "id": "", "inputs": { - "accountId": "mock-sv-da-1-kms", - "description": "(managed by Pulumi)", - "displayName": "KMS Service Account (mock sv-da-1)" + "enableServerSideApply": "true" }, - "name": "mock-sv-da-1-kms-sa", - "provider": "", - "type": "gcp:serviceaccount/account:Account" - }, - { - "custom": false, - "id": "", - "inputs": {}, - "name": "mock-sv-da-1-kms", - "provider": "", - "type": "cn:gcp:ServiceAccount" - }, - { - "custom": true, - "id": "", - "inputs": {}, - "name": "participantKmsServiceAccountKey", + "name": "k8s-imgpull-sv-sv", "provider": "", - "type": "gcp:serviceaccount/key:Key" + "type": "pulumi:providers:kubernetes" }, { "custom": true, "id": "", "inputs": { - "apiVersion": "v1", - "kind": "Secret", + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "VirtualService", "metadata": { - "name": "splice-app-sv-ledger-api-user", + "name": "cometbft-loopback", "namespace": "sv-1" }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "ledger-api-user": "sv1-sv-client-id@clients" - } + "spec": { + "exportTo": [ + "." + ], + "gateways": [ + "mesh" + ], + "hosts": [ + "mock.global.canton.network.digitalasset.com" + ], + "tcp": [ + { + "match": [ + { + "gateways": [ + "mesh" + ] + } + ], + "route": [ + { + "destination": { + "host": "istio-ingress-cometbft.cluster-ingress.svc.cluster.local" + } + } + ] + } + ] } }, - "name": "splice-auth0-user-sv-1-sv-sv", + "name": "loopback-cometbft-sv-1", "provider": "", - "type": "kubernetes:core/v1:Secret" + "type": "kubernetes:networking.istio.io/v1alpha3:VirtualService" }, { "custom": true, "id": "", "inputs": { - "apiVersion": "v1", - "kind": "Secret", + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "VirtualService", "metadata": { - "name": "splice-app-sv-ledger-api-user", + "name": "cometbft-loopback", "namespace": "sv-da-1" }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "ledger-api-user": "sv-da-1-sv-client-id@clients" - } + "spec": { + "exportTo": [ + "." + ], + "gateways": [ + "mesh" + ], + "hosts": [ + "mock.global.canton.network.digitalasset.com" + ], + "tcp": [ + { + "match": [ + { + "gateways": [ + "mesh" + ] + } + ], + "route": [ + { + "destination": { + "host": "istio-ingress-cometbft.cluster-ingress.svc.cluster.local" + } + } + ] + } + ] } }, - "name": "splice-auth0-user-sv-da-1-sv-sv", + "name": "loopback-cometbft-sv-da-1", "provider": "", - "type": "kubernetes:core/v1:Secret" + "type": "kubernetes:networking.istio.io/v1alpha3:VirtualService" }, { "custom": true, "id": "", "inputs": { - "apiVersion": "v1", - "kind": "Secret", + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "ServiceEntry", "metadata": { - "name": "splice-app-sv-ledger-api-user", - "namespace": "sv" + "name": "loopback", + "namespace": "sv-1" + }, + "spec": { + "exportTo": [ + "." + ], + "hosts": [ + "mock.global.canton.network.digitalasset.com" + ], + "ports": [ + { + "name": "http-port", + "number": 80, + "protocol": "HTTP" + }, + { + "name": "tls", + "number": 443, + "protocol": "TLS" + }, + { + "name": "grpc-domain", + "number": 5008, + "protocol": "GRPC" + }, + { + "name": "cometbft-0-0-p2p", + "number": 26006, + "protocol": "TCP" + }, + { + "name": "cometbft-0-1-p2p", + "number": 26016, + "protocol": "TCP" + }, + { + "name": "cometbft-0-2-p2p", + "number": 26026, + "protocol": "TCP" + }, + { + "name": "cometbft-1-0-p2p", + "number": 26106, + "protocol": "TCP" + }, + { + "name": "cometbft-1-1-p2p", + "number": 26116, + "protocol": "TCP" + }, + { + "name": "cometbft-1-2-p2p", + "number": 26126, + "protocol": "TCP" + }, + { + "name": "cometbft-2-0-p2p", + "number": 26206, + "protocol": "TCP" + }, + { + "name": "cometbft-2-1-p2p", + "number": 26216, + "protocol": "TCP" + }, + { + "name": "cometbft-2-2-p2p", + "number": 26226, + "protocol": "TCP" + }, + { + "name": "cometbft-3-0-p2p", + "number": 26306, + "protocol": "TCP" + }, + { + "name": "cometbft-3-1-p2p", + "number": 26316, + "protocol": "TCP" + }, + { + "name": "cometbft-3-2-p2p", + "number": 26326, + "protocol": "TCP" + }, + { + "name": "cometbft-4-0-p2p", + "number": 26406, + "protocol": "TCP" + }, + { + "name": "cometbft-4-1-p2p", + "number": 26416, + "protocol": "TCP" + }, + { + "name": "cometbft-4-2-p2p", + "number": 26426, + "protocol": "TCP" + }, + { + "name": "cometbft-5-0-p2p", + "number": 26506, + "protocol": "TCP" + }, + { + "name": "cometbft-5-1-p2p", + "number": 26516, + "protocol": "TCP" + }, + { + "name": "cometbft-5-2-p2p", + "number": 26526, + "protocol": "TCP" + }, + { + "name": "cometbft-6-0-p2p", + "number": 26606, + "protocol": "TCP" + }, + { + "name": "cometbft-6-1-p2p", + "number": 26616, + "protocol": "TCP" + }, + { + "name": "cometbft-6-2-p2p", + "number": 26626, + "protocol": "TCP" + }, + { + "name": "cometbft-7-0-p2p", + "number": 26706, + "protocol": "TCP" + }, + { + "name": "cometbft-7-1-p2p", + "number": 26716, + "protocol": "TCP" + }, + { + "name": "cometbft-7-2-p2p", + "number": 26726, + "protocol": "TCP" + }, + { + "name": "cometbft-8-0-p2p", + "number": 26806, + "protocol": "TCP" + }, + { + "name": "cometbft-8-1-p2p", + "number": 26816, + "protocol": "TCP" + }, + { + "name": "cometbft-8-2-p2p", + "number": 26826, + "protocol": "TCP" + }, + { + "name": "cometbft-9-0-p2p", + "number": 26906, + "protocol": "TCP" + }, + { + "name": "cometbft-9-1-p2p", + "number": 26916, + "protocol": "TCP" + }, + { + "name": "cometbft-9-2-p2p", + "number": 26926, + "protocol": "TCP" + } + ], + "resolution": "DNS" + } + }, + "name": "loopback-service-entry-sv-1", + "provider": "", + "type": "kubernetes:networking.istio.io/v1alpha3:ServiceEntry" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "ServiceEntry", + "metadata": { + "name": "loopback", + "namespace": "sv-da-1" + }, + "spec": { + "exportTo": [ + "." + ], + "hosts": [ + "mock.global.canton.network.digitalasset.com" + ], + "ports": [ + { + "name": "http-port", + "number": 80, + "protocol": "HTTP" + }, + { + "name": "tls", + "number": 443, + "protocol": "TLS" + }, + { + "name": "grpc-domain", + "number": 5008, + "protocol": "GRPC" + }, + { + "name": "cometbft-0-0-p2p", + "number": 26006, + "protocol": "TCP" + }, + { + "name": "cometbft-0-1-p2p", + "number": 26016, + "protocol": "TCP" + }, + { + "name": "cometbft-0-2-p2p", + "number": 26026, + "protocol": "TCP" + }, + { + "name": "cometbft-1-0-p2p", + "number": 26106, + "protocol": "TCP" + }, + { + "name": "cometbft-1-1-p2p", + "number": 26116, + "protocol": "TCP" + }, + { + "name": "cometbft-1-2-p2p", + "number": 26126, + "protocol": "TCP" + }, + { + "name": "cometbft-2-0-p2p", + "number": 26206, + "protocol": "TCP" + }, + { + "name": "cometbft-2-1-p2p", + "number": 26216, + "protocol": "TCP" + }, + { + "name": "cometbft-2-2-p2p", + "number": 26226, + "protocol": "TCP" + }, + { + "name": "cometbft-3-0-p2p", + "number": 26306, + "protocol": "TCP" + }, + { + "name": "cometbft-3-1-p2p", + "number": 26316, + "protocol": "TCP" + }, + { + "name": "cometbft-3-2-p2p", + "number": 26326, + "protocol": "TCP" + }, + { + "name": "cometbft-4-0-p2p", + "number": 26406, + "protocol": "TCP" + }, + { + "name": "cometbft-4-1-p2p", + "number": 26416, + "protocol": "TCP" + }, + { + "name": "cometbft-4-2-p2p", + "number": 26426, + "protocol": "TCP" + }, + { + "name": "cometbft-5-0-p2p", + "number": 26506, + "protocol": "TCP" + }, + { + "name": "cometbft-5-1-p2p", + "number": 26516, + "protocol": "TCP" + }, + { + "name": "cometbft-5-2-p2p", + "number": 26526, + "protocol": "TCP" + }, + { + "name": "cometbft-6-0-p2p", + "number": 26606, + "protocol": "TCP" + }, + { + "name": "cometbft-6-1-p2p", + "number": 26616, + "protocol": "TCP" + }, + { + "name": "cometbft-6-2-p2p", + "number": 26626, + "protocol": "TCP" + }, + { + "name": "cometbft-7-0-p2p", + "number": 26706, + "protocol": "TCP" + }, + { + "name": "cometbft-7-1-p2p", + "number": 26716, + "protocol": "TCP" + }, + { + "name": "cometbft-7-2-p2p", + "number": 26726, + "protocol": "TCP" + }, + { + "name": "cometbft-8-0-p2p", + "number": 26806, + "protocol": "TCP" + }, + { + "name": "cometbft-8-1-p2p", + "number": 26816, + "protocol": "TCP" + }, + { + "name": "cometbft-8-2-p2p", + "number": 26826, + "protocol": "TCP" + }, + { + "name": "cometbft-9-0-p2p", + "number": 26906, + "protocol": "TCP" + }, + { + "name": "cometbft-9-1-p2p", + "number": 26916, + "protocol": "TCP" + }, + { + "name": "cometbft-9-2-p2p", + "number": 26926, + "protocol": "TCP" + } + ], + "resolution": "DNS" + } + }, + "name": "loopback-service-entry-sv-da-1", + "provider": "", + "type": "kubernetes:networking.istio.io/v1alpha3:ServiceEntry" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "VirtualService", + "metadata": { + "name": "direct-loopback-through-ingress-gateway", + "namespace": "sv-1" + }, + "spec": { + "exportTo": [ + "." + ], + "gateways": [ + "mesh" + ], + "hosts": [ + "mock.global.canton.network.digitalasset.com", + "validator.mock.global.canton.network.digitalasset.com", + "*.validator.mock.global.canton.network.digitalasset.com", + "validator1.mock.global.canton.network.digitalasset.com", + "*.validator1.mock.global.canton.network.digitalasset.com", + "splitwell.mock.global.canton.network.digitalasset.com", + "*.splitwell.mock.global.canton.network.digitalasset.com", + "sv-2.mock.global.canton.network.digitalasset.com", + "*.sv-2.mock.global.canton.network.digitalasset.com", + "sv-1.mock.global.canton.network.digitalasset.com", + "*.sv-1.mock.global.canton.network.digitalasset.com", + "sv.mock.global.canton.network.digitalasset.com", + "*.sv.mock.global.canton.network.digitalasset.com" + ], + "http": [ + { + "match": [ + { + "gateways": [ + "mesh" + ] + } + ], + "route": [ + { + "destination": { + "host": "istio-ingress.cluster-ingress.svc.cluster.local" + } + } + ] + } + ], + "tls": [ + { + "match": [ + { + "gateways": [ + "mesh" + ], + "sniHosts": [ + "mock.global.canton.network.digitalasset.com", + "validator.mock.global.canton.network.digitalasset.com", + "*.validator.mock.global.canton.network.digitalasset.com", + "validator1.mock.global.canton.network.digitalasset.com", + "*.validator1.mock.global.canton.network.digitalasset.com", + "splitwell.mock.global.canton.network.digitalasset.com", + "*.splitwell.mock.global.canton.network.digitalasset.com", + "sv-2.mock.global.canton.network.digitalasset.com", + "*.sv-2.mock.global.canton.network.digitalasset.com", + "sv-1.mock.global.canton.network.digitalasset.com", + "*.sv-1.mock.global.canton.network.digitalasset.com", + "sv.mock.global.canton.network.digitalasset.com", + "*.sv.mock.global.canton.network.digitalasset.com" + ] + } + ], + "route": [ + { + "destination": { + "host": "istio-ingress.cluster-ingress.svc.cluster.local" + } + } + ] + } + ] + } + }, + "name": "loopback-virtual-service-sv-1", + "provider": "", + "type": "kubernetes:networking.istio.io/v1alpha3:VirtualService" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "VirtualService", + "metadata": { + "name": "direct-loopback-through-ingress-gateway", + "namespace": "sv-da-1" + }, + "spec": { + "exportTo": [ + "." + ], + "gateways": [ + "mesh" + ], + "hosts": [ + "mock.global.canton.network.digitalasset.com", + "validator.mock.global.canton.network.digitalasset.com", + "*.validator.mock.global.canton.network.digitalasset.com", + "validator1.mock.global.canton.network.digitalasset.com", + "*.validator1.mock.global.canton.network.digitalasset.com", + "splitwell.mock.global.canton.network.digitalasset.com", + "*.splitwell.mock.global.canton.network.digitalasset.com", + "sv-2.mock.global.canton.network.digitalasset.com", + "*.sv-2.mock.global.canton.network.digitalasset.com", + "sv-1.mock.global.canton.network.digitalasset.com", + "*.sv-1.mock.global.canton.network.digitalasset.com", + "sv.mock.global.canton.network.digitalasset.com", + "*.sv.mock.global.canton.network.digitalasset.com" + ], + "http": [ + { + "match": [ + { + "gateways": [ + "mesh" + ] + } + ], + "route": [ + { + "destination": { + "host": "istio-ingress.cluster-ingress.svc.cluster.local" + } + } + ] + } + ], + "tls": [ + { + "match": [ + { + "gateways": [ + "mesh" + ], + "sniHosts": [ + "mock.global.canton.network.digitalasset.com", + "validator.mock.global.canton.network.digitalasset.com", + "*.validator.mock.global.canton.network.digitalasset.com", + "validator1.mock.global.canton.network.digitalasset.com", + "*.validator1.mock.global.canton.network.digitalasset.com", + "splitwell.mock.global.canton.network.digitalasset.com", + "*.splitwell.mock.global.canton.network.digitalasset.com", + "sv-2.mock.global.canton.network.digitalasset.com", + "*.sv-2.mock.global.canton.network.digitalasset.com", + "sv-1.mock.global.canton.network.digitalasset.com", + "*.sv-1.mock.global.canton.network.digitalasset.com", + "sv.mock.global.canton.network.digitalasset.com", + "*.sv.mock.global.canton.network.digitalasset.com" + ] + } + ], + "route": [ + { + "destination": { + "host": "istio-ingress.cluster-ingress.svc.cluster.local" + } + } + ] + } + ] + } + }, + "name": "loopback-virtual-service-sv-da-1", + "provider": "", + "type": "kubernetes:networking.istio.io/v1alpha3:VirtualService" + }, + { + "custom": true, + "id": "", + "inputs": { + "bucket": "mock-sv-1-bulk-committed", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectCreator" + }, + "name": "mock-sv-1-bulk-committed-sa-role-creator", + "provider": "", + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" + }, + { + "custom": true, + "id": "", + "inputs": { + "bucket": "mock-sv-1-bulk-committed", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectViewer" + }, + "name": "mock-sv-1-bulk-committed-sa-role-reader", + "provider": "", + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" + }, + { + "custom": true, + "id": "", + "inputs": { + "location": "europe-west6", + "name": "mock-sv-1-bulk-committed" + }, + "name": "mock-sv-1-bulk-committed", + "provider": "", + "type": "gcp:storage/bucket:Bucket" + }, + { + "custom": true, + "id": "", + "inputs": {}, + "name": "mock-sv-1-bulk-sa-hmac", + "provider": "", + "type": "gcp:storage/hmacKey:HmacKey" + }, + { + "custom": true, + "id": "", + "inputs": { + "accountId": "mock-sv-1-bulk-sa", + "displayName": "Service Account for Bulk-Storage Bucket Read/Write Access" + }, + "name": "mock-sv-1-bulk-sa", + "provider": "", + "type": "gcp:serviceaccount/account:Account" + }, + { + "custom": true, + "id": "", + "inputs": { + "bucket": "mock-sv-1-bulk-staging", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectUser" + }, + "name": "mock-sv-1-bulk-staging-sa-role", + "provider": "", + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" + }, + { + "custom": true, + "id": "", + "inputs": { + "location": "europe-west6", + "name": "mock-sv-1-bulk-staging" + }, + "name": "mock-sv-1-bulk-staging", + "provider": "", + "type": "gcp:storage/bucket:Bucket" + }, + { + "custom": true, + "id": "", + "inputs": { + "bucket": "mock-sv-da-1-bulk-committed", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectCreator" + }, + "name": "mock-sv-da-1-bulk-committed-sa-role-creator", + "provider": "", + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" + }, + { + "custom": true, + "id": "", + "inputs": { + "bucket": "mock-sv-da-1-bulk-committed", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectViewer" + }, + "name": "mock-sv-da-1-bulk-committed-sa-role-reader", + "provider": "", + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" + }, + { + "custom": true, + "id": "", + "inputs": { + "location": "europe-west6", + "name": "mock-sv-da-1-bulk-committed" + }, + "name": "mock-sv-da-1-bulk-committed", + "provider": "", + "type": "gcp:storage/bucket:Bucket" + }, + { + "custom": true, + "id": "", + "inputs": {}, + "name": "mock-sv-da-1-bulk-sa-hmac", + "provider": "", + "type": "gcp:storage/hmacKey:HmacKey" + }, + { + "custom": true, + "id": "", + "inputs": { + "accountId": "mock-sv-da-1-bulk-sa", + "displayName": "Service Account for Bulk-Storage Bucket Read/Write Access" + }, + "name": "mock-sv-da-1-bulk-sa", + "provider": "", + "type": "gcp:serviceaccount/account:Account" + }, + { + "custom": true, + "id": "", + "inputs": { + "bucket": "mock-sv-da-1-bulk-staging", + "member": "serviceAccount:undefined", + "role": "roles/storage.objectUser" + }, + "name": "mock-sv-da-1-bulk-staging-sa-role", + "provider": "", + "type": "gcp:storage/bucketIAMMember:BucketIAMMember" + }, + { + "custom": true, + "id": "", + "inputs": { + "location": "europe-west6", + "name": "mock-sv-da-1-bulk-staging" + }, + "name": "mock-sv-da-1-bulk-staging", + "provider": "", + "type": "gcp:storage/bucket:Bucket" + }, + { + "custom": true, + "id": "", + "inputs": { + "condition": { + "description": "(managed by Pulumi)", + "expression": "resource.name.startsWith(\"projects/da-cn-devnet/locations/us-central1/keyRings/sv-da-1_participant_mock\")", + "title": "\"sv-da-1_participant_mock\" keyring" + }, + "role": "roles/cloudkms.admin" + }, + "name": "mock-sv-da-1-kms-roles/cloudkms.admin-\"sv-da-1_participant_mock\"-keyring-iam", + "provider": "", + "type": "gcp:projects/iAMMember:IAMMember" + }, + { + "custom": true, + "id": "", + "inputs": { + "condition": { + "description": "(managed by Pulumi)", + "expression": "resource.name.startsWith(\"projects/da-cn-devnet/locations/us-central1/keyRings/sv-da-1_participant_mock\")", + "title": "\"sv-da-1_participant_mock\" keyring" + }, + "role": "roles/cloudkms.cryptoOperator" + }, + "name": "mock-sv-da-1-kms-roles/cloudkms.cryptoOperator-\"sv-da-1_participant_mock\"-keyring-iam", + "provider": "", + "type": "gcp:projects/iAMMember:IAMMember" + }, + { + "custom": true, + "id": "", + "inputs": { + "accountId": "mock-sv-da-1-kms", + "description": "(managed by Pulumi)", + "displayName": "KMS Service Account (mock sv-da-1)" + }, + "name": "mock-sv-da-1-kms-sa", + "provider": "", + "type": "gcp:serviceaccount/account:Account" + }, + { + "custom": false, + "id": "", + "inputs": {}, + "name": "mock-sv-da-1-kms", + "provider": "", + "type": "cn:gcp:ServiceAccount" + }, + { + "custom": true, + "id": "", + "inputs": {}, + "name": "participantKmsServiceAccountKey", + "provider": "", + "type": "gcp:serviceaccount/key:Key" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "secret": "c3BsaXR3ZWxsc2VjcmV0Mg==" + } + }, + "kind": "Secret", + "metadata": { + "name": "splice-app-validator-onboarding-splitwell2", + "namespace": "sv-1" + }, + "type": "Opaque" + }, + "name": "splice-app-sv-1-validator-onboarding-splitwell2", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "secret": "dmFsaWRhdG9yMXNlY3JldDI=" + } + }, + "kind": "Secret", + "metadata": { + "name": "splice-app-validator-onboarding-validator12", + "namespace": "sv-1" + }, + "type": "Opaque" + }, + "name": "splice-app-sv-1-validator-onboarding-validator12", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "private": "c3ZkYTEtbW9jay1jb21ldGJmdC1nb3Zlcm5hbmNlLWtleS1wcml2YXRlLWtleQ==", + "public": "c3ZkYTEtbW9jay1jb21ldGJmdC1nb3Zlcm5hbmNlLWtleS1wdWJsaWMta2V5" + } + }, + "kind": "Secret", + "metadata": { + "name": "splice-app-sv-cometbft-governance-key", + "namespace": "sv-da-1" + }, + "type": "Opaque" + }, + "name": "splice-app-sv-da-1-cometbft-governance-key", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "private": "c3ZkYTEtbW9jay1pZC1wcml2YXRlLWtleQ==", + "public": "c3ZkYTEtbW9jay1pZC1wdWJsaWMta2V5" + } + }, + "kind": "Secret", + "metadata": { + "name": "splice-app-sv-key", + "namespace": "sv-da-1" + }, + "type": "Opaque" + }, + "name": "splice-app-sv-da-1-key", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-sv-ledger-api-auth", + "namespace": "sv-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "audience": "https://canton.network.global", + "client-id": "sv1-sv-client-id", + "client-secret": "***", + "ledger-api-user": "sv1-sv-client-id@clients", + "url": "https://canton-network-dev.us.auth0.com/.well-known/openid-configuration" + } + } + }, + "name": "splice-auth0-secret-sv-1-sv", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-validator-ledger-api-auth", + "namespace": "sv-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "audience": "https://canton.network.global", + "client-id": "sv1-validator-client-id", + "client-secret": "***", + "ledger-api-user": "sv1-validator-client-id@clients", + "url": "https://canton-network-dev.us.auth0.com/.well-known/openid-configuration" + } + } + }, + "name": "splice-auth0-secret-sv-1-validator", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-sv-ledger-api-auth", + "namespace": "sv-da-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "audience": "https://canton.network.global", + "client-id": "sv-da-1-sv-client-id", + "client-secret": "***", + "ledger-api-user": "sv-da-1-sv-client-id@clients", + "url": "https://canton-network-dev.us.auth0.com/.well-known/openid-configuration" + } + } + }, + "name": "splice-auth0-secret-sv-da-1-sv", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-validator-ledger-api-auth", + "namespace": "sv-da-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "audience": "https://canton.network.global", + "client-id": "sv-da-1-validator-client-id", + "client-secret": "***", + "ledger-api-user": "sv-da-1-validator-client-id@clients", + "url": "https://canton-network-dev.us.auth0.com/.well-known/openid-configuration" + } + } + }, + "name": "splice-auth0-secret-sv-da-1-validator", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-cns-ui-auth", + "namespace": "sv-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "client-id": "sv-1-cns-ui-client-id", + "url": "https://canton-network-dev.us.auth0.com" + } + } + }, + "name": "splice-auth0-ui-secret-sv-1-cns", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-sv-ui-auth", + "namespace": "sv-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "client-id": "sv-1-sv-ui-client-id", + "url": "https://canton-network-dev.us.auth0.com" + } + } + }, + "name": "splice-auth0-ui-secret-sv-1-sv", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-wallet-ui-auth", + "namespace": "sv-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "client-id": "sv-1-wallet-ui-client-id", + "url": "https://canton-network-dev.us.auth0.com" + } + } + }, + "name": "splice-auth0-ui-secret-sv-1-wallet", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-cns-ui-auth", + "namespace": "sv-da-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "client-id": "sv-da-1-cns-ui-client-id", + "url": "https://canton-network-dev.us.auth0.com" + } + } + }, + "name": "splice-auth0-ui-secret-sv-da-1-cns", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-sv-ui-auth", + "namespace": "sv-da-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "client-id": "sv-da-1-sv-ui-client-id", + "url": "https://canton-network-dev.us.auth0.com" + } + } + }, + "name": "splice-auth0-ui-secret-sv-da-1-sv", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-wallet-ui-auth", + "namespace": "sv-da-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "client-id": "sv-da-1-wallet-ui-client-id", + "url": "https://canton-network-dev.us.auth0.com" + } + } + }, + "name": "splice-auth0-ui-secret-sv-da-1-wallet", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-sv-ledger-api-user", + "namespace": "sv-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "ledger-api-user": "sv1-sv-client-id@clients" + } + } + }, + "name": "splice-auth0-user-sv-1-sv-sv", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-sv-ledger-api-user", + "namespace": "sv-da-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "ledger-api-user": "sv-da-1-sv-client-id@clients" + } + } + }, + "name": "splice-auth0-user-sv-da-1-sv-sv", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "splice-app-sv-ledger-api-user", + "namespace": "sv" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "ledger-api-user": "sv-client-id@clients" + } + } + }, + "name": "splice-auth0-user-sv-sv-sv", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": false, + "id": "", + "inputs": { + "appLabel": "scan-app", + "globalLimits": { + "fillInterval": "60s", + "maxTokens": 2147483647, + "tokensPerFill": 2147483647 + }, + "inboundPort": 5012, + "namespace": "sv-1", + "rateLimits": { + "/api/scan/livez": { + "name": "livez", + "type": "unlimited" + }, + "/api/scan/readyz": { + "name": "readyz", + "type": "unlimited" + }, + "/api/scan/status": { + "name": "status", + "type": "unlimited" + }, + "/api/scan/v0/acs": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "acs", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/api/scan/v0/active-synchronizer-serial": { + "name": "activeSynchronizerSerial", + "type": "unlimited" + }, + "/api/scan/v0/admin/sv/previous-sv-reward-weight": { + "name": "sv-previous-sv-reward-weight", + "type": "unlimited" + }, + "/api/scan/v0/admin/sv/voterequests": { + "name": "sv-voterequests", + "type": "unlimited" + }, + "/api/scan/v0/admin/sv/voteresults": { + "name": "sv-voteresults", + "type": "unlimited" + }, + "/api/scan/v0/admin/validator": { + "name": "validator-licenses", + "type": "unlimited" + }, + "/api/scan/v0/amulet-config-for-round": { + "name": "amulet-config-for-round", + "type": "banned" + }, + "/api/scan/v0/amulet-price": { + "name": "amulet-price-votes", + "type": "unlimited" + }, + "/api/scan/v0/amulet-rules": { + "name": "amulet-rules", + "type": "unlimited" + }, + "/api/scan/v0/ans-entries": { + "name": "ans-entries", + "type": "unlimited" + }, + "/api/scan/v0/ans-rules": { + "name": "ans-rules", + "type": "unlimited" + }, + "/api/scan/v0/backfilling": { + "name": "backfilling", + "type": "unlimited" + }, + "/api/scan/v0/closed-rounds": { + "name": "closed-rounds", + "type": "unlimited" + }, + "/api/scan/v0/domains": { + "name": "domains", + "type": "unlimited" + }, + "/api/scan/v0/dso": { + "name": "dso-info", + "type": "unlimited" + }, + "/api/scan/v0/dso-party-id": { + "name": "dso-party-id", + "type": "unlimited" + }, + "/api/scan/v0/dso-sequencers": { + "name": "dso-sequencers", + "type": "unlimited" + }, + "/api/scan/v0/events": { + "name": "events", + "type": "unlimited" + }, + "/api/scan/v0/external-party-amulet-rules": { + "name": "external-party-amulet-rules", + "type": "unlimited" + }, + "/api/scan/v0/feature-support": { + "name": "feature-support", + "type": "unlimited" + }, + "/api/scan/v0/featured-apps": { + "name": "featured-apps", + "type": "unlimited" + }, + "/api/scan/v0/history/bulk/acs": { + "name": "listBulkAcsSnapshotObjects", + "type": "unlimited" + }, + "/api/scan/v0/history/bulk/updates": { + "name": "listBulkUpdateHistoryObjects", + "type": "unlimited" + }, + "/api/scan/v0/holdings": { + "name": "holdings", + "type": "unlimited" + }, + "/api/scan/v0/internal/reward-accounting-process": { + "name": "reward-accounting-process", + "type": "unlimited" + }, + "/api/scan/v0/lsu": { + "name": "lsu", + "type": "unlimited" + }, + "/api/scan/v0/migrations": { + "name": "migrations-schedule", + "type": "unlimited" + }, + "/api/scan/v0/open-and-issuing-mining-rounds": { + "name": "open-and-issuing-mining-rounds", + "type": "unlimited" + }, + "/api/scan/v0/roll-forward-lsu": { + "name": "rollForwardLsu", + "type": "unlimited" + }, + "/api/scan/v0/scans": { + "name": "scans", + "type": "unlimited" + }, + "/api/scan/v0/splice-instance-names": { + "name": "splice-instance-names", + "type": "unlimited" + }, + "/api/scan/v0/state": { + "name": "state", + "type": "unlimited" + }, + "/api/scan/v0/sv-bft-sequencers": { + "name": "bft-sequencers", + "type": "unlimited" + }, + "/api/scan/v0/synchronizer-bootstrapping-transactions": { + "name": "synchronizer-bootstrapping-transactions", + "type": "unlimited" + }, + "/api/scan/v0/synchronizer-identities": { + "name": "synchronizer-identities", + "type": "unlimited" + }, + "/api/scan/v0/transfer-command": { + "name": "transfer-command-status", + "type": "unlimited" + }, + "/api/scan/v0/transfer-command-counter": { + "name": "transfer-command-counter", + "type": "unlimited" + }, + "/api/scan/v0/transfer-preapprovals": { + "name": "transfer-preapprovals", + "type": "unlimited" + }, + "/api/scan/v0/unclaimed-development-fund-coupons": { + "name": "unclaimed-development-fund-coupons", + "type": "unlimited" + }, + "/api/scan/v0/updates": { + "name": "v0-updates", + "type": "unlimited" + }, + "/api/scan/v0/validators": { + "name": "validators", + "type": "unlimited" + }, + "/api/scan/v0/voterequest": { + "name": "voterequest", + "type": "unlimited" + }, + "/api/scan/v0/voterequests": { + "name": "voterequests", + "type": "unlimited" + }, + "/api/scan/v1/domains": { + "name": "domains-v1", + "type": "unlimited" + }, + "/api/scan/v1/holdings": { + "name": "holdings", + "type": "unlimited" + }, + "/api/scan/v1/state": { + "name": "state", + "type": "unlimited" + }, + "/api/scan/v1/updates": { + "name": "v1-updates", + "type": "banned" + }, + "/api/scan/v2/updates": { + "name": "v2-updates", + "type": "unlimited" + }, + "/api/scan/version": { + "name": "version", + "type": "unlimited" + }, + "/registry/allocation-instruction/v1/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocation-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocation-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocation-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocation-instruction/v2/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocation-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocation/v2/settlement-factory": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-settlement-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocations/v1": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocations", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocations/v2": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocations-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/metadata/v1/info": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-metadata-info", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "overrides": { + "test": { + "fillInterval": "60s", + "ips": [ + "192.68.78.50" + ], + "maxTokens": 250, + "tokensPerFill": 250 + } + }, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/metadata/v1/instruments": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-metadata-instruments", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/transfer-instruction/v1": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-transfer-instruction", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/transfer-instruction/v1/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 730, + "name": "registry-transfer-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 730, + "type": "limited" + }, + "/registry/transfer-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-transfer-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/transfer-instruction/v2/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-transfer-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + } + } + }, + "name": "splice-sv-1-scan-app-rate-limit", + "provider": "", + "type": "splice:RateLimit" + }, + { + "custom": false, + "id": "", + "inputs": { + "appLabel": "scan-app", + "globalLimits": { + "fillInterval": "60s", + "maxTokens": 2147483647, + "tokensPerFill": 2147483647 + }, + "inboundPort": 5012, + "namespace": "sv-da-1", + "rateLimits": { + "/api/scan/livez": { + "name": "livez", + "type": "unlimited" + }, + "/api/scan/readyz": { + "name": "readyz", + "type": "unlimited" + }, + "/api/scan/status": { + "name": "status", + "type": "unlimited" + }, + "/api/scan/v0/acs": { + "fillInterval": "60s", + "maxTokens": 500, + "name": "acs", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 10, + "tokensPerFill": 5 + }, + "tokensPerFill": 500, + "type": "limited" + }, + "/api/scan/v0/active-synchronizer-serial": { + "name": "activeSynchronizerSerial", + "type": "unlimited" + }, + "/api/scan/v0/admin/sv/previous-sv-reward-weight": { + "name": "sv-previous-sv-reward-weight", + "type": "unlimited" + }, + "/api/scan/v0/admin/sv/voterequests": { + "name": "sv-voterequests", + "type": "unlimited" + }, + "/api/scan/v0/admin/sv/voteresults": { + "name": "sv-voteresults", + "type": "unlimited" + }, + "/api/scan/v0/admin/validator": { + "name": "validator-licenses", + "type": "unlimited" + }, + "/api/scan/v0/amulet-config-for-round": { + "name": "amulet-config-for-round", + "type": "banned" + }, + "/api/scan/v0/amulet-price": { + "name": "amulet-price-votes", + "type": "unlimited" + }, + "/api/scan/v0/amulet-rules": { + "name": "amulet-rules", + "type": "unlimited" + }, + "/api/scan/v0/ans-entries": { + "name": "ans-entries", + "type": "unlimited" + }, + "/api/scan/v0/ans-rules": { + "name": "ans-rules", + "type": "unlimited" + }, + "/api/scan/v0/backfilling": { + "name": "backfilling", + "type": "unlimited" + }, + "/api/scan/v0/closed-rounds": { + "name": "closed-rounds", + "type": "unlimited" + }, + "/api/scan/v0/domains": { + "name": "domains", + "type": "unlimited" + }, + "/api/scan/v0/dso": { + "name": "dso-info", + "type": "unlimited" + }, + "/api/scan/v0/dso-party-id": { + "name": "dso-party-id", + "type": "unlimited" + }, + "/api/scan/v0/dso-sequencers": { + "name": "dso-sequencers", + "type": "unlimited" + }, + "/api/scan/v0/events": { + "name": "events", + "type": "unlimited" + }, + "/api/scan/v0/external-party-amulet-rules": { + "name": "external-party-amulet-rules", + "type": "unlimited" + }, + "/api/scan/v0/feature-support": { + "name": "feature-support", + "type": "unlimited" + }, + "/api/scan/v0/featured-apps": { + "name": "featured-apps", + "type": "unlimited" + }, + "/api/scan/v0/history/bulk/acs": { + "name": "listBulkAcsSnapshotObjects", + "type": "unlimited" + }, + "/api/scan/v0/history/bulk/updates": { + "name": "listBulkUpdateHistoryObjects", + "type": "unlimited" + }, + "/api/scan/v0/holdings": { + "name": "holdings", + "type": "unlimited" + }, + "/api/scan/v0/internal/reward-accounting-process": { + "name": "reward-accounting-process", + "type": "unlimited" + }, + "/api/scan/v0/lsu": { + "name": "lsu", + "type": "unlimited" + }, + "/api/scan/v0/migrations": { + "name": "migrations-schedule", + "type": "unlimited" + }, + "/api/scan/v0/open-and-issuing-mining-rounds": { + "name": "open-and-issuing-mining-rounds", + "type": "unlimited" + }, + "/api/scan/v0/roll-forward-lsu": { + "name": "rollForwardLsu", + "type": "unlimited" + }, + "/api/scan/v0/scans": { + "name": "scans", + "type": "unlimited" + }, + "/api/scan/v0/splice-instance-names": { + "name": "splice-instance-names", + "type": "unlimited" + }, + "/api/scan/v0/state": { + "name": "state", + "type": "unlimited" + }, + "/api/scan/v0/sv-bft-sequencers": { + "name": "bft-sequencers", + "type": "unlimited" + }, + "/api/scan/v0/synchronizer-bootstrapping-transactions": { + "name": "synchronizer-bootstrapping-transactions", + "type": "unlimited" + }, + "/api/scan/v0/synchronizer-identities": { + "name": "synchronizer-identities", + "type": "unlimited" + }, + "/api/scan/v0/transfer-command": { + "name": "transfer-command-status", + "type": "unlimited" + }, + "/api/scan/v0/transfer-command-counter": { + "name": "transfer-command-counter", + "type": "unlimited" + }, + "/api/scan/v0/transfer-preapprovals": { + "name": "transfer-preapprovals", + "type": "unlimited" + }, + "/api/scan/v0/unclaimed-development-fund-coupons": { + "name": "unclaimed-development-fund-coupons", + "type": "unlimited" + }, + "/api/scan/v0/updates": { + "name": "v0-updates", + "type": "unlimited" + }, + "/api/scan/v0/validators": { + "name": "validators", + "type": "unlimited" + }, + "/api/scan/v0/voterequest": { + "name": "voterequest", + "type": "unlimited" + }, + "/api/scan/v0/voterequests": { + "name": "voterequests", + "type": "unlimited" + }, + "/api/scan/v1/domains": { + "name": "domains-v1", + "type": "unlimited" + }, + "/api/scan/v1/holdings": { + "name": "holdings", + "type": "unlimited" + }, + "/api/scan/v1/state": { + "name": "state", + "type": "unlimited" + }, + "/api/scan/v1/updates": { + "name": "v1-updates", + "type": "banned" + }, + "/api/scan/v2/updates": { + "name": "v2-updates", + "type": "unlimited" + }, + "/api/scan/version": { + "name": "version", + "type": "unlimited" + }, + "/registry/allocation-instruction/v1/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocation-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocation-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocation-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocation-instruction/v2/allocation-factory": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocation-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocation/v2/settlement-factory": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-settlement-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocations/v1": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocations", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/allocations/v2": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-allocations-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/metadata/v1/info": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-metadata-info", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "overrides": { + "test": { + "fillInterval": "60s", + "ips": [ + "192.68.78.50" + ], + "maxTokens": 250, + "tokensPerFill": 250 + } + }, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/metadata/v1/instruments": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-metadata-instruments", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/transfer-instruction/v1": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-transfer-instruction", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/transfer-instruction/v1/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 730, + "name": "registry-transfer-factory", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 730, + "type": "limited" + }, + "/registry/transfer-instruction/v2": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-transfer-instruction-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + }, + "/registry/transfer-instruction/v2/transfer-factory": { + "fillInterval": "60s", + "maxTokens": 720, + "name": "registry-transfer-factory-v2", + "perIpLimits": { + "fillInterval": "60s", + "maxTokens": 120, + "tokensPerFill": 120 + }, + "tokensPerFill": 720, + "type": "limited" + } + } + }, + "name": "splice-sv-da-1-scan-app-rate-limit", + "provider": "", + "type": "splice:RateLimit" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "accessKey": "", + "secretAccessKey": "" + } + }, + "kind": "Secret", + "metadata": { + "name": "splice-app-bulk-storage-credentials", + "namespace": "sv-1" + }, + "type": "Opaque" + }, + "name": "sv-1-bulk-storage-credentials", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "length": 16, + "overrideSpecial": "_%@", + "special": true + }, + "name": "sv-1-cn-apps-pg-cnadmin-passwd", + "provider": "", + "type": "random:index/randomPassword:RandomPassword" + }, + { + "custom": false, + "id": "", + "inputs": { + "active": true, + "alias": "cn-apps-pg", + "cloudSqlConfig": { + "databaseVersion": "POSTGRES_14", + "enabled": true, + "enterprisePlus": false, + "flags": { + "maintenance_work_mem": "2000000", + "max_wal_size": "20480", + "random_page_cost": "1.1", + "temp_file_limit": "2147483647", + "work_mem": "16384" + }, + "maintenanceWindow": { + "day": 2, + "hour": 8 + }, + "protected": true, + "tier": "apps-pg-override-tier-sv-1" + }, + "defaultUserName": "cnadmin", + "deletionProtection": true, + "instanceName": "cn-apps-pg", + "logicalDecoding": true, + "namespace": { + "logicalName": "sv-1", + "ns": { + "__aliases": [], + "__name": "sv-1", + "__providers": {}, + "__pulumiCustomResource": true, + "__pulumiResource": true, + "__pulumiType": "kubernetes:core/v1:Namespace", + "__transformations": [], + "__version": "4.28.0", + "apiVersion": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "id": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "kind": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "metadata": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "spec": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "status": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "urn": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi." + } + }, + "retainDbResourcesOnDelete": false, + "secretName": "cn-apps-pg-secrets" + }, + "name": "sv-1-cn-apps-pg", + "provider": "", + "type": "canton:cloud:postgres" + }, + { + "custom": true, + "id": "", + "inputs": { + "databaseVersion": "POSTGRES_14", + "deletionProtection": false, + "region": "europe-west6", + "settings": { + "activationPolicy": "ALWAYS", + "backupConfiguration": { + "enabled": true, + "pointInTimeRecoveryEnabled": true + }, + "databaseFlags": [ + { + "name": "random_page_cost", + "value": "1.1" + }, + { + "name": "temp_file_limit", + "value": "2147483647" + }, + { + "name": "max_wal_size", + "value": "20480" + }, + { + "name": "maintenance_work_mem", + "value": "2000000" + }, + { + "name": "work_mem", + "value": "16384" + }, + { + "name": "cloudsql.logical_decoding", + "value": "on" + } + ], + "deletionProtectionEnabled": true, + "edition": "ENTERPRISE", + "insightsConfig": { + "queryInsightsEnabled": true + }, + "ipConfiguration": { + "enablePrivatePathForGoogleCloudServices": true, + "ipv4Enabled": false, + "privateNetwork": "projects/test-project/global/networks/default" + }, + "locationPreference": { + "zone": "europe-west6-a" + }, + "maintenanceWindow": { + "day": 2, + "hour": 8 + }, + "tier": "apps-pg-override-tier-sv-1", + "userLabels": { + "cluster": "mock" + } + } + }, + "name": "sv-1-cn-apps-pg", + "provider": "", + "type": "gcp:sql/databaseInstance:DatabaseInstance" + }, + { + "custom": true, + "id": "", + "inputs": { + "name": "cantonnet" + }, + "name": "sv-1-db-cn-apps-pg-cantonnet", + "provider": "", + "type": "gcp:sql/database:Database" + }, + { + "custom": true, + "id": "", + "inputs": { + "name": "cantonnet" + }, + "name": "sv-1-db-participant-pg-cantonnet", + "provider": "", + "type": "gcp:sql/database:Database" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "imagePullSecrets": [ + { + "name": "docker-reg-cred" + } + ], + "kind": "ServiceAccount", + "metadata": { + "name": "default", + "namespace": "sv-1" + } + }, + "name": "sv-1-default", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", + "type": "kubernetes:core/v1:ServiceAccountPatch" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "docker-reg-cred", + "namespace": "sv-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + } + }, + "type": "kubernetes.io/dockerconfigjson" + }, + "name": "sv-1-docker-reg-cred", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-info", + "compat": "true", + "maxHistory": 10, + "name": "info", + "namespace": "sv-1", + "timeout": 600, + "values": { + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "deploymentDetails": { + "configDigest": { + "allowedIpRanges": { + "type": "md5", + "value": "90eedde4a8599204a45dcb972e212c8b" + }, + "approvedSvIdentities": { + "type": "md5", + "value": "5871224b744b45122540483fddbd550f" + } + }, + "network": "test", + "sv": { + "version": "0.3.20" + }, + "synchronizer": { + "current": { + "chainIdSuffix": "4", + "migrationId": 2, + "synchronizerSerialId": 9, + "version": "0.3.20" + }, + "legacy": { + "chainIdSuffix": "4", + "migrationId": 2, + "synchronizerSerialId": 8, + "version": "0.3.20" + }, + "successor": { + "chainIdSuffix": "4", + "migrationId": 2, + "synchronizerSerialId": 10, + "version": "0.3.21" + } + } + }, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "istioVirtualService": { + "gateway": "cluster-ingress/cn-http-gateway", + "host": "info.sv-2.mock.global.canton.network.digitalasset.com" + }, + "runtimeDetails": { + "scanUrl": "http://scan-app.sv-1:5012", + "synchronizerSerialId": 9 + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + }, + "version": "0.3.20" + }, + "name": "sv-1-info", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-cluster-ingress-runbook", + "compat": "true", + "maxHistory": 10, + "name": "ingress-sv", + "namespace": "sv-1", + "timeout": 600, + "values": { + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet", + "svIngressName": "sv-2", + "svNamespace": "sv-1" + }, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "ingress": { + "decentralizedSynchronizer": { + "migrationIds": [ + "9", + "8", + "7", + "10" + ] + } + }, + "rateLimit": { + "scan": { + "enable": false + } + }, + "spliceDomainNames": { + "nameServiceDomain": "cns" + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ], + "withSvIngress": true + }, + "version": "0.3.20" + }, + "name": "sv-1-ingress-sv", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "length": 16, + "overrideSpecial": "_%@", + "special": true + }, + "name": "sv-1-participant-pg-cnadmin-passwd", + "provider": "", + "type": "random:index/randomPassword:RandomPassword" + }, + { + "custom": false, + "id": "", + "inputs": { + "active": true, + "alias": "participant-pg", + "cloudSqlConfig": { + "databaseVersion": "POSTGRES_14", + "enabled": true, + "enterprisePlus": false, + "flags": { + "maintenance_work_mem": "2000000", + "max_wal_size": "20480", + "random_page_cost": "1.1", + "temp_file_limit": "2147483647", + "work_mem": "16384" + }, + "maintenanceWindow": { + "day": 2, + "hour": 8 + }, + "protected": true, + "tier": "db-custom-2-7680" + }, + "defaultUserName": "cnadmin", + "deletionProtection": true, + "disableProtection": false, + "instanceName": "participant-pg", + "logicalDecoding": false, + "namespace": { + "logicalName": "sv-1", + "ns": { + "__aliases": [], + "__name": "sv-1", + "__providers": {}, + "__pulumiCustomResource": true, + "__pulumiResource": true, + "__pulumiType": "kubernetes:core/v1:Namespace", + "__transformations": [], + "__version": "4.28.0", + "apiVersion": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "id": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "kind": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "metadata": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "spec": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "status": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "urn": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi." + } + }, + "retainDbResourcesOnDelete": false, + "secretName": "participant-pg-secrets" + }, + "name": "sv-1-participant-pg", + "provider": "", + "type": "canton:cloud:postgres" + }, + { + "custom": true, + "id": "", + "inputs": { + "databaseVersion": "POSTGRES_14", + "deletionProtection": false, + "region": "europe-west6", + "settings": { + "activationPolicy": "ALWAYS", + "backupConfiguration": { + "enabled": true, + "pointInTimeRecoveryEnabled": true + }, + "databaseFlags": [ + { + "name": "random_page_cost", + "value": "1.1" + }, + { + "name": "temp_file_limit", + "value": "2147483647" + }, + { + "name": "max_wal_size", + "value": "20480" + }, + { + "name": "maintenance_work_mem", + "value": "2000000" + }, + { + "name": "work_mem", + "value": "16384" + } + ], + "deletionProtectionEnabled": true, + "edition": "ENTERPRISE", + "insightsConfig": { + "queryInsightsEnabled": true + }, + "ipConfiguration": { + "enablePrivatePathForGoogleCloudServices": true, + "ipv4Enabled": false, + "privateNetwork": "projects/test-project/global/networks/default" + }, + "locationPreference": { + "zone": "europe-west6-a" + }, + "maintenanceWindow": { + "day": 2, + "hour": 8 + }, + "tier": "db-custom-2-7680", + "userLabels": { + "cluster": "mock" + } + } + }, + "name": "sv-1-participant-pg", + "provider": "", + "type": "gcp:sql/databaseInstance:DatabaseInstance" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-participant", + "compat": "true", + "maxHistory": 10, + "name": "participant", + "namespace": "sv-1", + "timeout": 600, + "values": { + "additionalEnvVars": [ + { + "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", + "value": "# Ignore missing ACS commitment and commitment mismatches\ncanton.participants.participant.parameters.stores.safe-to-prune-commitment-state = \"all\"\n" + }, + { + "name": "CUSTOM_MOCK_ENV_VAR_NAME", + "value": "CUSTOM_MOCK_ENV_VAR_VALUE" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomParticipantJvmFlag", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "auth": { + "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json", + "targetAudience": "https://canton.network.global" + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "enableHealthProbes": true, + "enablePostgresMetrics": true, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "logAsyncFlush": false, + "logLevel": "INFO", + "logLevelStdout": "DEBUG", + "metrics": { + "enable": true + }, + "participantAdminUserNameFrom": { + "secretKeyRef": { + "key": "ledger-api-user", + "name": "splice-app-sv-ledger-api-user", + "optional": false + } + }, + "persistence": { + "databaseName": "participant_2", + "port": 5432, + "postgresName": "participant-pg", + "schema": "participant", + "secretName": "participant-pg-secrets" + }, + "resources": { + "limits": { + "memory": "18Gi" + }, + "requests": { + "memory": "12Gi" + } + }, + "serviceAccountName": "sv", + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + }, + "version": "0.3.20" + }, + "name": "sv-1-participant", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "EnvoyFilter", + "metadata": { + "annotations": { + "proxy.istio.io/config": "proxyStatsMatcher:\n inclusionRegexps:\n - \".*http_local_rate_limit.*\"" + }, + "name": "scan-app-rate-limit", + "namespace": "sv-1" + }, + "spec": { + "configPatches": [ + { + "applyTo": "HTTP_FILTER", + "match": { + "context": "SIDECAR_INBOUND", + "listener": { + "filterChain": { + "filter": { + "name": "envoy.filters.network.http_connection_manager" + } + } + } + }, + "patch": { + "operation": "INSERT_BEFORE", + "value": { + "name": "envoy.filters.http.local_ratelimit", + "typed_config": { + "@type": "type.googleapis.com/udpa.type.v1.TypedStruct", + "type_url": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", + "value": { + "stat_prefix": "http_local_rate_limiter" + } + } + } + } + }, + { + "applyTo": "HTTP_ROUTE", + "match": { + "context": "SIDECAR_INBOUND", + "routeConfiguration": { + "vhost": { + "name": "inbound|http|5012", + "route": { + "action": "ANY" + } + } + } + }, + "patch": { + "operation": "MERGE", + "value": { + "route": { + "rate_limits": [ + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "acs", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/api/scan/v0/acs" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "acs", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/api/scan/v0/acs" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + } + ] + }, + "typed_per_filter_config": { + "envoy.filters.http.local_ratelimit": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", + "descriptors": [ + { + "entries": [ + { + "key": "header_match", + "value": "acs" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "acs" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip", + "value": "192.68.78.50" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 250, + "tokens_per_fill": 250 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-instruments" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-instruments" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 730, + "tokens_per_fill": 730 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + } + ], + "filter_enabled": { + "default_value": { + "denominator": "HUNDRED", + "numerator": 100 + }, + "runtime_key": "local_rate_limit_enabled" + }, + "filter_enforced": { + "default_value": { + "denominator": "HUNDRED", + "numerator": 100 + }, + "runtime_key": "local_rate_limit_enforced" + }, + "response_headers_to_add": [ + { + "append_action": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "x-local-rate-limit", + "value": "true" + } + } + ], + "stat_prefix": "http_local_rate_limiter", + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 2147483647, + "tokens_per_fill": 2147483647 + } + } + } + } + } + } + ], + "workloadSelector": { + "labels": { + "app": "scan-app" + } + } + } + }, + "name": "sv-1-scan-app-rate-limit", + "provider": "", + "type": "kubernetes:networking.istio.io/v1alpha3:EnvoyFilter" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-scan", + "compat": "true", + "maxHistory": 10, + "name": "scan", + "namespace": "sv-1", + "timeout": 600, + "values": { + "additionalEnvVars": [ + { + "name": "CUSTOM_MOCK_ENV_VAR_NAME", + "value": "CUSTOM_MOCK_ENV_VAR_VALUE" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomScanAppJvmFlag", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "apiRequestLogLevel": "DEBUG", + "bulkStorage": { + "committed": { + "bucketName": "mock-sv-1-bulk-committed", + "endpoint": "https://storage.googleapis.com", + "region": "europe-west6", + "secretName": "splice-app-bulk-storage-credentials" + }, + "staging": { + "bucketName": "mock-sv-1-bulk-staging", + "endpoint": "https://storage.googleapis.com", + "region": "europe-west6", + "secretName": "splice-app-bulk-storage-credentials" + } + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "enablePostgresMetrics": true, + "failOnAppVersionMismatch": true, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "isFirstSv": true, + "logAsyncFlush": false, + "logLevel": "WARN", + "metrics": { + "enable": true + }, + "participantAddress": "participant", + "persistence": { + "databaseName": "scan_sv_1", + "port": 5432, + "postgresName": "cn-apps-pg", + "schema": "scan_sv_1", + "secretName": "cn-apps-pg-secrets", + "user": "cnadmin" + }, + "publicUrl": "https://scan.sv-2.mock.global.canton.network.digitalasset.com", + "resources": { + "limits": { + "memory": "2048Mi" + }, + "requests": { + "cpu": "0.5", + "memory": "1536Mi" + } + }, + "spliceInstanceNames": { + "amuletName": "Amulet", + "amuletNameAcronym": "AMT", + "nameServiceName": "Amulet Name Service", + "nameServiceNameAcronym": "ANS", + "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", + "networkName": "Splice" + }, + "synchronizers": { + "current": { + "cantonBft": { + "p2pUrl": "https://sequencer-p2p-9.sv-2.mock.global.canton.network.digitalasset.com" + }, + "mediator": "global-domain-9-mediator", + "sequencer": "global-domain-9-sequencer" + }, + "legacy": { + "mediator": "global-domain-8-mediator", + "sequencer": "global-domain-8-sequencer" + }, + "successor": { + "cantonBft": { + "p2pUrl": "https://sequencer-p2p-10.sv-2.mock.global.canton.network.digitalasset.com" + }, + "mediator": "global-domain-10-mediator", + "sequencer": "global-domain-10-sequencer" + } + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + }, + "version": "0.3.20" + }, + "name": "sv-1-scan", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-sv-node", + "compat": "true", + "maxHistory": 10, + "name": "sv-app", + "namespace": "sv-1", + "timeout": 600, + "values": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "additionalEnvVars": [ + { + "name": "CUSTOM_MOCK_ENV_VAR_NAME", + "value": "CUSTOM_MOCK_ENV_VAR_VALUE" + }, + { + "name": "ADDITIONAL_CONFIG_MEDIATOR_PRUNING", + "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.mediator.pruning {\n cron = \"0 0 * * *\"\n max-duration = \"30m\"\n retention = \"7d\"\n }" + }, + { + "name": "ADDITIONAL_CONFIG_CANTON_BFT_PRUNING", + "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.sequencer.canton-bft-pruning {\n cron = \"0 /10 * * * ?\"\n max-duration = \"5m\"\n retention = \"15 days\"\n }" + }, + { + "name": "ADDITIONAL_CONFIG_ADDITIONAL_PACKAGES_TO_UNVET", + "value": "canton.sv-apps.sv.additional-packages-to-unvet.\"splice-wallet-payments\" = [\"0.1.15\", \"0.1.16\"]\ncanton.sv-apps.sv.additional-packages-to-unvet.\"splice-amulet\" = [\"0.1.15\"]" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomSvAppJvmFlag", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "apiRequestLogLevel": "DEBUG", + "approvedSvIdentities": [ + { + "name": "Digital-Asset-2", + "publicKey": "sv1-id-public-key", + "rewardWeightBps": 100000 + }, + { + "name": "SV1", + "publicKey": "PUBLIC_KEY_1==", + "rewardWeightBps": 1000000 + }, + { + "extraBeneficiaries": [ + { + "beneficiary": "mock-validator-1::123456789012345678901234567890123456789012234567890123456789012345678", + "weight": 100000 + }, + { + "beneficiary": "Broadridge-validator-1::1220c89a73e7b47f16dfb48a521eeedfe2a8620ea1b19b815ab427388d9f5427faa5", + "weight": 50000 + } + ], + "name": "SV2", + "publicKey": "PUBLIC_KEY_2==", + "rewardWeightBps": 150000 + }, + { + "name": "Digital-Asset-1", + "publicKey": "svda1-mock-id-public-key", + "rewardWeightBps": 150000 + }, + { + "name": "DA-Helm-Test-Node", + "publicKey": "sv-id-public-key", + "rewardWeightBps": 10000 + } + ], + "auth": { + "audience": "https://canton.network.global", + "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json" + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "contactPoint": "sv-support@digitalasset.com", + "delegatelessAutomationExpectedTaskDuration": 5000, + "delegatelessAutomationExpiredRewardCouponBatchSize": 100, + "delegatelessAutomationExpiredRewardCouponNumBatches": 100, + "disableOnboardingParticipantPromotionDelay": false, + "enablePostgresMetrics": true, + "expectedValidatorOnboardings": [ + { + "expiresIn": "24h", + "secretFrom": { + "secretKeyRef": { + "key": "secret", + "name": "splice-app-validator-onboarding-splitwell2", + "optional": false + } + } + }, + { + "expiresIn": "24h", + "secretFrom": { + "secretKeyRef": { + "key": "secret", + "name": "splice-app-validator-onboarding-validator12", + "optional": false + } + } + } + ], + "failOnAppVersionMismatch": true, + "identitiesExport": { + "bucket": { + "bucketName": "da-cn-data-dumps", + "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", + "projectId": "da-cn-devnet", + "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" + }, + "prefix": "mock/sv-1" + }, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "initialAmuletPrice": "0.0517", + "initialPackageConfigJson": "{\"amuletVersion\": \"0.1.4\",\"amuletNameServiceVersion\": \"0.1.4\",\"dsoGovernanceVersion\": \"0.1.7\",\"validatorLifecycleVersion\": \"0.1.1\",\"walletVersion\": \"0.1.4\",\"walletPaymentsVersion\": \"0.1.4\"}", + "initialRound": "0", + "initialSynchronizerFeesConfig": { + "baseRateBurstAmount": 200000, + "baseRateBurstWindowMins": 20, + "extraTrafficPrice": 16.67, + "minTopupAmount": 200000, + "readVsWriteScalingFactor": 4 + }, + "isDevNet": false, + "logAsyncFlush": false, + "logLevel": "WARN", + "maxVettingDelay": "1m", + "metrics": { + "enable": true + }, + "nodeIdentifier": "Digital-Asset-2", + "onboardingFoundingSvRewardWeightBps": 100000, + "onboardingName": "Digital-Asset-2", + "onboardingRoundZeroDuration": "2 h", + "onboardingType": "found-dso", + "participantAddress": "participant", + "periodicTopologySnapshotConfig": { + "backupInterval": "12h", + "location": { + "bucket": { + "bucketName": "cn-topology-snapshots", + "jsonCredentials": "{\"projectId\":\"da-cn-shared\",\"bucketName\":\"topology-snapshot-bucket-name\",\"secretName\":\"gcp-topology-snapshot-bucket-sa-key-secret\",\"jsonCredentials\":\"topology-snapshot-bucket-sa-key-secret-creds\",\"bucketSaKeySecret\":\"gcp-topology-snapshot-bucket-sa-key-example\",\"bucketSaIamAccount\":\"da-cn-examplet@da-cn-shared.iam.gserviceaccount.com\"}", + "projectId": "da-cn-shared", + "secretName": "cn-gcp-bucket-da-cn-shared-cn-topology-snapshots" + }, + "prefix": "mock/sv-1" + } + }, + "permissionedSynchronizer": false, + "persistence": { + "databaseName": "sv_sv_1", + "port": 5432, + "postgresName": "cn-apps-pg", + "schema": "sv_sv_1", + "secretName": "cn-apps-pg-secrets", + "user": "cnadmin" + }, + "pvc": { + "volumeName": "sv-app-global-domain-migration-hd-pvc", + "volumeStorageClass": "hyperdisk-standard-rwo" + }, + "resources": { + "limits": { + "memory": "2Gi" + }, + "requests": { + "cpu": "1", + "memory": "1Gi" + } + }, + "scan": { + "internalUrl": "http://scan-app.sv-1:5012", + "publicUrl": "https://scan.sv-2.mock.global.canton.network.digitalasset.com" + }, + "spliceInstanceNames": { + "amuletName": "Amulet", + "amuletNameAcronym": "AMT", + "nameServiceName": "Amulet Name Service", + "nameServiceNameAcronym": "ANS", + "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", + "networkName": "Splice" + }, + "synchronizers": { + "additionalLegacy": [ + { + "cometBFT": { + "connectionUri": "http://global-domain-7-cometbft-cometbft-rpc:26657", + "enabled": true + }, + "mediatorAddress": "global-domain-7-mediator", + "sequencerAddress": "global-domain-7-sequencer", + "sequencerPruningConfig": { + "enabled": true, + "pruningInterval": "1 hour", + "retentionPeriod": "30 days" + }, + "sequencerPublicUrl": "https://sequencer-7.sv-2.mock.global.canton.network.digitalasset.com" + } + ], + "current": { + "enableBftSequencer": true, + "mediatorAddress": "global-domain-9-mediator", + "sequencerAddress": "global-domain-9-sequencer", + "sequencerPruningConfig": { + "enabled": true, + "pruningInterval": "1 hour", + "retentionPeriod": "30 days" + }, + "sequencerPublicUrl": "https://sequencer-9.sv-2.mock.global.canton.network.digitalasset.com" + }, + "legacy": { + "cometBFT": { + "connectionUri": "http://global-domain-8-cometbft-cometbft-rpc:26657", + "enabled": true + }, + "mediatorAddress": "global-domain-8-mediator", + "sequencerAddress": "global-domain-8-sequencer", + "sequencerPruningConfig": { + "enabled": true, + "pruningInterval": "1 hour", + "retentionPeriod": "30 days" + }, + "sequencerPublicUrl": "https://sequencer-8.sv-2.mock.global.canton.network.digitalasset.com" + }, + "skipInitialization": true, + "successor": { + "enableBftSequencer": true, + "mediatorAddress": "global-domain-10-mediator", + "sequencerAddress": "global-domain-10-sequencer", + "sequencerPruningConfig": { + "enabled": true, + "pruningInterval": "1 hour", + "retentionPeriod": "30 days" + }, + "sequencerPublicUrl": "https://sequencer-10.sv-2.mock.global.canton.network.digitalasset.com" + } + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + } + }, + "version": "0.3.20" + }, + "name": "sv-1-sv-app", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "sv-docker-reg-cred", + "namespace": "sv-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + } + }, + "type": "kubernetes.io/dockerconfigjson" + }, + "name": "sv-1-sv-docker-reg-cred", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "imagePullSecrets": [ + { + "name": "sv-docker-reg-cred" + } + ], + "kind": "ServiceAccount", + "metadata": { + "name": "sv", + "namespace": "sv-1" + } + }, + "name": "sv-1-sv", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv::04da6b54-80e4-46f7-96ec-b56ff0331ba9", + "type": "kubernetes:core/v1:ServiceAccountPatch" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-validator", + "compat": "true", + "maxHistory": 10, + "name": "validator-sv-1", + "namespace": "sv-1", + "timeout": 600, + "values": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "additionalEnvVars": [ + { + "name": "ADDITIONAL_CONFIG_TOPOLOGY_METRICS_EXPORT", + "value": "canton.validator-apps.validator_backend.automation.topology-metrics-polling-interval = 5m\n" + }, + { + "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", + "value": "canton.validator-apps.validator_backend.participant-pruning-schedule {\n cron = \"0 /10 * * * ?\"\n max-duration = \"5m\"\n retention = \"30d\"\n }" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1", + "additionalUsers": [], + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "apiRequestLogLevel": "DEBUG", + "appDars": [], + "auth": { + "audience": "https://canton.network.global", + "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json" + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "contactPoint": "sv-support@digitalasset.com", + "disableAllocateLedgerApiUserParty": true, + "disableAuth": false, + "enablePostgresMetrics": true, + "failOnAppVersionMismatch": true, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "logAsyncFlush": false, + "logLevel": "WARN", + "maxVettingDelay": "1m", + "metrics": { + "enable": true + }, + "nodeIdentifier": "Digital-Asset-2", + "participantAddress": "participant", + "participantIdentitiesDumpPeriodicBackup": { + "backupInterval": "10m", + "location": { + "bucket": { + "bucketName": "da-cn-data-dumps", + "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", + "projectId": "da-cn-devnet", + "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" + }, + "prefix": "mock/sv-1" + } + }, + "persistence": { + "databaseName": "validator_sv_1", + "port": 5432, + "postgresName": "cn-apps-pg", + "schema": "validator_sv_1", + "secretName": "cn-apps-pg-secrets", + "user": "cnadmin" + }, + "pvc": { + "volumeName": "domain-migration-validator-hd-pvc", + "volumeStorageClass": "hyperdisk-standard-rwo" + }, + "resources": { + "limits": { + "memory": "4Gi" + }, + "requests": { + "memory": "2Gi" + } + }, + "scanAddress": "http://scan-app.sv-1:5012", + "spliceInstanceNames": { + "amuletName": "Amulet", + "amuletNameAcronym": "AMT", + "nameServiceName": "Amulet Name Service", + "nameServiceNameAcronym": "ANS", + "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", + "networkName": "Splice" + }, + "svValidator": true, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ], + "topup": { + "enabled": true, + "minTopupInterval": "1m", + "targetThroughput": 0 + }, + "useSequencerConnectionsFromScan": true, + "validatorWalletUsers": [ + "google-oauth2|1234567890", + "auth0|64529b128448ded6aa68048f" + ], + "walletSweep": { + "mock::11111111111111111111111111111111111111111111111111111111111111111111": { + "maxBalanceUSD": 12345, + "minBalanceUSD": 17, + "receiver": "mock-2::222222222222222222222222222222222222222222222222222222222222222222222" + } + } + } + }, + "version": "0.3.20" + }, + "name": "sv-1-validator-sv-1", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "labels": { + "istio-injection": "enabled" + }, + "name": "sv-1" + } + }, + "name": "sv-1", + "provider": "", + "type": "kubernetes:core/v1:Namespace" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "data": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "accessKey": "", + "secretAccessKey": "" + } + }, + "kind": "Secret", + "metadata": { + "name": "splice-app-bulk-storage-credentials", + "namespace": "sv-da-1" + }, + "type": "Opaque" + }, + "name": "sv-da-1-bulk-storage-credentials", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "length": 16, + "overrideSpecial": "_%@", + "special": true + }, + "name": "sv-da-1-cn-apps-pg-cnadmin-passwd", + "provider": "", + "type": "random:index/randomPassword:RandomPassword" + }, + { + "custom": false, + "id": "", + "inputs": { + "active": true, + "alias": "cn-apps-pg", + "cloudSqlConfig": { + "databaseVersion": "POSTGRES_14", + "enabled": true, + "enterprisePlus": false, + "flags": { + "maintenance_work_mem": "2000000", + "max_wal_size": "20480", + "random_page_cost": "1.1", + "temp_file_limit": "2147483647", + "work_mem": "16384" + }, + "maintenanceWindow": { + "day": 2, + "hour": 8 + }, + "protected": true, + "tier": "apps-pg-override-tier" + }, + "defaultUserName": "cnadmin", + "deletionProtection": true, + "instanceName": "cn-apps-pg", + "logicalDecoding": false, + "namespace": { + "logicalName": "sv-da-1", + "ns": { + "__aliases": [], + "__name": "sv-da-1", + "__providers": {}, + "__pulumiCustomResource": true, + "__pulumiResource": true, + "__pulumiType": "kubernetes:core/v1:Namespace", + "__transformations": [], + "__version": "4.28.0", + "apiVersion": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "id": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "kind": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "metadata": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "spec": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "status": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", + "urn": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi." + } + }, + "retainDbResourcesOnDelete": false, + "secretName": "cn-apps-pg-secrets" + }, + "name": "sv-da-1-cn-apps-pg", + "provider": "", + "type": "canton:cloud:postgres" + }, + { + "custom": true, + "id": "", + "inputs": { + "databaseVersion": "POSTGRES_14", + "deletionProtection": false, + "region": "europe-west6", + "settings": { + "activationPolicy": "ALWAYS", + "backupConfiguration": { + "enabled": true, + "pointInTimeRecoveryEnabled": true + }, + "databaseFlags": [ + { + "name": "random_page_cost", + "value": "1.1" + }, + { + "name": "temp_file_limit", + "value": "2147483647" + }, + { + "name": "max_wal_size", + "value": "20480" + }, + { + "name": "maintenance_work_mem", + "value": "2000000" + }, + { + "name": "work_mem", + "value": "16384" + } + ], + "deletionProtectionEnabled": true, + "edition": "ENTERPRISE", + "insightsConfig": { + "queryInsightsEnabled": true + }, + "ipConfiguration": { + "enablePrivatePathForGoogleCloudServices": true, + "ipv4Enabled": false, + "privateNetwork": "projects/test-project/global/networks/default" + }, + "locationPreference": { + "zone": "europe-west6-a" + }, + "maintenanceWindow": { + "day": 2, + "hour": 8 + }, + "tier": "apps-pg-override-tier", + "userLabels": { + "cluster": "mock" + } + } + }, + "name": "sv-da-1-cn-apps-pg", + "provider": "", + "type": "gcp:sql/databaseInstance:DatabaseInstance" + }, + { + "custom": true, + "id": "", + "inputs": { + "name": "cantonnet" + }, + "name": "sv-da-1-db-cn-apps-pg-cantonnet", + "provider": "", + "type": "gcp:sql/database:Database" + }, + { + "custom": true, + "id": "", + "inputs": { + "name": "cantonnet" + }, + "name": "sv-da-1-db-participant-pg-cantonnet", + "provider": "", + "type": "gcp:sql/database:Database" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "imagePullSecrets": [ + { + "name": "docker-reg-cred" + } + ], + "kind": "ServiceAccount", + "metadata": { + "name": "default", + "namespace": "sv-da-1" + } + }, + "name": "sv-da-1-default", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", + "type": "kubernetes:core/v1:ServiceAccountPatch" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "v1", + "kind": "Secret", + "metadata": { + "name": "docker-reg-cred", + "namespace": "sv-da-1" + }, + "stringData": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + } + }, + "type": "kubernetes.io/dockerconfigjson" + }, + "name": "sv-da-1-docker-reg-cred", + "provider": "", + "type": "kubernetes:core/v1:Secret" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-info", + "compat": "true", + "maxHistory": 10, + "name": "info", + "namespace": "sv-da-1", + "timeout": 600, + "values": { + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "deploymentDetails": { + "configDigest": { + "allowedIpRanges": { + "type": "md5", + "value": "90eedde4a8599204a45dcb972e212c8b" + }, + "approvedSvIdentities": { + "type": "md5", + "value": "5871224b744b45122540483fddbd550f" + } + }, + "network": "test", + "sv": { + "version": "0.3.20" + }, + "synchronizer": { + "current": { + "chainIdSuffix": "4", + "migrationId": 2, + "synchronizerSerialId": 9, + "version": "0.3.20" + }, + "legacy": { + "chainIdSuffix": "4", + "migrationId": 2, + "synchronizerSerialId": 8, + "version": "0.3.20" + }, + "successor": { + "chainIdSuffix": "4", + "migrationId": 2, + "synchronizerSerialId": 10, + "version": "0.3.21" + } + } + }, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "istioVirtualService": { + "gateway": "cluster-ingress/cn-http-gateway", + "host": "info.sv-1.mock.global.canton.network.digitalasset.com" + }, + "runtimeDetails": { + "scanUrl": "http://scan-app.sv-da-1:5012", + "synchronizerSerialId": 9 + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + }, + "version": "0.3.20" + }, + "name": "sv-da-1-info", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-cluster-ingress-runbook", + "compat": "true", + "maxHistory": 10, + "name": "ingress-sv", + "namespace": "sv-da-1", + "timeout": 600, + "values": { + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet", + "svIngressName": "sv-1", + "svNamespace": "sv-da-1" + }, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "ingress": { + "decentralizedSynchronizer": { + "migrationIds": [ + "9", + "8", + "7", + "10" + ] + } + }, + "rateLimit": { + "scan": { + "enable": false + } + }, + "spliceDomainNames": { + "nameServiceDomain": "cns" + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ], + "withSvIngress": true }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - "ledger-api-user": "sv-client-id@clients" - } - } - }, - "name": "splice-auth0-user-sv-sv-sv", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "name": "cantonnet" + "version": "0.3.20" }, - "name": "sv-1-db-participant-pg-cantonnet", + "name": "sv-da-1-ingress-sv", "provider": "", - "type": "gcp:sql/database:Database" + "type": "kubernetes:helm.sh/v3:Release" }, { "custom": true, @@ -256,7 +5012,7 @@ "overrideSpecial": "_%@", "special": true }, - "name": "sv-1-participant-pg-cnadmin-passwd", + "name": "sv-da-1-participant-pg-cnadmin-passwd", "provider": "", "type": "random:index/randomPassword:RandomPassword" }, @@ -290,10 +5046,10 @@ "instanceName": "participant-pg", "logicalDecoding": false, "namespace": { - "logicalName": "sv-1", + "logicalName": "sv-da-1", "ns": { "__aliases": [], - "__name": "sv-1", + "__name": "sv-da-1", "__providers": {}, "__pulumiCustomResource": true, "__pulumiResource": true, @@ -312,7 +5068,7 @@ "retainDbResourcesOnDelete": false, "secretName": "participant-pg-secrets" }, - "name": "sv-1-participant-pg", + "name": "sv-da-1-participant-pg", "provider": "", "type": "canton:cloud:postgres" }, @@ -374,7 +5130,7 @@ } } }, - "name": "sv-1-participant-pg", + "name": "sv-da-1-participant-pg", "provider": "", "type": "gcp:sql/databaseInstance:DatabaseInstance" }, @@ -386,10 +5142,14 @@ "compat": "true", "maxHistory": 10, "name": "participant", - "namespace": "sv-1", + "namespace": "sv-da-1", "timeout": 600, "values": { "additionalEnvVars": [ + { + "name": "GOOGLE_APPLICATION_CREDENTIALS", + "value": "/app/gcp-credentials.json" + }, { "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", "value": "# Ignore missing ACS commitment and commitment mismatches\ncanton.participants.participant.parameters.stores.safe-to-prune-commitment-state = \"all\"\n" @@ -435,7 +5195,28 @@ }, "enableHealthProbes": true, "enablePostgresMetrics": true, + "extraVolumeMounts": [ + { + "mountPath": "/app/gcp-credentials.json", + "name": "gcp-credentials", + "subPath": "googleCredentials" + } + ], + "extraVolumes": [ + { + "name": "gcp-credentials", + "secret": { + "secretName": "gke-credentials" + } + } + ], "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "kms": { + "keyRingId": "sv-da-1_participant_mock", + "locationId": "us-central1", + "projectId": "da-cn-devnet", + "type": "gcp" + }, "logAsyncFlush": false, "logLevel": "INFO", "logLevelStdout": "DEBUG", @@ -475,7 +5256,7 @@ }, "version": "0.3.20" }, - "name": "sv-1-participant", + "name": "sv-da-1-participant", "provider": "", "type": "kubernetes:helm.sh/v3:Release" }, @@ -483,227 +5264,1098 @@ "custom": true, "id": "", "inputs": { - "apiVersion": "v1", - "kind": "Secret", - "metadata": { - "name": "sv-docker-reg-cred", - "namespace": "sv-1" - }, - "stringData": { - "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", - "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" - } - }, - "type": "kubernetes.io/dockerconfigjson" - }, - "name": "sv-1-sv-docker-reg-cred", - "provider": "", - "type": "kubernetes:core/v1:Secret" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "imagePullSecrets": [ - { - "name": "sv-docker-reg-cred" - } - ], - "kind": "ServiceAccount", - "metadata": { - "name": "sv", - "namespace": "sv-1" - } - }, - "name": "sv-1-sv", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-1-sv::undefined_id", - "type": "kubernetes:core/v1:ServiceAccountPatch" - }, - { - "custom": true, - "id": "", - "inputs": { - "apiVersion": "v1", - "kind": "Namespace", + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "EnvoyFilter", "metadata": { - "labels": { - "istio-injection": "enabled" - }, - "name": "sv-1" - } - }, - "name": "sv-1", - "provider": "", - "type": "kubernetes:core/v1:Namespace" - }, - { - "custom": true, - "id": "", - "inputs": { - "name": "cantonnet" - }, - "name": "sv-da-1-db-participant-pg-cantonnet", - "provider": "", - "type": "gcp:sql/database:Database" - }, - { - "custom": true, - "id": "", - "inputs": { - "length": 16, - "overrideSpecial": "_%@", - "special": true - }, - "name": "sv-da-1-participant-pg-cnadmin-passwd", - "provider": "", - "type": "random:index/randomPassword:RandomPassword" - }, - { - "custom": false, - "id": "", - "inputs": { - "active": true, - "alias": "participant-pg", - "cloudSqlConfig": { - "databaseVersion": "POSTGRES_14", - "enabled": true, - "enterprisePlus": false, - "flags": { - "maintenance_work_mem": "2000000", - "max_wal_size": "20480", - "random_page_cost": "1.1", - "temp_file_limit": "2147483647", - "work_mem": "16384" - }, - "maintenanceWindow": { - "day": 2, - "hour": 8 + "annotations": { + "proxy.istio.io/config": "proxyStatsMatcher:\n inclusionRegexps:\n - \".*http_local_rate_limit.*\"" }, - "protected": true, - "tier": "db-custom-2-7680" - }, - "defaultUserName": "cnadmin", - "deletionProtection": true, - "disableProtection": false, - "instanceName": "participant-pg", - "logicalDecoding": false, - "namespace": { - "logicalName": "sv-da-1", - "ns": { - "__aliases": [], - "__name": "sv-da-1", - "__providers": {}, - "__pulumiCustomResource": true, - "__pulumiResource": true, - "__pulumiType": "kubernetes:core/v1:Namespace", - "__transformations": [], - "__version": "4.28.0", - "apiVersion": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "id": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "kind": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "metadata": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "spec": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "status": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi.", - "urn": "Calling [toJSON] on an [Output] is not supported.\n\nTo get the value of an Output as a JSON value or JSON string consider either:\n 1: o.apply(v => v.toJSON())\n 2: o.apply(v => JSON.stringify(v))\n\nSee https://www.pulumi.com/docs/concepts/inputs-outputs for more details.\nThis function may throw in a future version of @pulumi/pulumi." - } + "name": "scan-app-rate-limit", + "namespace": "sv-da-1" }, - "retainDbResourcesOnDelete": false, - "secretName": "participant-pg-secrets" - }, - "name": "sv-da-1-participant-pg", - "provider": "", - "type": "canton:cloud:postgres" - }, - { - "custom": true, - "id": "", - "inputs": { - "databaseVersion": "POSTGRES_14", - "deletionProtection": false, - "region": "europe-west6", - "settings": { - "activationPolicy": "ALWAYS", - "backupConfiguration": { - "enabled": true, - "pointInTimeRecoveryEnabled": true - }, - "databaseFlags": [ - { - "name": "random_page_cost", - "value": "1.1" - }, - { - "name": "temp_file_limit", - "value": "2147483647" - }, - { - "name": "max_wal_size", - "value": "20480" - }, + "spec": { + "configPatches": [ { - "name": "maintenance_work_mem", - "value": "2000000" + "applyTo": "HTTP_FILTER", + "match": { + "context": "SIDECAR_INBOUND", + "listener": { + "filterChain": { + "filter": { + "name": "envoy.filters.network.http_connection_manager" + } + } + } + }, + "patch": { + "operation": "INSERT_BEFORE", + "value": { + "name": "envoy.filters.http.local_ratelimit", + "typed_config": { + "@type": "type.googleapis.com/udpa.type.v1.TypedStruct", + "type_url": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", + "value": { + "stat_prefix": "http_local_rate_limiter" + } + } + } + } }, { - "name": "work_mem", - "value": "16384" + "applyTo": "HTTP_ROUTE", + "match": { + "context": "SIDECAR_INBOUND", + "routeConfiguration": { + "vhost": { + "name": "inbound|http|5012", + "route": { + "action": "ANY" + } + } + } + }, + "patch": { + "operation": "MERGE", + "value": { + "route": { + "rate_limits": [ + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "acs", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/api/scan/v0/acs" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "acs", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/api/scan/v0/acs" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-info", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/info" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-metadata-instruments", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/metadata/v1/instruments" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v1/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v1/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-settlement-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation/v2/settlement-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocations-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocations/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2/allocation-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-allocation-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/allocation-instruction/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-factory-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2/transfer-factory" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2" + } + } + ] + } + } + ] + }, + { + "actions": [ + { + "header_value_match": { + "descriptor_value": "registry-transfer-instruction-v2", + "expect_match": true, + "headers": [ + { + "name": ":path", + "string_match": { + "ignore_case": true, + "prefix": "/registry/transfer-instruction/v2" + } + } + ] + } + }, + { + "request_headers": { + "descriptor_key": "client_ip", + "header_name": "x-forwarded-for" + } + } + ] + } + ] + }, + "typed_per_filter_config": { + "envoy.filters.http.local_ratelimit": { + "@type": "type.googleapis.com/envoy.extensions.filters.http.local_ratelimit.v3.LocalRateLimit", + "descriptors": [ + { + "entries": [ + { + "key": "header_match", + "value": "acs" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 500, + "tokens_per_fill": 500 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "acs" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 10, + "tokens_per_fill": 5 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip", + "value": "192.68.78.50" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 250, + "tokens_per_fill": 250 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-info" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-instruments" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-metadata-instruments" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 730, + "tokens_per_fill": 730 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-settlement-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocations-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-allocation-instruction-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-factory-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 720, + "tokens_per_fill": 720 + } + }, + { + "entries": [ + { + "key": "header_match", + "value": "registry-transfer-instruction-v2" + }, + { + "key": "client_ip" + } + ], + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 120, + "tokens_per_fill": 120 + } + } + ], + "filter_enabled": { + "default_value": { + "denominator": "HUNDRED", + "numerator": 100 + }, + "runtime_key": "local_rate_limit_enabled" + }, + "filter_enforced": { + "default_value": { + "denominator": "HUNDRED", + "numerator": 100 + }, + "runtime_key": "local_rate_limit_enforced" + }, + "response_headers_to_add": [ + { + "append_action": "OVERWRITE_IF_EXISTS_OR_ADD", + "header": { + "key": "x-local-rate-limit", + "value": "true" + } + } + ], + "stat_prefix": "http_local_rate_limiter", + "token_bucket": { + "fill_interval": "60s", + "max_tokens": 2147483647, + "tokens_per_fill": 2147483647 + } + } + } + } + } } ], - "deletionProtectionEnabled": true, - "edition": "ENTERPRISE", - "insightsConfig": { - "queryInsightsEnabled": true - }, - "ipConfiguration": { - "enablePrivatePathForGoogleCloudServices": true, - "ipv4Enabled": false, - "privateNetwork": "projects/test-project/global/networks/default" - }, - "locationPreference": { - "zone": "europe-west6-a" - }, - "maintenanceWindow": { - "day": 2, - "hour": 8 - }, - "tier": "db-custom-2-7680", - "userLabels": { - "cluster": "mock" + "workloadSelector": { + "labels": { + "app": "scan-app" + } } } }, - "name": "sv-da-1-participant-pg", + "name": "sv-da-1-scan-app-rate-limit", "provider": "", - "type": "gcp:sql/databaseInstance:DatabaseInstance" + "type": "kubernetes:networking.istio.io/v1alpha3:EnvoyFilter" }, { "custom": true, "id": "", "inputs": { - "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-participant", + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-scan", "compat": "true", "maxHistory": 10, - "name": "participant", + "name": "scan", "namespace": "sv-da-1", "timeout": 600, "values": { "additionalEnvVars": [ - { - "name": "GOOGLE_APPLICATION_CREDENTIALS", - "value": "/app/gcp-credentials.json" - }, - { - "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", - "value": "# Ignore missing ACS commitment and commitment mismatches\ncanton.participants.participant.parameters.stores.safe-to-prune-commitment-state = \"all\"\n" - }, { "name": "CUSTOM_MOCK_ENV_VAR_NAME", "value": "CUSTOM_MOCK_ENV_VAR_VALUE" } ], - "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomParticipantJvmFlag", + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomScanAppJvmFlag", "affinity": { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { @@ -727,9 +6379,20 @@ } } }, - "auth": { - "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json", - "targetAudience": "https://canton.network.global" + "apiRequestLogLevel": "DEBUG", + "bulkStorage": { + "committed": { + "bucketName": "mock-sv-da-1-bulk-committed", + "endpoint": "https://storage.googleapis.com", + "region": "europe-west6", + "secretName": "splice-app-bulk-storage-credentials" + }, + "staging": { + "bucketName": "mock-sv-da-1-bulk-staging", + "endpoint": "https://storage.googleapis.com", + "region": "europe-west6", + "secretName": "splice-app-bulk-storage-credentials" + } }, "cluster": { "dnsName": "mock.global.canton.network.digitalasset.com", @@ -737,59 +6400,63 @@ "hostname": "mock.global.canton.network.digitalasset.com", "name": "cn-mocknet" }, - "enableHealthProbes": true, "enablePostgresMetrics": true, - "extraVolumeMounts": [ - { - "mountPath": "/app/gcp-credentials.json", - "name": "gcp-credentials", - "subPath": "googleCredentials" - } - ], - "extraVolumes": [ - { - "name": "gcp-credentials", - "secret": { - "secretName": "gke-credentials" - } - } - ], + "failOnAppVersionMismatch": true, "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", - "kms": { - "keyRingId": "sv-da-1_participant_mock", - "locationId": "us-central1", - "projectId": "da-cn-devnet", - "type": "gcp" - }, + "isFirstSv": false, "logAsyncFlush": false, "logLevel": "INFO", - "logLevelStdout": "DEBUG", "metrics": { "enable": true }, - "participantAdminUserNameFrom": { - "secretKeyRef": { - "key": "ledger-api-user", - "name": "splice-app-sv-ledger-api-user", - "optional": false - } - }, + "participantAddress": "participant", "persistence": { - "databaseName": "participant_2", + "databaseName": "scan_sv_da_1", "port": 5432, - "postgresName": "participant-pg", - "schema": "participant", - "secretName": "participant-pg-secrets" + "postgresName": "cn-apps-pg", + "schema": "scan_sv_da_1", + "secretName": "cn-apps-pg-secrets", + "user": "cnadmin" }, + "publicUrl": "https://scan.sv-1.mock.global.canton.network.digitalasset.com", "resources": { "limits": { - "memory": "18Gi" + "memory": "2048Mi" }, "requests": { - "memory": "12Gi" + "cpu": "0.5", + "memory": "1536Mi" + } + }, + "spliceInstanceNames": { + "amuletName": "Amulet", + "amuletNameAcronym": "AMT", + "nameServiceName": "Amulet Name Service", + "nameServiceNameAcronym": "ANS", + "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", + "networkName": "Splice" + }, + "sponsorScanUrl": "http://scan-app.sv-1:5012", + "synchronizers": { + "current": { + "cantonBft": { + "p2pUrl": "https://sequencer-p2p-9.sv-1.mock.global.canton.network.digitalasset.com" + }, + "mediator": "global-domain-9-mediator", + "sequencer": "global-domain-9-sequencer" + }, + "legacy": { + "mediator": "global-domain-8-mediator", + "sequencer": "global-domain-8-sequencer" + }, + "successor": { + "cantonBft": { + "p2pUrl": "https://sequencer-p2p-10.sv-1.mock.global.canton.network.digitalasset.com" + }, + "mediator": "global-domain-10-mediator", + "sequencer": "global-domain-10-sequencer" } }, - "serviceAccountName": "sv", "tolerations": [ { "effect": "NoSchedule", @@ -800,7 +6467,254 @@ }, "version": "0.3.20" }, - "name": "sv-da-1-participant", + "name": "sv-da-1-scan", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-sv-node", + "compat": "true", + "maxHistory": 10, + "name": "sv-app", + "namespace": "sv-da-1", + "timeout": 600, + "values": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "additionalEnvVars": [ + { + "name": "CUSTOM_MOCK_ENV_VAR_NAME", + "value": "CUSTOM_MOCK_ENV_VAR_VALUE" + }, + { + "name": "ADDITIONAL_CONFIG_NO_BFT_SEQUENCER_CONNECTION", + "value": "canton.sv-apps.sv.bft-sequencer-connection = false" + }, + { + "name": "ADDITIONAL_CONFIG_MEDIATOR_PRUNING", + "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.mediator.pruning {\n cron = \"0 0 * * *\"\n max-duration = \"30m\"\n retention = \"7d\"\n }" + }, + { + "name": "ADDITIONAL_CONFIG_CANTON_BFT_PRUNING", + "value": "canton.sv-apps.sv.local-synchronizer-nodes.current.sequencer.canton-bft-pruning {\n cron = \"0 /10 * * * ?\"\n max-duration = \"5m\"\n retention = \"15 days\"\n }" + }, + { + "name": "ADDITIONAL_CONFIG_ADDITIONAL_PACKAGES_TO_UNVET", + "value": "canton.sv-apps.sv.additional-packages-to-unvet.\"splice-wallet-payments\" = [\"0.1.15\", \"0.1.16\"]\ncanton.sv-apps.sv.additional-packages-to-unvet.\"splice-amulet\" = [\"0.1.15\"]" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomSvAppJvmFlag", + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "apiRequestLogLevel": "DEBUG", + "approvedSvIdentities": [ + { + "name": "Digital-Asset-2", + "publicKey": "sv1-id-public-key", + "rewardWeightBps": 100000 + }, + { + "name": "SV1", + "publicKey": "PUBLIC_KEY_1==", + "rewardWeightBps": 1000000 + }, + { + "extraBeneficiaries": [ + { + "beneficiary": "mock-validator-1::123456789012345678901234567890123456789012234567890123456789012345678", + "weight": 100000 + }, + { + "beneficiary": "Broadridge-validator-1::1220c89a73e7b47f16dfb48a521eeedfe2a8620ea1b19b815ab427388d9f5427faa5", + "weight": 50000 + } + ], + "name": "SV2", + "publicKey": "PUBLIC_KEY_2==", + "rewardWeightBps": 150000 + }, + { + "name": "Digital-Asset-1", + "publicKey": "svda1-mock-id-public-key", + "rewardWeightBps": 150000 + }, + { + "name": "DA-Helm-Test-Node", + "publicKey": "sv-id-public-key", + "rewardWeightBps": 10000 + } + ], + "auth": { + "audience": "https://canton.network.global", + "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json" + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "contactPoint": "sv-support@digitalasset.com", + "decentralizedSynchronizerUrl": "http://global-domain-9-sequencer.sv-1:5008", + "delegatelessAutomationExpectedTaskDuration": 5000, + "delegatelessAutomationExpiredRewardCouponBatchSize": 100, + "delegatelessAutomationExpiredRewardCouponNumBatches": 100, + "disableOnboardingParticipantPromotionDelay": false, + "enablePostgresMetrics": true, + "expectedValidatorOnboardings": [], + "failOnAppVersionMismatch": true, + "identitiesExport": { + "bucket": { + "bucketName": "da-cn-data-dumps", + "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", + "projectId": "da-cn-devnet", + "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" + }, + "prefix": "mock/sv-da-1" + }, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "initialAmuletPrice": "0.0517", + "isDevNet": false, + "joinWithKeyOnboarding": { + "sponsorApiUrl": "http://sv-app.sv-1:5014" + }, + "logAsyncFlush": false, + "logLevel": "INFO", + "maxVettingDelay": "1m", + "metrics": { + "enable": true + }, + "nodeIdentifier": "Digital-Asset-1", + "onboardingName": "Digital-Asset-1", + "onboardingType": "join-with-key", + "participantAddress": "participant", + "permissionedSynchronizer": false, + "persistence": { + "databaseName": "sv_sv_da_1", + "port": 5432, + "postgresName": "cn-apps-pg", + "schema": "sv_sv_da_1", + "secretName": "cn-apps-pg-secrets", + "user": "cnadmin" + }, + "pvc": { + "volumeName": "sv-app-global-domain-migration-hd-pvc", + "volumeStorageClass": "hyperdisk-standard-rwo" + }, + "resources": { + "limits": { + "memory": "2Gi" + }, + "requests": { + "cpu": "1", + "memory": "1Gi" + } + }, + "scan": { + "internalUrl": "http://scan-app.sv-da-1:5012", + "publicUrl": "https://scan.sv-1.mock.global.canton.network.digitalasset.com" + }, + "spliceInstanceNames": { + "amuletName": "Amulet", + "amuletNameAcronym": "AMT", + "nameServiceName": "Amulet Name Service", + "nameServiceNameAcronym": "ANS", + "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", + "networkName": "Splice" + }, + "synchronizers": { + "additionalLegacy": [ + { + "cometBFT": { + "connectionUri": "http://global-domain-7-cometbft-cometbft-rpc:26657", + "enabled": true, + "externalGovernanceKey": true + }, + "mediatorAddress": "global-domain-7-mediator", + "sequencerAddress": "global-domain-7-sequencer", + "sequencerPruningConfig": { + "enabled": true, + "pruningInterval": "1 hour", + "retentionPeriod": "30 days" + }, + "sequencerPublicUrl": "https://sequencer-7.sv-1.mock.global.canton.network.digitalasset.com" + } + ], + "current": { + "enableBftSequencer": true, + "mediatorAddress": "global-domain-9-mediator", + "sequencerAddress": "global-domain-9-sequencer", + "sequencerPruningConfig": { + "enabled": true, + "pruningInterval": "1 hour", + "retentionPeriod": "30 days" + }, + "sequencerPublicUrl": "https://sequencer-9.sv-1.mock.global.canton.network.digitalasset.com" + }, + "legacy": { + "cometBFT": { + "connectionUri": "http://global-domain-8-cometbft-cometbft-rpc:26657", + "enabled": true, + "externalGovernanceKey": true + }, + "mediatorAddress": "global-domain-8-mediator", + "sequencerAddress": "global-domain-8-sequencer", + "sequencerPruningConfig": { + "enabled": true, + "pruningInterval": "1 hour", + "retentionPeriod": "30 days" + }, + "sequencerPublicUrl": "https://sequencer-8.sv-1.mock.global.canton.network.digitalasset.com" + }, + "skipInitialization": true, + "successor": { + "enableBftSequencer": true, + "mediatorAddress": "global-domain-10-mediator", + "sequencerAddress": "global-domain-10-sequencer", + "sequencerPruningConfig": { + "enabled": true, + "pruningInterval": "1 hour", + "retentionPeriod": "30 days" + }, + "sequencerPublicUrl": "https://sequencer-10.sv-1.mock.global.canton.network.digitalasset.com" + } + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ] + } + }, + "version": "0.3.20" + }, + "name": "sv-da-1-sv-app", "provider": "", "type": "kubernetes:helm.sh/v3:Release" }, @@ -843,9 +6757,165 @@ } }, "name": "sv-da-1-sv", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-da-1-sv::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, + { + "custom": true, + "id": "", + "inputs": { + "chart": "oci://ghcr.io/digital-asset/decentralized-canton-sync-dev/helm/splice-validator", + "compat": "true", + "maxHistory": 10, + "name": "validator-sv-da-1", + "namespace": "sv-da-1", + "timeout": 600, + "values": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": { + "additionalEnvVars": [ + { + "name": "ADDITIONAL_CONFIG_NO_BFT_SEQUENCER_CONNECTION", + "value": "canton.validator-apps.validator_backend.disable-sv-validator-bft-sequencer-connection = true" + }, + { + "name": "ADDITIONAL_CONFIG_TOPOLOGY_METRICS_EXPORT", + "value": "canton.validator-apps.validator_backend.automation.topology-metrics-polling-interval = 5m\n" + }, + { + "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", + "value": "canton.validator-apps.validator_backend.participant-pruning-schedule {\n cron = \"0 0 * * *\"\n max-duration = \"30m\"\n retention = \"7d\"\n }" + } + ], + "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1", + "additionalUsers": [], + "affinity": { + "nodeAffinity": { + "requiredDuringSchedulingIgnoredDuringExecution": { + "nodeSelectorTerms": [ + { + "matchExpressions": [ + { + "key": "cn_apps", + "operator": "Exists" + }, + { + "key": "cn_apps", + "operator": "In", + "values": [ + "hyperdisk" + ] + } + ] + } + ] + } + } + }, + "apiRequestLogLevel": "DEBUG", + "appDars": [], + "auth": { + "audience": "https://canton.network.global", + "jwksUrl": "https://canton-network-dev.us.auth0.com/.well-known/jwks.json" + }, + "cluster": { + "dnsName": "mock.global.canton.network.digitalasset.com", + "fixedTokens": false, + "hostname": "mock.global.canton.network.digitalasset.com", + "name": "cn-mocknet" + }, + "contactPoint": "sv-support@digitalasset.com", + "disableAllocateLedgerApiUserParty": true, + "disableAuth": false, + "enablePostgresMetrics": true, + "failOnAppVersionMismatch": true, + "imageRepo": "us-central1-docker.pkg.dev/da-cn-shared/ghcr/digital-asset/decentralized-canton-sync-dev/docker", + "logAsyncFlush": false, + "logLevel": "INFO", + "maxVettingDelay": "1m", + "metrics": { + "enable": true + }, + "nodeIdentifier": "Digital-Asset-1", + "participantAddress": "participant", + "participantIdentitiesDumpPeriodicBackup": { + "backupInterval": "10m", + "location": { + "bucket": { + "bucketName": "da-cn-data-dumps", + "jsonCredentials": "{\"bucketName\":\"data-export-bucket-name\",\"secretName\":\"data-export-bucket-sa-key-secret\",\"jsonCredentials\":\"data-export-bucket-sa-key-secret-creds\"}", + "projectId": "da-cn-devnet", + "secretName": "cn-gcp-bucket-da-cn-devnet-da-cn-data-dumps" + }, + "prefix": "mock/sv-da-1" + } + }, + "persistence": { + "databaseName": "validator_sv_da_1", + "port": 5432, + "postgresName": "cn-apps-pg", + "schema": "validator_sv_da_1", + "secretName": "cn-apps-pg-secrets", + "user": "cnadmin" + }, + "pvc": { + "volumeName": "domain-migration-validator-hd-pvc", + "volumeStorageClass": "hyperdisk-standard-rwo" + }, + "resources": { + "limits": { + "memory": "4Gi" + }, + "requests": { + "memory": "2Gi" + } + }, + "scanAddress": "http://scan-app.sv-da-1:5012", + "spliceInstanceNames": { + "amuletName": "Amulet", + "amuletNameAcronym": "AMT", + "nameServiceName": "Amulet Name Service", + "nameServiceNameAcronym": "ANS", + "networkFaviconUrl": "https://www.hyperledger.org/hubfs/hyperledgerfavicon.png", + "networkName": "Splice" + }, + "svValidator": true, + "synchronizer": { + "connectionType": "trust-single", + "url": "https://sequencer-9.sv-2.mock.global.canton.network.digitalasset.com" + }, + "tolerations": [ + { + "effect": "NoSchedule", + "key": "cn_apps", + "operator": "Exists" + } + ], + "topup": { + "enabled": true, + "minTopupInterval": "1m", + "targetThroughput": 0 + }, + "useSequencerConnectionsFromScan": true, + "validatorWalletUsers": [ + "google-oauth2|1234567890", + "auth0|68c2c41e6470184569e4521e" + ], + "walletSweep": { + "mock-3::33333333333333333333333333333333333333333333333333333333333333333333": { + "maxBalanceUSD": 67890, + "minBalanceUSD": 42, + "receiver": "mock-4::444444444444444444444444444444444444444444444444444444444444444444444" + } + } + } + }, + "version": "0.3.20" + }, + "name": "sv-da-1-validator-sv-da-1", + "provider": "", + "type": "kubernetes:helm.sh/v3:Release" + }, { "custom": true, "id": "projects/da-cn-devnet/locations/us-central1/keyRings/sv-da-1_participant_mock", @@ -1155,7 +7225,7 @@ } }, "name": "sv-sv", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-sv-sv::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -1220,6 +7290,20 @@ "provider": "", "type": "kubernetes:core/v1:ServiceAccount" }, + { + "custom": true, + "id": "", + "inputs": { + "name": "cnadmin", + "password": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": null + } + }, + "name": "user-sv-1-cn-apps-pg-cnadmin", + "provider": "", + "type": "gcp:sql/user:User" + }, { "custom": true, "id": "", @@ -1234,6 +7318,20 @@ "provider": "", "type": "gcp:sql/user:User" }, + { + "custom": true, + "id": "", + "inputs": { + "name": "cnadmin", + "password": { + "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", + "value": null + } + }, + "name": "user-sv-da-1-cn-apps-pg-cnadmin", + "provider": "", + "type": "gcp:sql/user:User" + }, { "custom": true, "id": "", diff --git a/cluster/expected/validator-runbook/expected.json b/cluster/expected/validator-runbook/expected.json index b27b7190d9..2d29b242f6 100644 --- a/cluster/expected/validator-runbook/expected.json +++ b/cluster/expected/validator-runbook/expected.json @@ -16,7 +16,7 @@ } }, "name": "cluster-ingress-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-cluster-ingress-default::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-cluster-ingress-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { @@ -388,7 +388,7 @@ } }, "name": "validator-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-validator-default::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-validator-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { diff --git a/cluster/expected/validator1/expected.json b/cluster/expected/validator1/expected.json index cea2b9a7e6..a44642038f 100644 --- a/cluster/expected/validator1/expected.json +++ b/cluster/expected/validator1/expected.json @@ -537,7 +537,7 @@ } }, "name": "validator1-default", - "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-validator1-default::undefined_id", + "provider": "urn:pulumi:test-stack::test-project::pulumi:providers:kubernetes::k8s-imgpull-validator1-default::04da6b54-80e4-46f7-96ec-b56ff0331ba9", "type": "kubernetes:core/v1:ServiceAccountPatch" }, { diff --git a/cluster/pulumi/canton-network/src/dso.ts b/cluster/pulumi/canton-network/src/dso.ts index dcecc2fa0c..971708ef23 100644 --- a/cluster/pulumi/canton-network/src/dso.ts +++ b/cluster/pulumi/canton-network/src/dso.ts @@ -20,6 +20,7 @@ import { interface DsoArgs { auth0Client: Auth0Client; decentralizedSynchronizerUpgradeConfig: DecentralizedSynchronizerMigrationConfig; + exportSvResources?: boolean; } export class Dso extends pulumi.ComponentResource { @@ -31,15 +32,19 @@ export class Dso extends pulumi.ComponentResource { svConf: StaticSvConfig, extraDependsOn: CnInput[] = [] ): Promise { - const xns = exactNamespace(svConf.nodeName, true); + const xns = exactNamespace(svConf.nodeName, true, this.args.exportSvResources); const dynamicConfig = configForSv(svConf.nodeName); - return await installSvNodeStandalone( + const sv = await installSvNodeStandalone( xns, svConf, dynamicConfig, this.args.auth0Client, - extraDependsOn + extraDependsOn, + (this.args.exportSvResources ?? false) ? { action: 'export' } : undefined ); + // we never run import from here so the following is ok + // migration code is temporary so it doesn't have to be beautiful + return sv!; } private async installDso() { diff --git a/cluster/pulumi/canton-network/src/index.ts b/cluster/pulumi/canton-network/src/index.ts index 9c8e97c3a7..f87e5151b2 100644 --- a/cluster/pulumi/canton-network/src/index.ts +++ b/cluster/pulumi/canton-network/src/index.ts @@ -1,5 +1,6 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 +import * as pulumi from '@pulumi/pulumi'; import { Auth0ClientType, getAuth0Config, Auth0Fetch } from '@canton-network/splice-pulumi-common'; import { installClusterVersion } from './clusterVersion'; @@ -11,20 +12,29 @@ async function auth0CacheAndInstallCluster(auth0Fetch: Auth0Fetch) { installClusterVersion(); - await installCluster(auth0Fetch); + const dso = await installCluster(auth0Fetch); await auth0Fetch.saveAuth0Cache(); + + return (await dso?.allSvs)?.map(sv => ({ + nodeName: sv.nodeName, + databaseInstanceName: sv.appsPostgres.databaseId, + databaseSecretName: sv.appsPostgres.secretName, + })); } async function main() { const auth0FetchOutput = getAuth0Config(Auth0ClientType.MAINSTACK); - auth0FetchOutput.apply(async auth0Fetch => { - await auth0CacheAndInstallCluster(auth0Fetch); + const svs = auth0FetchOutput.apply(async auth0Fetch => { + const svs = await auth0CacheAndInstallCluster(auth0Fetch); scheduleLoadGenerator(auth0Fetch, []); + + return svs; }); + + return svs; } -// eslint-disable-next-line @typescript-eslint/no-floating-promises -main(); +export const svs = pulumi.output(main()); diff --git a/cluster/pulumi/canton-network/src/installCluster.ts b/cluster/pulumi/canton-network/src/installCluster.ts index 5dde062edc..a856f1fd7f 100644 --- a/cluster/pulumi/canton-network/src/installCluster.ts +++ b/cluster/pulumi/canton-network/src/installCluster.ts @@ -4,10 +4,17 @@ import { Auth0Client, config, DecentralizedSynchronizerUpgradeConfig, + exactNamespace, isDevNet, spliceConfig, } from '@canton-network/splice-pulumi-common'; -import { Resource } from '@pulumi/pulumi'; +import { configForSv, coreSvsToDeploy } from '@canton-network/splice-pulumi-common-sv'; +import { + configureScanBigQuery, + ScanBigQueryArgs, +} from '@canton-network/splice-pulumi-common-sv/src/bigQuery'; +import { InstalledSv } from '@canton-network/splice-pulumi-common-sv/src/sv'; +import { CloudPostgres } from '@canton-network/splice-pulumi-common/src/postgres'; import { activeVersion } from '../../common'; import { installChaosMesh } from './chaosMesh'; @@ -20,27 +27,87 @@ console.error(`Launching with isDevNet: ${isDevNet}`); const enableChaosMesh = config.envFlag('ENABLE_CHAOS_MESH'); -export async function installCluster(auth0Client: Auth0Client): Promise { +export async function installCluster(auth0Client: Auth0Client): Promise { console.error( activeVersion.type === 'local' ? 'Using locally built charts by default' : `Using charts from the container registry by default, version ${activeVersion.version}` ); + // TODO(#6719) once all clusters have been migrated this can be removed. const dso = spliceConfig.configuration.synchronizerMigration.splitSvDeploymentEnabled ? undefined : new Dso('dso', { auth0Client, decentralizedSynchronizerUpgradeConfig: DecentralizedSynchronizerUpgradeConfig, + exportSvResources: + spliceConfig.configuration.synchronizerMigration.migrateToSplitSvDeployment, }); - const allSvs = (await dso?.allSvs) ?? []; + const locallyInstalledSvs = await dso?.allSvs; + const bigQueryArgs = [...iterateBigQueryArgs(locallyInstalledSvs)]; + if (bigQueryArgs.length > 1) { + throw new Error( + `Multiple SVs with BigQuery configuration found: ${bigQueryArgs.map(arg => arg.namespace.logicalName).join(', ')}` + ); + } + for (const args of bigQueryArgs) { + await configureScanBigQuery(args); + } - const svDependencies = allSvs.flatMap(sv => [sv.scan, sv.svApp, sv.validatorApp, sv.ingress]); + const svDependencies = (locallyInstalledSvs ?? []).flatMap(sv => [ + sv.scan, + sv.svApp, + sv.validatorApp, + sv.ingress, + ]); installDocs(); if (enableChaosMesh) { installChaosMesh({ dependsOn: svDependencies }); } + + return dso; +} + +function* iterateBigQueryArgs( + locallyInstalledSvs?: Array +): Generator { + if (locallyInstalledSvs === undefined) { + for (const sv of coreSvsToDeploy) { + const config = configForSv(sv.nodeName); + const bigQueryConfig = config?.scanApp?.bigQuery; + const cloudSqlEnabled = (config.appsPg?.cloudSql ?? spliceConfig.pulumiProjectConfig.cloudSql) + .enabled; + if (bigQueryConfig !== undefined && cloudSqlEnabled) { + const namespace = exactNamespace(sv.nodeName, true, true); + yield { + namespace, + bigQueryConfig: bigQueryConfig, + scanReference: { + type: 'external', + databaseInstanceNamePrefix: `${namespace.logicalName}-cn-apps-pg`, + }, + }; + } + } + } else { + // TODO(#6719) once all clusters have been migrated this can be removed. + for (const sv of locallyInstalledSvs) { + const config = configForSv(sv.nodeName); + const bigQueryConfig = config?.scanApp?.bigQuery; + if (bigQueryConfig !== undefined && sv.appsPostgres instanceof CloudPostgres) { + yield { + namespace: sv.namespace, + bigQueryConfig: bigQueryConfig, + scanReference: { + type: 'local', + databaseInstance: sv.appsPostgres.databaseInstance, + chart: sv.scan, + }, + }; + } + } + } } diff --git a/cluster/pulumi/common-sv/src/bigQuery.ts b/cluster/pulumi/common-sv/src/bigQuery.ts index a17cd6c273..0d4c1c84d3 100644 --- a/cluster/pulumi/common-sv/src/bigQuery.ts +++ b/cluster/pulumi/common-sv/src/bigQuery.ts @@ -12,14 +12,16 @@ import { import { clusterProdLike, config } from '@canton-network/splice-pulumi-common/src/config'; import { spliceConfig } from '@canton-network/splice-pulumi-common/src/config/config'; import { - Postgres, - CloudPostgres, + defaultUserName, generatePassword, + getCloudSdkZone, privateNetworkId, } from '@canton-network/splice-pulumi-common/src/postgres'; import { ExactNamespace, CLUSTER_BASENAME, + GCP_PROJECT, + GCP_REGION, commandScriptPath, } from '@canton-network/splice-pulumi-common/src/utils'; @@ -63,12 +65,16 @@ function pickDatastreamPeeringCidr(): string { return ip.fromLong(ip.toLong(baseSubnet.networkAddress) + baseSubnet.length) + '/29'; } -function installNatVm(postgres: CloudPostgres): gcp.compute.Instance { - const vmName = `${postgres.namespace.logicalName}-nat-vm`; +function installNatVm( + namespace: ExactNamespace, + zone: string, + databaseInstance: gcp.sql.DatabaseInstance +): gcp.compute.Instance { + const vmName = `${namespace.logicalName}-nat-vm`; // from https://cloud.google.com/datastream/docs/private-connectivity#set-up-reverse-proxy const startupScript = pulumi.interpolate`#! /bin/bash -export DB_ADDR=${postgres.address} +export DB_ADDR=${databaseInstance.privateIpAddress} export DB_PORT=${dbPort} # Enable the VM to receive packets whose destinations do @@ -105,7 +111,7 @@ iptables-save return new gcp.compute.Instance(vmName, { machineType: 'e2-micro', - zone: postgres.zone, + zone, bootDisk: { initializeParams: { image: 'debian-cloud/debian-12', @@ -129,14 +135,15 @@ iptables-save } function installDatastream( - postgres: CloudPostgres, + namespace: ExactNamespace, + databaseInstance: gcp.sql.DatabaseInstance, source: gcp.datastream.ConnectionProfile, destination: gcp.datastream.ConnectionProfile, bigQueryDataset: gcp.bigquery.Dataset, pubRepSlots: pulumi.Resource ): gcp.datastream.Stream { - const streamName = `${postgres.namespace.logicalName}-scan-update-history`; - const schemaName = scanAppDatabaseName(postgres); + const streamName = `${namespace.logicalName}-scan-update-history`; + const schemaName = scanAppDatabaseName(namespace); return new gcp.datastream.Stream( streamName, { @@ -175,7 +182,7 @@ function installDatastream( cluster: CLUSTER_BASENAME, }, }, - { dependsOn: [postgres, source, destination, bigQueryDataset, pubRepSlots] } + { dependsOn: [databaseInstance, source, destination, bigQueryDataset, pubRepSlots] } ); } @@ -209,11 +216,11 @@ you have to manually enable the API as described for that cluster. */ function installBigqueryConnectionProfile( - postgres: CloudPostgres, + namespace: ExactNamespace, bigQuery: gcp.bigquery.Dataset, pcc: gcp.datastream.PrivateConnection ): gcp.datastream.ConnectionProfile { - const profileName = `${postgres.namespace.logicalName}-scan-bq-cxn`; + const profileName = `${namespace.logicalName}-scan-bq-cxn`; return new gcp.datastream.ConnectionProfile( profileName, { @@ -229,18 +236,19 @@ function installBigqueryConnectionProfile( ); } -function scanAppDatabaseName(postgres: Postgres) { - return `scan_${postgres.namespace.logicalName.replace(/-/g, '_')}`; +function scanAppDatabaseName(namespace: ExactNamespace): string { + return `scan_${namespace.logicalName.replace(/-/g, '_')}`; } function installPostgresConnectionProfile( - postgres: CloudPostgres, - scan: InstalledHelmChart, + namespace: ExactNamespace, + databaseInstance: gcp.sql.DatabaseInstance, + scan: InstalledHelmChart | undefined, natVm: gcp.compute.Instance, connection: gcp.datastream.PrivateConnection, replicatorPassword: PostgresPassword ): gcp.datastream.ConnectionProfile { - const profileName = `${postgres.namespace.logicalName}-scan-update-history-cxn`; + const profileName = `${namespace.logicalName}-scan-update-history-cxn`; // TODO (#454) may have to await scan migration or pub/rep slots command return new gcp.datastream.ConnectionProfile( @@ -254,7 +262,7 @@ function installPostgresConnectionProfile( port: dbPort, username: replicatorUserName, password: replicatorPassword.contents, - database: scanAppDatabaseName(postgres), + database: scanAppDatabaseName(namespace), }, privateConnectivity: { privateConnection: connection.name, @@ -263,14 +271,14 @@ function installPostgresConnectionProfile( cluster: CLUSTER_BASENAME, }, }, - { dependsOn: [natVm, connection, postgres.databaseInstance, scan] } + { dependsOn: [natVm, connection, databaseInstance, ...(scan !== undefined ? [scan] : [])] } ); } function installPrivateConnectivityConfiguration( - postgres: CloudPostgres + namespace: ExactNamespace ): gcp.datastream.PrivateConnection { - const privateConnectionName = `${postgres.namespace.logicalName}-scan-update-history-datastream-vpc`; + const privateConnectionName = `${namespace.logicalName}-scan-update-history-datastream-vpc`; return new gcp.datastream.PrivateConnection( privateConnectionName, { @@ -314,36 +322,43 @@ function installDatastreamToNatVmFirewallRule( // TODO (DACH-NY/canton-network-internal#342) if we disable default egress rule, we need another firewall // rule for Nat VM -> Postgres -function installReplicatorPassword(postgres: CloudPostgres): PostgresPassword { - const secretName = `${postgres.namespace.logicalName}-${replicatorUserName}-passwd`; - const password = generatePassword(`${postgres.instanceName}-${replicatorUserName}-passwd`, { - parent: postgres, +function installReplicatorPassword(namespace: ExactNamespace): PostgresPassword { + const secretName = `${namespace.logicalName}-${replicatorUserName}-passwd`; + const password = generatePassword(`cn-apps-pg-${replicatorUserName}-passwd`, { + aliases: [ + { + parent: getLegacyParentUrn(namespace), + }, + ], protect: spliceConfig.pulumiProjectConfig.cloudSql.protected, }).result; return { contents: password, - secret: installPostgresPasswordSecret(postgres.namespace, password, secretName), + secret: installPostgresPasswordSecret(namespace, password, secretName), }; } function createPostgresReplicatorUser( - postgres: CloudPostgres, + namespace: ExactNamespace, + databaseInstance: gcp.sql.DatabaseInstance, password: PostgresPassword ): gcp.sql.User { - const name = `${postgres.namespace.logicalName}-user-${replicatorUserName}`; + const name = `${namespace.logicalName}-user-${replicatorUserName}`; return new gcp.sql.User( name, { - instance: postgres.databaseInstance.name, + instance: databaseInstance.name, name: replicatorUserName, password: password.contents, }, { - parent: postgres, - deletedWith: postgres.databaseInstance, - retainOnDelete: true, + aliases: [ + { + parent: getLegacyParentUrn(namespace), + }, + ], protect: spliceConfig.pulumiProjectConfig.cloudSql.protected, - dependsOn: [postgres.databaseInstance, password.secret], + dependsOn: [password.secret], } ); } @@ -354,66 +369,134 @@ needs the 'Cloud SQL Editor' IAM role in the relevant GCP project */ function createPublicationAndReplicationSlots( - postgres: CloudPostgres, + namespace: ExactNamespace, + databaseInstance: gcp.sql.DatabaseInstance, replicatorUser: gcp.sql.User, - scan: InstalledHelmChart + scan: InstalledHelmChart | undefined ) { - const dbName = scanAppDatabaseName(postgres); + const dbName = scanAppDatabaseName(namespace); const schemaName = dbName; const path = commandScriptPath('cluster/pulumi/canton-network/bigquery-cloudsql.sh'); const scriptArgs = pulumi.interpolate`\\ --private-network-project="${gcp.organizations.getProjectOutput({}).apply(proj => proj.name)}" \\ --compute-region="${cloudsdkComputeRegion()}" \\ - --service-account-email="${postgres.databaseInstance.serviceAccountEmailAddress}" \\ + --service-account-email="${databaseInstance.serviceAccountEmailAddress}" \\ --schema-name="${schemaName}" \\ --tables-to-replicate-joined="${tablesToReplicate.join(', ')}" \\ - --postgres-user-name="${postgres.user.name}" \\ + --postgres-user-name="${defaultUserName}" \\ --publication-name="${publicationName}" \\ --replication-slot-name="${replicationSlotName}" \\ --replicator-user-name="${replicatorUserName}" \\ - --postgres-instance-name="${postgres.databaseInstance.name}" \\ - --scan-app-database-name="${scanAppDatabaseName(postgres)}" \\ + --postgres-instance-name="${databaseInstance.name}" \\ + --scan-app-database-name="${scanAppDatabaseName(namespace)}" \\ --flyway-migration-to-wait-for="${flywayMigrationToWaitFor}" \\ `; return new command.local.Command( - `${postgres.namespace.logicalName}-${replicatorUserName}-pub-replicate-slots`, + `${namespace.logicalName}-${replicatorUserName}-pub-replicate-slots`, { create: pulumi.interpolate`'${path}' create-pub-rep-slot ${scriptArgs}`, delete: pulumi.interpolate`'${path}' delete-pub-rep-slot ${scriptArgs}`, }, { - deletedWith: postgres.databaseInstance, - dependsOn: [scan, postgres.databaseInstance, replicatorUser], + dependsOn: [databaseInstance, replicatorUser, ...(scan !== undefined ? [scan] : [])], deleteBeforeReplace: true, } ); } -export function configureScanBigQuery( - postgres: CloudPostgres, - scanBigQuery: ScanBigQueryConfig, - scan: InstalledHelmChart -): void { - const passwordSecret = installReplicatorPassword(postgres); +export async function configureScanBigQuery({ + namespace, + bigQueryConfig, + scanReference, +}: ScanBigQueryArgs): Promise { + const zone = getCloudSdkZone(); + const [databaseInstance, scanChart] = await (async () => { + switch (scanReference.type) { + case 'local': + return [scanReference.databaseInstance, scanReference.chart]; + case 'external': + return [await getScanDb(scanReference.databaseInstanceNamePrefix, zone), undefined]; + } + })(); + const passwordSecret = installReplicatorPassword(namespace); const pubRepSlots = createPublicationAndReplicationSlots( - postgres, - createPostgresReplicatorUser(postgres, passwordSecret), - scan + namespace, + databaseInstance, + createPostgresReplicatorUser(namespace, databaseInstance, passwordSecret), + scanChart ); - const natVm = installNatVm(postgres); - const dataset = installBigqueryDataset(scanBigQuery); - const pcc = installPrivateConnectivityConfiguration(postgres); - const destinationProfile = installBigqueryConnectionProfile(postgres, dataset, pcc); + const natVm = installNatVm(namespace, zone, databaseInstance); + const dataset = installBigqueryDataset(bigQueryConfig); + const pcc = installPrivateConnectivityConfiguration(namespace); + const destinationProfile = installBigqueryConnectionProfile(namespace, dataset, pcc); const sourceProfile = installPostgresConnectionProfile( - postgres, - scan, + namespace, + databaseInstance, + scanChart, natVm, pcc, passwordSecret ); - installDatastreamToNatVmFirewallRule(postgres.namespace, pcc, natVm); - installDatastream(postgres, sourceProfile, destinationProfile, dataset, pubRepSlots); + installDatastreamToNatVmFirewallRule(namespace, pcc, natVm); + installDatastream( + namespace, + databaseInstance, + sourceProfile, + destinationProfile, + dataset, + pubRepSlots + ); + + return { + datasetId: dataset.id, + }; +} + +export type ScanBigQueryArgs = { + namespace: ExactNamespace; + bigQueryConfig: ScanBigQueryConfig; + scanReference: ScanReference; +}; + +type ScanReference = + | { + type: 'local'; + databaseInstance: gcp.sql.DatabaseInstance; + chart: InstalledHelmChart; + } + | { + type: 'external'; + databaseInstanceNamePrefix: string; + }; + +export type ScanBigQuery = { + datasetId: pulumi.Output; +}; + +async function getScanDb( + instanceNamePrefix: string, + zone: string +): Promise { + const result = await gcp.sql.getDatabaseInstances({ + project: GCP_PROJECT, + region: GCP_REGION, + zone, + }); + const instanceName = + result.instances.find( + instance => + instance.name.startsWith(instanceNamePrefix) && + instance.settings?.[0]?.userLabels?.cluster === CLUSTER_BASENAME + )?.name ?? + (() => { + throw new Error( + `Could not find SV apps database instance with prefix [${instanceNamePrefix}] and user label [cluster=${CLUSTER_BASENAME}].` + ); + })(); + return gcp.sql.DatabaseInstance.get(instanceNamePrefix, instanceName); +} - return; +function getLegacyParentUrn(namespace: ExactNamespace): pulumi.URN { + return `urn:pulumi:canton-network.${CLUSTER_BASENAME}::canton-network::canton:cloud:postgres::${namespace.logicalName}-cn-apps-pg`; } diff --git a/cluster/pulumi/common-sv/src/sv.ts b/cluster/pulumi/common-sv/src/sv.ts index 658e9bda69..b37104203d 100644 --- a/cluster/pulumi/common-sv/src/sv.ts +++ b/cluster/pulumi/common-sv/src/sv.ts @@ -89,16 +89,18 @@ import { topologySnapshotConfig } from '@canton-network/splice-pulumi-common/src import { Resource } from '@pulumi/pulumi'; import pick from 'lodash/pick'; -import { configureScanBigQuery } from './bigQuery'; import { installInfo } from './info'; +// TODO(#6719) once all clusters have been migrated move the whole module to the sv project export async function installSvNodeStandalone( xns: ExactNamespace, staticConfig: StaticSvConfig, config: SingleSvConfiguration, auth0Client: Auth0Client, - extraDependsOn: CnInput[] = [] -): Promise { + extraDependsOn: CnInput[] = [], + // TODO(#6719) once all clusters have been migrated remove this + migrationArgs?: MigrationArgs +): Promise { const nodeName = staticConfig.nodeName; const [sv1StaticConfig, ...otherSvsStaticConfigs] = coreSvsToDeploy; const isFoundingSv = nodeName === sv1StaticConfig.nodeName; @@ -184,7 +186,8 @@ export async function installSvNodeStandalone( ...config, }, DecentralizedSynchronizerUpgradeConfig, - extraDependsOn + extraDependsOn, + migrationArgs ); } @@ -235,27 +238,23 @@ export function installSvKeySecret( } export type InstalledSv = { + namespace: ExactNamespace; + nodeName: string; validatorApp: Resource; svApp: InstalledHelmChart; scan: InstalledHelmChart; canton: SynchronizerNodes; ingress: Resource; + appsPostgres: postgres.Postgres; }; export async function installSvNode( xns: ExactNamespace, baseConfig: SvConfig, decentralizedSynchronizerUpgradeConfig: DecentralizedSynchronizerMigrationConfig, - extraDependsOn: CnInput[] = [] -): Promise { - const loopback = installSvLoopback(xns, decentralizedSynchronizerUpgradeConfig.usesCometbft()); - const imagePullDeps = imagePullSecret(xns); - - const auth0Secrets: CnInput[] = await installSvAppSecrets( - xns, - baseConfig.auth0Client - ); - + extraDependsOn: CnInput[] = [], + migrationArgs?: MigrationArgs +): Promise { const periodicBackupConfig: BucketConfig | undefined = baseConfig.periodicBackupConfig ? { ...baseConfig.periodicBackupConfig, @@ -292,65 +291,179 @@ export async function installSvNode( bulkStorageBuckets, }; - const identitiesBackupConfigSecret = installBucketSecret( - xns, - config.identitiesBackupLocation.bucket - ); + if (migrationArgs?.action === 'import') { + const appsPostgres = await installAppsPostgres(xns, config, migrationArgs); - const topologySnapshotConfigSecret = periodicTopologySnapshotConfig - ? installBucketSecret(xns, periodicTopologySnapshotConfig.location.bucket) - : undefined; - const backupConfigSecret: pulumi.Resource | undefined = config.periodicBackupConfig - ? config.periodicBackupConfig.location.bucket != config.identitiesBackupLocation.bucket - ? installBucketSecret(xns, config.periodicBackupConfig.location.bucket) - : identitiesBackupConfigSecret - : undefined; + return undefined; + } else { + const loopback = installSvLoopback(xns, decentralizedSynchronizerUpgradeConfig.usesCometbft()); + const imagePullDeps = imagePullSecret(xns); - const participantBootstrapDumpSecret: pulumi.Resource | undefined = config.bootstrappingDumpConfig - ? await fetchAndInstallParticipantBootstrapDump(xns, config.bootstrappingDumpConfig) - : undefined; + const auth0Secrets: CnInput[] = await installSvAppSecrets( + xns, + baseConfig.auth0Client + ); - const dependsOn: CnInput[] = auth0Secrets - .concat( - config.onboarding.type == 'join-with-key' - ? installSvKeySecret(xns, config.onboarding.keys) - : [] - ) - .concat( - config.onboarding.type == 'join-with-key' && - config.onboarding.sponsorRelease !== undefined && - spliceConfig.pulumiProjectConfig.interAppsDependencies - ? [config.onboarding.sponsorRelease] - : [] - ) - .concat( - config.expectedValidatorOnboardings.map(onboarding => - installValidatorOnboardingSecret(xns, onboarding.name, onboarding.secret) + const identitiesBackupConfigSecret = installBucketSecret( + xns, + config.identitiesBackupLocation.bucket + ); + + const topologySnapshotConfigSecret = periodicTopologySnapshotConfig + ? installBucketSecret(xns, periodicTopologySnapshotConfig.location.bucket) + : undefined; + const backupConfigSecret: pulumi.Resource | undefined = config.periodicBackupConfig + ? config.periodicBackupConfig.location.bucket != config.identitiesBackupLocation.bucket + ? installBucketSecret(xns, config.periodicBackupConfig.location.bucket) + : identitiesBackupConfigSecret + : undefined; + + const participantBootstrapDumpSecret: pulumi.Resource | undefined = + config.bootstrappingDumpConfig + ? await fetchAndInstallParticipantBootstrapDump(xns, config.bootstrappingDumpConfig) + : undefined; + + const dependsOn: CnInput[] = auth0Secrets + .concat( + config.onboarding.type == 'join-with-key' + ? installSvKeySecret(xns, config.onboarding.keys) + : [] ) - ) - .concat([identitiesBackupConfigSecret]) - .concat(backupConfigSecret ? [backupConfigSecret] : []) - .concat(topologySnapshotConfigSecret ? [topologySnapshotConfigSecret] : []) - .concat(participantBootstrapDumpSecret ? [participantBootstrapDumpSecret] : []) - .concat(loopback) - .concat(imagePullDeps) - .concat( - config.cometBftGovernanceKey - ? svCometBftGovernanceKeySecret(xns, config.cometBftGovernanceKey) - : [] - ) - .concat( - bulkStorageBuckets - ? [ - bulkStorageBuckets.staging.secret, - bulkStorageBuckets.staging.bucket, - bulkStorageBuckets.committed.secret, - bulkStorageBuckets.committed.bucket, - ] - : [] - ) - .concat(extraDependsOn); + .concat( + config.onboarding.type == 'join-with-key' && + config.onboarding.sponsorRelease !== undefined && + spliceConfig.pulumiProjectConfig.interAppsDependencies + ? [config.onboarding.sponsorRelease] + : [] + ) + .concat( + config.expectedValidatorOnboardings.map(onboarding => + installValidatorOnboardingSecret(xns, onboarding.name, onboarding.secret) + ) + ) + .concat([identitiesBackupConfigSecret]) + .concat(backupConfigSecret ? [backupConfigSecret] : []) + .concat(topologySnapshotConfigSecret ? [topologySnapshotConfigSecret] : []) + .concat(participantBootstrapDumpSecret ? [participantBootstrapDumpSecret] : []) + .concat(loopback) + .concat(imagePullDeps) + .concat( + config.cometBftGovernanceKey + ? svCometBftGovernanceKeySecret(xns, config.cometBftGovernanceKey) + : [] + ) + .concat( + bulkStorageBuckets + ? [ + bulkStorageBuckets.staging.secret, + bulkStorageBuckets.staging.bucket, + bulkStorageBuckets.committed.secret, + bulkStorageBuckets.committed.bucket, + ] + : [] + ) + .concat(extraDependsOn); + + const appsPostgres = await installAppsPostgres(xns, config, migrationArgs); + + const canton = new SynchronizerNodes( + decentralizedSynchronizerUpgradeConfig, + { + ...config.nodeConfigs, + self: { ...config.cometBft, nodeName: config.nodeName }, + }, + config.ingressName + ); + + const svApp = installSvApp( + decentralizedSynchronizerUpgradeConfig, + { ...config, periodicTopologySnapshotConfig }, + xns, + dependsOn, + appsPostgres, + canton + ); + + const scan = installScan( + xns, + config, + decentralizedSynchronizerUpgradeConfig, + dependsOn, + canton, + svApp, + appsPostgres + ); + + installInfo( + xns, + `info.${config.ingressName}.${CLUSTER_HOSTNAME}`, + 'cluster-ingress/cn-http-gateway', + decentralizedSynchronizerUpgradeConfig, + `http://scan-app.${config.nodeName}:5012`, + scan, + config.version + ); + + const validatorApp = await installValidator( + appsPostgres, + xns, + decentralizedSynchronizerUpgradeConfig, + baseConfig, + backupConfigSecret, + canton, + svApp, + scan + ); + + const ingress = installSpliceHelmChart( + xns, + 'ingress-sv', + 'splice-cluster-ingress-runbook', + { + withSvIngress: true, + ingress: { + decentralizedSynchronizer: { + migrationIds: decentralizedSynchronizerUpgradeConfig + .runningMigrations() + .map(x => x.id.toString()), + }, + }, + spliceDomainNames: { + nameServiceDomain: ansDomainPrefix, + }, + cluster: { + hostname: CLUSTER_HOSTNAME, + svNamespace: xns.logicalName, + svIngressName: config.ingressName, + }, + rateLimit: { + scan: { + enable: false, + }, + }, + }, + config.version, + { dependsOn: [xns.ns] } + ); + return { + namespace: xns, + nodeName: config.nodeName, + canton, + validatorApp, + svApp, + scan, + ingress, + appsPostgres, + }; + } +} + +async function installAppsPostgres( + xns: ExactNamespace, + config: SvConfig, + migrationArgs?: MigrationArgs +): Promise { const defaultPostgres = config.splitPostgresInstances ? undefined : await postgres.installPostgres( @@ -362,7 +475,7 @@ export async function installSvNode( spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, false, { - logicalDecoding: !!baseConfig.scanApp?.bigQuery, + logicalDecoding: !!config.scanApp?.bigQuery, } ); @@ -373,99 +486,30 @@ export async function installSvNode( `cn-apps-pg`, `cn-apps-pg`, config.version, - svConfig.appsPg?.cloudSql ?? spliceConfig.pulumiProjectConfig.cloudSql, + config.appsPg?.cloudSql ?? spliceConfig.pulumiProjectConfig.cloudSql, spliceConfig.pulumiProjectConfig.defaultSplicePostgresConfig, true, { - logicalDecoding: !!baseConfig.scanApp?.bigQuery, + logicalDecoding: !!config.scanApp?.bigQuery, + ...(() => { + switch (migrationArgs?.action) { + case 'import': + return { + existingInstanceName: migrationArgs.databaseInstanceName, + existingSecretName: migrationArgs.databaseSecretName, + }; + case 'export': + return { + retainDbResourcesOnDelete: true, + }; + case undefined: + return {}; + } + })(), } )); - const canton = new SynchronizerNodes( - decentralizedSynchronizerUpgradeConfig, - { - ...config.nodeConfigs, - self: { ...config.cometBft, nodeName: config.nodeName }, - }, - config.ingressName - ); - - const svApp = installSvApp( - decentralizedSynchronizerUpgradeConfig, - { ...config, periodicTopologySnapshotConfig }, - xns, - dependsOn, - appsPostgres, - canton - ); - - const scan = installScan( - xns, - config, - decentralizedSynchronizerUpgradeConfig, - dependsOn, - canton, - svApp, - appsPostgres - ); - - installInfo( - xns, - `info.${config.ingressName}.${CLUSTER_HOSTNAME}`, - 'cluster-ingress/cn-http-gateway', - decentralizedSynchronizerUpgradeConfig, - `http://scan-app.${config.nodeName}:5012`, - scan, - config.version - ); - - if (baseConfig.scanApp?.bigQuery && appsPostgres instanceof postgres.CloudPostgres) { - configureScanBigQuery(appsPostgres, baseConfig.scanApp!.bigQuery, scan); - } - - const validatorApp = await installValidator( - appsPostgres, - xns, - decentralizedSynchronizerUpgradeConfig, - baseConfig, - backupConfigSecret, - canton, - svApp, - scan - ); - - const ingress = installSpliceHelmChart( - xns, - 'ingress-sv', - 'splice-cluster-ingress-runbook', - { - withSvIngress: true, - ingress: { - decentralizedSynchronizer: { - migrationIds: decentralizedSynchronizerUpgradeConfig - .runningMigrations() - .map(x => x.id.toString()), - }, - }, - spliceDomainNames: { - nameServiceDomain: ansDomainPrefix, - }, - cluster: { - hostname: CLUSTER_HOSTNAME, - svNamespace: xns.logicalName, - svIngressName: config.ingressName, - }, - rateLimit: { - scan: { - enable: false, - }, - }, - }, - config.version, - { dependsOn: [xns.ns] } - ); - - return { canton, validatorApp, svApp, scan, ingress }; + return appsPostgres; } function persistenceConfig(postgresDb: postgres.Postgres, dbName: string): PersistenceConfig { @@ -767,3 +811,19 @@ function installScan( ), }); } + +export type MigrationArgs = + | { + action: 'import'; + databaseInstanceName: string; + databaseSecretName: string; + } + | { + action: 'export'; + }; + +export type SvsMigrationOutput = Array<{ + nodeName: string; + databaseInstanceName: string; + databaseSecretName: string; +}>; diff --git a/cluster/pulumi/common/src/config/migrationSchema.ts b/cluster/pulumi/common/src/config/migrationSchema.ts index b1ffcf27a7..e159021dd1 100644 --- a/cluster/pulumi/common/src/config/migrationSchema.ts +++ b/cluster/pulumi/common/src/config/migrationSchema.ts @@ -60,6 +60,8 @@ export const SynchronizerMigrationSchema = z activeDatabaseId: z.number().optional(), attachPvc: z.boolean().default(true), frozenMigrationId: z.number(), + // TODO(#6719) once all clusters have been migrated the following two flags can be removed, hardcoding splitSvDeploymentEnabled to true. splitSvDeploymentEnabled: z.boolean().default(false), + migrateToSplitSvDeployment: z.boolean().default(false), }) .strict(); diff --git a/cluster/pulumi/common/src/dump-config-common.ts b/cluster/pulumi/common/src/dump-config-common.ts index 2cd9acb2a4..ad80aafe42 100644 --- a/cluster/pulumi/common/src/dump-config-common.ts +++ b/cluster/pulumi/common/src/dump-config-common.ts @@ -24,6 +24,7 @@ export enum PulumiFunction { GCP_GET_SECRET_VERSION = 'gcp:secretmanager/getSecretVersion:getSecretVersion', GCP_GET_CLUSTER = 'gcp:container/getCluster:getCluster', STD_BASE64_DECODE = 'std:index:base64decode', + GCP_GET_DATABASE_INSTANCES = 'gcp:sql/getDatabaseInstances:getDatabaseInstances', } export class SecretsFixtureMap extends Map { @@ -227,20 +228,22 @@ export async function initDumpConfig({ process.stdout.write(buffer); process.stdout.write('\n'); - if (args.type === 'pulumi:pulumi:StackReference') { - const [organization, project, stack] = args.name.split('/'); - return { - id: args.name + '_id', - state: { - ...args.inputs, - outputs: pulumi.output(stackOutputsProvider(project, stack) ?? {}), - }, - }; - } else { - return { - id: args.inputs.name + '_id', - state: args.inputs, - }; + switch (args.type) { + case 'pulumi:pulumi:StackReference': { + const [organization, project, stack] = args.name.split('/'); + return { + id: args.name + '_id', + state: { + ...args.inputs, + outputs: pulumi.output(stackOutputsProvider(project, stack) ?? {}), + }, + }; + } + default: + return { + id: args.id ?? args.inputs.name + '_id', + state: args.inputs, + }; } }, call: function (args: pulumi.runtime.MockCallArgs) { @@ -372,6 +375,15 @@ export async function initDumpConfig({ ); break; } + case PulumiFunction.GCP_GET_DATABASE_INSTANCES: + return { + instances: [ + { + name: 'sv-1-cn-apps-pg-7ca4614', + settings: [{ userLabels: { cluster: 'mock' } }], + }, + ], + }; default: console.error('WARN unhandled call in setMockOptions: ', args); } diff --git a/cluster/pulumi/common/src/postgres.ts b/cluster/pulumi/common/src/postgres.ts index 7c8e3a7bd1..798f6a3ec8 100644 --- a/cluster/pulumi/common/src/postgres.ts +++ b/cluster/pulumi/common/src/postgres.ts @@ -109,13 +109,7 @@ export class CloudPostgres defaultUserName, retainDbResourcesOnDelete = false, } = args; - const zoneFromEnv = config.optionalEnv('DB_CLOUDSDK_COMPUTE_ZONE') || GCP_ZONE; - if (!zoneFromEnv) { - throw new Error( - 'GCP_ZONE is not set in the environment, and DB_CLOUDSDK_COMPUTE_ZONE is also not set. One of these must be set to specify the zone for the Cloud SQL instance.' - ); - } - const zone = zoneFromEnv; + const zone = getCloudSdkZone(); const databaseInstanceImportOpts = existingInstanceName !== undefined @@ -325,7 +319,7 @@ export class CloudPostgres active: args.active ?? true, deletionProtection: (args.disableProtection ?? false) ? false : args.cloudSqlConfig.protected, logicalDecoding: args.logicalDecoding ?? false, - defaultUserName: args.userName ?? 'cnadmin', + defaultUserName: args.userName ?? defaultUserName, retainDbResourcesOnDelete: args.retainDbResourcesOnDelete ?? false, }; super('canton:cloud:postgres', name, resolvedArgs, opts); @@ -956,3 +950,15 @@ export function installPasswordWithParent( }).result; return installPostgresPasswordSecret(xns, password, secretName); } + +export function getCloudSdkZone(): string { + const zoneFromEnv = config.optionalEnv('DB_CLOUDSDK_COMPUTE_ZONE') || GCP_ZONE; + if (!zoneFromEnv) { + throw new Error( + 'CLOUDSDK_COMPUTE_ZONE is not set in the environment, and DB_CLOUDSDK_COMPUTE_ZONE is also not set. One of these must be set to specify the zone for the Cloud SQL instance.' + ); + } + return zoneFromEnv; +} + +export const defaultUserName = 'cnadmin'; diff --git a/cluster/pulumi/common/src/stackReferences.ts b/cluster/pulumi/common/src/stackReferences.ts index ca1d52d77b..29a8123307 100644 --- a/cluster/pulumi/common/src/stackReferences.ts +++ b/cluster/pulumi/common/src/stackReferences.ts @@ -10,6 +10,14 @@ export const infraStack = new pulumi.StackReference(`organization/infra/infra.${ export class StackReferences { private static refCache: Partial> = {}; + public static get cantonNetwork(): pulumi.StackReference { + const projectName = 'canton-network'; + const stackName = `${projectName}.${CLUSTER_BASENAME}`; + return (StackReferences.refCache[stackName] ??= new pulumi.StackReference( + `organization/${projectName}/${stackName}` + )); + } + public static svCanton(sv: string, migrationId: number): pulumi.StackReference { const projectName = 'sv-canton'; const stackName = `${projectName}.${sv}-migration-${migrationId}.${CLUSTER_BASENAME}`; diff --git a/cluster/pulumi/sv/src/installNode.ts b/cluster/pulumi/sv/src/installNode.ts index 9415760155..5b6ba4ead3 100644 --- a/cluster/pulumi/sv/src/installNode.ts +++ b/cluster/pulumi/sv/src/installNode.ts @@ -15,11 +15,17 @@ import { svConfigs, svRunbookConfig, } from '@canton-network/splice-pulumi-common-sv'; -import { installSvNodeStandalone } from '@canton-network/splice-pulumi-common-sv/src/sv'; +import { + installSvNodeStandalone, + MigrationArgs, + SvsMigrationOutput, +} from '@canton-network/splice-pulumi-common-sv/src/sv'; +import { StackReferences } from '@canton-network/splice-pulumi-common/src/stackReferences'; import { installParticipant } from './participant'; export async function installNode(sv: string, auth0Client: Auth0Client): Promise { + // TODO(#6719) once all clusters have been migrated hardcode splitSvDeploymentEnabled to true const splitSvDeploymentEnabled = spliceConfig.configuration.synchronizerMigration.splitSvDeploymentEnabled; const staticConfig = findStaticConfigOrFail(sv); @@ -30,8 +36,17 @@ export async function installNode(sv: string, auth0Client: Auth0Client): Promise const auth0Config = auth0Client.getCfg(); const ledgerApiUserSecret = installLedgerApiUserSecret(auth0Client, xns, 'sv', 'sv'); const ledgerApiUserSecretSource = auth0UserNameEnvVarSource('sv', true); - if (splitSvDeploymentEnabled && staticConfig.nodeName !== svRunbookConfig.nodeName) { - await installSvNodeStandalone(xns, staticConfig, config, auth0Client); + // TODO(#6719) once all clusters have been migrated remove this + const migrateToSplitSvDeployment = + spliceConfig.configuration.synchronizerMigration.migrateToSplitSvDeployment; + if ( + (splitSvDeploymentEnabled || migrateToSplitSvDeployment) && + staticConfig.nodeName !== svRunbookConfig.nodeName + ) { + const migrationArgs = migrateToSplitSvDeployment + ? await getMigrationArgsForSv(staticConfig.nodeName) + : undefined; + await installSvNodeStandalone(xns, staticConfig, config, auth0Client, [], migrationArgs); } await installParticipant( { @@ -58,3 +73,18 @@ function findStaticConfigOrFail(sv: string): StaticSvConfig { return svConfig; } } + +// TODO(#6719) once all clusters have been migrated remove this +async function getMigrationArgsForSv(nodeName: string): Promise { + const svs = (await StackReferences.cantonNetwork.requireOutputValue('svs')) as SvsMigrationOutput; + const sv = + svs.find(sv => sv.nodeName === nodeName) ?? + (() => { + throw new Error(`No migration output found for SV: ${nodeName}`); + })(); + return { + action: 'import', + databaseInstanceName: sv.databaseInstanceName, + databaseSecretName: sv.databaseSecretName, + }; +} From 905ead3cf829334795e830a2b7a00d7faa4099ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Fri, 7 Aug 2026 18:10:27 +0200 Subject: [PATCH 208/329] Revert "[ci] Bump the development-dependencies group across 1 directory with 8 updates" (#6726) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 48911a80c33f10f96e25e156597ed01e393a1abc. --------- Signed-off-by: Oriol Muñoz --- .../canton-network/tsconfig.eslint.json | 1 - cluster/pulumi/circleci/tsconfig.eslint.json | 1 - cluster/pulumi/cluster/tsconfig.eslint.json | 1 - cluster/pulumi/common-sv/tsconfig.eslint.json | 1 - .../common-validator/tsconfig.eslint.json | 1 - cluster/pulumi/common/package.json | 2 +- cluster/pulumi/common/src/postgres.ts | 6 +- cluster/pulumi/common/src/serviceAccount.ts | 3 +- cluster/pulumi/common/tsconfig.eslint.json | 1 - .../pulumi/deployment/tsconfig.eslint.json | 1 - cluster/pulumi/eslint.config.mjs | 2 +- cluster/pulumi/gcp/tsconfig.eslint.json | 1 - cluster/pulumi/gha/tsconfig.eslint.json | 1 - cluster/pulumi/infra/src/cloudArmor.ts | 4 +- cluster/pulumi/infra/tsconfig.eslint.json | 1 - .../multi-validator/tsconfig.eslint.json | 1 - .../pulumi/observability/tsconfig.eslint.json | 1 - cluster/pulumi/operator/tsconfig.eslint.json | 1 - cluster/pulumi/package-lock.json | 470 ++++-------------- cluster/pulumi/package.json | 12 +- cluster/pulumi/policies/tsconfig.eslint.json | 1 - cluster/pulumi/splitwell/tsconfig.eslint.json | 1 - cluster/pulumi/sv-canton/tsconfig.eslint.json | 1 - .../pulumi/sv-runbook/tsconfig.eslint.json | 1 - cluster/pulumi/sv/tsconfig.eslint.json | 1 - cluster/pulumi/tsconfig.eslint.json | 5 - .../validator-runbook/tsconfig.eslint.json | 1 - .../pulumi/validator1/tsconfig.eslint.json | 1 - 28 files changed, 103 insertions(+), 421 deletions(-) delete mode 100644 cluster/pulumi/canton-network/tsconfig.eslint.json delete mode 100644 cluster/pulumi/circleci/tsconfig.eslint.json delete mode 100644 cluster/pulumi/cluster/tsconfig.eslint.json delete mode 100644 cluster/pulumi/common-sv/tsconfig.eslint.json delete mode 100644 cluster/pulumi/common-validator/tsconfig.eslint.json delete mode 100644 cluster/pulumi/common/tsconfig.eslint.json delete mode 100644 cluster/pulumi/deployment/tsconfig.eslint.json delete mode 100644 cluster/pulumi/gcp/tsconfig.eslint.json delete mode 100644 cluster/pulumi/gha/tsconfig.eslint.json delete mode 100644 cluster/pulumi/infra/tsconfig.eslint.json delete mode 100644 cluster/pulumi/multi-validator/tsconfig.eslint.json delete mode 100644 cluster/pulumi/observability/tsconfig.eslint.json delete mode 100644 cluster/pulumi/operator/tsconfig.eslint.json delete mode 100644 cluster/pulumi/policies/tsconfig.eslint.json delete mode 100644 cluster/pulumi/splitwell/tsconfig.eslint.json delete mode 100644 cluster/pulumi/sv-canton/tsconfig.eslint.json delete mode 100644 cluster/pulumi/sv-runbook/tsconfig.eslint.json delete mode 100644 cluster/pulumi/sv/tsconfig.eslint.json delete mode 100644 cluster/pulumi/tsconfig.eslint.json delete mode 100644 cluster/pulumi/validator-runbook/tsconfig.eslint.json delete mode 100644 cluster/pulumi/validator1/tsconfig.eslint.json diff --git a/cluster/pulumi/canton-network/tsconfig.eslint.json b/cluster/pulumi/canton-network/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/canton-network/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/circleci/tsconfig.eslint.json b/cluster/pulumi/circleci/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/circleci/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/cluster/tsconfig.eslint.json b/cluster/pulumi/cluster/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/cluster/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/common-sv/tsconfig.eslint.json b/cluster/pulumi/common-sv/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/common-sv/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/common-validator/tsconfig.eslint.json b/cluster/pulumi/common-validator/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/common-validator/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/common/package.json b/cluster/pulumi/common/package.json index 3caf421b9f..c8891effcc 100644 --- a/cluster/pulumi/common/package.json +++ b/cluster/pulumi/common/package.json @@ -35,7 +35,7 @@ "devDependencies": { "@jest/globals": "^30.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.17.25", + "@types/lodash": "^4.17.24", "@types/ws": "^8.18.1", "dedent": "^1.7.2" } diff --git a/cluster/pulumi/common/src/postgres.ts b/cluster/pulumi/common/src/postgres.ts index 798f6a3ec8..e86c3fabd8 100644 --- a/cluster/pulumi/common/src/postgres.ts +++ b/cluster/pulumi/common/src/postgres.ts @@ -509,7 +509,8 @@ export class SplicePostgres extends pulumi.ComponentResource implements Postgres instanceName: string, installPassword: (parent: Resource) => k8s.core.v1.Secret, splicePostgresHelmMigrationConfig: - SplicePostgresMigrateConfig | SplicePostgresDockerImageConfig, + | SplicePostgresMigrateConfig + | SplicePostgresDockerImageConfig, values?: LegacyChartValues, overrideDbSizeFromValues?: boolean, disableProtection?: boolean, @@ -925,7 +926,8 @@ export function installSplicePostgres( instanceName, parent => installPasswordWithParent(parent, xns, instanceName, secretName), splicePostgresHelmMigrationConfig as - SplicePostgresMigrateConfig | SplicePostgresDockerImageConfig, + | SplicePostgresMigrateConfig + | SplicePostgresDockerImageConfig, chartValues, overrideDbSizeFromValues, opts.disableProtection, diff --git a/cluster/pulumi/common/src/serviceAccount.ts b/cluster/pulumi/common/src/serviceAccount.ts index d23c386728..62803263c8 100644 --- a/cluster/pulumi/common/src/serviceAccount.ts +++ b/cluster/pulumi/common/src/serviceAccount.ts @@ -4,7 +4,8 @@ import * as gcp from '@pulumi/gcp'; import * as pulumi from '@pulumi/pulumi'; type Role = - string | { id: string; condition: { title: string; description: string; expression: string } }; + | string + | { id: string; condition: { title: string; description: string; expression: string } }; const roleToPulumiName = (role: Role): string => { if (typeof role === 'string') { diff --git a/cluster/pulumi/common/tsconfig.eslint.json b/cluster/pulumi/common/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/common/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/deployment/tsconfig.eslint.json b/cluster/pulumi/deployment/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/deployment/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/eslint.config.mjs b/cluster/pulumi/eslint.config.mjs index 8717187764..d5a0756535 100644 --- a/cluster/pulumi/eslint.config.mjs +++ b/cluster/pulumi/eslint.config.mjs @@ -32,7 +32,7 @@ export default defineConfig([{ sourceType: "script", parserOptions: { - project: ["./tsconfig.eslint.json"], + project: ["./tsconfig.json"], }, }, diff --git a/cluster/pulumi/gcp/tsconfig.eslint.json b/cluster/pulumi/gcp/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/gcp/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/gha/tsconfig.eslint.json b/cluster/pulumi/gha/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/gha/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/infra/src/cloudArmor.ts b/cluster/pulumi/infra/src/cloudArmor.ts index 82fda8ef65..2ab7f7e286 100644 --- a/cluster/pulumi/infra/src/cloudArmor.ts +++ b/cluster/pulumi/infra/src/cloudArmor.ts @@ -76,9 +76,7 @@ export function configureCloudArmorPolicy( // Step 2: Add predefined WAF rules if (cac.predefinedWafRules && cac.predefinedWafRules.length > 0) { - addPredefinedWafRules( - /*securityPolicy, args.predefinedWafRules, cac.allRulesPreviewOnly, ruleOpts*/ - ); + addPredefinedWafRules(/*securityPolicy, args.predefinedWafRules, cac.allRulesPreviewOnly, ruleOpts*/); } // Step 3: Add IP whitelisting rules diff --git a/cluster/pulumi/infra/tsconfig.eslint.json b/cluster/pulumi/infra/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/infra/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/multi-validator/tsconfig.eslint.json b/cluster/pulumi/multi-validator/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/multi-validator/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/observability/tsconfig.eslint.json b/cluster/pulumi/observability/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/observability/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/operator/tsconfig.eslint.json b/cluster/pulumi/operator/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/operator/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/package-lock.json b/cluster/pulumi/package-lock.json index 8f668a304e..829177c65a 100644 --- a/cluster/pulumi/package-lock.json +++ b/cluster/pulumi/package-lock.json @@ -35,20 +35,20 @@ "commander": "^14.0.3" }, "devDependencies": { - "@eslint/eslintrc": "^3.3.6", - "@eslint/js": "9.39.5", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@jest/globals": "^30.4.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/request": "^2.48.13", - "@typescript-eslint/eslint-plugin": "^8.65.0", + "@typescript-eslint/eslint-plugin": "^8.61.1", "@typescript-eslint/parser": "^8.57.2", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-promise": "^7.3.0", "jest": "^30.4.2", - "minimatch": "10.2.6", - "prettier": "^3.9.6", - "ts-jest": "^29.4.12", + "minimatch": "10.2.5", + "prettier": "^3.8.4", + "ts-jest": "^29.4.11", "typescript": "^5.9.3" } }, @@ -108,7 +108,7 @@ "devDependencies": { "@jest/globals": "^30.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.17.25", + "@types/lodash": "^4.17.24", "@types/ws": "^8.18.1", "dedent": "^1.7.2" } @@ -1013,9 +1013,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", - "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { @@ -1025,7 +1025,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.3.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -1061,9 +1061,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.5", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", - "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -3581,9 +3581,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", - "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", "dev": true, "license": "MIT" }, @@ -3736,17 +3736,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", - "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/type-utils": "8.65.0", - "@typescript-eslint/utils": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3759,7 +3759,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.65.0", + "@typescript-eslint/parser": "^8.61.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -3775,16 +3775,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", - "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/typescript-estree": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3" }, "engines": { @@ -3799,64 +3799,15 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", - "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", - "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.66.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@typescript-eslint/project-service": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", - "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.66.0", - "@typescript-eslint/types": "^8.66.0", + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", "debug": "^4.4.3" }, "engines": { @@ -3871,14 +3822,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", - "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0" + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3888,24 +3839,10 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", - "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", - "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", "dev": true, "license": "MIT", "engines": { @@ -3920,15 +3857,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", - "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0", - "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3944,91 +3881,10 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/project-service": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", - "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.65.0", - "@typescript-eslint/types": "^8.65.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", - "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", - "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", - "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.65.0", - "@typescript-eslint/tsconfig-utils": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, "node_modules/@typescript-eslint/types": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", - "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", "dev": true, "license": "MIT", "engines": { @@ -4040,16 +3896,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", - "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.66.0", - "@typescript-eslint/tsconfig-utils": "8.66.0", - "@typescript-eslint/types": "8.66.0", - "@typescript-eslint/visitor-keys": "8.66.0", + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -4067,48 +3923,17 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.66.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", - "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.66.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@typescript-eslint/utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", - "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0" + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -4122,95 +3947,14 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/project-service": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", - "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.65.0", - "@typescript-eslint/types": "^8.65.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", - "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", - "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", - "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.65.0", - "@typescript-eslint/tsconfig-utils": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", - "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/types": "8.61.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -4221,20 +3965,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", - "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", @@ -6416,20 +6146,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", @@ -8900,19 +8616,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", - "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/puzrin" - }, - { - "type": "github", - "url": "https://github.com/sponsors/nodeca" - } - ], + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -9263,12 +8969,12 @@ } }, "node_modules/minimatch": { - "version": "10.2.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", - "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.8" + "brace-expansion": "^5.0.5" }, "engines": { "node": "18 || 20 || >=22" @@ -10268,9 +9974,9 @@ } }, "node_modules/prettier": { - "version": "3.9.6", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", - "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", "dev": true, "license": "MIT", "bin": { @@ -10719,9 +10425,9 @@ } }, "node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11524,9 +11230,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.12", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.12.tgz", - "integrity": "sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==", + "version": "29.4.11", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", + "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", "dev": true, "license": "MIT", "dependencies": { @@ -11536,7 +11242,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.8.5", + "semver": "^7.8.0", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, diff --git a/cluster/pulumi/package.json b/cluster/pulumi/package.json index 1ac82f9ff8..f6e4c34262 100644 --- a/cluster/pulumi/package.json +++ b/cluster/pulumi/package.json @@ -3,20 +3,20 @@ "version": "1.0.0", "main": "src/index.ts", "devDependencies": { - "@eslint/eslintrc": "^3.3.6", + "@eslint/eslintrc": "^3.3.5", "@jest/globals": "^30.4.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/request": "^2.48.13", - "@typescript-eslint/eslint-plugin": "^8.65.0", + "@typescript-eslint/eslint-plugin": "^8.61.1", "@typescript-eslint/parser": "^8.57.2", - "@eslint/js": "9.39.5", + "@eslint/js": "9.39.4", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-promise": "^7.3.0", "jest": "^30.4.2", - "minimatch": "10.2.6", - "prettier": "^3.9.6", - "ts-jest": "^29.4.12", + "minimatch": "10.2.5", + "prettier": "^3.8.4", + "ts-jest": "^29.4.11", "typescript": "^5.9.3" }, "scripts": { diff --git a/cluster/pulumi/policies/tsconfig.eslint.json b/cluster/pulumi/policies/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/policies/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/splitwell/tsconfig.eslint.json b/cluster/pulumi/splitwell/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/splitwell/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/sv-canton/tsconfig.eslint.json b/cluster/pulumi/sv-canton/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/sv-canton/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/sv-runbook/tsconfig.eslint.json b/cluster/pulumi/sv-runbook/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/sv-runbook/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/sv/tsconfig.eslint.json b/cluster/pulumi/sv/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/sv/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/tsconfig.eslint.json b/cluster/pulumi/tsconfig.eslint.json deleted file mode 100644 index 843ee98bc1..0000000000 --- a/cluster/pulumi/tsconfig.eslint.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["*.ts"], - "exclude": [] -} diff --git a/cluster/pulumi/validator-runbook/tsconfig.eslint.json b/cluster/pulumi/validator-runbook/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/validator-runbook/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/validator1/tsconfig.eslint.json b/cluster/pulumi/validator1/tsconfig.eslint.json deleted file mode 100644 index f88dce88fd..0000000000 --- a/cluster/pulumi/validator1/tsconfig.eslint.json +++ /dev/null @@ -1 +0,0 @@ -{"extends": "./tsconfig.json"} From 3ac4865f49bdd72e4ac99fba73a5ab4704c0ee02 Mon Sep 17 00:00:00 2001 From: Richard Kapolnai <53859003+richardkapolnai-da@users.noreply.github.com> Date: Fri, 7 Aug 2026 19:24:00 +0200 Subject: [PATCH 209/329] fix typo in ValidatorReonboardingIntegrationTest.scala (#5535) Signed-off-by: Richard Kapolnai <53859003+richardkapolnai-da@users.noreply.github.com> Co-authored-by: Martin Florian --- .../tests/ValidatorReonboardingIntegrationTest.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ValidatorReonboardingIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ValidatorReonboardingIntegrationTest.scala index 7d5251a6d6..821e3af09c 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ValidatorReonboardingIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ValidatorReonboardingIntegrationTest.scala @@ -274,7 +274,7 @@ class ValidatorReonboardingIntegrationTest extends ValidatorReonboardingIntegrat val lockedAmount = walletUsdToAmulet(BigDecimal(50)) actAndCheck( - "alice locks a amulet that both aliceParty and aliceValidatorWalletParty are stake holders", + "alice locks an amulet that has both aliceParty and aliceValidatorWalletParty as stakeholders", lockAmulets( aliceValidatorBackend, aliceParty, From 1bee84c41d9706c1c3ea19457c1c8b6eb5ccbe1c Mon Sep 17 00:00:00 2001 From: Pasindu Tennage Date: Fri, 7 Aug 2026 20:18:26 +0200 Subject: [PATCH 210/329] Bump versions after 0.7.1 release (#6729) [ci] Signed-off-by: Pasindu Tennage Signed-off-by: pasindutennage-da --- LATEST_RELEASE | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LATEST_RELEASE b/LATEST_RELEASE index faef31a435..39e898a4f9 100644 --- a/LATEST_RELEASE +++ b/LATEST_RELEASE @@ -1 +1 @@ -0.7.0 +0.7.1 diff --git a/VERSION b/VERSION index 39e898a4f9..7486fdbc50 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.1 +0.7.2 From 376c143e50ba230ef5e8d965b14ccdeaa43eef48 Mon Sep 17 00:00:00 2001 From: Jaeyoon Cho Date: Mon, 10 Aug 2026 12:06:41 +0900 Subject: [PATCH 211/329] SV UI: remove "proposal reason" from update feature app weight proposal form (#6689) --------- Signed-off-by: JYC11 --- .../tests/SvFrontendIntegrationTest.scala | 1 - .../forms/update-featured-app-form.test.tsx | 30 +------------------ .../proposal-details-content.test.tsx | 11 ------- .../governance/proposal-summary.test.tsx | 5 ---- .../forms/UpdateFeaturedAppForm.tsx | 15 +--------- .../governance/ProposalDetailsContent.tsx | 9 ------ .../components/governance/ProposalSummary.tsx | 2 -- apps/sv/frontend/src/utils/governance.ts | 8 ++--- apps/sv/frontend/src/utils/types.ts | 2 -- 9 files changed, 5 insertions(+), 78 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala index 1e39e21e7b..20be650e0b 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvFrontendIntegrationTest.scala @@ -1468,7 +1468,6 @@ class SvFrontendIntegrationTest fillOutTextField("update-featured-app-partyId", providerPartyId) selectFirstMuiOption("update-featured-app-rightCid-dropdown") fillOutTextField("update-featured-app-activityWeight", newActivityWeight.toString) - fillOutTextField("update-featured-app-reason", "increasing weight") } clue("vote the update request to execution") { diff --git a/apps/sv/frontend/src/__tests__/governance/forms/update-featured-app-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/update-featured-app-form.test.tsx index 8bf22d3cc8..a71c1b0afd 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/update-featured-app-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/update-featured-app-form.test.tsx @@ -68,9 +68,6 @@ describe('Update Featured App Form', () => { const activityWeightInput = screen.getByTestId('update-featured-app-activityWeight'); await user.type(activityWeightInput, '2.5'); - - const reasonInput = screen.getByTestId('update-featured-app-reason'); - await user.type(reasonInput, 'test'); }; test('should render all Form components', () => { @@ -112,7 +109,6 @@ describe('Update Featured App Form', () => { expect(rightCidDropdown).toBeDisabled(); expect(screen.getByTestId('update-featured-app-activityWeight')).toBeInTheDocument(); - expect(screen.getByTestId('update-featured-app-reason')).toBeInTheDocument(); expect(screen.getByText('Review Proposal')).toBeInTheDocument(); }); @@ -139,7 +135,7 @@ describe('Update Featured App Form', () => { }); }); - test('should send new activity weight and reason to backend', async () => { + test('should send new activity weight to backend', async () => { let requestBody = ''; server.use( http.post(`${svUrl}/v0/admin/sv/voterequest/create`, async ({ request }) => { @@ -171,7 +167,6 @@ describe('Update Featured App Form', () => { await waitFor(() => { expect(requestBody).toContain('"newActivityWeight":"2.5"'); - expect(requestBody).toContain('"reason":"test"'); }); }); @@ -205,28 +200,6 @@ describe('Update Featured App Form', () => { }); }); - test('reason is required', async () => { - const user = userEvent.setup(); - - render( - - - - ); - - const reasonInput = screen.getByTestId('update-featured-app-reason'); - const actionInput = screen.getByTestId('update-featured-app-action'); - - await user.click(reasonInput); - await user.click(actionInput); // blur to trigger validation - - await waitFor(() => { - expect(screen.getByTestId('update-featured-app-reason-error').textContent).toBe( - 'Reason is required' - ); - }); - }); - test('communicates when the provider has no featured app rights to update', async () => { const user = userEvent.setup(); @@ -338,6 +311,5 @@ describe('Update Featured App Form', () => { expect(screen.getByTestId('updateRight-field').textContent).toBe('rightCid123'); expect(screen.getByTestId('config-change-current-value').textContent).toBe('1.0'); expect(screen.getByTestId('config-change-new-value').textContent).toBe('2.5'); - expect(screen.getByTestId('updateReason-field').textContent).toBe('test'); }); }); diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx index cbeea63329..003e8da795 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx @@ -300,7 +300,6 @@ describe('Proposal Details Content', () => { proposal: { rightContractId: 'rightCid123', newActivityWeight: '2.5', - reason: 'boosting rewards', } as UpdateFeatureAppProposal, } as ProposalDetails; @@ -337,16 +336,6 @@ describe('Proposal Details Content', () => { const newFeaturedAppWeight = screen.getByTestId('config-change-new-value'); expect(newFeaturedAppWeight.textContent).toMatch('2.5'); - - const updateFeaturedReasonLabel = screen.getByTestId( - 'proposal-details-update-feature-reason-label' - ); - expect(updateFeaturedReasonLabel.textContent).toMatch('Reason'); - - const updateFeaturedReasonValue = screen.getByTestId( - 'proposal-details-update-feature-reason-value' - ); - expect(updateFeaturedReasonValue.textContent).toMatch('boosting rewards'); }); test('should show only new weight when featured app right is not found', async () => { diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx index 66a23b706b..74beb26805 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx @@ -207,7 +207,6 @@ describe('Review Proposal Component', () => { const rightCid = 'bcde123456'; const currentActivityWeight = '1.0'; const newActivityWeight = '2.5'; - const reason = 'boosting rewards'; render( { rightCid={rightCid} currentActivityWeight={currentActivityWeight} newActivityWeight={newActivityWeight} - reason={reason} onEdit={() => {}} onSubmit={() => {}} /> @@ -254,9 +252,6 @@ describe('Review Proposal Component', () => { currentActivityWeight ); expect(screen.getByTestId('config-change-new-value').textContent).toBe(newActivityWeight); - - expect(screen.getByTestId('updateReason-title').textContent).toBe('Reason'); - expect(screen.getByTestId('updateReason-field').textContent).toBe(reason); }); test('should render review proposal component for dso rules config', () => { diff --git a/apps/sv/frontend/src/components/forms/UpdateFeaturedAppForm.tsx b/apps/sv/frontend/src/components/forms/UpdateFeaturedAppForm.tsx index dc71ed562b..39d655253b 100644 --- a/apps/sv/frontend/src/components/forms/UpdateFeaturedAppForm.tsx +++ b/apps/sv/frontend/src/components/forms/UpdateFeaturedAppForm.tsx @@ -19,7 +19,6 @@ import { validateExpiration, validateExpiryEffectiveDate, validatePartyId, - validateReason, validateRequiredActivityWeight, validateSummary, validateUrl, @@ -57,7 +56,6 @@ export const UpdateFeaturedAppForm: React.FC = () => { partyId: '', rightCid: '', newActivityWeight: '', - reason: '', }; const form = useAppForm({ @@ -70,7 +68,7 @@ export const UpdateFeaturedAppForm: React.FC = () => { tag: 'SRARC_UpdateFeaturedAppRight', value: { rightCid: value.rightCid as ContractId, - update: { reason: value.reason, newActivityWeight: value.newActivityWeight }, + update: { reason: '', newActivityWeight: value.newActivityWeight }, }, }, }, @@ -124,7 +122,6 @@ export const UpdateFeaturedAppForm: React.FC = () => { rightCid={form.state.values.rightCid} newActivityWeight={form.state.values.newActivityWeight} currentActivityWeight={currentWeight} - reason={form.state.values.reason} onEdit={() => setShowConfirmation(false)} onSubmit={() => {}} /> @@ -198,16 +195,6 @@ export const UpdateFeaturedAppForm: React.FC = () => { )} - validateReason(value), - onChange: ({ value }) => validateReason(value), - }} - > - {field => } - - = pro )} @@ -713,13 +712,11 @@ const UnfeatureAppSection = ({ rightContractId }: UnfeatureAppSectionProps) => { interface UpdateFeatureAppSectionProps { rightContractId: string; newActivityWeight: string; - reason: string; } const UpdateFeatureAppSection = ({ rightContractId, newActivityWeight, - reason, }: UpdateFeatureAppSectionProps) => { const svAdminClient = useSvAdminClient(); const providerQuery = useQuery({ @@ -781,12 +778,6 @@ const UpdateFeatureAppSection = ({ /> } /> - ); }; diff --git a/apps/sv/frontend/src/components/governance/ProposalSummary.tsx b/apps/sv/frontend/src/components/governance/ProposalSummary.tsx index c563c4c216..3260b71b0f 100644 --- a/apps/sv/frontend/src/components/governance/ProposalSummary.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalSummary.tsx @@ -55,7 +55,6 @@ type ProposalSummaryProps = BaseProposalSummaryProps & rightCid: string; currentActivityWeight: string; newActivityWeight: string; - reason: string; } ); @@ -176,7 +175,6 @@ export const ProposalSummary: React.FC = props => { /> } /> - )} diff --git a/apps/sv/frontend/src/utils/governance.ts b/apps/sv/frontend/src/utils/governance.ts index 3e9c27a282..18d298dc67 100644 --- a/apps/sv/frontend/src/utils/governance.ts +++ b/apps/sv/frontend/src/utils/governance.ts @@ -160,8 +160,7 @@ export function buildProposal(action: ActionRequiringConfirmation, dsoInfo?: Dso case 'SRARC_UpdateFeaturedAppRight': return createUpdateFeatureAppProposal( dsoAction.value.rightCid, - dsoAction.value.update.newActivityWeight, - dsoAction.value.update.reason + dsoAction.value.update.newActivityWeight ); case 'SRARC_SetConfig': return createDsoRulesConfigProposal(dsoAction.value.baseConfig, dsoAction.value.newConfig); @@ -194,10 +193,9 @@ function createGrantFeatureAppProposal( function createUpdateFeatureAppProposal( rightContractId: string, - newActivityWeight: string, - reason: string + newActivityWeight: string ): UpdateFeatureAppProposal { - return { rightContractId, newActivityWeight, reason }; + return { rightContractId, newActivityWeight }; } function createRevokeFeatureAppProposal(rightContractId: string): UnfeatureAppProposal { diff --git a/apps/sv/frontend/src/utils/types.ts b/apps/sv/frontend/src/utils/types.ts index 6329201013..e7b3377f7f 100644 --- a/apps/sv/frontend/src/utils/types.ts +++ b/apps/sv/frontend/src/utils/types.ts @@ -32,7 +32,6 @@ export interface UnfeatureAppProposal { export interface UpdateFeatureAppProposal { rightContractId: string; newActivityWeight: string; - reason: string; } export interface UnclaimedActivityRecordProposal { @@ -213,7 +212,6 @@ export interface UpdateFeatureAppFormData extends CommonProposalFormData { partyId: string; rightCid: string; newActivityWeight: string; - reason: string; } export type NonConfigProposalFormData = From 64ba9399960eaec84c88369d62a35cb433163d65 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Mon, 10 Aug 2026 10:31:54 +0200 Subject: [PATCH 212/329] Upgrade Canton to 3.5.13-snapshot.20260809.19149.0.v1b50fc03 (#6735) [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index 6532a840ed..00cd4ebb3f 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.12", - "oss_sha256": "sha256:03cz2bprd43g721q6j324lfhrkm7v10cslwnwcvm34zm9jqfk2ds", - "canton_base_image_sha256": "sha256:218c4e6ea4a38f4ade82772197f6901d37169af6c2cb1a4c8e47277bbbbe114c", - "canton_participant_image_sha256": "sha256:1950cf9c465b08f52952febcb87cb0393afa7fcb9ea8eab2d9356e3f83028840", - "canton_mediator_image_sha256": "sha256:67504bed9cf6ecbbf50180024a24e6252479c65649ce84962a7afec24f7ce607", - "canton_sequencer_image_sha256": "sha256:359fd1728f8cc174f2c53132a982f4118a4c8aee1d3625c10447091258bce1b9" + "version": "3.5.13-snapshot.20260809.19149.0.v1b50fc03", + "oss_sha256": "sha256:1vsbqcsv5srcglh8wyn6awiygwicr69jvh0gdfbrb47q2mv6nk5a", + "canton_base_image_sha256": "sha256:954329e7c556607afcbb1e9b8913cd2fe859cd0a52986e4f1905c7cc5a35670d", + "canton_participant_image_sha256": "sha256:54822a7e367a14ba96cbc4c8a7516e28671d8d525241c5c44471c0d2886b9a10", + "canton_mediator_image_sha256": "sha256:a321fca163dbdabb954ba60d242fccfe2ef76c9622ba1da2c6ca9e0a3e44c137", + "canton_sequencer_image_sha256": "sha256:a3ee3e0e9e191f6390110e5d7cfbda3f2e0d7aea9d11988b847cf56f9f463daf" } From 6182397a9328ac4a8bc6083622fa35b02a5b1603 Mon Sep 17 00:00:00 2001 From: Martin Florian Date: Mon, 10 Aug 2026 10:57:55 +0200 Subject: [PATCH 213/329] Reconcile setBalanceRequestSubmissionWindowSize (#6728) Main part of #4715 Once merged, I'll make sure to alert SVs about the upcoming change right away. [ci] Signed-off-by: Martin Florian --- ...conciliationTimeBasedIntegrationTest.scala | 21 +++++++++++++++++-- ...DynamicSynchronizerParametersTrigger.scala | 2 ++ .../splice/sv/config/SvAppConfig.scala | 5 +++++ docs/src/release_notes_upcoming.rst | 9 ++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DynamicSynchronizerParamsReconciliationTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DynamicSynchronizerParamsReconciliationTimeBasedIntegrationTest.scala index 8cba8a2422..fea29008fb 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DynamicSynchronizerParamsReconciliationTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DynamicSynchronizerParamsReconciliationTimeBasedIntegrationTest.scala @@ -1,6 +1,6 @@ package org.lfdecentralizedtrust.splice.integration.tests -import com.digitalasset.canton.config.NonNegativeFiniteDuration +import com.digitalasset.canton.config.{NonNegativeFiniteDuration, PositiveFiniteDuration} import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.IntegrationTestWithIsolatedEnvironment import org.lfdecentralizedtrust.splice.util.{TimeTestUtil, WalletTestUtil} @@ -23,7 +23,12 @@ class DynamicSynchronizerParamsReconciliationTimeBasedIntegrationTest svApps = config.svApps + (InstanceName.tryCreate("sv1Local") -> config - .svApps(InstanceName.tryCreate(s"sv1"))) + + .svApps(InstanceName.tryCreate(s"sv1")) + .copy( + // Non-default value (Canton's default is 2min) to check that the + // reconciliation trigger applies it to the synchronizer. + setBalanceRequestSubmissionWindowSize = PositiveFiniteDuration.ofMinutes(4) + )) + (InstanceName.tryCreate("sv1") -> config .svApps(InstanceName.tryCreate(s"sv1")) @@ -60,6 +65,12 @@ class DynamicSynchronizerParamsReconciliationTimeBasedIntegrationTest .trafficControl .getOrElse(throw new RuntimeException("Traffic control parameters not found")) .freeConfirmationResponses shouldBe false + // FoundDso does not set the submission window size, so bootstrapping leaves it at Canton's default + sv1Backend.participantClient.topology.synchronizer_parameters + .get_dynamic_synchronizer_parameters(synchronizerId) + .trafficControl + .getOrElse(throw new RuntimeException("Traffic control parameters not found")) + .setBalanceRequestSubmissionWindowSize shouldBe PositiveFiniteDuration.ofMinutes(2) sv1Backend.stop() sv1LocalBackend.startSync() @@ -76,6 +87,12 @@ class DynamicSynchronizerParamsReconciliationTimeBasedIntegrationTest .trafficControl .getOrElse(throw new RuntimeException("Traffic control parameters not found")) .freeConfirmationResponses shouldBe true + // sv1Local is configured with a non-default window size which the trigger applies. + sv1Backend.participantClient.topology.synchronizer_parameters + .get_dynamic_synchronizer_parameters(synchronizerId) + .trafficControl + .getOrElse(throw new RuntimeException("Traffic control parameters not found")) + .setBalanceRequestSubmissionWindowSize shouldBe PositiveFiniteDuration.ofMinutes(4) } // We go slightly above 48h as time is not actually completely still in simtime, the microseconds still advance. diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/singlesv/ReconcileDynamicSynchronizerParametersTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/singlesv/ReconcileDynamicSynchronizerParametersTrigger.scala index f4a0cbc02a..bab6b0900c 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/singlesv/ReconcileDynamicSynchronizerParametersTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/singlesv/ReconcileDynamicSynchronizerParametersTrigger.scala @@ -196,6 +196,8 @@ class ReconcileDynamicSynchronizerParametersTrigger( maxBaseTrafficAccumulationDuration = PositiveFiniteDuration.tryOfSeconds( domainFeesConfig.baseRateTrafficLimits.burstWindow.microseconds / 1000_000 ), + setBalanceRequestSubmissionWindowSize = + PositiveFiniteDuration.fromConfig(config.setBalanceRequestSubmissionWindowSize), freeConfirmationResponses = enableFreeConfirmationResponses, ) }, diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala index ae629e488b..5db47a2e22 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/config/SvAppConfig.scala @@ -454,6 +454,11 @@ case class SvAppBackendConfig( convertFeaturedAppActivityMarkerObservers: Boolean = true, // Whether to ensure that heuristic free confirmation responses get enabled on the synchronizer via the ReconcileDynamicSynchronizerConfigTrigger. enableFreeConfirmationResponses: Boolean = true, + // Target value for the setBalanceRequestSubmissionWindowSize traffic control parameter, + // applied to the synchronizer via the ReconcileDynamicSynchronizerParametersTrigger. + // The default matches Canton's current default as of 3.5.12 + setBalanceRequestSubmissionWindowSize: PositiveFiniteDuration = + PositiveFiniteDuration.ofMinutes(2), packageVettingCache: PackageVettingLookupService.CacheConfig = PackageVettingLookupService.CacheConfig(), useInternalSequencerApi: Boolean = false, diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 7488c245e7..4ba2241225 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -7,3 +7,12 @@ .. release-notes:: Upcoming + - SV App + + - The SV app now reconciles the ``setBalanceRequestSubmissionWindowSize`` traffic control parameter of the global synchronizer against a new SV app config value ``set-balance-request-submission-window-size``, which defaults to Canton's current default of 2 minutes. + This parameter defines the time window used to compute the max sequencing time of traffic purchase (top-up) requests. + Canton lowered its default from 4 minutes to 2 minutes (see the `Canton 3.5.1 release notes `_). + Networks bootstrapped on an older version (DevNet, TestNet, MainNet) still use the old value for this parameter. + By upgrading to this version, SVs agree to change this parameter to 2 minutes (unless they override the new SV app config value). + The change takes effect once a sufficient number of SVs have upgraded. + From e7d46ff1e7edaa9ea0d1f682a19d49898f893757 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Mon, 10 Aug 2026 11:18:55 +0200 Subject: [PATCH 214/329] Fix lsu dashboard participant status panel (#6736) also update unresponsive parties to show all in the tooltip [static] Signed-off-by: Nicu Reut --- cluster/expected/observability/expected.json | 4 +- .../grafana-dashboards/canton/lsu-status.json | 70 +++++++++++++++++-- .../canton/unresponsive_parties.json | 4 +- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index f042086d6b..69eca6f23d 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -181,7 +181,7 @@ "data": { "acknowledgements.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 3410,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 16,\n \"panels\": [],\n \"title\": \"Client Delay\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 9,\n \"panels\": [],\n \"title\": \"Canton\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The most recent acknowledgement the sequencer has seen from that member. Acknowledgements are only sent when the member also receives an event addressed to them so a node that never receives anything can lag behind. Sequencers only receive topology transactions which are rare so we filter them out.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 2\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": false\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"asc\"\n }\n },\n \"pluginVersion\": \"12.0.2\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_block_acknowledgments_micros{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-sequencer\\\",member!~\\\"SEQ::.*\\\",member=~\\\"$member\\\"} / 1000\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} M$migration {{member}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Most recent acknowledgement\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The difference most recent acknowledgement the sequencer has seen from that member and the maximum across all members. Acknowledgements are only sent when the member also receives an event addressed to them so a node that never receives anything can lag behind. Sequencers only receive topology transactions which are rare so we filter them out.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 11,\n \"x\": 0,\n \"y\": 12\n },\n \"id\": 13,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.0.2\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"(max without (member) (daml_sequencer_block_acknowledgments_micros{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-sequencer\\\"}) - ignoring (member) group_right daml_sequencer_block_acknowledgments_micros{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-sequencer\\\",member!~\\\"SEQ::.*\\\",member=~\\\"$member\\\"}) / 1000000\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} M$migration {{member}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Acknowledgement lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The increase in acknowledgement time in acknowledgements over the last 10min for a given member. This should usually be around 1. If a node was down, they should show > 1. If the value is < 1 the node might never catch up. Acknowledgements are only sent when the member also receives an event addressed to them so a node that never receives anything can lag behind. Sequencers only receive topology transactions which are rare so we filter them out.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 13,\n \"x\": 11,\n \"y\": 12\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.0.2\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"delta(daml_sequencer_block_acknowledgments_micros{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-sequencer\\\",member!~\\\"SEQ::.*\\\",member=~\\\"$member\\\"}[10m]) / (1000000 * 60 * 10)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} M$migration {{member}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Acknowledgement catchup speed\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 22\n },\n \"id\": 10,\n \"panels\": [],\n \"title\": \"CometBFT\",\n \"type\": \"row\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"30s\",\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"Prometheus\",\n \"value\": \"prometheus\"\n },\n \"includeAll\": false,\n \"name\": \"DS\",\n \"options\": [],\n \"query\": \"prometheus\",\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"datasource\"\n },\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_client_handler_delay,namespace)\",\n \"includeAll\": true,\n \"label\": \"Namespace\",\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(daml_sequencer_client_handler_delay,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_client_handler_delay{namespace=~\\\"$namespace\\\"},job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"migration\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_client_handler_delay{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"/global-domain-(?\\\\d+)-sequencer/g\",\n \"sort\": 1,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_block_acknowledgments_micros,member)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"member\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_block_acknowledgments_micros,member)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Acknowledgements\",\n \"uid\": \"3ccfda97-fb9c-4413-bc05-5ed6f2c888f7\",\n \"version\": 3\n}\n", "acs-commitment-performance.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 6,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 12,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"builder\",\n \"expr\": \"max by(namespace, job) (daml_participant_sync_commitments_active_stakeholder_groups{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"})\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"active stakeholder groups\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Measures the time that the participant node spends computing commitments each reconciliation interval. This measures the full time needed to compute the commitments for all counter parties.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": true,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 18,\n \"x\": 6,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.99, sum(rate(daml_participant_sync_commitments_compute_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"}[$__rate_interval])) by (namespace, job, le))\",\n \"legendFormat\": \"{{namespace}} {{job}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"ACS Computation Time (0.99 quantile)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Measures the time between the end of a commitment period, and the time when the sequencer observes the corresponding commitment.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"µs\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 24,\n \"x\": 0,\n \"y\": 14\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace, job) (daml_participant_sync_commitments_sequencing_time{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"})\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{job}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"ACS Commitment Sequencing Time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Measures how many times the catch-up mode has been triggered over the last hour\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 24,\n \"x\": 0,\n \"y\": 27\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job)(increase(daml_participant_sync_commitments_catchup_mode_enabled_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"}[1h]))\",\n \"legendFormat\": \"{{namespace}} {{job}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"ACS Commitment Catchup in the last hour\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Lag behind the timestamp the commitment checkpoint was created and the timestamp of the checkpoint; periodic lag spikes during commitment computation are expected\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"log\": 2,\n \"type\": \"log\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 41\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"min by (namespace, job) ((timestamp(daml_participant_sync_commitments_last_locally_checkpointed{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"}) - ((daml_participant_sync_commitments_last_locally_checkpointed{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"} > 0) / 1e6)))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Commitment Checkpoint Lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Lag behind the timestamp of when the last completed commitment interval was processed and the timestamp of the interval\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"log\": 2,\n \"type\": \"log\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 41\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"min by (namespace, job) (timestamp(daml_participant_sync_commitments_last_locally_completed{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"}) - ((daml_participant_sync_commitments_last_locally_completed{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"} > 0) / 1e6))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \" Completed ACS Commitment Lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Lag behind the timestamp the commitment was received and the timestamp of the commitment\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"log\": 2,\n \"type\": \"log\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 49\n },\n \"id\": 8,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"min by (namespace, job) (timestamp(daml_participant_sync_commitments_last_incoming_received{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"}) - ((daml_participant_sync_commitments_last_incoming_received{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"} > 0) / 1e6))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \" Incoming Received ACS Commitment Lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Lag behind the timestamp the commitment was processed and the timestamp of the commitment\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"log\": 2,\n \"type\": \"log\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 49\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(daml_participant_sync_commitments_last_incoming_processed{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"}) - ((daml_participant_sync_commitments_last_incoming_processed{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"} > 0) / 1e6))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \" Incoming Processed ACS Commitment Lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Timestamp of the latest incoming ACS commitment period end that has been received and enqueued, but not yet processed by the participant.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 57\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace, job)(daml_participant_sync_commitments_last_incoming_received{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"} != 0)/1e3\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Last Incoming Received ACS Commitment\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Timestamp of the latest incoming ACS commitment period end that was fully processed by the participant.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 57\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace, job)(daml_participant_sync_commitments_last_incoming_processed{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"} != 0)/1e3\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Last Incoming Processed ACS Commitment\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Timestamp of the latest locally completed ACS commitment interval. Crash recovery will start reingesting from this timestamp on or from the latest checkpointed ACS commitment interval on, whichever is later.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 65\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace, job)(daml_participant_sync_commitments_last_locally_completed{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"} != 0)/1e3\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"C\"\n }\n ],\n \"title\": \"Last Locally Completed ACS Commitment\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Timestamp of the latest checkpointed ACS commitment in microseconds. Crash recovery will start reingesting from this timestamp on or from the latest locally completed ACS commitment interval on, whichever is later.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 65\n },\n \"id\": 7,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace, job)(daml_participant_sync_commitments_last_locally_checkpointed{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"} != 0)/1e3\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"D\"\n }\n ],\n \"title\": \"Last Locally Checkpointed ACS Commitment\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"30s\",\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_participant_sync_commitments_compute_duration_seconds,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_participant_sync_commitments_compute_duration_seconds,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_participant_sync_commitments_compute_duration_seconds,job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_participant_sync_commitments_compute_duration_seconds,job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"ACS Commitment Performance\",\n \"uid\": \"eesa90lstfk00b\",\n \"version\": 2,\n \"weekStart\": \"\"\n}\n", - "lsu-status.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"description\": \"LSU Sequencing Tests between Topology Freeze and Upgrade Time\",\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 10,\n \"panels\": [],\n \"title\": \"Report Overview\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"LSU Sequencing Test Messages Received in the last 5m by sender\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"neutral\": 0\n },\n \"displayName\": \"${__field.labels.sender}\",\n \"mappings\": [],\n \"max\": 15,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 5\n },\n {\n \"color\": \"green\",\n \"value\": 8\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 19,\n \"options\": {\n \"minVizHeight\": 75,\n \"minVizWidth\": 75,\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showThresholdLabels\": false,\n \"showThresholdMarkers\": true,\n \"sizing\": \"auto\",\n \"text\": {}\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by (sender) (increase(daml_received_lsu_sequencing_test_messages_total{namespace=\\\"$namespace\\\"}[5m]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{sender}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"LSU Sequencing Test Mesages Received in the last 5m\",\n \"type\": \"gauge\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 20,\n \"w\": 24,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum by (sender) (rate(daml_received_lsu_sequencing_test_messages_total{namespace=\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{sender}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"LSU Sequencing Test Rate by Sender\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 29\n },\n \"id\": 21,\n \"panels\": [],\n \"title\": \"Handshakes\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 6,\n \"x\": 0,\n \"y\": 30\n },\n \"id\": 22,\n \"options\": {\n \"minVizHeight\": 75,\n \"minVizWidth\": 75,\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showThresholdLabels\": false,\n \"showThresholdMarkers\": true,\n \"sizing\": \"auto\"\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"count(max by (namespace, job, member)(daml_sequencer_public_api_handshakes_total{namespace=\\\"$namespace\\\", job=~\\\"$job\\\"}) > 0)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"Unique Members\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Unique Members Handshaking\",\n \"type\": \"gauge\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 18,\n \"x\": 6,\n \"y\": 30\n },\n \"id\": 23,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"footer\": {\n \"countRows\": false,\n \"fields\": \"\",\n \"reducer\": [\n \"sum\"\n ],\n \"show\": false\n },\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by (member) (increase(daml_sequencer_public_api_handshakes_total{namespace=\\\"$namespace\\\", job=~\\\"$job\\\"}[$__range]))\",\n \"format\": \"table\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"{{member}}\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Handshakes per Member\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"indexByName\": {},\n \"renameByName\": {}\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 24,\n \"panels\": [],\n \"title\": \"LSU State\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [\n {\n \"options\": {\n \"0\": {\n \"index\": 0,\n \"text\": \"0: Unset / initial\"\n },\n \"1\": {\n \"index\": 1,\n \"text\": \"1: LSU announcement received\"\n },\n \"2\": {\n \"index\": 2,\n \"text\": \"2: Relevant sequencer successors known\"\n },\n \"3\": {\n \"index\": 3,\n \"text\": \"3: Handshake with successor done\"\n },\n \"4\": {\n \"index\": 4,\n \"text\": \"4: Topology local copy done\"\n },\n \"5\": {\n \"index\": 5,\n \"text\": \"5: LSU is done (node ready to connect to new synchronizer)\"\n }\n },\n \"type\": \"value\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 40\n },\n \"id\": 25,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"footer\": {\n \"countRows\": false,\n \"fields\": \"\",\n \"reducer\": [\n \"sum\"\n ],\n \"show\": false\n },\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by (job, node) (daml_participant_lsu_status{namespace=\\\"$namespace\\\"})\",\n \"format\": \"table\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"{{job}} {{node}}\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Participant LSU Status\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"indexByName\": {},\n \"renameByName\": {}\n }\n }\n ],\n \"type\": \"table\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"30s\",\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": true,\n \"current\": {\n \"text\": \"sv-1\",\n \"value\": \"sv-1\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_participant_lsu_status,namespace)\",\n \"includeAll\": false,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_participant_lsu_status,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": [\n \"global-domain-8-sequencer\"\n ],\n \"value\": [\n \"global-domain-8-sequencer\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_public_api_handshakes_total{namespace=\\\"$namespace\\\"},job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_public_api_handshakes_total{namespace=\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-3h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Logical Synchronizer Upgrade\",\n \"uid\": \"cnqlq8j\",\n \"version\": 12,\n \"weekStart\": \"\"\n}\n", + "lsu-status.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"description\": \"LSU Sequencing Tests between Topology Freeze and Upgrade Time\",\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 10,\n \"panels\": [],\n \"title\": \"Report Overview\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"LSU Sequencing Test Messages Received in the last 5m by sender\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"neutral\": 0\n },\n \"displayName\": \"${__field.labels.sender}\",\n \"mappings\": [],\n \"max\": 15,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 5\n },\n {\n \"color\": \"green\",\n \"value\": 8\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 19,\n \"options\": {\n \"minVizHeight\": 75,\n \"minVizWidth\": 75,\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showThresholdLabels\": false,\n \"showThresholdMarkers\": true,\n \"sizing\": \"auto\",\n \"text\": {}\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by (sender) (increase(daml_received_lsu_sequencing_test_messages_total{namespace=\\\"$namespace\\\"}[5m]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{sender}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"LSU Sequencing Test Mesages Received in the last 5m\",\n \"type\": \"gauge\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 20,\n \"w\": 24,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum by (sender) (rate(daml_received_lsu_sequencing_test_messages_total{namespace=\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{sender}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"LSU Sequencing Test Rate by Sender\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 29\n },\n \"id\": 21,\n \"panels\": [],\n \"title\": \"Handshakes\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 6,\n \"x\": 0,\n \"y\": 30\n },\n \"id\": 22,\n \"options\": {\n \"minVizHeight\": 75,\n \"minVizWidth\": 75,\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showThresholdLabels\": false,\n \"showThresholdMarkers\": true,\n \"sizing\": \"auto\"\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"count(max by (namespace, job, member)(daml_sequencer_public_api_handshakes_total{namespace=\\\"$namespace\\\", job=~\\\"$job\\\"}) > 0)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"Unique Members\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Unique Members Handshaking\",\n \"type\": \"gauge\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 18,\n \"x\": 6,\n \"y\": 30\n },\n \"id\": 23,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"footer\": {\n \"countRows\": false,\n \"fields\": \"\",\n \"reducer\": [\n \"sum\"\n ],\n \"show\": false\n },\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by (member) (increase(daml_sequencer_public_api_handshakes_total{namespace=\\\"$namespace\\\", job=~\\\"$job\\\"}[$__range]))\",\n \"format\": \"table\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"{{member}}\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Handshakes per Member\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"indexByName\": {},\n \"renameByName\": {}\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 24,\n \"panels\": [],\n \"title\": \"LSU State\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [\n {\n \"options\": {\n \"0\": {\n \"index\": 0,\n \"text\": \"0: Unset / initial\"\n },\n \"1\": {\n \"index\": 1,\n \"text\": \"1: LSU announcement received\"\n },\n \"2\": {\n \"index\": 2,\n \"text\": \"2: Relevant sequencer successors known\"\n },\n \"3\": {\n \"index\": 3,\n \"text\": \"3: Handshake with successor done\"\n },\n \"4\": {\n \"index\": 4,\n \"text\": \"4: Topology local copy done\"\n },\n \"5\": {\n \"index\": 5,\n \"text\": \"5: LSU is done (node ready to connect to new synchronizer)\"\n }\n },\n \"type\": \"value\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"successor_psid\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 749\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"namespace\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 144\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"job\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 147\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 544\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 40\n },\n \"id\": 25,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"footer\": {\n \"countRows\": false,\n \"fields\": \"\",\n \"reducer\": [\n \"sum\"\n ],\n \"show\": false\n },\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_participant_lsu_status{namespace=\\\"$namespace\\\"}\",\n \"format\": \"table\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"{{job}} {{node}}\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Participant LSU Status\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true,\n \"__name__\": true,\n \"component\": true,\n \"container\": true,\n \"endpoint\": true,\n \"instance\": true,\n \"job\": false,\n \"node\": true,\n \"otel_scope_name\": true,\n \"pod\": true,\n \"service\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {}\n }\n }\n ],\n \"type\": \"table\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"30s\",\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": true,\n \"current\": {\n \"text\": \"sv-1\",\n \"value\": \"sv-1\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_participant_lsu_status,namespace)\",\n \"includeAll\": false,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_participant_lsu_status,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_public_api_handshakes_total{namespace=\\\"$namespace\\\"},job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_public_api_handshakes_total{namespace=\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-3h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Logical Synchronizer Upgrade\",\n \"uid\": \"cnqlq8j\",\n \"version\": 12,\n \"weekStart\": \"\"\n}\n", "node-health.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"panels\": [],\n \"repeat\": \"namespace\",\n \"title\": \"$namespace\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Status of the participant (healthy or not)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"axisPlacement\": \"auto\",\n \"fillOpacity\": 70,\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"green\",\n \"value\": 1\n }\n ]\n },\n \"unit\": \"Alive\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 20,\n \"w\": 24,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 1,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"auto\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{job}} {{node}} {{component}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Health Status\",\n \"type\": \"state-timeline\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": [\n \"sv\",\n \"sv-1\",\n \"sv-10\",\n \"sv-11\",\n \"sv-12\",\n \"sv-13\",\n \"sv-14\",\n \"sv-2\",\n \"sv-3\",\n \"sv-4\",\n \"sv-5\",\n \"sv-6\",\n \"sv-7\",\n \"sv-8\",\n \"sv-9\",\n \"sv-da-1\",\n \"validator\",\n \"validator-stable-old\",\n \"validator1\",\n \"splitwell\",\n \"party-allocator\"\n ],\n \"value\": [\n \"sv\",\n \"sv-1\",\n \"sv-10\",\n \"sv-11\",\n \"sv-12\",\n \"sv-13\",\n \"sv-14\",\n \"sv-2\",\n \"sv-3\",\n \"sv-4\",\n \"sv-5\",\n \"sv-6\",\n \"sv-7\",\n \"sv-8\",\n \"sv-9\",\n \"sv-da-1\",\n \"validator\",\n \"validator-stable-old\",\n \"validator1\",\n \"splitwell\",\n \"party-allocator\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Node health\",\n \"uid\": \"cnf4nq4\",\n \"version\": 2,\n \"weekStart\": \"\"\n}\n", "participant-pruning.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 19523,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Age of the oldest event age at hourly granularity\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"log\": 2,\n \"type\": \"log\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"h\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 15,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"min by (namespace, job) (daml_pruning_max_event_age{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\"})\",\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Max Event Age\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"30s\",\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_pruning_max_event_age,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_pruning_max_event_age,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_pruning_max_event_age,job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_pruning_max_event_age,job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Participant Pruning\",\n \"uid\": \"076b0c9a-d21c-4cc6-8aa4-83414353bbcb\",\n \"version\": 9\n}\n", "participant.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 6,\n \"panels\": [],\n \"title\": \"Sequencer Connection\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Delay on event processing of the participant, compared to the sequencers it is connected to.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"ms\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byFrameRefID\",\n \"options\": \"A\"\n },\n \"properties\": [\n {\n \"id\": \"custom.lineWidth\",\n \"value\": 5\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byFrameRefID\",\n \"options\": \"B\"\n },\n \"properties\": [\n {\n \"id\": \"custom.lineStyle\",\n \"value\": {\n \"dash\": [\n 10,\n 10\n ],\n \"fill\": \"dash\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_client_handler_delay{namespace=~\\\"$namespace\\\",component=\\\"participant\\\",job=~\\\"$job\\\", node=~\\\"$participant\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"_ overall - {{namespace}} {{job}} {{node}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_client_handler_delay_per_connection{namespace=~\\\"$namespace\\\",component=\\\"participant\\\",job=~\\\"$job\\\", node=~\\\"$participant\\\"}\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{job}} {{node}} {{sequencer}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Participant Client Delay\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Sequencer submissions currently running. Note this is at the level of a sequencer submission not a Daml transaction.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"submissions\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 12,\n \"x\": 0,\n \"y\": 11\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"expr\": \"sum by(synchronizer, type, namespace, job, node) (daml_sequencer_client_submissions_in_flight{namespace=~\\\"$namespace\\\", component=\\\"participant\\\", job=~\\\"$job\\\", node=~\\\"$participant\\\"})\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"1m\",\n \"legendFormat\": \"{{namespace}} {{job}} {{node}} {{sync}} {{type}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"In Flight submissions\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Submissions that were not sequenced\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"epm\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 12,\n \"x\": 12,\n \"y\": 11\n },\n \"id\": 7,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"increase(daml_sequencer_client_submissions_dropped{namespace=~\\\"$namespace\\\",component=\\\"participant\\\",job=~\\\"$job\\\", node=~\\\"$participant\\\"}[1m])\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"1m\",\n \"legendFormat\": \"{{namespace}} {{job}} {{node}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Dropped Submissiones\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 12,\n \"x\": 0,\n \"y\": 21\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_client_handler_max_in_flight_event_batches{namespace=~\\\"$namespace\\\",component=\\\"participant\\\",job=~\\\"$job\\\", node=~\\\"$participant\\\"}\",\n \"instant\": false,\n \"legendFormat\": \"max {{namespace}} {{job}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_client_handler_actual_in_flight_event_batches{namespace=~\\\"$namespace\\\",component=\\\"participant\\\",job=~\\\"$job\\\", node=~\\\"$participant\\\"}\",\n \"legendFormat\": \"actual {{namespace}} {{job}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Sequencer Client Batches \",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Events received from the sequencer\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"epm\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 12,\n \"x\": 12,\n \"y\": 21\n },\n \"id\": 13,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"rate(daml_sequencer_client_handler_sequencer_events{namespace=~\\\"$namespace\\\",component=\\\"participant\\\",job=~\\\"$job\\\", node=~\\\"$participant\\\"}[$__rate_interval])\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"1m\",\n \"legendFormat\": \"{{namespace}} {{job}} {{node}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Events received\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 8,\n \"panels\": [],\n \"title\": \"Commands\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Daml commands through the synchronous CommandService currently in flight\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"submissions\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 12,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_participant_api_commands_max_in_flight_length{namespace=~\\\"$namespace\\\",component=\\\"participant\\\",job=~\\\"$job\\\", node=~\\\"$participant\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"1m\",\n \"legendFormat\": \"{{namespace}} {{job}} {{node}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"In Flight Commands\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 42\n },\n \"id\": 14,\n \"panels\": [],\n \"title\": \"Validation Requests\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of requests that are currently being validated. This also covers requests submitted by other participants.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 43\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"daml_participant_inflight_validation_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$participant\\\"}\",\n \"legendFormat\": \"actual {{namespace}} {{job}} {{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_participant_max_inflight_validation_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$participant\\\"}\",\n \"instant\": false,\n \"legendFormat\": \"max {{namespace}} {{job}} {{node}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Number of requests being validated\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 51\n },\n \"id\": 5,\n \"panels\": [],\n \"title\": \"Status\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Status of the participant (healthy or not)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"axisPlacement\": \"auto\",\n \"fillOpacity\": 70,\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 0\n },\n {\n \"color\": \"green\",\n \"value\": 1\n }\n ]\n },\n \"unit\": \"Alive\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 24,\n \"x\": 0,\n \"y\": 52\n },\n \"id\": 4,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"auto\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_health_status{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\", node=~\\\"$participant\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{job}} {{node}} {{component}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Health Status\",\n \"type\": \"state-timeline\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"5m\",\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_participant_api_commands_max_in_flight_length,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_participant_api_commands_max_in_flight_length,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_participant_api_commands_max_in_flight_length{namespace=~\\\"$namespace\\\"},job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_participant_api_commands_max_in_flight_length{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_participant_api_commands_max_in_flight_length{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"participant\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_participant_api_commands_max_in_flight_length{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"sort\": 7,\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Participant\",\n \"uid\": \"edkzo5ukgeqyoc\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", @@ -191,7 +191,7 @@ "sequencer_messages.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 9,\n \"panels\": [],\n \"title\": \"Messages\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The average per second messages for the last 24h\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n }\n },\n \"mappings\": [],\n \"unit\": \"mps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 7,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 8,\n \"options\": {\n \"displayLabels\": [\n \"name\",\n \"value\",\n \"percent\"\n ],\n \"legend\": {\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"values\": [\n \"percent\",\n \"value\"\n ]\n },\n \"pieType\": \"donut\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": true\n },\n \"sort\": \"desc\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"exemplar\": false,\n \"expr\": \"sum by(type) (rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[24h]))\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"B\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Last 24h messages / s\",\n \"type\": \"piechart\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byFrameRefID\",\n \"options\": \"total\"\n },\n \"properties\": [\n {\n \"id\": \"displayName\",\n \"value\": \"total\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 17,\n \"x\": 7,\n \"y\": 1\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"expr\": \"sum(rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"expr\": \"sum by(type) (rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"type\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"expr\": \"sum(rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[1h]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"hourly total\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[1h] offset 30d))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"hourly total 30 days ago\",\n \"range\": true,\n \"refId\": \"B\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Message Rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 20,\n \"x\": 0,\n \"y\": 12\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"expr\": \"sum by(member) (rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=\\\"send-confirmation-request\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Confirmation Requests Rate by Member\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 4,\n \"x\": 20,\n \"y\": 12\n },\n \"id\": 6,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sort_desc(sum(delta(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\",job=\\\"$job\\\",type=\\\"send-confirmation-request\\\"}[1h])) by (member))\",\n \"format\": \"table\",\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Confirmation Requests by Member in the last Hour\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Time\": \"\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 20,\n \"x\": 0,\n \"y\": 23\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"expr\": \"sum by(member) (rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=\\\"send-confirmation-response\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Confirmation Responses Rate by Member\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 4,\n \"x\": 20,\n \"y\": 23\n },\n \"id\": 15,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sort_desc(sum(delta(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\",job=\\\"$job\\\",type=\\\"send-confirmation-response\\\"}[1h])) by (member))\",\n \"format\": \"table\",\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Confirmation Responses by Member in the last Hour\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Time\": \"\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 24,\n \"x\": 0,\n \"y\": 34\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\",member=~\\\"PAR::.*\\\"}[$__rate_interval])) by (member)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Message Rate by Participant\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 12,\n \"x\": 0,\n \"y\": 46\n },\n \"id\": 7,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\",member=~\\\"MED::.*\\\"}[$__rate_interval])) by (member)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Message Rate by Mediator\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 12,\n \"x\": 12,\n \"y\": 46\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by(member) (rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", member!~\\\"(MED|PAR)::.*\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Message Rate by Member excluding Mediator/Participant\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 57\n },\n \"id\": 10,\n \"panels\": [],\n \"title\": \"Message Bytes\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byFrameRefID\",\n \"options\": \"total\"\n },\n \"properties\": [\n {\n \"id\": \"displayName\",\n \"value\": \"total\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 24,\n \"x\": 0,\n \"y\": 58\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by(type) (rate(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"type\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[1h]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"hourly total\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[1h] offset 30d))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"hourly total 30 days ago\",\n \"range\": true,\n \"refId\": \"B\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Message Byte Rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Average size per sequencer message in the last hour\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byFrameRefID\",\n \"options\": \"total\"\n },\n \"properties\": [\n {\n \"id\": \"displayName\",\n \"value\": \"total\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 24,\n \"x\": 0,\n \"y\": 69\n },\n \"id\": 13,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(increase(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[1h])) / sum(increase(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[1h]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by(type) (increase(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[1h])) / sum by(type) (increase(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\"}[1h]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"type\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Average Sequencer Message Size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 20,\n \"x\": 0,\n \"y\": 80\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"topk(\\n 100,\\n avg_over_time(\\n (\\n sum by(member) (\\n rate(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=\\\"send-confirmation-request\\\"}[$__rate_interval])\\n )\\n )[$__range:]\\n )\\n)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Top 100 Confirmation Requests Byte Rate by Member\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"\",\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"sv-1\",\n \"value\": \"sv-1\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_block_events_total,namespace)\",\n \"includeAll\": false,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_block_events_total,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"global-domain-4-sequencer\",\n \"value\": \"global-domain-4-sequencer\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\"},job)\",\n \"includeAll\": false,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-30m\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Sequencer Messages\",\n \"uid\": \"fdjrxql2alblsd\",\n \"version\": 2,\n \"weekStart\": \"\"\n}\n", "sequencer_subscriptions.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 1115,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"panels\": [],\n \"title\": \"Subscriptions\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 4,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 1,\n \"options\": {\n \"minVizHeight\": 75,\n \"minVizWidth\": 75,\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\"lastNotNull\"],\n \"fields\": \"\",\n \"values\": false\n },\n \"showThresholdLabels\": false,\n \"showThresholdMarkers\": true,\n \"sizing\": \"auto\"\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_public_api_subscriptions\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} M{{migration_id}}\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Subscriptions\",\n \"type\": \"gauge\"\n },\n {\n \"datasource\": {\n \"default\": true,\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 20,\n \"x\": 4,\n \"y\": 1\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [\"lastNotNull\", \"max\"],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_public_api_subscriptions{namespace=~\\\"$namespace\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} M{{migration_id}}\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Number of active subscriptions\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 10\n },\n \"id\": 5,\n \"panels\": [],\n \"title\": \"Subscription Lag\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 11\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"rate(daml_cache_misses{namespace=~\\\"$namespace\\\", cache=\\\"events-fan-out-buffer\\\"}[$__rate_interval]) > 0\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"outside buffer {{namespace}} {{subscriber}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Subscriber cache misses\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"dateTimeAsIso\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 21\n },\n \"id\": 8,\n \"options\": {\n \"legend\": {\n \"calcs\": [\"lastNotNull\", \"min\"],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"single\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_public_api_subscription_last_timestamp{namespace=~\\\"$namespace\\\"} / 1000\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{subscriber}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Subscribers\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"µs\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 7,\n \"options\": {\n \"legend\": {\n \"calcs\": [\"lastNotNull\", \"max\"],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_last_timestamp{namespace=~\\\"$namespace\\\"} - daml_sequencer_head_timestamp{namespace=~\\\"$namespace\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} diff\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Head vs Last Timestamp Diff\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": [\"sv-1\"],\n \"value\": [\"sv-1\"]\n },\n \"definition\": \"label_values(daml_sequencer_public_api_subscriptions,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_public_api_subscriptions,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Sequencer Subscriptions\",\n \"uid\": \"685922ac-4aca-4b28-a1a1-4375a93a514d\",\n \"version\": 3\n}\n", "sequencer_topology_transactions.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 154,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 7,\n \"panels\": [],\n \"title\": \"Topology\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byFrameRefID\",\n \"options\": \"total\"\n },\n \"properties\": [\n {\n \"id\": \"displayName\",\n \"value\": \"total\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 16,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by(type) (rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=~\\\"send-topology|send-time-proof\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"type\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=~\\\"send-topology|send-time-proof\\\"}[1h]))\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"hourly total\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Topology send rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 8,\n \"x\": 16,\n \"y\": 1\n },\n \"id\": 3,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"footer\": {\n \"countRows\": false,\n \"fields\": \"\",\n \"reducer\": [\n \"sum\"\n ],\n \"show\": false\n },\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sort_desc(sum by(member) (delta(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=\\\"send-topology\\\", member=~\\\"PAR::.*\\\"}[24h])))\",\n \"format\": \"table\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Topology transactions participants last 24h\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Time\": \"\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 16,\n \"x\": 0,\n \"y\": 13\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": false\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"expr\": \"sum by(member) (rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", member=~\\\"PAR::.*\\\", type=\\\"send-topology\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Topology Rate by Participant\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 8,\n \"x\": 16,\n \"y\": 13\n },\n \"id\": 4,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"footer\": {\n \"countRows\": false,\n \"fields\": \"\",\n \"reducer\": [\n \"sum\"\n ],\n \"show\": false\n },\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sort_desc(sum by(member) (delta(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=\\\"send-topology\\\", member!~\\\"PAR::.*\\\"}[24h])))\",\n \"format\": \"table\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Topology transactions non participants last 24h\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Time\": \"\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 26\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"expr\": \"sum by(member) (rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", member!~\\\"PAR::.*\\\", type=\\\"send-topology\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Topology Rate by Non Participant\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 36\n },\n \"id\": 8,\n \"panels\": [],\n \"title\": \"Time Proofs\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 16,\n \"x\": 0,\n \"y\": 37\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Max\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by(member) (rate(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=\\\"send-time-proof\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Time Proofs\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 8,\n \"x\": 16,\n \"y\": 37\n },\n \"id\": 6,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"footer\": {\n \"countRows\": false,\n \"fields\": \"\",\n \"reducer\": [\n \"sum\"\n ],\n \"show\": false\n },\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sort_desc(sum by(member) (delta(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=\\\"send-time-proof\\\"}[24h])))\",\n \"format\": \"table\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Time Proofs last 24h\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Time\": \"\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 50\n },\n \"id\": 10,\n \"panels\": [],\n \"title\": \"Bytes\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byFrameRefID\",\n \"options\": \"total\"\n },\n \"properties\": [\n {\n \"id\": \"displayName\",\n \"value\": \"total\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 24,\n \"x\": 0,\n \"y\": 51\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by(type) (rate(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=~\\\"send-topology|send-time-proof\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"type\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=~\\\"send-topology|send-time-proof\\\"}[1h]))\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"hourly total\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Topology send rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 63\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by(member) (rate(daml_sequencer_block_event_bytes_total{namespace=\\\"$namespace\\\", job=\\\"$job\\\", type=~\\\"send-topology|send-time-proof\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"total\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Topology Rate by Non Participant\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"\",\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"sv-1\",\n \"value\": \"sv-1\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_block_events_total,namespace)\",\n \"includeAll\": false,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_block_events_total,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"global-domain-3-sequencer\",\n \"value\": \"global-domain-3-sequencer\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\"},job)\",\n \"includeAll\": false,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_block_events_total{namespace=\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-30m\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Sequencer Topology Transactions\",\n \"uid\": \"2f351a91-c0b3-4c6b-b5e2-25d8b9cc1304\",\n \"version\": 6\n}\n", - "unresponsive_parties.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Time\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 579\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"showHeader\": true,\n \"sortBy\": [\n {\n \"desc\": true,\n \"displayName\": \"{component=\\\"mediator\\\", container=\\\"mediator\\\", endpoint=\\\"cm-metrics\\\", instance=\\\"10.40.11.98:10013\\\", job=\\\"global-domain-7-mediator\\\", migration_id=\\\"7\\\", namespace=\\\"sv-6\\\", node=\\\"mediator\\\", otel_scope_name=\\\"canton\\\", participant=\\\"DA-Helm-Test-Node::12208273e690b127deff0174c189b2251d7b5cf9216cd53ed700f726c8e8792807ca\\\", party=\\\"DSO::12209471e1a52edc2995ad347371597a5872f2704cb2cb4bb330a849e7309598259e\\\", pod=\\\"global-domain-7-mediator-69d6489748-rw56k\\\", service=\\\"global-domain-7-mediator\\\"}\"\n }\n ]\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sort_desc(max by (party) (increase(daml_mediator_timeout_non_responsive_participants_total{namespace=~\\\"$namespace\\\",party=~\\\"$party\\\",participant=~\\\"$participant\\\"}[1h])))\",\n \"format\": \"table\",\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Number of missed confirmations by party in the last hour\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Value\": \"Value\",\n \"party\": \"Party\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Time\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 579\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 3,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"showHeader\": true,\n \"sortBy\": [\n {\n \"desc\": true,\n \"displayName\": \"{component=\\\"mediator\\\", container=\\\"mediator\\\", endpoint=\\\"cm-metrics\\\", instance=\\\"10.40.11.98:10013\\\", job=\\\"global-domain-7-mediator\\\", migration_id=\\\"7\\\", namespace=\\\"sv-6\\\", node=\\\"mediator\\\", otel_scope_name=\\\"canton\\\", participant=\\\"DA-Helm-Test-Node::12208273e690b127deff0174c189b2251d7b5cf9216cd53ed700f726c8e8792807ca\\\", party=\\\"DSO::12209471e1a52edc2995ad347371597a5872f2704cb2cb4bb330a849e7309598259e\\\", pod=\\\"global-domain-7-mediator-69d6489748-rw56k\\\", service=\\\"global-domain-7-mediator\\\"}\"\n }\n ]\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sort_desc(max by (participant) (increase(daml_mediator_timeout_non_responsive_participants_total{namespace=~\\\"$namespace\\\",party=~\\\"$party\\\",participant=~\\\"$participant\\\"}[1h])))\",\n \"format\": \"table\",\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Number of missed confirmations by participant in the last hour\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Time\": \"\",\n \"Value\": \"Value\",\n \"participant\": \"Participant\",\n \"party\": \"Party\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 16,\n \"w\": 24,\n \"x\": 0,\n \"y\": 10\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"max by (party, participant) (rate(daml_mediator_timeout_non_responsive_participants_total{namespace=~\\\"$namespace\\\",party=~\\\"$party\\\",participant=~\\\"$participant\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{party}}@{{participant}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate of missed confirmations\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,namespace)\",\n \"includeAll\": false,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,party)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"party\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,party)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,participant)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"participant\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,participant)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-3h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Unresponsive Parties\",\n \"uid\": \"cnck4jp\",\n \"version\": 7,\n \"weekStart\": \"\"\n}\n" + "unresponsive_parties.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Time\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 579\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"showHeader\": true,\n \"sortBy\": [\n {\n \"desc\": true,\n \"displayName\": \"{component=\\\"mediator\\\", container=\\\"mediator\\\", endpoint=\\\"cm-metrics\\\", instance=\\\"10.40.11.98:10013\\\", job=\\\"global-domain-7-mediator\\\", migration_id=\\\"7\\\", namespace=\\\"sv-6\\\", node=\\\"mediator\\\", otel_scope_name=\\\"canton\\\", participant=\\\"DA-Helm-Test-Node::12208273e690b127deff0174c189b2251d7b5cf9216cd53ed700f726c8e8792807ca\\\", party=\\\"DSO::12209471e1a52edc2995ad347371597a5872f2704cb2cb4bb330a849e7309598259e\\\", pod=\\\"global-domain-7-mediator-69d6489748-rw56k\\\", service=\\\"global-domain-7-mediator\\\"}\"\n }\n ]\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sort_desc(max by (party) (increase(daml_mediator_timeout_non_responsive_participants_total{namespace=~\\\"$namespace\\\",party=~\\\"$party\\\",participant=~\\\"$participant\\\"}[1h])))\",\n \"format\": \"table\",\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Number of missed confirmations by party in the last hour\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Value\": \"Value\",\n \"party\": \"Party\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Time\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 579\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 3,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"showHeader\": true,\n \"sortBy\": [\n {\n \"desc\": true,\n \"displayName\": \"{component=\\\"mediator\\\", container=\\\"mediator\\\", endpoint=\\\"cm-metrics\\\", instance=\\\"10.40.11.98:10013\\\", job=\\\"global-domain-7-mediator\\\", migration_id=\\\"7\\\", namespace=\\\"sv-6\\\", node=\\\"mediator\\\", otel_scope_name=\\\"canton\\\", participant=\\\"DA-Helm-Test-Node::12208273e690b127deff0174c189b2251d7b5cf9216cd53ed700f726c8e8792807ca\\\", party=\\\"DSO::12209471e1a52edc2995ad347371597a5872f2704cb2cb4bb330a849e7309598259e\\\", pod=\\\"global-domain-7-mediator-69d6489748-rw56k\\\", service=\\\"global-domain-7-mediator\\\"}\"\n }\n ]\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sort_desc(max by (participant) (increase(daml_mediator_timeout_non_responsive_participants_total{namespace=~\\\"$namespace\\\",party=~\\\"$party\\\",participant=~\\\"$participant\\\"}[1h])))\",\n \"format\": \"table\",\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Number of missed confirmations by participant in the last hour\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Time\": \"\",\n \"Value\": \"Value\",\n \"participant\": \"Participant\",\n \"party\": \"Party\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 16,\n \"w\": 24,\n \"x\": 0,\n \"y\": 10\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"max by (party, participant) (rate(daml_mediator_timeout_non_responsive_participants_total{namespace=~\\\"$namespace\\\",party=~\\\"$party\\\",participant=~\\\"$participant\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{party}}@{{participant}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate of missed confirmations\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,namespace)\",\n \"includeAll\": false,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,party)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"party\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,party)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,participant)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"participant\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_mediator_timeout_non_responsive_participants_total,participant)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-3h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Unresponsive Parties\",\n \"uid\": \"cnck4jp\",\n \"version\": 7,\n \"weekStart\": \"\"\n}\n" }, "kind": "ConfigMap", "metadata": { diff --git a/cluster/pulumi/observability/grafana-dashboards/canton/lsu-status.json b/cluster/pulumi/observability/grafana-dashboards/canton/lsu-status.json index 4c2ac3e811..76d09f4923 100644 --- a/cluster/pulumi/observability/grafana-dashboards/canton/lsu-status.json +++ b/cluster/pulumi/observability/grafana-dashboards/canton/lsu-status.json @@ -458,7 +458,56 @@ ] } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "successor_psid" + }, + "properties": [ + { + "id": "custom.width", + "value": 749 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "namespace" + }, + "properties": [ + { + "id": "custom.width", + "value": 144 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "job" + }, + "properties": [ + { + "id": "custom.width", + "value": 147 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "custom.width", + "value": 544 + } + ] + } + ] }, "gridPos": { "h": 10, @@ -488,7 +537,7 @@ }, "disableTextWrap": false, "editorMode": "code", - "expr": "max by (job, node) (daml_participant_lsu_status{namespace=\"$namespace\"})", + "expr": "daml_participant_lsu_status{namespace=\"$namespace\"}", "format": "table", "fullMetaSearch": false, "includeNullMetadata": true, @@ -505,8 +554,19 @@ "id": "organize", "options": { "excludeByName": { - "Time": true + "Time": true, + "__name__": true, + "component": true, + "container": true, + "endpoint": true, + "instance": true, + "job": false, + "node": true, + "otel_scope_name": true, + "pod": true, + "service": true }, + "includeByName": {}, "indexByName": {}, "renameByName": {} } @@ -548,10 +608,10 @@ { "current": { "text": [ - "global-domain-8-sequencer" + "All" ], "value": [ - "global-domain-8-sequencer" + "$__all" ] }, "datasource": { diff --git a/cluster/pulumi/observability/grafana-dashboards/canton/unresponsive_parties.json b/cluster/pulumi/observability/grafana-dashboards/canton/unresponsive_parties.json index 4cc0a1b83c..2d56e45941 100644 --- a/cluster/pulumi/observability/grafana-dashboards/canton/unresponsive_parties.json +++ b/cluster/pulumi/observability/grafana-dashboards/canton/unresponsive_parties.json @@ -297,8 +297,8 @@ }, "tooltip": { "hideZeros": false, - "mode": "single", - "sort": "none" + "mode": "multi", + "sort": "desc" } }, "pluginVersion": "12.4.0", From 39f9a9ba7bbf9097f53a1689e742b14ebe172653 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Mon, 10 Aug 2026 14:05:16 +0200 Subject: [PATCH 215/329] Parametrize chaos mesh schedule (#6737) Signed-off-by: Julien Tinguely --- cluster/deployment/mock/config.yaml | 2 ++ cluster/pulumi/canton-network/src/chaosMesh.ts | 12 +++++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cluster/deployment/mock/config.yaml b/cluster/deployment/mock/config.yaml index 048f18524d..3381999101 100644 --- a/cluster/deployment/mock/config.yaml +++ b/cluster/deployment/mock/config.yaml @@ -202,6 +202,8 @@ loadTester: scaleUpStep: 3 windowStartUTC: "02:30" windowDurationMinutes: 150 +chaosMesh: + podKillSchedule: '@every 120m' svs: sv: !include(sv.yaml) participant: diff --git a/cluster/pulumi/canton-network/src/chaosMesh.ts b/cluster/pulumi/canton-network/src/chaosMesh.ts index f901863d3b..80256ea123 100644 --- a/cluster/pulumi/canton-network/src/chaosMesh.ts +++ b/cluster/pulumi/canton-network/src/chaosMesh.ts @@ -16,6 +16,7 @@ const chaosMeshSchema = z.object({ chaosMesh: z .object({ dabftLatency: z.string().optional(), + podKillSchedule: z.string().optional(), }) .optional(), }); @@ -30,6 +31,7 @@ export const podKillSchedule = ( chaosMeshNs: k8s.core.v1.Namespace, appName: string, appNs: string, + schedule: string | undefined, dependsOn: Resource[] ): k8s.apiextensions.CustomResource => new k8s.apiextensions.CustomResource( @@ -42,8 +44,7 @@ export const podKillSchedule = ( namespace: chaosMeshNs.metadata.name, }, spec: { - // TODO(DACH-NY/canton-network-node#10689) Reduce this back to 5min once Canton sequencers stop being so slow - schedule: '@every 60m', + schedule: schedule ?? '@every 60m', historyLimit: 2, concurrencyPolicy: 'Forbid', type: 'PodChaos', @@ -255,7 +256,12 @@ export const installChaosMesh = ({ dependsOn }: ChaosMeshArguments): k8s.helm.v3 'scan-app', 'sv-app', 'validator-app', - ].forEach(name => podKillSchedule(ns, name, 'sv-4', [roleBinding, ...dependsOn])); + ].forEach(name => + podKillSchedule(ns, name, 'sv-4', config.chaosMesh?.podKillSchedule, [ + roleBinding, + ...dependsOn, + ]) + ); if (config.chaosMesh?.dabftLatency) { dabftLatency(ns, config.chaosMesh.dabftLatency, [roleBinding, ...dependsOn]); } From d0150bf3eca285a335802ce44bf9b23bc1b8cb66 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Mon, 10 Aug 2026 16:23:08 +0200 Subject: [PATCH 216/329] Update bft dashboard to latest version (#6739) [static] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- cluster/expected/observability/expected.json | 2 +- .../canton-bft/bft-ordering-performance.json | 98 +++++++++++++++++++ 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 69eca6f23d..a582dc7f96 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -116,7 +116,7 @@ "inputs": { "apiVersion": "v1", "data": { - "bft-ordering-performance.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 8,\n \"links\": [],\n \"liveNow\": false,\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 16,\n \"panels\": [],\n \"title\": \"Global\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Wall-clock duration between the instant a request is received by the CantonBFT orderer and the instant the fully assembled ordered block metadata is stored, just before the request is pushed to the post-ordering stages. Its meaningfulness requires wall-clock synchronization, as not all ordered requests are received and timestamped on this node.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 12,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_global_requests_ordering_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests end-to-end ordering latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 12,\n \"x\": 12,\n \"y\": 1\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node, ordering_stage) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-availability-total\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total batch dissemination and consensus time (permanence in availability module)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of blocks ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 17,\n \"w\": 8,\n \"x\": 0,\n \"y\": 13\n },\n \"id\": 107,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node, is_block_empty) (irate(daml_sequencer_bftordering_global_ordered_blocks_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}.{{node}} (empty: {{is_block_empty}})\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - blocks/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of batches (proofs of availability) ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 17,\n \"w\": 8,\n \"x\": 8,\n \"y\": 13\n },\n \"id\": 108,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_global_ordered_batches_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"interval\": \"\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - batches/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 17,\n \"w\": 8,\n \"x\": 16,\n \"y\": 13\n },\n \"id\": 109,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_global_ordered_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - requests/s\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 30\n },\n \"id\": 12,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-PekkoP2PGrpcConnectionManagingActor\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"PekkoP2PGrpcConnectionManagingActor queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 31\n },\n \"id\": 103,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (daml_sequencer_bftordering_performance_moduleQueueSize_PekkoP2PGrpcConnectionManagingActor{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"})\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"PekkoP2PGrpcConnectionManagingActor queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Latency between sending a message and receiving it on the other side\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 13,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node, source_sequencer) (rate(daml_sequencer_bftordering_p2p_send_grpc_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{source_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P gRPC network latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The rate of gRPC message send retries\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_p2p_send_sends_retried{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"gRPC send retry rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 17,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_p2p_send_network_write_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P network client write latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node) (rate(daml_sequencer_bftordering_p2p_receive_processing_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P receive processing latency\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Networking\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 7,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 18,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-mempool\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Mempool module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 32\n },\n \"id\": 101,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_mempool{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Mempool module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-availability\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Availability module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 38\n },\n \"id\": 102,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_availability{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Availability module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time waited for a batch while there are outstanding consensus proposal requests and no dissemination is in progress\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 44\n },\n \"id\": 24,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Time spent waiting for batches\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Cumulative latency of a batch dissemination until a PoA is formed and it can be present multiple times for a given batch if it regresses due to topology changes\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 44\n },\n \"id\": 21,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-dissemination-total\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total dissemination time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Insertion in proposal queue until next event, like re-sign, re-dissemination, [re-]proposal or batch ordered (emitted 1 or more times per batch)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 50\n },\n \"id\": 23,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-queued-for-block-inclusion\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Time waited for batch inclusion in consensus proposal\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of regressions of a single batch due to topology changes\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 50\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_availability_batch_regressions{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}, {{stage}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch dissemination regressions rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 56\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-hash-batchId\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch ID hashing latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 56\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-sign-local-batchId\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Local Batch ID signing latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Insertion in batch queue until extraction and batch send to availability\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 62\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"request-queued-for-batch-inclusion\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Request batch inclusion latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 62\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-sign-remote-batchId\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Remote batch ID signing latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 68\n },\n \"id\": 26,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-sign-local-batches\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sign local batches\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 68\n },\n \"id\": 8,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-signature-verify-ack\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch ID signature verification\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 74\n },\n \"id\": 82,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.availability.data.db.DbAvailabilityStore.addBatch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert batch\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 74\n },\n \"id\": 28,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.availability.data.db.DbAvailabilityStore.fetchBatches\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Fetch local batches from DB\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 80\n },\n \"id\": 22,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-validation\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch validation\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Summary of cache hits, misses, and evictions for the Availability store's batch data cache\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"hideTimeOverride\": false,\n \"id\": 110,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_cache_hits{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\", cache=\\\"batch-cache\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} Cache Hits\",\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_cache_misses{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\", cache=\\\"batch-cache\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} Cache Misses\",\n \"refId\": \"B\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_cache_evictions{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\", cache=\\\"batch-cache\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} Cache Evictions\",\n \"refId\": \"C\"\n }\n ],\n \"title\": \"Availability Store Cache Statistics\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Mempool+Availability\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 45,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 33\n },\n \"id\": 80,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-consensus\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 33\n },\n \"id\": 104,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_consensus{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 40\n },\n \"id\": 81,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-segment-module\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Segment module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 40\n },\n \"id\": 105,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_segment_module{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Segment module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Block commit time at the consensus (PBFT) level.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [\n {\n \"options\": {\n \"match\": \"null\",\n \"result\": {\n \"text\": \"N/A\"\n }\n },\n \"type\": \"special\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 2\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 47\n },\n \"hideTimeOverride\": true,\n \"id\": 39,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_consensus_commit_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus Block Latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time elapsed between sending a PrePrepare and seeing that the block has been ordered\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 46,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-segment-proposal-to-commit-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus segment block proposal to commit latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time elapsed between ordered blocks proposed by a segment\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 54\n },\n \"id\": 88,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-segment-block-commit-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus segment block ordering latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time between a proposal request to the availability module needed to continue a segment and a PrePrepare being built\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 54\n },\n \"id\": 87,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-block-proposal-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Wait for proposal\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time between the epoch completion and the start of the next epoch\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 60\n },\n \"id\": 86,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-epoch-start-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Wait for epoch start\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time between the last led segment's completion and the epoch completion\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 60\n },\n \"id\": 83,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-epoch-completion-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Wait for epoch completion\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 67\n },\n \"id\": 63,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.startEpoch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Start epoch (DB)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 67\n },\n \"id\": 47,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.completeEpoch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Complete epoch (DB)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 73\n },\n \"id\": 64,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addPrePrepare\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert pre-prepare\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 73\n },\n \"id\": 50,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addPreparesAtomically\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert prepares\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 80\n },\n \"id\": 52,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addOrderedBlockAtomically\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert ordered block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"id\": 66,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-signature-verify-poa-ack\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Check PoA ack signature\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 86\n },\n \"id\": 68,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-validate-signed-message\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Validate message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 86\n },\n \"id\": 76,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"sign-BftSignedConsensusMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sign message\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - main protocol\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 33\n },\n \"id\": 89,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 24,\n \"x\": 0,\n \"y\": 34\n },\n \"id\": 51,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_epoch_view_changes{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"View changes occurred (rate)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 41\n },\n \"id\": 84,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addViewChangeMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert view change message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 41\n },\n \"id\": 77,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-postponed-view-messages-queue-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 48\n },\n \"id\": 97,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_postponed_view_messages_queue_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 48\n },\n \"id\": 95,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_postponed_view_messages_queue_max_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages queue max size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 55\n },\n \"id\": 94,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_postponed_view_messages_dropped_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages dropped (rate)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 55\n },\n \"id\": 100,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_postponed_view_messages_duplicates_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages duplicated (rate)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 62\n },\n \"id\": 112,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, Leader) (rate(daml_sequencer_bftordering_consensus_view_change_progress_latency_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"interval\": \"\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{Leader}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Time until progress in view\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - view change\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 34\n },\n \"id\": 90,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 35\n },\n \"id\": 72,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-validate-consensus-certificate\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Validate state transfer consensus certificate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to transfer a given epoch from peer(s)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"id\": 111,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\", ordering_stage=~\\\"state-transfer-total-epoch-transfer-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"Data transfer\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\", ordering_stage=~\\\"state-transfer-store-epochs\\\"}[$__rate_interval])))\",\n \"hide\": false,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Epoch Transfer Duration\",\n \"transformations\": [\n {\n \"id\": \"calculateField\",\n \"options\": {\n \"alias\": \"Total epoch transfer duration\",\n \"binary\": {\n \"left\": \"Data transfer\",\n \"reducer\": \"sum\",\n \"right\": \"Complete + start DB query\"\n },\n \"mode\": \"binary\",\n \"reduce\": {\n \"reducer\": \"sum\"\n }\n }\n }\n ],\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 42\n },\n \"id\": 91,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"state-transfer-postponed-consensus-messages-queue-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 42\n },\n \"id\": 96,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_state_transfer_postponed_consensus_messages_queue_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 49\n },\n \"id\": 98,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_state_transfer_postponed_consensus_messages_queue_max_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages queue max size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 49\n },\n \"id\": 99,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_state_transfer_postponed_consensus_messages_dropped{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages dropped (rate)\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - state transfer\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 35\n },\n \"id\": 38,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 36\n },\n \"id\": 85,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_incoming_retransmission_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Incoming retransmission requests rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 36\n },\n \"id\": 40,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_outgoing_retransmission_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Outgoing retransmission requests rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 42\n },\n \"id\": 41,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_retransmitted_messages_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmitted messages rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 42\n },\n \"id\": 42,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_retransmitted_commit_certificates_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmitted commit certificates rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 48\n },\n \"id\": 78,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"sign-BftSignedRetransmissionMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sign retransmission message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 48\n },\n \"id\": 79,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"verify-signature-BftSignedRetransmissionMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Verify retransmission message signature\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 54\n },\n \"id\": 43,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_discarded_wrong_epoch_retransmission_responses_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmission responses discarded due to wrong epoch - rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 54\n },\n \"id\": 44,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_discarded_rate_limited_retransmission_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmission requests discarded due to rate limiting - rate\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - retransmissions\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 36\n },\n \"id\": 59,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 37\n },\n \"id\": 48,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.latestEpoch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load latest epoch\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 37\n },\n \"id\": 53,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadEpochProgress\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load epoch progress\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 45\n },\n \"id\": 54,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadCompleteBlocks\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load completed blocks\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - CFT\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 37\n },\n \"id\": 29,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 55,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-output\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Output module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 38\n },\n \"id\": 106,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_output{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Output module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time needed to inspect requests within a block\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 45\n },\n \"id\": 36,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"output-block-inspection\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block inspection\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 45\n },\n \"id\": 30,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"output-block-fetch-batches\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total block batches fetch time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 52\n },\n \"id\": 37,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.output.data.db.DbOutputMetadataStore.insertBlockIfMissing\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 52\n },\n \"id\": 34,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": false\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_topology_query_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Query topology\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 59\n },\n \"id\": 31,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.output.data.db.DbOutputMetadataStore.insertLeaderSelectionPolicyState\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Save blacklist leader selection policy state\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 59\n },\n \"id\": 32,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.output.data.db.DbOutputMetadataStore.insertEpochIfMissing\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert epoch metadata\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 66\n },\n \"id\": 113,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"output-backpressure\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sequencer core backpressure duration\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Output\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 60,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 35,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadEpochInfo\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load epoch info\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 56,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadOrderedBlocks\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load ordered blocks\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Output - CFT\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 62,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to compute the pruning point, based on retention factors and current time\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 40\n },\n \"id\": 25,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"pruning-compute-pruning-point\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Compute pruning point\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to evict unordered, but expired, batches in the availability DB\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 40\n },\n \"id\": 27,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.availability.data.db.DbAvailabilityStore.gc\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Availability DB - evict expired batches\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to create new table partitions, when necessary\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 61,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.pruning.PartitionManager.PartitionCreatorImpl.createPartitionsIfNeeded\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Create partitions\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to drop table partitions that can be pruned\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 67,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.pruning.PartitionManager.PartitionPrunerImpl.prune\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Prune (Delete partitions)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to start the Partition Manager, which may include creating new partitions\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 69,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.pruning.PartitionManager.create\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Partition manager startup\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Pruning\",\n \"type\": \"row\"\n }\n ],\n \"refresh\": \"30s\",\n \"schemaVersion\": 38,\n \"style\": \"dark\",\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": false,\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status,namespace}\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"label\": \"Namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"label\": \"Job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"node\",\n \"label\": \"Node\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"Percentile\",\n \"multi\": false,\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": false,\n \"text\": \".5\",\n \"value\": \".5\"\n },\n {\n \"selected\": false,\n \"text\": \".6\",\n \"value\": \".6\"\n },\n {\n \"selected\": false,\n \"text\": \".7\",\n \"value\": \".7\"\n },\n {\n \"selected\": false,\n \"text\": \".75\",\n \"value\": \".75\"\n },\n {\n \"selected\": false,\n \"text\": \".8\",\n \"value\": \".8\"\n },\n {\n \"selected\": false,\n \"text\": \".9\",\n \"value\": \".9\"\n },\n {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n {\n \"selected\": false,\n \"text\": \".99\",\n \"value\": \".99\"\n },\n {\n \"selected\": false,\n \"text\": \".999\",\n \"value\": \".999\"\n }\n ],\n \"query\": \".5,.6,.7,.75,.8,.9,.95,.99,.999\",\n \"queryValue\": \"\",\n \"skipUrlSync\": false,\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-5m\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"BFT ordering (performance)\",\n \"uid\": \"c2237cb1-9018-41ac-8eef-24a28ab28f20\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", + "bft-ordering-performance.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 8,\n \"links\": [],\n \"liveNow\": false,\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 16,\n \"panels\": [],\n \"title\": \"Global\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Wall-clock duration between the instant a request is received by the CantonBFT orderer and the instant the fully assembled ordered block metadata is stored, just before the request is pushed to the post-ordering stages. Its meaningfulness requires wall-clock synchronization, as not all ordered requests are received and timestamped on this node.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 12,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_global_requests_ordering_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests end-to-end ordering latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 12,\n \"x\": 12,\n \"y\": 1\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node, ordering_stage) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-availability-total\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total batch dissemination and consensus time (permanence in availability module)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of blocks ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 17,\n \"w\": 8,\n \"x\": 0,\n \"y\": 13\n },\n \"id\": 107,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node, is_block_empty) (irate(daml_sequencer_bftordering_global_ordered_blocks_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}.{{node}} (empty: {{is_block_empty}})\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - blocks/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of batches (proofs of availability) ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 17,\n \"w\": 8,\n \"x\": 8,\n \"y\": 13\n },\n \"id\": 108,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_global_ordered_batches_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"interval\": \"\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - batches/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 17,\n \"w\": 8,\n \"x\": 16,\n \"y\": 13\n },\n \"id\": 109,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_global_ordered_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - requests/s\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 30\n },\n \"id\": 12,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-PekkoP2PGrpcConnectionManagingActor\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"PekkoP2PGrpcConnectionManagingActor queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 31\n },\n \"id\": 103,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (daml_sequencer_bftordering_performance_moduleQueueSize_PekkoP2PGrpcConnectionManagingActor{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"})\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"PekkoP2PGrpcConnectionManagingActor queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Latency between sending a message and receiving it on the other side\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 13,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node, source_sequencer) (rate(daml_sequencer_bftordering_p2p_send_grpc_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{source_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P gRPC network latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The rate of gRPC message send retries\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_p2p_send_sends_retried{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"gRPC send retry rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 17,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_p2p_send_network_write_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P network client write latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node) (rate(daml_sequencer_bftordering_p2p_receive_processing_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P receive processing latency\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Networking\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 7,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 18,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-mempool\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Mempool module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 32\n },\n \"id\": 101,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_mempool{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Mempool module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-availability\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Availability module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 38\n },\n \"id\": 102,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_availability{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Availability module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time waited for a batch while there are outstanding consensus proposal requests and no dissemination is in progress\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 44\n },\n \"id\": 24,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Time spent waiting for batches\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Cumulative latency of a batch dissemination until a PoA is formed and it can be present multiple times for a given batch if it regresses due to topology changes\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 44\n },\n \"id\": 21,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-dissemination-total\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total dissemination time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Insertion in proposal queue until next event, like re-sign, re-dissemination, [re-]proposal or batch ordered (emitted 1 or more times per batch)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 50\n },\n \"id\": 23,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-queued-for-block-inclusion\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Time waited for batch inclusion in consensus proposal\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of regressions of a single batch due to topology changes\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 50\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_availability_batch_regressions{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}, {{stage}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch dissemination regressions rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 56\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-hash-batchId\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch ID hashing latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 56\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-sign-local-batchId\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Local Batch ID signing latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Insertion in batch queue until extraction and batch send to availability\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 62\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"request-queued-for-batch-inclusion\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Request batch inclusion latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 62\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-sign-remote-batchId\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Remote batch ID signing latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 68\n },\n \"id\": 26,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-sign-local-batches\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sign local batches\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 68\n },\n \"id\": 8,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"availability-signature-verify-ack\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch ID signature verification\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 74\n },\n \"id\": 82,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.availability.data.db.DbAvailabilityStore.addBatch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert batch\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 74\n },\n \"id\": 28,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.availability.data.db.DbAvailabilityStore.fetchBatches\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Fetch local batches from DB\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 80\n },\n \"id\": 22,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"batch-validation\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batch validation\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Summary of cache hits, misses, and evictions for the Availability store's batch data cache\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"hideTimeOverride\": false,\n \"id\": 110,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_cache_hits{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\", cache=\\\"batch-cache\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} Cache Hits\",\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_cache_misses{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\", cache=\\\"batch-cache\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} Cache Misses\",\n \"refId\": \"B\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_cache_evictions{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\", cache=\\\"batch-cache\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} Cache Evictions\",\n \"refId\": \"C\"\n }\n ],\n \"title\": \"Availability Store Cache Statistics\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Mempool+Availability\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 45,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 33\n },\n \"id\": 80,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-consensus\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 33\n },\n \"id\": 104,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_consensus{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 40\n },\n \"id\": 81,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-segment-module\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Segment module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 40\n },\n \"id\": 105,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_segment_module{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Segment module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Block commit time at the consensus (PBFT) level.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [\n {\n \"options\": {\n \"match\": \"null\",\n \"result\": {\n \"text\": \"N/A\"\n }\n },\n \"type\": \"special\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 2\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 47\n },\n \"hideTimeOverride\": true,\n \"id\": 39,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_consensus_commit_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus Block Latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time elapsed between sending a PrePrepare and seeing that the block has been ordered\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 46,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-segment-proposal-to-commit-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus segment block proposal to commit latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time elapsed between ordered blocks proposed by a segment\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 54\n },\n \"id\": 88,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-segment-block-commit-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Consensus segment block ordering latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time between a proposal request to the availability module needed to continue a segment and a PrePrepare being built\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 54\n },\n \"id\": 87,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-block-proposal-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Wait for proposal\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time between the epoch completion and the start of the next epoch\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 60\n },\n \"id\": 86,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-epoch-start-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Wait for epoch start\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time between the last led segment's completion and the epoch completion\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 60\n },\n \"id\": 83,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-epoch-completion-wait\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Wait for epoch completion\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 67\n },\n \"id\": 63,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.startEpoch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Start epoch (DB)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 67\n },\n \"id\": 47,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.completeEpoch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Complete epoch (DB)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 73\n },\n \"id\": 64,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addPrePrepare\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert pre-prepare\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 73\n },\n \"id\": 50,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addPreparesAtomically\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert prepares\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 80\n },\n \"id\": 52,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addOrderedBlockAtomically\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert ordered block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"id\": 66,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-signature-verify-poa-ack\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Check PoA ack signature\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 86\n },\n \"id\": 68,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-validate-signed-message\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Validate message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 86\n },\n \"id\": 76,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"sign-BftSignedConsensusMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sign message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 92\n },\n \"id\": 114,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, Leader) (rate(daml_sequencer_bftordering_consensus_relative_segment_latency_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"interval\": \"\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{Leader}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Relative peer segment completion delay\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - main protocol\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 33\n },\n \"id\": 89,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 24,\n \"x\": 0,\n \"y\": 34\n },\n \"id\": 51,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_epoch_view_changes{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"View changes occurred (rate)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 41\n },\n \"id\": 84,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.addViewChangeMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert view change message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 41\n },\n \"id\": 77,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-postponed-view-messages-queue-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 48\n },\n \"id\": 97,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_postponed_view_messages_queue_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 48\n },\n \"id\": 95,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_postponed_view_messages_queue_max_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages queue max size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 55\n },\n \"id\": 94,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_postponed_view_messages_dropped_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages dropped (rate)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 55\n },\n \"id\": 100,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_postponed_view_messages_duplicates_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed view messages duplicated (rate)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 62\n },\n \"id\": 112,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, Leader) (rate(daml_sequencer_bftordering_consensus_view_change_progress_latency_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"interval\": \"\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{Leader}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Time until progress in view\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - view change\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 34\n },\n \"id\": 90,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 35\n },\n \"id\": 72,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"consensus-validate-consensus-certificate\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Validate state transfer consensus certificate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to transfer a given epoch from peer(s)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"id\": 111,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\", ordering_stage=~\\\"state-transfer-total-epoch-transfer-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"Data transfer\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\", ordering_stage=~\\\"state-transfer-store-epochs\\\"}[$__rate_interval])))\",\n \"hide\": false,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Epoch Transfer Duration\",\n \"transformations\": [\n {\n \"id\": \"calculateField\",\n \"options\": {\n \"alias\": \"Total epoch transfer duration\",\n \"binary\": {\n \"left\": \"Data transfer\",\n \"reducer\": \"sum\",\n \"right\": \"Complete + start DB query\"\n },\n \"mode\": \"binary\",\n \"reduce\": {\n \"reducer\": \"sum\"\n }\n }\n }\n ],\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 42\n },\n \"id\": 91,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"state-transfer-postponed-consensus-messages-queue-latency\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 42\n },\n \"id\": 96,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_state_transfer_postponed_consensus_messages_queue_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 49\n },\n \"id\": 98,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_state_transfer_postponed_consensus_messages_queue_max_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages queue max size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 49\n },\n \"id\": 99,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_state_transfer_postponed_consensus_messages_dropped{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Postponed consensus messages dropped (rate)\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - state transfer\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 35\n },\n \"id\": 38,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 36\n },\n \"id\": 85,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_incoming_retransmission_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Incoming retransmission requests rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 36\n },\n \"id\": 40,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_outgoing_retransmission_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Outgoing retransmission requests rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 42\n },\n \"id\": 41,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_retransmitted_messages_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmitted messages rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 42\n },\n \"id\": 42,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_retransmitted_commit_certificates_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmitted commit certificates rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 48\n },\n \"id\": 78,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"sign-BftSignedRetransmissionMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sign retransmission message\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 48\n },\n \"id\": 79,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"verify-signature-BftSignedRetransmissionMessage\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Verify retransmission message signature\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 0,\n \"y\": 54\n },\n \"id\": 43,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_discarded_wrong_epoch_retransmission_responses_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmission responses discarded due to wrong epoch - rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 12,\n \"x\": 12,\n \"y\": 54\n },\n \"id\": 44,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_consensus_discarded_rate_limited_retransmission_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retransmission requests discarded due to rate limiting - rate\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - retransmissions\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 36\n },\n \"id\": 59,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 37\n },\n \"id\": 48,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.latestEpoch\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load latest epoch\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 37\n },\n \"id\": 53,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadEpochProgress\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load epoch progress\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 45\n },\n \"id\": 54,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadCompleteBlocks\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load completed blocks\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Consensus - CFT\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 37\n },\n \"id\": 29,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 55,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"module-queue-output\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Output module queue latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 38\n },\n \"id\": 106,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_performance_moduleQueueSize_output{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Output module queue size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time needed to inspect requests within a block\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 45\n },\n \"id\": 36,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"output-block-inspection\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block inspection\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 45\n },\n \"id\": 30,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"output-block-fetch-batches\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total block batches fetch time\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 52\n },\n \"id\": 37,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.output.data.db.DbOutputMetadataStore.insertBlockIfMissing\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 52\n },\n \"id\": 34,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": false\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_topology_query_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Query topology\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 59\n },\n \"id\": 31,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.output.data.db.DbOutputMetadataStore.insertLeaderSelectionPolicyState\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Save blacklist leader selection policy state\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\"\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 59\n },\n \"id\": 32,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.output.data.db.DbOutputMetadataStore.insertEpochIfMissing\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Insert epoch metadata\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 66\n },\n \"id\": 113,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"output-backpressure\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sequencer core backpressure duration\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Output\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 60,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 35,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadEpochInfo\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load epoch info\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 56,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.consensus.iss.data.db.DbEpochStore.loadOrderedBlocks\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Load ordered blocks\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Output - CFT\",\n \"type\": \"row\"\n },\n {\n \"collapsed\": true,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 62,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to compute the pruning point, based on retention factors and current time\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 40\n },\n \"id\": 25,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"pruning-compute-pruning-point\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Compute pruning point\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to evict unordered, but expired, batches in the availability DB\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 40\n },\n \"id\": 27,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.availability.data.db.DbAvailabilityStore.gc\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Availability DB - evict expired batches\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to create new table partitions, when necessary\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 61,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.pruning.PartitionManager.PartitionCreatorImpl.createPartitionsIfNeeded\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Create partitions\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to drop table partitions that can be pruned\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 67,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.pruning.PartitionManager.PartitionPrunerImpl.prune\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Prune (Delete partitions)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Time to start the Partition Manager, which may include creating new partitions\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 47\n },\n \"id\": 69,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_performance_ordering_stage_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ordering_stage=~\\\"com.digitalasset.canton.synchronizer.sequencer.block.bftordering.core.modules.pruning.PartitionManager.create\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Partition manager startup\",\n \"type\": \"timeseries\"\n }\n ],\n \"title\": \"Pruning\",\n \"type\": \"row\"\n }\n ],\n \"refresh\": \"30s\",\n \"schemaVersion\": 38,\n \"style\": \"dark\",\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": false,\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status,namespace}\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"label\": \"Namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"label\": \"Job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"node\",\n \"label\": \"Node\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"Percentile\",\n \"multi\": false,\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": false,\n \"text\": \".5\",\n \"value\": \".5\"\n },\n {\n \"selected\": false,\n \"text\": \".6\",\n \"value\": \".6\"\n },\n {\n \"selected\": false,\n \"text\": \".7\",\n \"value\": \".7\"\n },\n {\n \"selected\": false,\n \"text\": \".75\",\n \"value\": \".75\"\n },\n {\n \"selected\": false,\n \"text\": \".8\",\n \"value\": \".8\"\n },\n {\n \"selected\": false,\n \"text\": \".9\",\n \"value\": \".9\"\n },\n {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n {\n \"selected\": false,\n \"text\": \".99\",\n \"value\": \".99\"\n },\n {\n \"selected\": false,\n \"text\": \".999\",\n \"value\": \".999\"\n }\n ],\n \"query\": \".5,.6,.7,.75,.8,.9,.95,.99,.999\",\n \"queryValue\": \"\",\n \"skipUrlSync\": false,\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-5m\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"BFT ordering (performance)\",\n \"uid\": \"c2237cb1-9018-41ac-8eef-24a28ab28f20\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", "bft-ordering.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"description\": \"\",\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 8,\n \"links\": [],\n \"liveNow\": false,\n \"panels\": [\n {\n \"collapsed\": false,\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 64,\n \"panels\": [],\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Global overview (instance view)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"ISS proceeds in epochs with stable leader assignments to \\\"segments\\\" and stable topology (i.e., sequencers can be added and remove only at epoch boundaries and leaders are reassigned to segments also at epoch boundaries). This is the current epoch.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 5,\n \"x\": 0,\n \"y\": 1\n },\n \"hideTimeOverride\": false,\n \"id\": 72,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_epoch{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Current Epoch\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size in blocks of the current epoch.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 3,\n \"x\": 5,\n \"y\": 1\n },\n \"hideTimeOverride\": false,\n \"id\": 80,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_epoch_length{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Epoch Length\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of active validators\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 3,\n \"x\": 8,\n \"y\": 1\n },\n \"hideTimeOverride\": false,\n \"id\": 75,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_validators{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Validators\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of connected and authenticated P2P peers for the selected sequencer. It should be > 2/3 at all times.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"percentage\",\n \"steps\": [\n {\n \"color\": \"dark-red\",\n \"value\": null\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 67\n },\n {\n \"color\": \"dark-green\",\n \"value\": 100\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 13,\n \"x\": 11,\n \"y\": 1\n },\n \"hideTimeOverride\": true,\n \"id\": 53,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"center\",\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"last\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"textMode\": \"auto\"\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_p2p_connections_connected{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": true,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"Connected ({{namespace}}.{{node}})\",\n \"range\": false,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_p2p_connections_authenticated{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"hide\": false,\n \"instant\": true,\n \"legendFormat\": \"Authenticated ({{namespace}}.{{node}})\",\n \"range\": false,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Peers count\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 3,\n \"x\": 0,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 99,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_max_tolerated_faults{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Max tolerated faults\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The size of a weak quorum or non-faulty nodes, needed e.g. to disseminate batches\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 3,\n \"x\": 3,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 100,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_weak_quorum{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Weak quorum\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The size of a strong quorum, needed for ordering consensus\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 3,\n \"x\": 6,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 101,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_strong_quorum{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Strong quorum\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Latest block number ordered and available for the read path. When there is no traffic, a block can be empty.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 7,\n \"x\": 9,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 4,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job, node) (daml_sequencer_bftordering_global_ordered_blocks_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"})\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"instant\": true,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Output Blocks\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Total number of requests ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"locale\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 8,\n \"x\": 16,\n \"y\": 12\n },\n \"hideTimeOverride\": false,\n \"id\": 40,\n \"maxDataPoints\": 100,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"none\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"horizontal\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace,job,node) (histogram_sum(daml_sequencer_bftordering_output_block_size_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}))\",\n \"format\": \"time_series\",\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Output Requests\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Sequencers that are part of the BFT ordering topology\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisGridShow\": true,\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 12,\n \"x\": 0,\n \"y\": 24\n },\n \"id\": 91,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_topology_member{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"interval\": \"\",\n \"legendFormat\": \" (reporter: {{namespace}}.{{node}}) {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"BFT ordering members\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Sequencers that are allowed to propose blocks for ordering\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 12,\n \"x\": 12,\n \"y\": 24\n },\n \"id\": 95,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_topology_topology_leader{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \" (reporter: {{namespace}}.{{node}}) {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"BFT ordering leaders\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 8,\n \"x\": 0,\n \"y\": 45\n },\n \"id\": 96,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_p2p_authenticated_endpoint{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{endpoint}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P authenticated endpoints\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 8,\n \"x\": 8,\n \"y\": 45\n },\n \"id\": 97,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_p2p_unauthenticated_endpoint{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{endpoint}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P connected but unauthenticated endpoints\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"hidden\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.axisPlacement\",\n \"value\": \"hidden\"\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 21,\n \"w\": 8,\n \"x\": 16,\n \"y\": 45\n },\n \"id\": 98,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_p2p_disconnected_endpoint{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{endpoint}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"P2P disconnected endpoints\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Wall-clock duration between the instant a request is received by the CantonBFT orderer and the instant the fully assembled ordered block metadata is stored, just before the request is pushed to the post-ordering stages. Its meaningfulness requires wall-clock synchronization, as not all ordered requests are received and timestamped on this node.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 66\n },\n \"id\": 87,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by (namespace, job, node) (rate(daml_sequencer_bftordering_global_requests_ordering_latency_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests end-to-end ordering latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of requests ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 66\n },\n \"hideTimeOverride\": true,\n \"id\": 88,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\",\n \"sum\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_global_ordered_requests_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - requests/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of requests per consensus block.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 73\n },\n \"hideTimeOverride\": true,\n \"id\": 49,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\",\n \"sum\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_sum(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))) / histogram_count(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} ({{mode}})\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests / Block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of blocks ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 73\n },\n \"hideTimeOverride\": true,\n \"id\": 68,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by (namespace, job, node, is_block_empty) (irate(daml_sequencer_bftordering_global_ordered_blocks_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} (empty: {{is_block_empty}})\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Throughput - blocks/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches (proofs of availability) per consensus block. Note that < 1 average batch per block likely means empty blocks are being created.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 80\n },\n \"hideTimeOverride\": true,\n \"id\": 89,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\",\n \"sum\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_sum(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval]))) / histogram_count(sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}} ({{mode}})\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batches (PoAs) / Block\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of batches (proofs of availability) ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 80\n },\n \"hideTimeOverride\": true,\n \"id\": 90,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"min\",\n \"max\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"irate(daml_sequencer_bftordering_global_ordered_batches_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Throughput - batches/s\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Delay introduced by the sequencer core being too slow consuming blocks from ordering, in milliseconds\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"ms\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 87\n },\n \"hideTimeOverride\": true,\n \"id\": 102,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_sequencer_core_backpressure_current_delay_millis{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer core backpressure delay (ms)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the buffer in the subscription from the orderer to the sequencer core, in blocks\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 87\n },\n \"hideTimeOverride\": true,\n \"id\": 103,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\",\n \"min\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_sequencer_core_subscription_buffer_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\\n\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer core buffer size (blocks #)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"How many epochs a sequencer is blacklisted\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 94\n },\n \"id\": 86,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"builder\",\n \"expr\": \"daml_sequencer_bftordering_blacklist_sequencer{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{blacklist_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Blacklisted Sequencers\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 94\n },\n \"hideTimeOverride\": true,\n \"id\": 105,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\",\n \"min\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"increase(daml_sequencer_bftordering_topology_blacklisted_epochs{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[5m])\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}): {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Total number of epochs a sequencer was blacklisted in the last 5m\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 94\n },\n \"hideTimeOverride\": true,\n \"id\": 105,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"max\",\n \"min\",\n \"mean\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_topology_blacklisted_epochs{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\\n\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"hide\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}): {{sequencer_id}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Total number of epochs a sequencer is blacklisted\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the stream buffer at various stages\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 101\n },\n \"id\": 104,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_block_stream_buffer_size{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}.{{element}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Stream Buffer Metrics\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Wall-clock time of the ordered block being provided to the sequencer minus BFT time of the block\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [\n {\n \"options\": {\n \"match\": \"null\",\n \"result\": {\n \"text\": \"N/A\"\n }\n },\n \"type\": \"special\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 2\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 6,\n \"x\": 12,\n \"y\": 101\n },\n \"hideTimeOverride\": true,\n \"id\": 85,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_delay_duration_seconds{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block delay\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the payloads of blocks ordered and output to the read path.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 6,\n \"x\": 18,\n \"y\": 101\n },\n \"hideTimeOverride\": false,\n \"id\": 50,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, sum by(namespace, job, node, mode) (rate(daml_sequencer_bftordering_output_block_size_bytes{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}.{{mode}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Output Block Payload Size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Percentage of votes received by the currently selected sequencer at every major PBFT stage. Should be > 2/3 at all times, else a leader change (\\\"view change\\\" in PBFT parlance) occurs automatically.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"fillOpacity\": 70,\n \"lineWidth\": 0,\n \"spanNulls\": false\n },\n \"decimals\": 0,\n \"links\": [],\n \"mappings\": [],\n \"max\": 1,\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"percentage\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": null\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 67\n },\n {\n \"color\": \"#6ED0E0\",\n \"value\": 80\n },\n {\n \"color\": \"dark-green\",\n \"value\": 100\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 28,\n \"w\": 24,\n \"x\": 0,\n \"y\": 108\n },\n \"hideTimeOverride\": false,\n \"id\": 73,\n \"options\": {\n \"alignValue\": \"left\",\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"mergeValues\": true,\n \"rowHeight\": 0.9,\n \"showValue\": \"always\",\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_consensus_prepare_votes_percent{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) Prepare {{voting_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_bftordering_consensus_commit_votes_percent{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"hide\": false,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) Commit {{voting_sequencer}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"PBFT Voting Power %\",\n \"transformations\": [\n {\n \"id\": \"renameByRegex\",\n \"options\": {\n \"regex\": \"Prepare SEQ::(.*)::.*\",\n \"renamePattern\": \"Prepare $1\"\n }\n },\n {\n \"id\": \"renameByRegex\",\n \"options\": {\n \"regex\": \"Commit SEQ::(.*)::.*\",\n \"renamePattern\": \"Commit $1\"\n }\n }\n ],\n \"type\": \"state-timeline\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Count of non-compliant and possibly byzantine protocol behaviors detected by the currently selected sequencer.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"links\": [],\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Height for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#447ebc\",\n \"mode\": \"fixed\"\n }\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Total Transactions for last 3 hours\"\n },\n \"properties\": [\n {\n \"id\": \"color\",\n \"value\": {\n \"fixedColor\": \"#ef843c\",\n \"mode\": \"fixed\"\n }\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 136\n },\n \"hideTimeOverride\": false,\n \"id\": 74,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"mean\",\n \"max\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (namespace, job, node) (irate(daml_sequencer_bftordering_security_noncompliant_behavior_total{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", node=~\\\"$node\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Non-compliant behaviors\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Size of the incoming requests buffer (\\\"mempool\\\") for the selected sequencer. Its maximum size in bytes and requests count can be currently set via static configuration.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 136\n },\n \"hideTimeOverride\": true,\n \"id\": 78,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_ingress_requests_queued{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Mempool Size (requests)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of proposals requested by consensus.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 143\n },\n \"hideTimeOverride\": true,\n \"id\": 77,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_requested_proposals{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block proposals requested by consensus\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches requested to be proposed in consensus.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 143\n },\n \"hideTimeOverride\": true,\n \"id\": 79,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_requested_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Block proposals requested by consensus (batches)\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches that are being disseminated.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 150\n },\n \"hideTimeOverride\": true,\n \"id\": 82,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_disseminating_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ready_for_consensus=~\\\"false|true\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batches being disseminated\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches requested by the availability module and not yet provided.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 150\n },\n \"hideTimeOverride\": true,\n \"id\": 76,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_mempool_requested_batches{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Outstanding batch requests\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 157\n },\n \"id\": 55,\n \"panels\": [],\n \"repeat\": \"instance\",\n \"repeatDirection\": \"h\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Sequencer overview: $reporting_sequencer\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of requests in batches that are being disseminated.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 158\n },\n \"hideTimeOverride\": true,\n \"id\": 83,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_disseminating_requests{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ready_for_consensus=~\\\"false|true\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Requests being disseminated\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Total network ingress for the currently selected sequencer split by sender.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 100,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"never\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"links\": [],\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 6,\n \"x\": 12,\n \"y\": 158\n },\n \"id\": 59,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job, node, source_sequencer) (daml_sequencer_bftordering_p2p_receive_received_bytes_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"})\",\n \"format\": \"time_series\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{source_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Network Input\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Total network egress for the currently selected sequencer split by destination.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 100,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"never\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"links\": [],\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 6,\n \"x\": 18,\n \"y\": 158\n },\n \"id\": 58,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job, node, target_sequencer) (daml_sequencer_bftordering_p2p_send_sent_bytes_total{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\"})\",\n \"format\": \"time_series\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"(reporter: {{namespace}}.{{node}}) {{target_sequencer}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Network Output\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of payload bytes in requests within batches that are being disseminated.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 0,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"#7eb26d\",\n \"value\": null\n },\n {\n \"color\": \"#ef843c\",\n \"value\": 10\n },\n {\n \"color\": \"#e24d42\",\n \"value\": 20\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 0,\n \"y\": 165\n },\n \"hideTimeOverride\": true,\n \"id\": 84,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.5.12\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"daml_sequencer_bftordering_availability_disseminating_bytes{namespace=~\\\"$namespace\\\",job=~\\\"$job\\\",node=~\\\"$node\\\",ready_for_consensus=~\\\"false|true\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"30s\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}.{{node}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Bytes being disseminated\",\n \"type\": \"timeseries\"\n }\n ],\n \"refresh\": \"30s\",\n \"schemaVersion\": 38,\n \"style\": \"dark\",\n \"tags\": [\n \"bft-sequencers\"\n ],\n \"templating\": {\n \"list\": [\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status,namespace}\",\n \"hide\": 0,\n \"includeAll\": true,\n \"label\": \"Namespace\",\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"label\": \"Job\",\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"label\": \"Node\",\n \"multi\": true,\n \"name\": \"node\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_health_status{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},node)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 7,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"Percentile\",\n \"multi\": false,\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": false,\n \"text\": \".5\",\n \"value\": \".5\"\n },\n {\n \"selected\": false,\n \"text\": \".6\",\n \"value\": \".6\"\n },\n {\n \"selected\": false,\n \"text\": \".7\",\n \"value\": \".7\"\n },\n {\n \"selected\": false,\n \"text\": \".75\",\n \"value\": \".75\"\n },\n {\n \"selected\": false,\n \"text\": \".8\",\n \"value\": \".8\"\n },\n {\n \"selected\": false,\n \"text\": \".9\",\n \"value\": \".9\"\n },\n {\n \"selected\": true,\n \"text\": \".95\",\n \"value\": \".95\"\n },\n {\n \"selected\": false,\n \"text\": \".99\",\n \"value\": \".99\"\n },\n {\n \"selected\": false,\n \"text\": \".999\",\n \"value\": \".999\"\n }\n ],\n \"query\": \".5,.6,.7,.75,.8,.9,.95,.99,.999\",\n \"queryValue\": \"\",\n \"skipUrlSync\": false,\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-15m\",\n \"to\": \"now\"\n },\n \"timepicker\": {\n \"refresh_intervals\": [\n \"5s\",\n \"10s\",\n \"30s\",\n \"1m\",\n \"5m\",\n \"15m\",\n \"30m\",\n \"1h\",\n \"2h\",\n \"1d\"\n ],\n \"time_options\": [\n \"5m\",\n \"15m\",\n \"1h\",\n \"6h\",\n \"12h\",\n \"24h\",\n \"2d\",\n \"7d\",\n \"30d\"\n ]\n },\n \"timezone\": \"\",\n \"title\": \"BFT ordering\",\n \"uid\": \"UJyurCTWz\",\n \"version\": 2,\n \"weekStart\": \"\"\n}\n" }, "kind": "ConfigMap", diff --git a/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering-performance.json b/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering-performance.json index cf16254931..219d3a948d 100644 --- a/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering-performance.json +++ b/cluster/pulumi/observability/grafana-dashboards/canton-bft/bft-ordering-performance.json @@ -4535,6 +4535,104 @@ ], "title": "Sign message", "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "s" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 92 + }, + "id": 114, + "options": { + "legend": { + "calcs": [ + "min", + "mean", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "expr": "histogram_quantile($percentile, sum by(namespace, job, node, Leader) (rate(daml_sequencer_bftordering_consensus_relative_segment_latency_duration_seconds{namespace=~\"$namespace\", job=~\"$job\", node=~\"$node\"}[$__rate_interval])))", + "interval": "", + "legendFormat": "(reporter: {{namespace}}.{{node}}) {{Leader}}", + "range": true, + "refId": "A" + } + ], + "title": "Relative peer segment completion delay", + "type": "timeseries" } ], "title": "Consensus - main protocol", From b43c1c9c56d9cc8a5407136359857e10ae7bc62a Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Mon, 10 Aug 2026 17:11:30 +0200 Subject: [PATCH 217/329] Enable istio rate limiting metrics (#6614) Signed-off-by: Julien Tinguely --- cluster/expected/infra/expected.json | 7 +- cluster/expected/observability/expected.json | 4 +- cluster/expected/sv-runbook/expected.json | 3 - cluster/expected/sv/expected.json | 6 - .../templates/rateLimit.yaml | 6 - .../common/src/ratelimit/envoyRateLimiter.ts | 9 - cluster/pulumi/infra/src/istio.ts | 7 + .../istio-rate-limiting_alerts.yaml | 67 ++ .../grafana-dashboards/platform/istio.json | 651 ++++++++++++++++++ cluster/pulumi/observability/src/istio.ts | 3 +- .../pulumi/observability/src/observability.ts | 3 + 11 files changed, 738 insertions(+), 28 deletions(-) create mode 100644 cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml create mode 100644 cluster/pulumi/observability/grafana-dashboards/platform/istio.json diff --git a/cluster/expected/infra/expected.json b/cluster/expected/infra/expected.json index 1bc189c141..2a019811d7 100644 --- a/cluster/expected/infra/expected.json +++ b/cluster/expected/infra/expected.json @@ -1946,7 +1946,12 @@ "gatewayTopology": { "numTrustedProxies": 0 }, - "holdApplicationUntilProxyStarts": true + "holdApplicationUntilProxyStarts": true, + "proxyStatsMatcher": { + "inclusionRegexps": [ + ".*http_local_rate_limit.*" + ] + } }, "defaultHttpRetryPolicy": { "attempts": 0 diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index a582dc7f96..35ccb1602f 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -83,6 +83,7 @@ "deployment_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: deployments\n folder: canton-network\n interval: 1m\n rules:\n - uid: adkhl6u18pqtce\n title: Failing Stacks\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n exemplar: true\n expr: splice_deployment_pulumi_stack_status{state=\"failed\"}\n format: time_series\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: stacks_active\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: D\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - D\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: QP_wDqDnz\n panelId: 11\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"11\"\n description: The pulumi operator failed to update the {{ $labels.stack }} stack. Check the logs for the deployment.\n runbook_url: \"\"\n severity: critical\n summary: '{{ $labels.stack }} stack failed to update'\n labels:\n \"\": \"\"\n isPaused: false\n - uid: bdndg5g3x4kxsf\n title: Deployments running\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: splice_deployment_pulumi_stack_condition{type=\"Reconciling\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n dashboardUid: QP_wDqDnz\n panelId: 27\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"27\"\n description: A deployment is currently running for the {{ $labels.stack }} stack.\n runbook_url: \"\"\n severity: info\n summary: Stack {{ $labels.stack }} is being updated\n labels:\n \"\": \"\"\n isPaused: false\n", "dso_missed_confirmations_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: dso party missed confirmations\n folder: canton-network\n interval: 1m\n rules:\n - uid: ddso1miss0conf0a\n title: DSO Party Missed Confirmations\n condition: missed confirmations threshold exceeded\n data:\n - refId: missed confirmation rate\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (party) (increase(daml_mediator_timeout_non_responsive_participants_total{party=~\"DSO::.*\"}[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: missed confirmation rate\n - refId: missed confirmations threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: missed confirmation rate\n intervalMs: 1000\n maxDataPoints: 43200\n refId: missed confirmations threshold exceeded\n type: threshold\n dashboardUid: cnck4jp\n panelId: 1\n noDataState: OK\n execErrState: Alerting\n for: 0m\n annotations:\n __dashboardUid__: cnck4jp\n __panelId__: \"1\"\n severity: critical\n summary: The DSO party in namespace {{ $labels.namespace }} missed more than 0 of its confirmations over the last 10 minutes.\n labels: {}\n isPaused: false\n", "extra_k8s_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: storage\n folder: k8s\n interval: 5m\n rules:\n - uid: adlrbu5kog0sga\n title: KubePersistentVolumeTooFull\n condition: free_space_below_threshold\n data:\n - refId: free_space\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: kubelet_volume_stats_available_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"} / kubelet_volume_stats_capacity_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: free_space\n - refId: free_space_below_threshold\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.15\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: free_space\n intervalMs: 1000\n maxDataPoints: 43200\n refId: free_space_below_threshold\n type: threshold\n dashboardUid: 919b92a8e8041bd567af9edab12c840c\n panelId: 2\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: 919b92a8e8041bd567af9edab12c840c\n __panelId__: \"2\"\n severity: warning\n description: The PersistentVolume claimed by {{ index $labels \"persistentvolumeclaim\" }} in Namespace {{ index $labels \"namespace\" }} is running out of disk space. Currently {{ humanizePercentage (index $values \"free_space\").Value }} is available.\n runbook_url: https://runbooks.prometheus-operator.dev/runbooks/kubernetes/kubepersistentvolumefillingup\n summary: PersistentVolume is too full.\n isPaused: false\n", + "istio-rate-limiting_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: istio-rate-limit\n folder: platform\n interval: 5m\n rules:\n - uid: cfurlpifrmvi8w\n title: Enforced requests\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\"sv.*\"}[$__rate_interval])) by (namespace, pod)\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}} - {{pod}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnr56dj\n panelId: 9\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n runbook_url: \"\"\n severity: warning\n description: Envoy local rate limiting is active, requests are being rejected. Check the \"Istio Rate Limiting\" dashboard.\n summary: Envoy is rate limiting requests in {{ $labels.namespace }} on pod {{ $labels.pod }}.\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A\n isPaused: false\n", "load-tester_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: load-tester\n folder: canton-network\n interval: 1m\n rules:\n - uid: a1dbc4e8-7941-4351-9a14-f8573fd2be2b\n title: K6 Request Failure Rate Exceeded\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: B\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total{expected_response=\"false\"}) or on() vector(0)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: ($B / $A) * 100\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 50\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 1m\n annotations:\n description: ''\n runbook_url: ''\n summary: The k6 load tester experienced HTTP request failures at a rate past the acceptable threshold\n labels:\n gcloud_filter: ''\n isPaused: false\n - uid: ddogbytusmz9cb\n title: K6 Throughput Below Threshold\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: rate(k6_transfers_completed_total{scenario=\"generate_load\"}[$__rate_interval])\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.123\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: B\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n - refId: B\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n hide: false\n reducer: mean\n refId: B\n type: reduce\n dashboardUid: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n panelId: 28\n noDataState: Alerting\n execErrState: Alerting\n for: 15m # give some time for restarts\n annotations:\n __dashboardUid__: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n __panelId__: \"28\"\n severity: warning\n description: ''\n runbook_url: ''\n summary: Transaction rate from the k6 load tester is lower than expected threshold\n labels:\n '': ''\n isPaused: false\n", "mining-rounds_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: mining-rounds\n folder: canton-network\n interval: 1m\n rules:\n - uid: ae74w21qca2o0e\n title: Open Mining Rounds Not Advancing\n condition: No new round\n data:\n - refId: Round number diff\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: max(max(max_over_time(splice_sv_dso_store_latest_open_mining_round[30m])) by (namespace) - min(min_over_time(splice_sv_dso_store_latest_open_mining_round[30m])) by (namespace))\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Round number diff\n useBackend: false\n - refId: No new round\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.9\n type: gt\n datasource:\n type: __expr__\n uid: __expr__\n expression: Round number diff\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No new round\n type: threshold\n dashboardUid: ed94a332-4fa7-47f8-982b-fc997381175b\n panelId: 1\n noDataState: Alerting\n execErrState: OK\n for: 1m\n annotations:\n __dashboardUid__: ed94a332-4fa7-47f8-982b-fc997381175b\n __panelId__: \"1\"\n runbook_url: \"\"\n summary: \"The open mining rounds have not advanced in the last 30m\"\n description: 'None of our SV apps have seen the open mining round advancing in the last 30m. Either all of our SVs or a high enough number of SVs to break BFT guarantees are failing'\n severity: critical\n labels:\n priority: high\n isPaused: false\n - uid: ae74w21qca2o0d\n title: Issuing Rounds Not Advancing\n condition: No new round\n data:\n - refId: Round number diff\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: max(max(max_over_time(splice_sv_dso_store_latest_issuing_mining_round[30m])) by (namespace) - min(min_over_time(splice_sv_dso_store_latest_issuing_mining_round[30m])) by (namespace))\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Round number diff\n useBackend: false\n - refId: No new round\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.9\n type: gt\n datasource:\n type: __expr__\n uid: __expr__\n expression: Round number diff\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No new round\n type: threshold\n dashboardUid: ed94a332-4fa7-47f8-982b-fc997381175b\n panelId: 1\n noDataState: Alerting\n execErrState: OK\n for: 1m\n annotations:\n __dashboardUid__: ed94a332-4fa7-47f8-982b-fc997381175b\n __panelId__: \"2\"\n runbook_url: \"\"\n summary: \"The issuing mining rounds have not advanced in the last 30m\"\n description: 'None of our SV apps have seen the issuing mining round advancing in the last 30m. Either all of our SVs or a high enough number of SVs to break BFT guarantees are failing'\n severity: critical\n labels:\n priority: high\n isPaused: false\n", "mute_time_intervals.yaml": "apiVersion: 1\nmuteTimes:\n - orgId: 1\n name: sv1-mute\n time_intervals:\n - times:\n - start_time: '18:00'\n end_time: '24:00'\n weekdays:\n - wednesday\n location: UTC\n - orgId: 1\n name: sv-runbook-mute\n time_intervals:\n - times:\n - start_time: '18:00'\n end_time: '24:00'\n weekdays:\n - wednesday\n location: UTC\n - times:\n - start_time: '00:00'\n end_time: '06:00'\n - start_time: '12:00'\n end_time: '13:00'\n weekdays:\n - thursday\n location: UTC\n", @@ -323,6 +324,7 @@ "health.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"target\": {\n \"limit\": 100,\n \"matchAny\": false,\n \"tags\": [],\n \"type\": \"dashboard\"\n },\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 2,\n \"id\": 9,\n \"links\": [],\n \"liveNow\": false,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"fillOpacity\": 71,\n \"lineWidth\": 0\n },\n \"mappings\": [\n {\n \"options\": {\n \"0\": {\n \"index\": 1,\n \"text\": \"inactive\"\n },\n \"1\": {\n \"index\": 0,\n \"text\": \"active\"\n }\n },\n \"type\": \"value\"\n }\n ],\n \"max\": 1,\n \"min\": 0,\n \"noValue\": \"0\",\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"dark-red\",\n \"value\": null\n },\n {\n \"color\": \"green\",\n \"value\": 1\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 4,\n \"options\": {\n \"colWidth\": 1,\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": false\n },\n \"rowHeight\": 0.8,\n \"showValue\": \"never\",\n \"tooltip\": {\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"9.3.2\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"builder\",\n \"exemplar\": false,\n \"expr\": \"daml_health_status{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\"}\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"legendFormat\": \"{{pod}} {{component}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Components' Status\",\n \"type\": \"status-history\"\n }\n ],\n \"refresh\": false,\n \"schemaVersion\": 37,\n \"style\": \"dark\",\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"selected\": false,\n \"text\": \"Prometheus\",\n \"value\": \"Prometheus\"\n },\n \"hide\": 2,\n \"includeAll\": false,\n \"multi\": false,\n \"name\": \"datasource\",\n \"options\": [],\n \"query\": \"prometheus\",\n \"refresh\": 1,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"type\": \"datasource\"\n },\n {\n \"current\": {\n \"selected\": false,\n \"text\": \"static_local\",\n \"value\": \"static_local\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"definition\": \"label_values(daml_health_status, namespace)\",\n \"hide\": 0,\n \"includeAll\": false,\n \"multi\": false,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(daml_health_status, namespace)\",\n \"refId\": \"StandardVariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 0,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"definition\": \"label_values(daml_health_status{namespace=\\\"$namespace\\\"}, pod)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"pod\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(daml_health_status{namespace=\\\"$namespace\\\"}, pod)\",\n \"refId\": \"StandardVariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 0,\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-3h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Health\",\n \"uid\": \"IME_mP2Vk\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", "http.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 1182,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 15,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"label_replace(histogram_count(rate(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\", operation=~\\\"$operation\\\", status_code=~\\\"2.*\\\"}[$__rate_interval])), \\\"source_display\\\", \\\"Source UI: $1\\\", \\\"source_ui\\\", \\\"(.+)\\\")\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_service}} {{operation}} {{status_code}} {{source_display}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Handled Requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 9,\n \"x\": 15,\n \"y\": 0\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"histogram_count(rate(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\", operation=~\\\"$operation\\\", status_code!~\\\"2.*\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_service}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Errors\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 12\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, rate(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\", operation=~\\\"$operation\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_service}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Request Timing quantile $percentile\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"requests\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 22\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_http_requests_inflight{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\", operation=~\\\"$operation\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_service}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"In Flight Requests\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_requests_duration_seconds,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_requests_duration_seconds,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\"},job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},http_service)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"service\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},http_service)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\"},operation)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"operation\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\"},operation)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"0.9\",\n \"value\": \"0.9\"\n },\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": true,\n \"text\": \"0.9\",\n \"value\": \"0.9\"\n },\n {\n \"selected\": false,\n \"text\": \"0.95\",\n \"value\": \"0.95\"\n },\n {\n \"selected\": false,\n \"text\": \"0.99\",\n \"value\": \"0.99\"\n }\n ],\n \"query\": \"0.9,0.95,0.99\",\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-5m\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Http Server\",\n \"uid\": \"aerbb811lwveod\",\n \"version\": 4\n}\n", "http_client.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 15005,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 15,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"histogram_count(rate(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", operation=~\\\"$operation\\\", status_code=~\\\"2.*\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": false,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_client}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Client requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 9,\n \"x\": 15,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"histogram_count(rate(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", operation=~\\\"$operation\\\", status_code!~\\\"2.*\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} {{job}} {{http_client}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Errors\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 11\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, rate(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_client=~\\\"$http_client\\\", operation=~\\\"$operation\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} {{job}} {{http_client}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Client Requests Timing quantile 0.95 \",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 19\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"daml_http_client_requests_inflight{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_client=~\\\"$http_client\\\", operation=~\\\"$operation\\\"}\\n\",\n \"legendFormat\": \"{{namespace}} {{job}} {{http_client}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"In Flight Client Requests\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": [\n \"validator1\"\n ],\n \"value\": [\n \"validator1\"\n ]\n },\n \"definition\": \"label_values(daml_http_client_requests_duration_seconds,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_client_requests_duration_seconds,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"definition\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\"},job)\",\n \"description\": \"\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"definition\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_client=~\\\"$http_client\\\"},operation)\",\n \"description\": \"\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"operation\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_client=~\\\"$http_client\\\"},operation)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},http_client)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"http_client\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},http_client)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"0.9\",\n \"value\": \"0.9\"\n },\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": true,\n \"text\": \"0.9\",\n \"value\": \"0.9\"\n },\n {\n \"selected\": false,\n \"text\": \"0.95\",\n \"value\": \"0.95\"\n },\n {\n \"selected\": false,\n \"text\": \"0.99\",\n \"value\": \"0.99\"\n }\n ],\n \"query\": \"0.9,0.95,0.99\",\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-12h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Http Client\",\n \"uid\": \"a8a3113a-bccf-4728-b752-dd7a5d6f9bda\",\n \"version\": 3\n}\n", + "istio.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of HTTP 429 (Too Many Requests) responses per second, broken down by the namespace reporting the request and the destination service receiving it.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, destination_service) (rate(istio_requests_total{response_code=\\\"429\\\", namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Istio total rejected requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Fraction of requests evaluated by the Envoy local rate limit filter that were rejected with HTTP 429, per namespace and pod.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"percent\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\\n/\\nsum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enabled{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"legendFormat\": \"{{namespace}} - {{pod}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rejection ratio\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of number of requests for which rate limiting was applied (e.g.: 429 returned)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 8\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\",\n \"legendFormat\": \"{{namespace}} - {{pod}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Enforced requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate for responses without an available token (but not necessarily enforced)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 8\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_rate_limited{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace,job)\",\n \"legendFormat\": \"{{namespace}} - {{job}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rejected requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate for number of requests for which the rate limiter was consulted\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 16\n },\n \"id\": 8,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enabled{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\",\n \"legendFormat\": \"{{namespace}} - {{pod}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Enabled requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate for number of requests under limit responses from the token bucket\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 16\n },\n \"id\": 7,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_ok{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\",\n \"legendFormat\": \"{{namespace}} - {{pod}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Allowed requests\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Istio Rate Limiting\",\n \"uid\": \"cnr56dj\",\n \"version\": 3,\n \"weekStart\": \"\"\n}\n", "rate_limiters.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"target\": {\n \"limit\": 100,\n \"matchAny\": false,\n \"tags\": [],\n \"type\": \"dashboard\"\n },\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 510,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\", result!=\\\"accepted\\\"}[$__rate_interval])) by (result, limiter)\",\n \"instant\": false,\n \"legendFormat\": \"{{result}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rejections\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n }\n },\n \"mappings\": [],\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 4,\n \"options\": {\n \"displayLabels\": [\n \"name\",\n \"percent\"\n ],\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"values\": [\n \"percent\",\n \"value\"\n ]\n },\n \"pieType\": \"pie\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(increase(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\"}[$__range])) by (result)\",\n \"instant\": true,\n \"legendFormat\": \"{{result}}\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Requests by Result\",\n \"type\": \"piechart\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 24,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\"}[$__rate_interval])) by (limiter)\",\n \"instant\": false,\n \"legendFormat\": \"{{limiter}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"splice_rate_limiting_max_limit_per_second\",\n \"hide\": false,\n \"instant\": false,\n \"legendFormat\": \"{{limiter}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Total Requests\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"1m\",\n \"schemaVersion\": 41,\n \"tags\": [\n \"prometheus\",\n \"rate-limiting\"\n ],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total, namespace)\",\n \"includeAll\": false,\n \"label\": \"Namespace\",\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(splice_rate_limiting_total, namespace)\",\n \"refId\": \"StandardVariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": [\n \"scan-app\"\n ],\n \"value\": [\n \"scan-app\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\"},node_name)\",\n \"includeAll\": true,\n \"label\": \"Node Name\",\n \"multi\": true,\n \"name\": \"node_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\"},node_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"sort\": 3,\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\"},http_service)\",\n \"includeAll\": true,\n \"label\": \"HTTP Service\",\n \"multi\": true,\n \"name\": \"http_service\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\"},http_service)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"sort\": 1,\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", node_name=~\\\"$node_name\\\"},limiter)\",\n \"includeAll\": true,\n \"label\": \"Limiter\",\n \"multi\": true,\n \"name\": \"limiter\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", node_name=~\\\"$node_name\\\"},limiter)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"sort\": 2,\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Splice Rate Limiting\",\n \"uid\": \"splice-rate-limit-db\",\n \"version\": 3\n}\n" }, "kind": "ConfigMap", @@ -581,7 +583,7 @@ "metricRelabelings": [ { "action": "keep", - "regex": "istio_.*", + "regex": "(istio_.*|envoy_.*http_local_rate_limit_.*)", "sourceLabels": [ "__name__" ] diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index b4b1aceb72..800ea3b326 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -1625,9 +1625,6 @@ "apiVersion": "networking.istio.io/v1alpha3", "kind": "EnvoyFilter", "metadata": { - "annotations": { - "proxy.istio.io/config": "proxyStatsMatcher:\n inclusionRegexps:\n - \".*http_local_rate_limit.*\"" - }, "name": "scan-app-rate-limit", "namespace": "sv" }, diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index 90445f6f6a..a4e4d4a147 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -2924,9 +2924,6 @@ "apiVersion": "networking.istio.io/v1alpha3", "kind": "EnvoyFilter", "metadata": { - "annotations": { - "proxy.istio.io/config": "proxyStatsMatcher:\n inclusionRegexps:\n - \".*http_local_rate_limit.*\"" - }, "name": "scan-app-rate-limit", "namespace": "sv-1" }, @@ -5267,9 +5264,6 @@ "apiVersion": "networking.istio.io/v1alpha3", "kind": "EnvoyFilter", "metadata": { - "annotations": { - "proxy.istio.io/config": "proxyStatsMatcher:\n inclusionRegexps:\n - \".*http_local_rate_limit.*\"" - }, "name": "scan-app-rate-limit", "namespace": "sv-da-1" }, diff --git a/cluster/helm/splice-cluster-ingress-runbook/templates/rateLimit.yaml b/cluster/helm/splice-cluster-ingress-runbook/templates/rateLimit.yaml index 721668531e..e858d50853 100644 --- a/cluster/helm/splice-cluster-ingress-runbook/templates/rateLimit.yaml +++ b/cluster/helm/splice-cluster-ingress-runbook/templates/rateLimit.yaml @@ -11,12 +11,6 @@ kind: EnvoyFilter metadata: name: scan-rate-limit namespace: {{ .Release.Namespace }} - annotations: - # enable metrics for rate limit - proxy.istio.io/config: |- - proxyStatsMatcher: - inclusionRegexps: - - ".*http_local_rate_limit.*" spec: workloadSelector: labels: diff --git a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts index d2d8839868..e5944338b2 100644 --- a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts +++ b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts @@ -296,12 +296,6 @@ export class RateLimitEnvoyFilter extends pulumi.ComponentResource { const rateLimitActions = buildRateLimitActions(effectiveRateLimits || {}); - const enableEnvoyRateLimitMetricsAnnotation = ` -proxyStatsMatcher: - inclusionRegexps: - - ".*http_local_rate_limit.*" -`.trim(); - this.envoyFilter = new k8s.apiextensions.CustomResource( `${args.namespace}-${name}`, { @@ -310,9 +304,6 @@ proxyStatsMatcher: metadata: { name: name, namespace: args.namespace, - annotations: { - 'proxy.istio.io/config': enableEnvoyRateLimitMetricsAnnotation, - }, }, spec: { workloadSelector: { diff --git a/cluster/pulumi/infra/src/istio.ts b/cluster/pulumi/infra/src/istio.ts index b726f9ad80..e6179dd52a 100644 --- a/cluster/pulumi/infra/src/istio.ts +++ b/cluster/pulumi/infra/src/istio.ts @@ -143,6 +143,13 @@ function configureIstiod( }, // wait for the istio container to start before starting apps to avoid network errors holdApplicationUntilProxyStarts: true, + // Export the local rate limit filter counters (enabled/ok/rate_limited/enforced). + // Deliberately narrow: inclusionRegexps is *additive* on top of Istio's + // defaults, so a broad regex here would blow up Prometheus cardinality. + // docs: https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/local_rate_limit_filter#statistics + proxyStatsMatcher: { + inclusionRegexps: ['.*http_local_rate_limit.*'], + }, }, // We have clients retry so we disable istio’s automatic retries. defaultHttpRetryPolicy: { diff --git a/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml b/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml new file mode 100644 index 0000000000..3766cfc8ef --- /dev/null +++ b/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml @@ -0,0 +1,67 @@ +apiVersion: 1 +groups: + - orgId: 1 + name: istio-rate-limit + folder: platform + interval: 5m + rules: + - uid: cfurlpifrmvi8w + title: Enforced requests + condition: C + data: + - refId: A + relativeTimeRange: + from: 21600 + to: 0 + datasourceUid: prometheus + model: + datasource: + type: prometheus + uid: prometheus + editorMode: code + expr: sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~"sv.*"}[$__rate_interval])) by (namespace, pod) + instant: true + interval: "" + intervalMs: 30000 + legendFormat: '{{namespace}} - {{pod}}' + maxDataPoints: 43200 + range: false + refId: A + - refId: C + datasourceUid: __expr__ + model: + conditions: + - evaluator: + params: + - 0 + type: gt + operator: + type: and + query: + params: [] + reducer: + params: [] + type: last + type: query + datasource: + type: __expr__ + uid: __expr__ + expression: A + intervalMs: 1000 + maxDataPoints: 43200 + refId: C + type: threshold + dashboardUid: cnr56dj + panelId: 9 + noDataState: OK + execErrState: Alerting + for: 5m + annotations: + runbook_url: "" + severity: warning + description: Envoy local rate limiting is active, requests are being rejected. Check the "Istio Rate Limiting" dashboard. + summary: Envoy is rate limiting requests in {{ $labels.namespace }} on pod {{ $labels.pod }}. + labels: + "": "" + gcloud_filter: resource.labels.namespace_name=%22{{ index "namespace" }}%22%0A + isPaused: false diff --git a/cluster/pulumi/observability/grafana-dashboards/platform/istio.json b/cluster/pulumi/observability/grafana-dashboards/platform/istio.json new file mode 100644 index 0000000000..eaee05bd49 --- /dev/null +++ b/cluster/pulumi/observability/grafana-dashboards/platform/istio.json @@ -0,0 +1,651 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "links": [], + "panels": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Rate of HTTP 429 (Too Many Requests) responses per second, broken down by the namespace reporting the request and the destination service receiving it.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "editorMode": "code", + "expr": "sum by(namespace, destination_service) (rate(istio_requests_total{response_code=\"429\", namespace=~\"$namespace\"}[$__rate_interval]))", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Istio total rejected requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Fraction of requests evaluated by the Envoy local rate limit filter that were rejected with HTTP 429, per namespace and pod.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 1, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "code", + "exemplar": false, + "expr": "sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\"$namespace\"}[$__rate_interval])) by (namespace, pod)\n/\nsum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enabled{namespace=~\"$namespace\"}[$__rate_interval])) by (namespace, pod)", + "format": "time_series", + "instant": false, + "interval": "", + "legendFormat": "{{namespace}} - {{pod}}", + "range": true, + "refId": "A" + } + ], + "title": "Rejection ratio", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Rate of number of requests for which rate limiting was applied (e.g.: 429 returned)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 9, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "editorMode": "code", + "expr": "sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\"$namespace\"}[$__rate_interval])) by (namespace, pod)", + "legendFormat": "{{namespace}} - {{pod}}", + "range": true, + "refId": "A" + } + ], + "title": "Enforced requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Rate for responses without an available token (but not necessarily enforced)", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "editorMode": "code", + "expr": "sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_rate_limited{namespace=~\"$namespace\"}[$__rate_interval])) by (namespace,job)", + "legendFormat": "{{namespace}} - {{job}}", + "range": true, + "refId": "A" + } + ], + "title": "Rejected requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Rate for number of requests for which the rate limiter was consulted", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 8, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "editorMode": "code", + "expr": "sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enabled{namespace=~\"$namespace\"}[$__rate_interval])) by (namespace, pod)", + "legendFormat": "{{namespace}} - {{pod}}", + "range": true, + "refId": "A" + } + ], + "title": "Enabled requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "description": "Rate for number of requests under limit responses from the token bucket", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "editorMode": "code", + "expr": "sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_ok{namespace=~\"$namespace\"}[$__rate_interval])) by (namespace, pod)", + "legendFormat": "{{namespace}} - {{pod}}", + "range": true, + "refId": "A" + } + ], + "title": "Allowed requests", + "type": "timeseries" + } + ], + "preload": false, + "schemaVersion": 42, + "tags": [], + "templating": { + "list": [ + { + "allowCustomValue": false, + "current": { + "text": "All", + "value": [ + "$__all" + ] + }, + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "definition": "label_values(namespace)", + "includeAll": true, + "multi": true, + "name": "namespace", + "options": [], + "query": { + "qryType": 1, + "query": "label_values(namespace)", + "refId": "PrometheusVariableQueryEditor-VariableQuery" + }, + "refresh": 1, + "regex": "", + "regexApplyTo": "value", + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Istio Rate Limiting", + "uid": "cnr56dj", + "version": 3, + "weekStart": "" +} diff --git a/cluster/pulumi/observability/src/istio.ts b/cluster/pulumi/observability/src/istio.ts index 4872133468..eeee71f00d 100644 --- a/cluster/pulumi/observability/src/istio.ts +++ b/cluster/pulumi/observability/src/istio.ts @@ -36,11 +36,10 @@ export function istioMonitoring( { port: 'http-envoy-prom', path: '/stats/prometheus', - // keep only istio metrics, drop envoy metrics metricRelabelings: [ { sourceLabels: ['__name__'], - regex: 'istio_.*', + regex: '(istio_.*' + '|envoy_.*http_local_rate_limit_.*)', action: 'keep', }, // drop instance label, we have the pod name diff --git a/cluster/pulumi/observability/src/observability.ts b/cluster/pulumi/observability/src/observability.ts index 10b302233e..fb2c708433 100644 --- a/cluster/pulumi/observability/src/observability.ts +++ b/cluster/pulumi/observability/src/observability.ts @@ -1033,6 +1033,9 @@ function createGrafanaAlerting(namespace: Input) { '$VERDICT_INGESTION_BATCH_SIZE_PENDING_PERIOD_MINUTES', monitoringConfig.alerting.alerts.trafficBasedRewards.verdictIngestionBatchSizePendingPeriodMinutes.toString() ), + 'istio-rate-limiting_alerts.yaml': readGrafanaAlertingFile( + 'istio-rate-limiting_alerts.yaml' + ), }, }).map(([k, v]) => [k, defaultAlertSubstitutions(v)]) ), From 648fb8ac7dac916e0fe738a507a30d4b07242148 Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 10 Aug 2026 16:31:48 -0400 Subject: [PATCH 218/329] Allow voting after effectivity date. (#6646) Signed-off-by: Matt Dziuban --- .../proposal-details-content.test.tsx | 124 ++++++++++++++++++ .../governance/ProposalDetailsContent.tsx | 7 +- docs/src/release_notes_upcoming.rst | 5 + 3 files changed, 130 insertions(+), 6 deletions(-) diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx index 003e8da795..f302ac5a36 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-details-content.test.tsx @@ -1242,3 +1242,127 @@ describe('Proposal Details > Votes & Voting', () => { expect(rejectButton).not.toBeDisabled(); }); }); + +describe('Open vote request whose effectivity has passed', () => { + const pastEffectivity = { + requester: 'sv1', + requesterIsYou: true, + votingThresholdDeadline: '2024-01-01 13:00', + voteTakesEffect: '2024-01-02 13:00', + status: 'In Progress', + } as ProposalVotingInformation; + + test('shows the vote form to an SV that has not voted', () => { + const votes: ProposalVote[] = [ + { sv: 'sv1', isYou: true, vote: 'no-vote' }, + { sv: 'sv3', vote: 'accepted', reason: { url: 'https://example.com', body: 'Reason' } }, + ]; + + render( + + + + ); + + const votingForm = screen.getByTestId('your-vote-form'); + expect(votingForm).toBeInTheDocument(); + expect(within(votingForm).getByTestId('your-vote-accept')).toBeInTheDocument(); + expect(within(votingForm).getByTestId('your-vote-reject')).toBeInTheDocument(); + }); + + test('shows the change-vote control to an SV that has already voted', async () => { + const user = userEvent.setup(); + const votes: ProposalVote[] = [ + { + sv: 'sv1', + isYou: true, + vote: 'accepted', + reason: { url: 'https://example.com', body: 'Reason' }, + }, + { sv: 'sv3', vote: 'accepted', reason: { url: 'https://example.com', body: 'Reason' } }, + ]; + + render( + + + + ); + + // An SV that has voted sees the Edit control rather than the form, until it starts editing. + expect(screen.queryByTestId('your-vote-form')).not.toBeInTheDocument(); + + const editButton = screen.getByTestId('your-vote-edit-button'); + expect(editButton).toBeInTheDocument(); + + await user.click(editButton); + + const votingForm = screen.getByTestId('your-vote-form'); + expect(votingForm).toBeInTheDocument(); + expect(within(votingForm).getByTestId('your-vote-reject')).toBeInTheDocument(); + }); + + test('shows SVs that have not voted as awaiting a response', () => { + const votes: ProposalVote[] = [ + { + sv: 'sv1', + isYou: true, + vote: 'accepted', + reason: { url: 'https://example.com', body: 'Reason' }, + }, + { sv: 'sv3', vote: 'no-vote' }, + ]; + + render( + + + + ); + + const noVoteTab = screen.getByTestId('no-vote-votes-tab'); + expect(noVoteTab.textContent).toMatch(/Awaiting Response/); + expect(noVoteTab.textContent).not.toMatch(/Did not Vote/); + + const statuses = screen + .getAllByTestId('proposal-details-vote-status-value') + .map(s => s.textContent); + expect(statuses).toContain('Awaiting Response'); + expect(statuses).not.toContain('No Vote'); + }); +}); + +describe('Closed proposal', () => { + test('does not show the vote form or the change-vote control', () => { + render( + + + + ); + + expect(screen.queryByTestId('your-vote-form')).not.toBeInTheDocument(); + expect(screen.queryByTestId('your-vote-edit-button')).not.toBeInTheDocument(); + }); +}); diff --git a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx index 329ecffe17..1f54d1ae55 100644 --- a/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalDetailsContent.tsx @@ -60,18 +60,13 @@ export interface ProposalDetailsContentProps { type VoteTab = Extract | 'all'; -const now = () => dayjs(); - export const ProposalDetailsContent: React.FC = props => { const { contractId, proposalDetails, votingInformation, votes, currentSvPartyId } = props; const votesHooks = useVotesHooks(); const dsoInfoQuery = useDsoInfos(); - const isEffective = - votingInformation.voteTakesEffect && dayjs(votingInformation.voteTakesEffect).isBefore(now()); - const isClosed = - !proposalDetails.isVoteRequest || isEffective || votingInformation.status === 'Rejected'; + const isClosed = !proposalDetails.isVoteRequest || votingInformation.status === 'Rejected'; const dsoConfigToCompareWith = useMemo(() => { if (proposalDetails.action === 'SRARC_SetConfig') { diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 4ba2241225..5ea32a4b9a 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -16,3 +16,8 @@ By upgrading to this version, SVs agree to change this parameter to 2 minutes (unless they override the new SV app config value). The change takes effect once a sufficient number of SVs have upgraded. + - The governance UI no longer stops an SV from casting or changing its vote once a + proposal's target effective time has passed. Votes are now accepted for as long as the + vote request is open, matching what the ledger allows. This makes it possible to reject a + proposal whose action fails to execute and which would otherwise remain in flight + indefinitely. From 221150f776c7ecb85b3dbe1c4e94972d5588f28a Mon Sep 17 00:00:00 2001 From: Robert Autenrieth <31539813+rautenrieth-da@users.noreply.github.com> Date: Tue, 11 Aug 2026 13:02:23 +0200 Subject: [PATCH 219/329] Bump aws_version (#6740) Signed-off-by: Robert Autenrieth --- build.sbt | 1 + project/CantonDependencies.scala | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index c38db07cc1..42963f079d 100644 --- a/build.sbt +++ b/build.sbt @@ -2087,6 +2087,7 @@ def mergeStrategy(oldStrategy: String => MergeStrategy): String => MergeStrategy case path if path.endsWith("scala-collection-compat.properties") => MergeStrategy.first // Don't really care about the notice file so just take any. case "META-INF/FastDoubleParser-NOTICE" => MergeStrategy.first + case "META-INF/FastDoubleParser-LICENSE" => MergeStrategy.first case "META-INF/license/LICENSE.boringssl.txt" => MergeStrategy.first case path if path.endsWith("/OSGI-INF/MANIFEST.MF") => MergeStrategy.first case x => diff --git a/project/CantonDependencies.scala b/project/CantonDependencies.scala index c15228e47b..93e5c65bd7 100644 --- a/project/CantonDependencies.scala +++ b/project/CantonDependencies.scala @@ -332,7 +332,7 @@ object CantonDependencies { "com.google.protobuf" % "protobuf-java-util" % protobuf_version // AWS SDK for Java API to encrypt/decrypt keys using AWS KMS - lazy val aws_version = "2.29.5" + lazy val aws_version = "2.49.4" lazy val aws_kms = "software.amazon.awssdk" % "kms" % aws_version lazy val aws_sts = "software.amazon.awssdk" % "sts" % aws_version From 0d24ad3ff4a0afb19a1aed553142c7660ca87415 Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Tue, 11 Aug 2026 10:00:13 -0400 Subject: [PATCH 220/329] Add advisory lock to `SqlIndexInitializationTrigger` (#6681) * Add advisory lock to `SqlIndexInitializationTrigger`. Fixes #4738 This adds an advisory lock around the index DDL statements in `SqlIndexInitializationTrigger`. When a trigger fails to acquire the lock, the task will be retried. Signed-off-by: Matt Dziuban --- .../SqlIndexInitializationTrigger.scala | 17 ++- .../splice/store/db/AdvisoryLockIds.scala | 1 + .../splice/store/db/AdvisoryLocks.scala | 63 +++++++++ ...lIndexInitializationTriggerStoreTest.scala | 57 ++++++-- .../splice/store/db/AdvisoryLocksTest.scala | 124 ++++++++++++++++++ .../automation/AcsSnapshotTriggerBase.scala | 3 +- .../splice/scan/store/AcsSnapshotStore.scala | 30 +---- .../canton_network_test_log.ignore.txt | 4 - test-full-class-names-non-integration.log | 1 + 9 files changed, 259 insertions(+), 41 deletions(-) create mode 100644 apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLocks.scala create mode 100644 apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLocksTest.scala diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala index 1f11182896..9e72fdf48c 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTrigger.scala @@ -13,12 +13,14 @@ import com.digitalasset.canton.tracing.TraceContext import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import org.lfdecentralizedtrust.splice.automation.SqlIndexInitializationTrigger.IndexAction +import org.lfdecentralizedtrust.splice.store.db.AdvisoryLocks import org.lfdecentralizedtrust.splice.util.PrettyInstances.* import slick.dbio.{DBIOAction, Effect, NoStream} import slick.jdbc.canton.ActionBasedSQLInterpolation.Implicits.actionBasedSQLInterpolationCanton import java.util.concurrent.atomic.AtomicReference import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future, Promise} +import scala.util.{Failure, Success} /** A trigger that asynchronously creates or drops SQL indexes at application startup. * @@ -113,12 +115,12 @@ class SqlIndexInitializationTrigger( override protected def completeTask(task: SqlIndexInitializationTrigger.Task)(implicit tc: TraceContext - ): Future[TaskOutcome] = task match { + ): Future[TaskOutcome] = (task match { case Task.ExecuteAction(IndexAction.Drop(indexName)) => logger.info(s"Dropping index $indexName") storage - .update( - sqlu"drop index concurrently if exists #$indexName", + .queryAndUpdate( + AdvisoryLocks.withDdlLock(sqlu"drop index concurrently if exists #$indexName"), "drop_" + indexName, ) .unwrap @@ -130,7 +132,7 @@ class SqlIndexInitializationTrigger( case Task.ExecuteAction(IndexAction.Create(indexName, createAction)) => logger.info(s"Creating index $indexName") storage - .update(createAction, "create_" + indexName) + .queryAndUpdate(AdvisoryLocks.withDdlLock(createAction), "create_" + indexName) .unwrap .map { _ => logger.info(s"Finished creating index $indexName") @@ -144,6 +146,13 @@ class SqlIndexInitializationTrigger( } logger.info(s"Confirmed action completed for index ${action.indexName}") Future.successful(TaskSuccess(s"Confirmed action completed for index ${action.indexName}")) + }).transform { + case Failure(e: AdvisoryLocks.FailedToAcquireLockException) => + // There was a concurrent DDL statement running. + // The action stays in `remainingActions`, so we retry it on the next poll. + logger.info(s"Skipping $task, another DDL statement was running currently", e) + Success(TaskNoop) + case other => other } } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLockIds.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLockIds.scala index ea5543b472..58d789fea4 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLockIds.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLockIds.scala @@ -16,4 +16,5 @@ object AdvisoryLockIds { private val base: Long = 0x73706c00 final val acsSnapshotDataInsert: Long = base + 1 + final val ddlStatement: Long = base + 2 } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLocks.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLocks.scala new file mode 100644 index 0000000000..1201db3c21 --- /dev/null +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLocks.scala @@ -0,0 +1,63 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.store.db + +import slick.dbio.{DBIOAction, Effect, NoStream} +import slick.jdbc.JdbcProfile +import slick.jdbc.canton.ActionBasedSQLInterpolation.Implicits.actionBasedSQLInterpolationCanton + +import scala.concurrent.ExecutionContext + +object AdvisoryLocks { + final case class FailedToAcquireLockException( + lockType: String, + lockId: Long, + ) extends RuntimeException(s"Failed to acquire $lockType advisory lock $lockId.") + + private def withLock[T, E <: Effect](lockType: String, lockId: Long)( + acquire: DBIOAction[Boolean, NoStream, Effect.Read], + onAcquired: DBIOAction[T, NoStream, E], + )(implicit ec: ExecutionContext): DBIOAction[T, NoStream, Effect.Read & E] = + acquire.flatMap(acquired => + if (acquired) onAcquired + else DBIOAction.failed(FailedToAcquireLockException(lockType, lockId)) + ) + + private def acquireSessionLock(lockId: Long): DBIOAction[Boolean, NoStream, Effect.Read] = + sql"select pg_try_advisory_lock($lockId)".as[Boolean].head + + private def releaseSessionLock(lockId: Long): DBIOAction[Boolean, NoStream, Effect.Read] = + sql"select pg_advisory_unlock($lockId)".as[Boolean].head + + /** Wraps the given action in a session-scoped advisory lock; useful for acquiring locks for + * queries like DDL that can't run in a transaction. + */ + def withSessionLock[T, E <: Effect](lockId: Long, action: DBIOAction[T, NoStream, E])(implicit + ec: ExecutionContext + ): DBIOAction[T, NoStream, Effect.Read & E] = + withLock("session-scoped", lockId)( + acquireSessionLock(lockId), + action.andFinally(releaseSessionLock(lockId)), + ).withPinnedSession + + def withDdlLock[T, E <: Effect](action: DBIOAction[T, NoStream, E])(implicit + ec: ExecutionContext + ): DBIOAction[T, NoStream, Effect.Read & E] = + withSessionLock(AdvisoryLockIds.ddlStatement, action) + + private def acquireTransactionalLock(lockId: Long): DBIOAction[Boolean, NoStream, Effect.Read] = + sql"SELECT pg_try_advisory_xact_lock($lockId)".as[Boolean].head + + /** Wraps the given action in a transactional advisory lock. */ + def withTransactionalLock[T, E <: Effect]( + profile: JdbcProfile, + lockId: Long, + action: DBIOAction[T, NoStream, E], + )(implicit + ec: ExecutionContext + ): DBIOAction[T, NoStream, Effect.Read & Effect.Transactional & E] = { + import profile.api.jdbcActionExtensionMethods + withLock("transactional", lockId)(acquireTransactionalLock(lockId), action).transactionally + } +} diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala index 9afeefda60..0fbbcfe077 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/automation/SqlIndexInitializationTriggerStoreTest.scala @@ -4,6 +4,7 @@ import com.daml.metrics.api.noop.NoOpMetricsFactory import org.lfdecentralizedtrust.splice.config.AutomationConfig import org.lfdecentralizedtrust.splice.environment.RetryProvider import org.lfdecentralizedtrust.splice.store.{StoreErrors, StoreTestBase} +import org.lfdecentralizedtrust.splice.store.db.AdvisoryLocksTestHelper import com.digitalasset.canton.concurrent.{FutureSupervisor, Threading} import com.digitalasset.canton.lifecycle.FutureUnlessShutdown import com.digitalasset.canton.logging.SuppressionRule @@ -13,13 +14,18 @@ import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.util.MonadUtil import com.digitalasset.canton.{FutureHelpers, HasActorSystem, HasExecutionContext} import org.lfdecentralizedtrust.splice.automation.SqlIndexInitializationTrigger.IndexAction -import org.lfdecentralizedtrust.splice.store.db.{AcsJdbcTypes, AcsTables, SplicePostgresTest} +import org.lfdecentralizedtrust.splice.store.db.{ + AcsJdbcTypes, + AcsTables, + AdvisoryLocks, + SplicePostgresTest, +} import org.slf4j.event.Level import slick.dbio.DBIOAction import slick.jdbc.{GetResult, PositionedResult} import slick.jdbc.canton.ActionBasedSQLInterpolation.Implicits.actionBasedSQLInterpolationCanton -import scala.concurrent.Future +import scala.concurrent.{Future, Promise} class SqlIndexInitializationTriggerStoreTest extends StoreTestBase @@ -29,7 +35,15 @@ class SqlIndexInitializationTriggerStoreTest with SplicePostgresTest with AcsJdbcTypes with AcsTables - with FutureHelpers { + with FutureHelpers + with AdvisoryLocksTestHelper { + + private val expectedIndexNames = Seq( + "updt_hist_crea_hi_mi_ci_import_updates", + "updt_hist_tran_hi_eth", + "dso_acs_store_sid_mid_pn_tid_rbio", + "scan_txlog_store_sid_effat_en_vot", + ) "SqlIndexInitializationTrigger" should { @@ -56,12 +70,7 @@ class SqlIndexInitializationTriggerStoreTest indexNames <- listIndexNames() _ <- dumpIndexes() } yield { - indexNames should contain allElementsOf Seq( - "updt_hist_crea_hi_mi_ci_import_updates", - "updt_hist_tran_hi_eth", - "dso_acs_store_sid_mid_pn_tid_rbio", - "scan_txlog_store_sid_effat_en_vot", - ) + indexNames should contain allElementsOf expectedIndexNames indexNames should not contain "scan_txlog_store_sid_en_vot" } } @@ -342,6 +351,35 @@ class SqlIndexInitializationTriggerStoreTest tasksResult.value shouldBe empty } } + + "skip index DDL quietly while another process holds the advisory lock" in { + val trigger = SqlIndexInitializationTrigger( + storage = storage, + triggerContext = triggerContext, + indexActions = List( + IndexAction.Create( + "test_index", + sqlu"create index concurrently if not exists test_index on update_history_creates (record_time)", + ) + ), + ) + val releaseLock = Promise[Unit]() + val (lockAcquired, lockReleased) = + holdLock(AdvisoryLocks.withDdlLock, DBIOAction.unit, releaseLock.future) + + for { + _ <- lockAcquired + _ <- trigger.runOnce() + indexNamesWhileLocked <- listIndexNames() + _ = indexNamesWhileLocked should not contain "test_index" + // The contended action is still pending and succeeds once the lock is released + _ = releaseLock.success(()) + _ <- lockReleased + _ <- runTriggerUntilAllTasksDone(trigger) + indexNamesAfter <- listIndexNames() + } yield indexNamesAfter should contain("test_index") + } + } private def listIndexNames(): Future[Seq[String]] = { @@ -416,6 +454,7 @@ class SqlIndexInitializationTriggerStoreTest loggerFactory, NoOpMetricsFactory, ) + override protected def cleanDb( storage: DbStorage )(implicit traceContext: TraceContext): FutureUnlessShutdown[?] = for { diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLocksTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLocksTest.scala new file mode 100644 index 0000000000..e97c0d9485 --- /dev/null +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/db/AdvisoryLocksTest.scala @@ -0,0 +1,124 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.store.db + +import com.digitalasset.canton.lifecycle.FutureUnlessShutdown +import com.digitalasset.canton.resource.DbStorage +import com.digitalasset.canton.store.db.DbTest +import com.digitalasset.canton.tracing.TraceContext +import com.digitalasset.canton.HasExecutionContext +import org.lfdecentralizedtrust.splice.store.StoreTestBase +import slick.dbio.{DBIOAction, Effect, NoStream} +import slick.jdbc.canton.ActionBasedSQLInterpolation.Implicits.actionBasedSQLInterpolationCanton + +import scala.concurrent.{Future, Promise} + +trait AdvisoryLocksTestHelper { _: DbTest with StoreTestBase with HasExecutionContext => + + /** Acquires a lock by calling [[withLock]] and holds it until `release` completes. Returns a + * future that completes once the lock is held, and a future that completes once the lock has + * been released. + */ + final def holdLock( + withLock: DBIOAction[Unit, NoStream, Effect.All] => DBIOAction[Unit, NoStream, Effect.All], + action: DBIOAction[Unit, NoStream, Effect.All], + release: Future[Unit], + ): (Future[Unit], Future[Unit]) = { + val acquired = Promise[Unit]() + val released = storage.underlying + .queryAndUpdate( + withLock(action.map(_ => acquired.success(())).flatMap(_ => DBIOAction.from(release))), + "hold lock", + ) + .failOnShutdown + released.failed.foreach(acquired.tryFailure) + (acquired.future, released) + } +} + +class AdvisoryLocksTest + extends StoreTestBase + with HasExecutionContext + with SplicePostgresTest + with AcsJdbcTypes + with AcsTables + with AdvisoryLocksTestHelper { + + private val testTable = "a" + + private def commonTests( + lockType: String, + lockId: Long, + withLock: ( + Long, + DBIOAction[Unit, NoStream, Effect.All], + ) => DBIOAction[Unit, NoStream, Effect.All], + ) = { + "release the lock after running an action" in { + for { + _ <- storage.underlying + .queryAndUpdate(withLock(lockId, DBIOAction.unit), "test lock") + .failOnShutdown + lockIsFree <- lockIsFree(lockId) + } yield lockIsFree shouldBe true + } + + "release the lock after an action fails" in { + for { + failure <- storage.underlying + .queryAndUpdate( + withLock(lockId, DBIOAction.failed(new RuntimeException("error"))), + "test lock", + ) + .failOnShutdown + .failed + _ = failure shouldBe a[RuntimeException] + lockIsFree <- lockIsFree(lockId) + } yield lockIsFree shouldBe true + } + + "fail fast while another session holds the lock" in { + val releaseLock = Promise[Unit]() + val createTable = sqlu"create table #$testTable (id int)".map(_ => ()) + val (lockAcquired, lockReleased) = + holdLock(withLock(lockId, _), createTable, releaseLock.future) + for { + _ <- lockAcquired + failure <- storage.underlying + .queryAndUpdate(withLock(lockId, createTable), "contended query") + .failOnShutdown + .failed + _ = releaseLock.success(()) + _ <- lockReleased + } yield failure shouldBe AdvisoryLocks.FailedToAcquireLockException(lockType, lockId) + } + } + + "AdvisoryLocks.withSessionLock" should commonTests( + "session-scoped", + AdvisoryLockIds.ddlStatement, + AdvisoryLocks.withSessionLock, + ) + + "AdvisoryLocks.withTransactionalLock" should commonTests( + "transactional", + AdvisoryLockIds.acsSnapshotDataInsert, + AdvisoryLocks.withTransactionalLock(profile, _, _), + ) + + /** Whether [[lockId]] can be acquired, i.e. nothing is holding it. Session-scoped and + * transactional locks share one lock space, so a session-scoped check also detects a + * detects a transactional holder. + */ + private def lockIsFree(lockId: Long): Future[Boolean] = + storage.underlying + .query(AdvisoryLocks.withSessionLock(lockId, DBIOAction.successful(true)), "check lock") + .failOnShutdown + .recover { case _: AdvisoryLocks.FailedToAcquireLockException => false } + + override protected def cleanDb( + storage: DbStorage + )(implicit traceContext: TraceContext): FutureUnlessShutdown[?] = + storage.update(sqlu"drop table if exists #$testTable", s"drop $testTable") +} diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/AcsSnapshotTriggerBase.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/AcsSnapshotTriggerBase.scala index 063596de69..affced0084 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/AcsSnapshotTriggerBase.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/AcsSnapshotTriggerBase.scala @@ -26,6 +26,7 @@ import org.apache.pekko.stream.Materializer import org.lfdecentralizedtrust.splice.scan.config.ScanStorageConfig import org.lfdecentralizedtrust.splice.store.UpdateHistory import org.lfdecentralizedtrust.splice.store.HistoryMetrics.AcsSnapshotsMetrics +import org.lfdecentralizedtrust.splice.store.db.AdvisoryLocks import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} @@ -126,7 +127,7 @@ abstract class AcsSnapshotTriggerBase( case Success(result) => snapshotMetrics.waitingForLock.updateValue(0) Success(result) - case Failure(e: AcsSnapshotStore.FailedToAcquireLockException) => + case Failure(e: AdvisoryLocks.FailedToAcquireLockException) => // It is expected that we sometimes fail to acquire the lock on the snapshot table. // The time until the lock is released is typically much larger than our task retry timeouts, // so we can just silently skip the task and try again later. diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/AcsSnapshotStore.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/AcsSnapshotStore.scala index b5dfc209a0..c51128cd09 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/AcsSnapshotStore.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/AcsSnapshotStore.scala @@ -8,7 +8,6 @@ import com.daml.ledger.javaapi.data.CreatedEvent import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet.{Amulet, LockedAmulet} import org.lfdecentralizedtrust.splice.scan.store.AcsSnapshotStore.{ AcsSnapshot, - FailedToAcquireLockException, IncrementalAcsSnapshot, IncrementalAcsSnapshotTable, QueryAcsSnapshotResult, @@ -17,7 +16,12 @@ import org.lfdecentralizedtrust.splice.scan.store.AcsSnapshotStore.{ } import org.lfdecentralizedtrust.splice.store.UpdateHistory.SelectFromCreateEvents import org.lfdecentralizedtrust.splice.store.{HardLimit, Limit, LimitHelpers, UpdateHistory} -import org.lfdecentralizedtrust.splice.store.db.{AcsJdbcTypes, AcsQueries, AdvisoryLockIds} +import org.lfdecentralizedtrust.splice.store.db.{ + AcsJdbcTypes, + AcsQueries, + AdvisoryLocks, + AdvisoryLockIds, +} import org.lfdecentralizedtrust.splice.util.{Contract, HoldingsSummary, PackageQualifiedName} import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.lifecycle.{CloseContext, FutureUnlessShutdown} @@ -220,22 +224,7 @@ class AcsSnapshotStore( private def withExclusiveSnapshotDataLock[T, E <: Effect]( action: DBIOAction[T, NoStream, E] ): DBIOAction[T, NoStream, Effect.Read & Effect.Transactional & E] = - (for { - lockResult <- sql"SELECT pg_try_advisory_xact_lock(${AdvisoryLockIds.acsSnapshotDataInsert})" - .as[Boolean] - .head - result <- lockResult match { - case true => action - // Lock conflicts can happen: - // - In production, if the application crashes while writing a snapshot and then restarts - // and tries to write another snapshot before the database has closed the connection and released the lock. - // - In production, if two triggers (ingesting and backfilling) happen to write a snapshot at the same time. - // - In testing, where multiple scan instances write to the same app database. - // In all cases, we want to fail immediately, and rely on the caller's infrastructure to retry. - case false => - DBIOAction.failed(FailedToAcquireLockException()) - } - } yield result).transactionally + AdvisoryLocks.withTransactionalLock(profile, AdvisoryLockIds.acsSnapshotDataInsert, action) def deleteSnapshot( snapshot: AcsSnapshot @@ -799,11 +788,6 @@ class AcsSnapshotStore( object AcsSnapshotStore { - case class FailedToAcquireLockException() - extends RuntimeException( - "Failed to acquire advisory lock for writing to the acs snapshot table." - ) - sealed trait IncrementalAcsSnapshotTable { def tableName: String } object IncrementalAcsSnapshotTable { case object Next extends IncrementalAcsSnapshotTable { diff --git a/project/ignore-patterns/canton_network_test_log.ignore.txt b/project/ignore-patterns/canton_network_test_log.ignore.txt index 927af211f4..7fea8e8d43 100644 --- a/project/ignore-patterns/canton_network_test_log.ignore.txt +++ b/project/ignore-patterns/canton_network_test_log.ignore.txt @@ -129,10 +129,6 @@ Circuit breaker .* tripped after .* failures.*(Command|Splice)CircuitBreakerTest # This is more likely in test code where we run multiple apps against the same database, all trying to create the same index. Index .* should be created and is invalid, dropping it -# TODO(#4738) - remove this -processTaskWithRetry failed with an unknown exception, not retrying.*SqlIndexInitializationTrigger.*PSQLException: ERROR: tuple concurrently updated -Skipping processing of.*due to unexpected failure.*SqlIndexInitializationTrigger.*PSQLException: ERROR: tuple concurrently updated - # Injected errors in s3Mock Simulated S3 error diff --git a/test-full-class-names-non-integration.log b/test-full-class-names-non-integration.log index fc29c49e92..374d4d63f4 100644 --- a/test-full-class-names-non-integration.log +++ b/test-full-class-names-non-integration.log @@ -37,6 +37,7 @@ org.lfdecentralizedtrust.splice.store.TxLogBackfillingStoreTest org.lfdecentralizedtrust.splice.store.UpdateHistoryBackfillingTest org.lfdecentralizedtrust.splice.store.UpdateHistoryTest org.lfdecentralizedtrust.splice.store.db.AcsSnapshotStoreTest +org.lfdecentralizedtrust.splice.store.db.AdvisoryLocksTest org.lfdecentralizedtrust.splice.store.db.DbExternalPartyWalletStoreTest org.lfdecentralizedtrust.splice.store.db.DbMultiDomainAcsStoreTest org.lfdecentralizedtrust.splice.store.db.DbScanRewardsReferenceStoreTest From bbee4205f853c4adb6526f77bebc0a34669e83c6 Mon Sep 17 00:00:00 2001 From: Jaeyoon Cho Date: Tue, 11 Aug 2026 23:02:31 +0900 Subject: [PATCH 221/329] use consistent 'should' style in DbScanAppRewardsStoreTest (#6146) Signed-off-by: JYC11 --- .../store/DbScanAppRewardsStoreTest.scala | 548 +++++++++--------- 1 file changed, 271 insertions(+), 277 deletions(-) diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala index ec923cb82a..eabc35301b 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbScanAppRewardsStoreTest.scala @@ -36,11 +36,8 @@ class DbScanAppRewardsStoreTest private val migrationId = 0L - "DbScanAppRewardsStore" should { - - // -- Test 1: Insert and read back a single row per table ---------------- - - "insert and read back app_activity_party_totals" in { + "insert and read back" should { + "app_activity_party_totals" in { for { (store, historyId) <- newStore() row = AppActivityPartyTotalT( @@ -58,7 +55,7 @@ class DbScanAppRewardsStoreTest } } - "insert and read back app_activity_round_totals" in { + "app_activity_round_totals" in { for { (store, historyId) <- newStore() row = AppActivityRoundTotalT( @@ -75,7 +72,7 @@ class DbScanAppRewardsStoreTest } } - "insert and read back app_reward_party_totals" in { + "app_reward_party_totals" in { for { (store, historyId) <- newStore() activityRow = AppActivityPartyTotalT( @@ -147,7 +144,7 @@ class DbScanAppRewardsStoreTest } } - "insert and read back app_reward_round_totals" in { + "app_reward_round_totals" in { for { (store, historyId) <- newStore() row = AppRewardRoundTotalT( @@ -165,7 +162,7 @@ class DbScanAppRewardsStoreTest } } - "insert and read back app_reward_batch_hashes" in { + "app_reward_batch_hashes" in { for { (store, historyId) <- newStore() hash = RewardHash( @@ -187,7 +184,7 @@ class DbScanAppRewardsStoreTest } } - "insert and read back app_reward_root_hashes" in { + "app_reward_root_hashes" in { for { (store, historyId) <- newStore() hash = RewardHash(Array[Byte](0xca.toByte, 0xfe.toByte, 0xba.toByte, 0xbe.toByte)) @@ -202,10 +199,10 @@ class DbScanAppRewardsStoreTest loaded.value shouldBe row } } + } - // -- Test 2: Batch inserts ---------------------------------------------- - - "batch insert multiple app_activity_party_totals and spot-check" in { + "batch insert" should { + "batch multiple app_activity_party_totals and spot-check" in { for { (store, historyId) <- newStore() rows = (0 until 10).map { i => @@ -227,7 +224,7 @@ class DbScanAppRewardsStoreTest } } - "batch insert multiple app_reward_batch_hashes and spot-check" in { + "batch multiple app_reward_batch_hashes and spot-check" in { for { (store, historyId) <- newStore() rows = (0 until 5).map { i => @@ -249,10 +246,10 @@ class DbScanAppRewardsStoreTest loaded.last shouldBe rows(4) } } + } - // -- Test 5: Duplicate key handling (reject) ---------------------------- - - "reject duplicate app_activity_party_totals on PK conflict" in { + "duplicate key handling" should { + "duplicate app_activity_party_totals on PK conflict" in { for { (store, historyId) <- newStore() row = AppActivityPartyTotalT( @@ -270,7 +267,7 @@ class DbScanAppRewardsStoreTest } } - "reject duplicate app_activity_round_totals on PK conflict" in { + "duplicate app_activity_round_totals on PK conflict" in { for { (store, historyId) <- newStore() row = AppActivityRoundTotalT( @@ -288,7 +285,7 @@ class DbScanAppRewardsStoreTest } } - "reject duplicate app_reward_root_hashes on PK conflict" in { + "duplicate app_reward_root_hashes on PK conflict" in { for { (store, historyId) <- newStore() row = AppRewardRootHashT( @@ -307,10 +304,11 @@ class DbScanAppRewardsStoreTest result.getMessage should (include("unique constraint") or include("duplicate key")) } } + } - // -- Aggregation tests --------------------------------------------------- + "aggregateActivityTotals" should { - "aggregateActivityTotals — single round, single party" in { + "single round, single party" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, roundNumber) @@ -330,7 +328,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — multiple parties with correct GROUP BY and seq_nums" in { + "multiple parties with correct GROUP BY and seq_nums" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, roundNumber) @@ -371,7 +369,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — empty round produces zero totals" in { + "empty round produces zero totals" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, roundNumber) @@ -387,7 +385,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — only aggregates records from own history_id" in { + "only aggregates records from own history_id" in { for { (store1, historyId1) <- newStore() (_, historyId2) <- newStore() @@ -409,7 +407,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — re-run for same round raises error" in { + "re-run for same round raises error" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, roundNumber) @@ -421,7 +419,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — rejects the first ingested round (possibly partial)" in { + "rejects the first ingested round (possibly partial)" in { for { (store, historyId) <- newStore() // roundNumber is the first ingested round, which may be partial @@ -438,7 +436,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — rejects round whose OpenMiningRound is not yet archived" in { + "rejects round whose OpenMiningRound is not yet archived" in { for { (store, historyId) <- newStore() _ <- insertActivityMeta( @@ -454,7 +452,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — succeeds for round 0 when marked complete" in { + "succeeds for round 0 when marked complete" in { for { (store, historyId) <- newStore() _ <- markRoundComplete(historyId, 0L) @@ -468,7 +466,7 @@ class DbScanAppRewardsStoreTest } } - "aggregateActivityTotals — rejects round 0 when not marked complete" in { + "rejects round 0 when not marked complete" in { for { (store, historyId) <- newStore() _ <- insertActivityRecord(historyId, 0L, Seq("alice::provider"), Seq(500L)) @@ -478,212 +476,267 @@ class DbScanAppRewardsStoreTest result.getMessage should include("Incomplete app activity for round 0") } } + } - "roundsWithComputedRewards" should { + "roundsWithComputedRewards" should { - "returns empty set for empty input" in { - for { - (store, _) <- newStore() - result <- store.roundsWithComputedRewards(Seq.empty) - } yield { - result shouldBe Set.empty - } + "returns empty set for empty input" in { + for { + (store, _) <- newStore() + result <- store.roundsWithComputedRewards(Seq.empty) + } yield { + result shouldBe Set.empty } + } - "returns correct subset" in { - for { - (store, historyId) <- newStore() - _ <- store.insertAppRewardRootHashes( - Seq( - AppRewardRootHashT(historyId, 10L, RewardHash(Array[Byte](1, 2, 3, 4))), - AppRewardRootHashT(historyId, 20L, RewardHash(Array[Byte](5, 6, 7, 8))), - AppRewardRootHashT(historyId, 30L, RewardHash(Array[Byte](9, 10, 11, 12))), - ) + "returns correct subset" in { + for { + (store, historyId) <- newStore() + _ <- store.insertAppRewardRootHashes( + Seq( + AppRewardRootHashT(historyId, 10L, RewardHash(Array[Byte](1, 2, 3, 4))), + AppRewardRootHashT(historyId, 20L, RewardHash(Array[Byte](5, 6, 7, 8))), + AppRewardRootHashT(historyId, 30L, RewardHash(Array[Byte](9, 10, 11, 12))), ) - result <- store.roundsWithComputedRewards(Seq(10L, 15L, 20L, 25L)) - } yield { - result shouldBe Set(10L, 20L) - } + ) + result <- store.roundsWithComputedRewards(Seq(10L, 15L, 20L, 25L)) + } yield { + result shouldBe Set(10L, 20L) } + } - "returns empty set when no matches" in { - for { - (store, historyId) <- newStore() - _ <- store.insertAppRewardRootHashes( - Seq( - AppRewardRootHashT(historyId, 10L, RewardHash(Array[Byte](1, 2, 3, 4))) - ) + "returns empty set when no matches" in { + for { + (store, historyId) <- newStore() + _ <- store.insertAppRewardRootHashes( + Seq( + AppRewardRootHashT(historyId, 10L, RewardHash(Array[Byte](1, 2, 3, 4))) ) - result <- store.roundsWithComputedRewards(Seq(20L, 30L)) - } yield { - result shouldBe Set.empty - } + ) + result <- store.roundsWithComputedRewards(Seq(20L, 30L)) + } yield { + result shouldBe Set.empty } + } + } + + "computeAndStoreRewards" should { + "returns correct summary counts" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + // 3 activity records, 2 parties (alice in 2 records, bob in 2) + _ <- insertActivityRecord( + historyId, + roundNumber, + Seq("alice::provider", "bob::provider"), + Seq(3000000L, 2000000L), + ) + _ <- insertActivityRecord( + historyId, + roundNumber, + Seq("alice::provider"), + Seq(1000000L), + ) + _ <- insertActivityRecord( + historyId, + roundNumber, + Seq("bob::provider"), + Seq(500000L), + ) + summary <- store.computeAndStoreRewards( + roundNumber, + batchSize = 100, + testInputs, + ) + } yield { + summary.activePartiesCount shouldBe 2L + summary.activityRecordsCount shouldBe 4L // sum of per-party counts: alice=2 + bob=2 + summary.rewardedPartiesCount shouldBe 2L + summary.batchesCreatedCount should be >= 1L + } } - "computeAndStoreRewards" should { + "non-zero threshold excludes low-activity parties from rewards" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + // alice has high activity, bob has low activity + _ <- insertActivityRecord( + historyId, + roundNumber, + Seq("alice::provider", "bob::provider"), + Seq(5000000L, 50000L), + ) + // totalIssuanceForFeaturedAppRewards is 0.45, and alice gets almost all of it. Only alice will therefore be above the threshold of 0.4 + nonZeroThresholdInputs = testInputs.copy( + appRewardCouponThreshold = RewardComputationInputs.fromBigDecimal(BigDecimal("0.4")) + ) + summary <- store.computeAndStoreRewards( + roundNumber, + batchSize = 100, + nonZeroThresholdInputs, + ) + rewardPartyTotals <- store.getAppRewardPartyTotalsByRound(roundNumber) + } yield { + summary.activePartiesCount shouldBe 2L + summary.rewardedPartiesCount shouldBe 1L // only alice above threshold + rewardPartyTotals should have size 1 + rewardPartyTotals.head.appProviderParty shouldBe "alice::provider" + } + } - "returns correct summary counts" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - // 3 activity records, 2 parties (alice in 2 records, bob in 2) - _ <- insertActivityRecord( - historyId, - roundNumber, - Seq("alice::provider", "bob::provider"), - Seq(3000000L, 2000000L), - ) - _ <- insertActivityRecord( - historyId, - roundNumber, - Seq("alice::provider"), - Seq(1000000L), - ) - _ <- insertActivityRecord( - historyId, - roundNumber, - Seq("bob::provider"), - Seq(500000L), - ) - summary <- store.computeAndStoreRewards( - roundNumber, - batchSize = 100, - testInputs, - ) - } yield { - summary.activePartiesCount shouldBe 2L - summary.activityRecordsCount shouldBe 4L // sum of per-party counts: alice=2 + bob=2 - summary.rewardedPartiesCount shouldBe 2L - summary.batchesCreatedCount should be >= 1L - } + "empty round returns zero counts" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + summary <- store.computeAndStoreRewards( + roundNumber, + batchSize = 100, + testInputs, + ) + } yield { + summary.activePartiesCount shouldBe 0L + summary.activityRecordsCount shouldBe 0L + summary.rewardedPartiesCount shouldBe 0L + summary.batchesCreatedCount shouldBe 1L // empty root batch } + } - "non-zero threshold excludes low-activity parties from rewards" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - // alice has high activity, bob has low activity - _ <- insertActivityRecord( - historyId, - roundNumber, - Seq("alice::provider", "bob::provider"), - Seq(5000000L, 50000L), - ) - // totalIssuanceForFeaturedAppRewards is 0.45, and alice gets almost all of it. Only alice will therefore be above the threshold of 0.4 - nonZeroThresholdInputs = testInputs.copy( - appRewardCouponThreshold = RewardComputationInputs.fromBigDecimal(BigDecimal("0.4")) - ) - summary <- store.computeAndStoreRewards( - roundNumber, - batchSize = 100, - nonZeroThresholdInputs, - ) - rewardPartyTotals <- store.getAppRewardPartyTotalsByRound(roundNumber) - } yield { - summary.activePartiesCount shouldBe 2L - summary.rewardedPartiesCount shouldBe 1L // only alice above threshold - rewardPartyTotals should have size 1 - rewardPartyTotals.head.appProviderParty shouldBe "alice::provider" - } + "rejects incomplete activity" in { + for { + (store, historyId) <- newStore() + // Activity in roundNumber but no meta row marking the round complete + _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(500L)) + result <- store + .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + .failed + } yield { + result.getMessage should include("Incomplete app activity") } + } - "empty round returns zero counts" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - summary <- store.computeAndStoreRewards( - roundNumber, - batchSize = 100, - testInputs, - ) - } yield { - summary.activePartiesCount shouldBe 0L - summary.activityRecordsCount shouldBe 0L - summary.rewardedPartiesCount shouldBe 0L - summary.batchesCreatedCount shouldBe 1L // empty root batch - } + "produces root hash for complete round" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) + _ <- store.computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + rootHash <- store.getAppRewardRootHashByRound(roundNumber) + activityTotals <- store.getAppActivityRoundTotalByRound(roundNumber) + } yield { + // Pipeline completed: activity aggregated and root hash produced + rootHash shouldBe defined + rootHash.value.rootHash.size shouldBe 32 + activityTotals shouldBe defined + activityTotals.value.totalRoundAppActivityWeight shouldBe 5000000L } + } + "re-run for same round raises error" in { + for { + (store, historyId) <- newStore() + _ <- markRoundComplete(historyId, roundNumber) + _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) + _ <- store.computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + result <- store + .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + .failed + } yield { + result shouldBe a[Exception] + } } - // -- assertMintingAllowanceWithinMintingCurve tests -------------------------------- - // Tested directly with fake round totals because normal computation - // cannot trigger the assertion — the tranche formula guarantees - // totalReward <= totalIssuance. This check is a safety net for bugs. + "rolls back when reward exceeds issuance" in { + for { + // Use a negative tolerance so any positive reward triggers the assertion + (store, historyId) <- newStore(rewardMintingAllowanceTolerance = BigDecimal(-1.0)) + _ <- markRoundComplete(historyId, roundNumber) + _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) + result <- store + .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) + .failed + } yield { + result.getMessage should include("exceeds minting curve allowance") + } + } + } - "assertMintingAllowanceWithinMintingCurve" should { + // Tested directly with fake round totals because normal computation + // cannot trigger the assertion — the tranche formula guarantees + // totalReward <= totalIssuance. This check is a safety net for bugs. + "assertMintingAllowanceWithinMintingCurve" should { + + def mkParams(totalIssuance: BigDecimal): RewardIssuanceParams = + RewardIssuanceParams( + issuancePerFeaturedAppTraffic_CCperMB = BigDecimal(0), + threshold_CC = BigDecimal(0), + totalIssuanceForFeaturedAppRewards = totalIssuance, + unclaimedAppRewardAmount = BigDecimal(0), + ) - def mkParams(totalIssuance: BigDecimal): RewardIssuanceParams = - RewardIssuanceParams( - issuancePerFeaturedAppTraffic_CCperMB = BigDecimal(0), - threshold_CC = BigDecimal(0), - totalIssuanceForFeaturedAppRewards = totalIssuance, - unclaimedAppRewardAmount = BigDecimal(0), + def insertRoundTotal(historyId: Long, round: Long, amount: BigDecimal): Future[Unit] = + futureUnlessShutdownToFuture( + storage.underlying.queryAndUpdate( + sqlu"""insert into app_reward_round_totals + (history_id, round_number, total_app_reward_minting_allowance, + total_app_reward_thresholded, total_app_reward_unclaimed, + rewarded_app_provider_parties_count) + values ($historyId, $round, $amount, 0, 0, 1)""".map(_ => ()), + "test.insertRoundTotal", ) + ) - def insertRoundTotal(historyId: Long, round: Long, amount: BigDecimal): Future[Unit] = - futureUnlessShutdownToFuture( - storage.underlying.queryAndUpdate( - sqlu"""insert into app_reward_round_totals - (history_id, round_number, total_app_reward_minting_allowance, - total_app_reward_thresholded, total_app_reward_unclaimed, - rewarded_app_provider_parties_count) - values ($historyId, $round, $amount, 0, 0, 1)""".map(_ => ()), - "test.insertRoundTotal", + "pass when reward amount is within issuance" in { + for { + (store, historyId) <- newStore() + _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0)) + _ <- futureUnlessShutdownToFuture( + storage.queryAndUpdate( + store + .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), + "test.assertMintingAllowanceWithinMintingCurve", ) ) + } yield succeed + } - "pass when reward amount is within issuance" in { - for { - (store, historyId) <- newStore() - _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0)) - _ <- futureUnlessShutdownToFuture( - storage.queryAndUpdate( - store - .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), - "test.assertMintingAllowanceWithinMintingCurve", - ) + "fail when reward amount exceeds issuance by more than tolerance" in { + for { + (store, historyId) <- newStore() + // Reward exceeds issuance by 2x tolerance (0.002 > 0.001) + _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.002)) + result <- futureUnlessShutdownToFuture( + storage.queryAndUpdate( + store.assertMintingAllowanceWithinMintingCurve( + roundNumber, + mkParams(BigDecimal(10.0)), + ), + "test.assertMintingAllowanceWithinMintingCurve", ) - } yield succeed - } - - "fail when reward amount exceeds issuance by more than tolerance" in { - for { - (store, historyId) <- newStore() - // Reward exceeds issuance by 2x tolerance (0.002 > 0.001) - _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.002)) - result <- futureUnlessShutdownToFuture( - storage.queryAndUpdate( - store.assertMintingAllowanceWithinMintingCurve( - roundNumber, - mkParams(BigDecimal(10.0)), - ), - "test.assertMintingAllowanceWithinMintingCurve", - ) - ).failed - } yield { - result.getMessage should include("exceeds minting curve allowance") - } + ).failed + } yield { + result.getMessage should include("exceeds minting curve allowance") } + } - "pass when reward amount exceeds issuance within tolerance" in { - for { - (store, historyId) <- newStore() - _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0005)) - _ <- futureUnlessShutdownToFuture( - storage.queryAndUpdate( - store - .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), - "test.assertMintingAllowanceWithinMintingCurve", - ) + "pass when reward amount exceeds issuance within tolerance" in { + for { + (store, historyId) <- newStore() + _ <- insertRoundTotal(historyId, roundNumber, BigDecimal(10.0005)) + _ <- futureUnlessShutdownToFuture( + storage.queryAndUpdate( + store + .assertMintingAllowanceWithinMintingCurve(roundNumber, mkParams(BigDecimal(10.0))), + "test.assertMintingAllowanceWithinMintingCurve", ) - } yield succeed - } + ) + } yield succeed } + } - // -- computeRewardTotals tests ------------------------------------------- + "computeRewardTotals" should { val rewardTotalsTestCases = Seq( // 5_000_000 / 1_000_000 * 2.0 = 10.0 @@ -808,74 +861,15 @@ class DbScanAppRewardsStoreTest ) rewardTotalsTestCases.foreach { tc => - s"computeRewardTotals — ${tc.description}" in { + tc.description in { RewardTotalsTests.run(tc) } } + } - // -- computeAndStoreRewards tests ------------------------------------------ - - "computeAndStoreRewards — rejects incomplete activity" in { - for { - (store, historyId) <- newStore() - // Activity in roundNumber but no meta row marking the round complete - _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(500L)) - result <- store - .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - .failed - } yield { - result.getMessage should include("Incomplete app activity") - } - } - - "computeAndStoreRewards — produces root hash for complete round" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) - _ <- store.computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - rootHash <- store.getAppRewardRootHashByRound(roundNumber) - activityTotals <- store.getAppActivityRoundTotalByRound(roundNumber) - } yield { - // Pipeline completed: activity aggregated and root hash produced - rootHash shouldBe defined - rootHash.value.rootHash.size shouldBe 32 - activityTotals shouldBe defined - activityTotals.value.totalRoundAppActivityWeight shouldBe 5000000L - } - } - - "computeAndStoreRewards — re-run for same round raises error" in { - for { - (store, historyId) <- newStore() - _ <- markRoundComplete(historyId, roundNumber) - _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) - _ <- store.computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - result <- store - .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - .failed - } yield { - result shouldBe a[Exception] - } - } - - "computeAndStoreRewards — rolls back when reward exceeds issuance" in { - for { - // Use a negative tolerance so any positive reward triggers the assertion - (store, historyId) <- newStore(rewardMintingAllowanceTolerance = BigDecimal(-1.0)) - _ <- markRoundComplete(historyId, roundNumber) - _ <- insertActivityRecord(historyId, roundNumber, Seq("alice::provider"), Seq(5000000L)) - result <- store - .computeAndStoreRewards(roundNumber, batchSize = 100, inputs = testInputs) - .failed - } yield { - result.getMessage should include("exceeds minting curve allowance") - } - } - - // -- computeRewardHashes tests -------------------------------------------- + "computeRewardHashes" should { - "computeRewardHashes — 3 activity parties, 2 rewarded, batchSize=2" in { + "3 activity parties, 2 rewarded, batchSize=2" in { // 2 rewarded parties fit in 1 batch of size 2 for { (_, batchHashes, _) <- setupAndComputeHashes( @@ -891,7 +885,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — single party produces single leaf batch" in { + "single party produces single leaf batch" in { for { (_, batchHashes, _) <- setupAndComputeHashes(partyCount = 1, batchSize = 100) } yield { @@ -901,7 +895,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — 3 levels: 9 parties, batchSize=2" in { + "3 levels: 9 parties, batchSize=2" in { // batchSize=2 → level 0: 5 batches, level 1: 3, level 2: 2 for { (_, batchHashes, _) <- setupAndComputeHashes(partyCount = 9, batchSize = 2) @@ -918,7 +912,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — exact boundary: 4 parties, batchSize=2" in { + "exact boundary: 4 parties, batchSize=2" in { // batchSize=2 → level 0: 2 batches, level 1: 1 batch (aggregation stops) for { (_, batchHashes, _) <- setupAndComputeHashes(partyCount = 4, batchSize = 2) @@ -932,7 +926,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — all parties fit in one batch, no aggregation" in { + "all parties fit in one batch, no aggregation" in { // 3 parties, batchSize=100 → single leaf batch, no aggregation for { (_, batchHashes, rootHash) <- setupAndComputeHashes(partyCount = 3, batchSize = 100) @@ -947,7 +941,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — root hash exists after multi-level aggregation" in { + "root hash exists after multi-level aggregation" in { for { (store, _, _) <- setupAndComputeHashes(partyCount = 5, batchSize = 2) computed <- store.roundsWithComputedRewards(Seq(roundNumber)) @@ -960,7 +954,7 @@ class DbScanAppRewardsStoreTest ("no activity parties", 0, -1), ("activity but no rewarded parties", 1, 0), ).foreach { case (desc, parties, rewarded) => - s"computeRewardHashes — $desc produces empty root hash" in { + s"$desc produces empty root hash" in { for { (store, batchHashes, rootHash) <- setupAndComputeHashes( partyCount = parties, @@ -982,7 +976,7 @@ class DbScanAppRewardsStoreTest } } - "computeRewardHashes — re-run for same round raises error" in { + "re-run for same round raises error" in { for { (store, _, _) <- setupAndComputeHashes(partyCount = 1, batchSize = 100) result <- store.computeRewardHashes(roundNumber, batchSize = 100).failed @@ -990,10 +984,11 @@ class DbScanAppRewardsStoreTest result shouldBe a[Exception] } } + } - // -- lookupBatchByHash tests ---------------------------------------------- + "lookupBatchByHash" should { - "lookupBatchByHash — returns None for non-existent hash" in { + "returns None for non-existent hash" in { for { (store, _) <- newStore() result <- store.lookupBatchByHash( @@ -1005,7 +1000,7 @@ class DbScanAppRewardsStoreTest } } - "lookupBatchByHash — leaf batch returns MintingAllowances" in { + "leaf batch returns MintingAllowances" in { for { (store, historyId) <- newStore() _ <- store.insertAppActivityPartyTotals( @@ -1037,7 +1032,7 @@ class DbScanAppRewardsStoreTest } } - "lookupBatchByHash — internal batch returns BatchOfBatches" in { + "internal batch returns BatchOfBatches" in { for { (store, historyId) <- newStore() // 4 parties / batchSize=2 → 2 level-0 batches → 1 level-1 batch @@ -1078,7 +1073,6 @@ class DbScanAppRewardsStoreTest childHashes shouldBe level0Hashes } } - } private val verdictCounter = new java.util.concurrent.atomic.AtomicLong(1) @@ -1344,7 +1338,7 @@ object DbScanAppRewardsStoreTest { * trafficPrice/amuletPrice = 1.0 so 1 MB of traffic = 1 CC of reward. */ val testInputs: RewardComputationInputs = { - import RewardComputationInputs.{fromBigDecimal as n} + import RewardComputationInputs.fromBigDecimal as n val tickDurationMicros = 600L * 1000000L val microsPerYear = 365L * 24 * 3600 * 1000000L val roundsPerYear = BigDecimal(microsPerYear) / BigDecimal(tickDurationMicros) From 4f34b25c3fe2f2b21d5b2c74ccd260e1d2e86a5a Mon Sep 17 00:00:00 2001 From: Jaeyoon Cho Date: Wed, 12 Aug 2026 06:53:07 +0900 Subject: [PATCH 222/329] fix sv network banner to always show (#6087) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jaeyoon Cho Signed-off-by: JYC11 Signed-off-by: Paweł Perek Co-authored-by: Paweł Perek --- apps/common/frontend/src/theme/index.ts | 3 ++ apps/sv/frontend/src/__tests__/sv.test.tsx | 7 ++++ apps/sv/frontend/src/components/Layout.tsx | 6 ++- .../src/components/layout/NetworkBanner.tsx | 34 ++++++++++++++++ .../components/layout/SvNavigationShell.tsx | 39 ++++++++++--------- .../src/hooks/useNetworkInstanceName.ts | 27 +++++++------ apps/sv/frontend/src/theme/tokens.ts | 8 +--- docs/src/release_notes_upcoming.rst | 4 ++ 8 files changed, 89 insertions(+), 39 deletions(-) create mode 100644 apps/sv/frontend/src/components/layout/NetworkBanner.tsx diff --git a/apps/common/frontend/src/theme/index.ts b/apps/common/frontend/src/theme/index.ts index 382e6a43fe..686eb1c6d3 100644 --- a/apps/common/frontend/src/theme/index.ts +++ b/apps/common/frontend/src/theme/index.ts @@ -37,6 +37,7 @@ declare module '@mui/material/styles' { testnet: string; devnet: string; scratchnet: string; + localnet: string; }; } // allow configuration using `createTheme` @@ -51,6 +52,7 @@ declare module '@mui/material/styles' { testnet: string; devnet: string; scratchnet: string; + localnet: string; }; } } @@ -97,6 +99,7 @@ let theme = createTheme({ testnet: '#C8F1FE', devnet: '#C6B2FF', scratchnet: '#FFFFFF', + localnet: '#BDC9DB', }, }, }); diff --git a/apps/sv/frontend/src/__tests__/sv.test.tsx b/apps/sv/frontend/src/__tests__/sv.test.tsx index fa6a398bc1..a687d97714 100644 --- a/apps/sv/frontend/src/__tests__/sv.test.tsx +++ b/apps/sv/frontend/src/__tests__/sv.test.tsx @@ -41,6 +41,13 @@ describe('SV user can', () => { expect(await screen.findAllByDisplayValue(svPartyId)).toBeDefined(); }); + test('can see the network name banner', async () => { + userEvent.setup(); + render(); + + await screen.findByText('You are on ScratchNet'); + }); + test('browse to the validator onboarding tab', async () => { const user = userEvent.setup(); render(); diff --git a/apps/sv/frontend/src/components/Layout.tsx b/apps/sv/frontend/src/components/Layout.tsx index caee956cc7..bbfca185e7 100644 --- a/apps/sv/frontend/src/components/Layout.tsx +++ b/apps/sv/frontend/src/components/Layout.tsx @@ -6,13 +6,14 @@ import { Loading, useUserState, useVotesHooks } from '@canton-network/splice-com import { Box, Container, GlobalStyles } from '@mui/material'; import { useLocation } from 'react-router'; +import { useFeatureSupport } from '../contexts/SvContext'; +import { useSvConfig } from '../utils'; import { partyIdScrollGlobalStyles } from './beta/identifierStyles'; import PartyIdScrollTracks from './PartyIdScrollTracks'; import SvNavigationShell from './layout/SvNavigationShell'; import { SvNavLinkItem } from './layout/SvNavLink'; -import { useFeatureSupport } from '../contexts/SvContext'; import { CONTENT_MAX_WIDTH, layoutTokens, PAGE_PX } from '../theme/tokens'; -import { useSvConfig } from '../utils'; +import NetworkBanner from './layout/NetworkBanner'; interface LayoutProps { children: React.ReactNode; @@ -75,6 +76,7 @@ const Layout: React.FC = ({ children }) => { + diff --git a/apps/sv/frontend/src/components/layout/NetworkBanner.tsx b/apps/sv/frontend/src/components/layout/NetworkBanner.tsx new file mode 100644 index 0000000000..9564059df7 --- /dev/null +++ b/apps/sv/frontend/src/components/layout/NetworkBanner.tsx @@ -0,0 +1,34 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as React from 'react'; + +import { Stack, Typography } from '@mui/material'; + +import { useNetworkInstanceName } from '../../hooks'; + +const NetworkBanner: React.FC = () => { + const networkInstanceName = useNetworkInstanceName(); + const knownColors = ['mainnet', 'testnet', 'devnet', 'scratchnet', 'localnet']; + const networkInstanceNameColor = knownColors.includes(networkInstanceName.toLowerCase()) + ? `colors.${networkInstanceName.toLowerCase()}` + : 'colors.neutral.30'; + return ( + + + You are on {networkInstanceName} + + + ); +}; + +export default NetworkBanner; diff --git a/apps/sv/frontend/src/components/layout/SvNavigationShell.tsx b/apps/sv/frontend/src/components/layout/SvNavigationShell.tsx index 0d17b031ef..d2a6c3974a 100644 --- a/apps/sv/frontend/src/components/layout/SvNavigationShell.tsx +++ b/apps/sv/frontend/src/components/layout/SvNavigationShell.tsx @@ -15,27 +15,28 @@ interface SvNavigationShellProps { } /** - * Figma "Navigation" component — nav row (network banner to be restored later). + * Figma "Navigation" component — network banner above the nav row. * Dev Mode: padding-bottom 64px, background #272727. - * `HEADER_PT` is temporary breathing room until the banner returns. */ -const SvNavigationShell: React.FC = ({ navLinks, onLogout, pageName }) => ( - - - +const SvNavigationShell: React.FC = ({ navLinks, onLogout, pageName }) => { + return ( + + + + - -); + ); +}; export default SvNavigationShell; diff --git a/apps/sv/frontend/src/hooks/useNetworkInstanceName.ts b/apps/sv/frontend/src/hooks/useNetworkInstanceName.ts index 8a8294b71a..06dd7c795d 100644 --- a/apps/sv/frontend/src/hooks/useNetworkInstanceName.ts +++ b/apps/sv/frontend/src/hooks/useNetworkInstanceName.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { useDsoInfos } from '../contexts/SvContext'; -export const useNetworkInstanceName: () => string | undefined = () => { +export const useNetworkInstanceName: () => string = () => { const dsoInfosQuery = useDsoInfos(); const scanUrls = dsoInfosQuery.data?.nodeStates.flatMap(nsContract => { @@ -13,11 +13,15 @@ export const useNetworkInstanceName: () => string | undefined = () => { }) as string[]; if (scanUrls === undefined) { - return undefined; + return 'Unknown Network'; } const instances = scanUrls .map(url => { + if (/\/\/localhost(?::\d+)?(?:\/|$)/.test(url)) { + return 'local'; + } + const regex = /(?<=\/\/(?:scan\.)sv-\d+\.)([a-zA-Z0-9-]+)/; return url.match(regex)?.[1]; @@ -28,22 +32,21 @@ export const useNetworkInstanceName: () => string | undefined = () => { return getNetworkName(instances[0]); } - return undefined; + return 'Unknown Network'; }; const getNetworkName = (network: string) => { - let networkName; - // NOTE: mainnet does not have the network/cluster name in the url. if (network === 'global') { - networkName = 'MainNet'; + return 'MainNet'; } else if (network === 'test') { - networkName = 'TestNet'; + return 'TestNet'; } else if (network === 'dev') { - networkName = 'DevNet'; - } else if (network?.startsWith('scratch')) { - networkName = 'ScratchNet'; + return 'DevNet'; + } else if (network === 'local') { + return 'LocalNet'; + } else if (network.startsWith('scratch')) { + return 'ScratchNet'; } - - return networkName; + return network.charAt(0).toUpperCase() + network.slice(1); }; diff --git a/apps/sv/frontend/src/theme/tokens.ts b/apps/sv/frontend/src/theme/tokens.ts index 7f3dcee073..5f6932c142 100644 --- a/apps/sv/frontend/src/theme/tokens.ts +++ b/apps/sv/frontend/src/theme/tokens.ts @@ -28,15 +28,11 @@ export const NAV_PILL_PX = '10px'; /** Figma content max width (nav row is full width; content uses this) */ export const CONTENT_MAX_WIDTH = 1583; -/** - * Temporary top padding while NetworkBanner is omitted (matches old 50px banner height). - * Remove when the banner is restored (e.g. with #6087). - */ -export const HEADER_PT = '50px'; - /** Figma Dev Mode — 64px space below nav row, present on every page */ export const HEADER_PB = 8; +export const HEADER_PT = 3; + /** Figma Dev Mode — fixed 145px gap between brand wordmark and nav cluster. */ export const NAV_BRAND_GAP = '145px'; diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 5ea32a4b9a..a434d4f026 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -21,3 +21,7 @@ vote request is open, matching what the ledger allows. This makes it possible to reject a proposal whose action fails to execute and which would otherwise remain in flight indefinitely. + + - Network banner now always shows. The network name is derived from the scan node's public URL: + MainNet/TestNet/DevNet/ScratchNet from the cluster subdomain, LocalNet for localhost, and a + capitalized fallback otherwise (Unknown Network when no scan URL is available). From 2984f04e5b551000f6cb738b743c771f8a34367c Mon Sep 17 00:00:00 2001 From: Jaeyoon Cho Date: Wed, 12 Aug 2026 12:07:56 +0900 Subject: [PATCH 223/329] SV UI: More descriptive names for Reward Config (#6578) Signed-off-by: Tim Emiola Signed-off-by: JYC11 Signed-off-by: Jaeyoon Cho Co-authored-by: Tim Emiola Co-authored-by: Divam <681060+dfordivam@users.noreply.github.com> Co-authored-by: Simon Meier --- .../forms/set-amulet-rules-form.test.tsx | 47 +++++++++++++ .../buildAmuletRulesConfigFromChanges.test.ts | 6 +- .../form-components/ConfigField.tsx | 66 ++++++++++++++----- .../src/utils/buildAmuletConfigChanges.ts | 26 +++++++- apps/sv/frontend/src/utils/types.ts | 8 +++ docs/src/release_notes_upcoming.rst | 7 +- 6 files changed, 138 insertions(+), 22 deletions(-) diff --git a/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx index ff010751fd..d12a78ac96 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx @@ -245,6 +245,53 @@ describe('Set Amulet Config Rules Form', () => { expect(changes.length).toBe(2); }); + test('reward config minting scheme renders as a dropdown', async () => { + const user = userEvent.setup(); + + render( + + + + ); + + // Minting scheme should render as a Select, not a TextField + const mintingField = screen.getByTestId('config-field-rewardConfigMintingVersion'); + expect(mintingField).toBeInTheDocument(); + const selectInput = mintingField.querySelector('[role="combobox"]') as HTMLElement; + expect(selectInput).toBeInTheDocument(); + + // Open dropdown and verify options + await user.click(selectInput); + expect(screen.getByText('Featured App Markers (pre CIP-104)')).toBeInTheDocument(); + expect(screen.getByText('Traffic-Based App Rewards (CIP-104)')).toBeInTheDocument(); + + // Select an option + await user.click(screen.getByText('Traffic-Based App Rewards (CIP-104)')); + + // Verify current value is shown after change + const currentValue = screen.getByTestId('config-current-value-rewardConfigMintingVersion'); + expect(currentValue).toBeInTheDocument(); + }); + + test('reward config dry-run scheme includes None option', async () => { + const user = userEvent.setup(); + + render( + + + + ); + + const dryRunField = screen.getByTestId('config-field-rewardConfigDryRunVersion'); + expect(dryRunField).toBeInTheDocument(); + const selectInput = dryRunField.querySelector('[role="combobox"]') as HTMLElement; + expect(selectInput).toBeInTheDocument(); + + // Open dropdown and verify None option exists + await user.click(selectInput); + expect(screen.getByText('None (disabled)')).toBeInTheDocument(); + }); + test('should show proposal review page after form completion', async () => { const user = userEvent.setup(); diff --git a/apps/sv/frontend/src/__tests__/utils/buildAmuletRulesConfigFromChanges.test.ts b/apps/sv/frontend/src/__tests__/utils/buildAmuletRulesConfigFromChanges.test.ts index a0bea6e3d9..1035962057 100644 --- a/apps/sv/frontend/src/__tests__/utils/buildAmuletRulesConfigFromChanges.test.ts +++ b/apps/sv/frontend/src/__tests__/utils/buildAmuletRulesConfigFromChanges.test.ts @@ -220,19 +220,19 @@ describe('buildAmuletRulesConfigFromChanges', () => { }, { fieldName: 'rewardConfigMintingVersion', - label: 'Reward config: Minting version', + label: 'Reward config: Minting scheme', currentValue: 'RewardVersion_FeaturedAppMarkers', newValue: 'RewardVersion_TrafficBasedAppRewards', }, { fieldName: 'rewardConfigDryRunVersion', - label: 'Reward config: Dry-run version', + label: 'Reward config: Dry-run minting scheme', currentValue: '', newValue: 'RewardVersion_TrafficBasedAppRewards', }, { fieldName: 'rewardConfigBatchSize', - label: 'Reward config: Batch size', + label: 'Reward config: Merkle tree batch size', currentValue: '100', newValue: '200', }, diff --git a/apps/sv/frontend/src/components/form-components/ConfigField.tsx b/apps/sv/frontend/src/components/form-components/ConfigField.tsx index fc9b09b44d..de91c9786b 100644 --- a/apps/sv/frontend/src/components/form-components/ConfigField.tsx +++ b/apps/sv/frontend/src/components/form-components/ConfigField.tsx @@ -2,7 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 import { Link as RouterLink } from 'react-router'; -import { Box, Divider, TextField as MuiTextField, Typography } from '@mui/material'; +import { + Box, + Divider, + FormControl, + MenuItem, + Select, + TextField as MuiTextField, + Typography, +} from '@mui/material'; import dayjs from 'dayjs'; import relativeTime from 'dayjs/plugin/relativeTime'; import { useFieldContext } from '../../hooks/formContext'; @@ -95,21 +103,49 @@ export const ConfigField: React.FC = props => { {configChange.fieldName} - - - field.handleChange({ - fieldName: configChange.fieldName, - value: e.target.value, - }) - } - /> + {configChange.options ? ( + + + + ) : ( + + field.handleChange({ + fieldName: configChange.fieldName, + value: e.target.value, + }) + } + /> + )} + + {configChange.description && ( + + {configChange.description} + + )} {!field.state.meta.isDefaultValue && ( ; + +const rewardVersionOptions = RewardVersion.keys.map(value => ({ + value, + label: rewardVersionLabels[value], +})); + function buildRewardConfigChanges( before: RewardConfig | null | undefined, after: RewardConfig | null | undefined @@ -327,33 +338,42 @@ function buildRewardConfigChanges( return [ { fieldName: 'rewardConfigMintingVersion', - label: 'Reward config: Minting version', + label: 'Reward config: Reward scheme', currentValue: before?.mintingVersion || '', newValue: after?.mintingVersion || '', + options: rewardVersionOptions, + description: 'Which reward scheme to use in production.', }, { fieldName: 'rewardConfigDryRunVersion', - label: 'Reward config: Dry-run version', + label: 'Reward config: Dry-run reward scheme', currentValue: before?.dryRunVersion || '', newValue: after?.dryRunVersion || '', + options: [{ value: '', label: 'None (disabled)' }, ...rewardVersionOptions], + description: + 'Which reward scheme to run in dry-run mode. Select "None (disabled)" to turn it off.', }, { fieldName: 'rewardConfigBatchSize', - label: 'Reward config: Batch size', + label: 'Reward config: Merkle tree batch size', currentValue: before?.batchSize || '', newValue: after?.batchSize || '', + description: 'Batch size for building the Merkle tree over minting allowances (default: 100)', }, { fieldName: 'rewardConfigRewardCouponTimeToLive', label: 'Reward config: Reward coupon time to live (microseconds)', currentValue: before?.rewardCouponTimeToLive.microseconds || '', newValue: after?.rewardCouponTimeToLive.microseconds || '', + description: 'Time-to-live for RewardCouponV2 contracts (default: 36 hours)', }, { fieldName: 'rewardConfigAppRewardCouponThreshold', label: 'Reward config: App reward coupon threshold ($)', currentValue: before?.appRewardCouponThreshold || '', newValue: after?.appRewardCouponThreshold || '', + description: + 'Minimum reward amount in USD below which no RewardCouponV2 is created (default: $0.50)', }, ] as ConfigChange[]; } diff --git a/apps/sv/frontend/src/utils/types.ts b/apps/sv/frontend/src/utils/types.ts index e7b3377f7f..b30b3cc598 100644 --- a/apps/sv/frontend/src/utils/types.ts +++ b/apps/sv/frontend/src/utils/types.ts @@ -63,6 +63,14 @@ export interface ConfigChange { * If the field should be disabled for editing. */ disabled?: boolean; + /** + * If set, render as a dropdown with these options instead of free text. + */ + options?: { value: string; label: string }[]; + /** + * Optional description shown as help text below the field. + */ + description?: string; } export interface UpdateSvRewardWeightProposal { diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index a434d4f026..17d5625652 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -21,7 +21,12 @@ vote request is open, matching what the ledger allows. This makes it possible to reject a proposal whose action fails to execute and which would otherwise remain in flight indefinitely. - + - Network banner now always shows. The network name is derived from the scan node's public URL: MainNet/TestNet/DevNet/ScratchNet from the cluster subdomain, LocalNet for localhost, and a capitalized fallback otherwise (Unknown Network when no scan URL is available). + + - SV UI + + - During the creation of ``AmuletRules_SetConfig`` proposal, for the ``rewardConfig`` + config field form improve the field descriptions and use drop-downs. From bf5edc767e3c94b76ef9455e0aa7b80b99dfa7c7 Mon Sep 17 00:00:00 2001 From: Martin Florian Date: Wed, 12 Aug 2026 12:03:31 +0200 Subject: [PATCH 224/329] Fix GCP log-based upgrade alerts (#6752) Apparently they changed something and the old query doesn't give signal anymore. I picked the new query based on looking at logs... and looking at historic data it looks right and not too spammy. Signed-off-by: Martin Florian --- cluster/expected/observability/expected.json | 2 +- cluster/pulumi/observability/src/gcpAlerts.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 35ccb1602f..7ec78fdef3 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -699,7 +699,7 @@ "id": "", "inputs": { "description": "Logs with ClusterUpdate events", - "filter": "\nresource.labels.cluster_name=\"cn-mocknet\"\nresource.type=~\"(gke_cluster|gke_nodepool)\"\njsonPayload.state=~\"STARTED\"", + "filter": "\nresource.labels.cluster_name=\"cn-mocknet\"\nresource.type=~\"(gke_cluster|gke_nodepool)\"\njsonPayload.@type=~\"UpgradeEvent\"", "labelExtractors": { "cluster": "EXTRACT(resource.labels.cluster_name)" }, diff --git a/cluster/pulumi/observability/src/gcpAlerts.ts b/cluster/pulumi/observability/src/gcpAlerts.ts index 3214c172a5..0c7ab73a8a 100644 --- a/cluster/pulumi/observability/src/gcpAlerts.ts +++ b/cluster/pulumi/observability/src/gcpAlerts.ts @@ -210,7 +210,7 @@ export function installClusterMaintenanceUpdateAlerts( filter: ` resource.labels.cluster_name="${CLUSTER_NAME}" resource.type=~"(gke_cluster|gke_nodepool)" -jsonPayload.state=~"STARTED"`, +jsonPayload.@type=~"UpgradeEvent"`, labelExtractors: { cluster: 'EXTRACT(resource.labels.cluster_name)', }, From 107a92670e3e01f3e49cfe8dbcb1e2997a220532 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Wed, 12 Aug 2026 12:30:45 +0200 Subject: [PATCH 225/329] Upgrade Canton to 3.5.13-snapshot.20260811.19159.0.va77f0cc3 (#6753) Has the fixed metrics. [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index 00cd4ebb3f..bd940930ca 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.13-snapshot.20260809.19149.0.v1b50fc03", - "oss_sha256": "sha256:1vsbqcsv5srcglh8wyn6awiygwicr69jvh0gdfbrb47q2mv6nk5a", - "canton_base_image_sha256": "sha256:954329e7c556607afcbb1e9b8913cd2fe859cd0a52986e4f1905c7cc5a35670d", - "canton_participant_image_sha256": "sha256:54822a7e367a14ba96cbc4c8a7516e28671d8d525241c5c44471c0d2886b9a10", - "canton_mediator_image_sha256": "sha256:a321fca163dbdabb954ba60d242fccfe2ef76c9622ba1da2c6ca9e0a3e44c137", - "canton_sequencer_image_sha256": "sha256:a3ee3e0e9e191f6390110e5d7cfbda3f2e0d7aea9d11988b847cf56f9f463daf" + "version": "3.5.13-snapshot.20260811.19159.0.va77f0cc3", + "oss_sha256": "sha256:1j0m0q3b83k6z5ay9qmdvijvswh29bppxsr8w2mc8kck8prx3sqs", + "canton_base_image_sha256": "sha256:103a57fccc1ccb7edf12f75b6382d205df82e6ebe2574c6b5e63db8fefc621da", + "canton_participant_image_sha256": "sha256:74e3c1d7cc1f19f9f10261e932613217245f55be774c78eb65c8470da887902d", + "canton_mediator_image_sha256": "sha256:79728083e2e01074a7d2c740ea5db26c12d808702c37e6500198d649683ea9f4", + "canton_sequencer_image_sha256": "sha256:dcfbf19a240d89df7aa33b0403e84e46eb0e3e1b77b82fc97d0c430e3be53da4" } From ba5c6323d6028280cf7d17ba75b26d5fb4bb73dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 12 Aug 2026 12:31:05 +0200 Subject: [PATCH 226/329] Fix rewardWeightBps specified with '_' breaking js_yaml >= 4.2 (#6749) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Signed-off-by: Oriol Muñoz --- .../TestNet/approved-sv-id-values.yaml | 8 +++---- cluster/expected/sv-runbook/expected.json | 12 +--------- cluster/expected/sv/expected.json | 24 ++----------------- .../common-sv/src/approvedIdentities.ts | 22 ++++++++++++++--- 4 files changed, 26 insertions(+), 40 deletions(-) diff --git a/cluster/configs/configs/configs/TestNet/approved-sv-id-values.yaml b/cluster/configs/configs/configs/TestNet/approved-sv-id-values.yaml index 197bfcd0bd..2e4293b683 100644 --- a/cluster/configs/configs/configs/TestNet/approved-sv-id-values.yaml +++ b/cluster/configs/configs/configs/TestNet/approved-sv-id-values.yaml @@ -1,13 +1,13 @@ approvedSvIdentities: - name: Digital-Asset-2 publicKey: PUBKEY_0== - rewardWeightBps: 100000 + rewardWeightBps: 100_000 - name: SV1 publicKey: PUBLIC_KEY_1== - rewardWeightBps: 1000000 + rewardWeightBps: 1_000_000 - name: SV2 publicKey: PUBLIC_KEY_2== - rewardWeightBps: 150000 + rewardWeightBps: 150_000 extraBeneficiaries: - beneficiary: "mock-validator-1::123456789012345678901234567890123456789012234567890123456789012345678" weight: 100000 @@ -16,4 +16,4 @@ approvedSvIdentities: - name: Digital-Asset-1 publicKey: THIS_IS_THE_WRONG_KEY # this is the right reward weight though - rewardWeightBps: 150000 + rewardWeightBps: 150_000 diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index 800ea3b326..253a40b837 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -221,7 +221,7 @@ }, "approvedSvIdentities": { "type": "md5", - "value": "5871224b744b45122540483fddbd550f" + "value": "6c876b92df2d92fe7ba83e883d031386" } }, "network": "test", @@ -1413,16 +1413,6 @@ "rewardWeightBps": 1000000 }, { - "extraBeneficiaries": [ - { - "beneficiary": "mock-validator-1::123456789012345678901234567890123456789012234567890123456789012345678", - "weight": 100000 - }, - { - "beneficiary": "Broadridge-validator-1::1220c89a73e7b47f16dfb48a521eeedfe2a8620ea1b19b815ab427388d9f5427faa5", - "weight": 50000 - } - ], "name": "SV2", "publicKey": "PUBLIC_KEY_2==", "rewardWeightBps": 150000 diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index a4e4d4a147..357082cc3f 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -2559,7 +2559,7 @@ }, "approvedSvIdentities": { "type": "md5", - "value": "5871224b744b45122540483fddbd550f" + "value": "6c876b92df2d92fe7ba83e883d031386" } }, "network": "test", @@ -4192,16 +4192,6 @@ "rewardWeightBps": 1000000 }, { - "extraBeneficiaries": [ - { - "beneficiary": "mock-validator-1::123456789012345678901234567890123456789012234567890123456789012345678", - "weight": 100000 - }, - { - "beneficiary": "Broadridge-validator-1::1220c89a73e7b47f16dfb48a521eeedfe2a8620ea1b19b815ab427388d9f5427faa5", - "weight": 50000 - } - ], "name": "SV2", "publicKey": "PUBLIC_KEY_2==", "rewardWeightBps": 150000 @@ -4874,7 +4864,7 @@ }, "approvedSvIdentities": { "type": "md5", - "value": "5871224b744b45122540483fddbd550f" + "value": "6c876b92df2d92fe7ba83e883d031386" } }, "network": "test", @@ -6537,16 +6527,6 @@ "rewardWeightBps": 1000000 }, { - "extraBeneficiaries": [ - { - "beneficiary": "mock-validator-1::123456789012345678901234567890123456789012234567890123456789012345678", - "weight": 100000 - }, - { - "beneficiary": "Broadridge-validator-1::1220c89a73e7b47f16dfb48a521eeedfe2a8620ea1b19b815ab427388d9f5427faa5", - "weight": 50000 - } - ], "name": "SV2", "publicKey": "PUBLIC_KEY_2==", "rewardWeightBps": 150000 diff --git a/cluster/pulumi/common-sv/src/approvedIdentities.ts b/cluster/pulumi/common-sv/src/approvedIdentities.ts index 61944dd782..cf5a1bbb10 100644 --- a/cluster/pulumi/common-sv/src/approvedIdentities.ts +++ b/cluster/pulumi/common-sv/src/approvedIdentities.ts @@ -19,11 +19,21 @@ export type ApprovedSvIdentity = { rewardWeightBps: number; }; +export type ApprovedSvIdentityFromYamlFile = { + name: string; + publicKey: string | pulumi.Output; + // js-yaml after 4.2 doesn't support specifying numbers with separating underscores (e.g. 100_000), + // which means that it will parse that as a string and break the on Helm schema validation. + // This is compliant with the yaml spec, which doesn't define numbers with underscores as possible. + // For normal numbers (e.g 100000), it still gets parsed as a number. + rewardWeightBps: string | number; +}; + export function approvedSvIdentitiesFile(): string | undefined { return getPathToPublicConfigFile('approved-sv-id-values.yaml'); } -function approvedSvIdentitiesFromFile(): ApprovedSvIdentity[] { +function approvedSvIdentitiesFromFile(): ApprovedSvIdentityFromYamlFile[] { const file = approvedSvIdentitiesFile(); return file ? loadYamlFromFile(file).approvedSvIdentities : []; } @@ -48,7 +58,12 @@ function approvedSvIdentitiesFromConfig(): ApprovedSvIdentity[] { } export function approvedSvIdentities(): ApprovedSvIdentity[] { - const fromFile = approvedSvIdentitiesFromFile(); + const rawFromFile = approvedSvIdentitiesFromFile(); + const fromFile: ApprovedSvIdentity[] = rawFromFile.map(identity => ({ + name: identity.name, + rewardWeightBps: parseInt(String(identity.rewardWeightBps).replace('_', ''), 10), + publicKey: identity.publicKey, + })); const fromConfig = approvedSvIdentitiesFromConfig(); // We override public keys to the locally configured one, @@ -60,7 +75,8 @@ export function approvedSvIdentities(): ApprovedSvIdentity[] { ); return _.uniqBy([...fromFile, ...fromConfig], 'name').map(identity => ({ - ...identity, + name: identity.name, + rewardWeightBps: identity.rewardWeightBps, publicKey: configuredPublicKeys[identity.name] ?? identity.publicKey, })); } From 572252523bb20a65c58734ddc3f78856880a0194 Mon Sep 17 00:00:00 2001 From: Jagath Weerasinghe Date: Wed, 12 Aug 2026 15:18:31 +0200 Subject: [PATCH 227/329] adjust_perf_test_benchmarks (#6761) Signed-off-by: Jagath Weerasinghe --- .github/store-perf-thresholds.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/store-perf-thresholds.json b/.github/store-perf-thresholds.json index fda469d37b..aa2a4b14fc 100644 --- a/.github/store-perf-thresholds.json +++ b/.github/store-perf-thresholds.json @@ -22,7 +22,7 @@ "UpdateHistoryIngestionPerformanceTest": { "_comment_avg_item_time_ns": "median*(1+ noise_margin + safety_margin) of last 30d. noise_margin=(p95-median)/median, safety_margin=10%. calculated on 2026-June-25", "splice_perf_ingestion_avg_item_time_ns": { - "max": 16100000 + "max": 27400000 }, "_comment_total_time_ns": "our hard max is 1h, but we set it to 20m based on the past data collected on 2026-June-25 to detect the trends earlier", "splice_perf_ingestion_total_time_ns": { From 859f75285d5336d982bfc9c4b9dd9c3e128ac9b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Wed, 12 Aug 2026 15:21:27 +0200 Subject: [PATCH 228/329] Allow arbitrary labels in GKE node config (#6758) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- cluster/pulumi/cluster/src/config.ts | 1 + cluster/pulumi/cluster/src/nodePools.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/cluster/pulumi/cluster/src/config.ts b/cluster/pulumi/cluster/src/config.ts index 9a65b4c92f..f988e2d9d8 100644 --- a/cluster/pulumi/cluster/src/config.ts +++ b/cluster/pulumi/cluster/src/config.ts @@ -9,6 +9,7 @@ const GkeNodePoolConfigSchema = z.object({ nodeType: z.string(), bootDiskSizeGb: z.number().optional(), zones: z.literal('*').or(z.array(z.string())).optional(), + labels: z.record(z.string(), z.string()).optional(), }); const GkeClusterConfigSchema = z.object({ nodePools: z.object({ diff --git a/cluster/pulumi/cluster/src/nodePools.ts b/cluster/pulumi/cluster/src/nodePools.ts index 6ea9b32dc0..bcaaab6170 100644 --- a/cluster/pulumi/cluster/src/nodePools.ts +++ b/cluster/pulumi/cluster/src/nodePools.ts @@ -74,6 +74,7 @@ function installAppsNodePools( ], labels: { cn_apps: 'hyperdisk', + ...config.labels, }, loggingVariant: 'DEFAULT', }, From 19b81a2259f106b7486f840a234c96e77598875b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 12 Aug 2026 15:23:36 +0200 Subject: [PATCH 229/329] Ignore stream restart warnings on shutdown (#6760) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci] Signed-off-by: Oriol Muñoz --- project/ignore-patterns/canton_network_test_log.ignore.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/project/ignore-patterns/canton_network_test_log.ignore.txt b/project/ignore-patterns/canton_network_test_log.ignore.txt index 7fea8e8d43..2212671dc0 100644 --- a/project/ignore-patterns/canton_network_test_log.ignore.txt +++ b/project/ignore-patterns/canton_network_test_log.ignore.txt @@ -134,6 +134,10 @@ Simulated S3 error # Shutdown issues Previous channel ManagedChannelImpl.* was garbage collected without being shut down! +# This is logged by pekko's RestartSource. Ideally we'd prevent it from logging that when shutting down, +# but it doesn't offer a clean way of doing that. Lowering log level also doesn't seem sensible, +# as we don't want to miss non-shutdown warnings in production. +Restarting stream due to failure.*Channel shutdown invoked # TODO(#564) - check what's happening here during shutdown Timeout 10 seconds expired, but readers are still active. Shutting down forcibly.*LsuIntegrationTest From 9a2e338e9151eba8f26cf7983a3bfe731c28c8fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 12 Aug 2026 15:29:28 +0200 Subject: [PATCH 230/329] Fix more LOCAL_VERDICT_INACTIVE_CONTRACTS in TestTokenV2SettlementIntegrationTest (#6759) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes https://github.com/DACH-NY/cn-test-failures/issues/9517 same logic as https://github.com/canton-network/splice/pull/6432 Signed-off-by: Oriol Muñoz --- ...TestTokenV2SettlementIntegrationTest.scala | 427 +++++++++--------- 1 file changed, 218 insertions(+), 209 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala index c61fbbeb86..135645a921 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TestTokenV2SettlementIntegrationTest.scala @@ -407,236 +407,245 @@ class TestTokenV2SettlementIntegrationTest val (aliceAllocationCids, aliceAllocateTx) = clue( "Alice uses the BatchingUtilityV2 to create two allocations and accept the allocation request in a single tx" ) { - val batchingUtility = batchingUtilityIds(aliceParty) - val aliceAmulets = aliceWalletClient - .list() - .amulets - .map(_.contract.contractId.toInterface(holdingv2.Holding.INTERFACE)) - val amuletSpec = aliceAllocationRequest.contract.payload.allocations.asScala - .filter(_.admin == dsoParty.toProtoPrimitive) - .loneElement - val usdcSpec = aliceAllocationRequest.contract.payload.allocations.asScala - .filter(_.admin == ttAdminParty.toProtoPrimitive) - .loneElement - val amuletAllocationFactory = sv1ScanBackend.getAllocationFactoryV2( - new allocationinstructionv2.AllocationFactory_Allocate( - aliceAllocationRequest.contract.payload.settlement, - amuletSpec, - aliceAllocationRequest.contract.payload.requestedAt, - aliceAmulets.asJava, - emptyExtraArgs, - java.util.List.of(aliceParty.toProtoPrimitive), + // UpdateExternalPartyConfigStateTrigger might run concurrently and cause a LOCAL_VERDICT_INACTIVE_CONTRACTS + // because of the ExternalPartyConfigState being updated. + // In the real world, we expect the venue to also just retry re-fetching all contexts + eventuallySucceeds() { + val batchingUtility = batchingUtilityIds(aliceParty) + val aliceAmulets = aliceWalletClient + .list() + .amulets + .map(_.contract.contractId.toInterface(holdingv2.Holding.INTERFACE)) + val amuletSpec = aliceAllocationRequest.contract.payload.allocations.asScala + .filter(_.admin == dsoParty.toProtoPrimitive) + .loneElement + val usdcSpec = aliceAllocationRequest.contract.payload.allocations.asScala + .filter(_.admin == ttAdminParty.toProtoPrimitive) + .loneElement + val amuletAllocationFactory = sv1ScanBackend.getAllocationFactoryV2( + new allocationinstructionv2.AllocationFactory_Allocate( + aliceAllocationRequest.contract.payload.settlement, + amuletSpec, + aliceAllocationRequest.contract.payload.requestedAt, + aliceAmulets.asJava, + emptyExtraArgs, + java.util.List.of(aliceParty.toProtoPrimitive), + ) ) - ) - val usdcContext = registry.getContext(Seq.empty) - val aliceAllocateUpdate = batchingUtility - .exerciseBatchingUtility_ExecuteBatch( - new HoldingMap( - Map( - new ScopedAccount( - dsoParty.toProtoPrimitive, - basicAccount(aliceParty), - ) -> Map[String, java.util.List[holdingv2.Holding.ContractId]]( - amuletInstrumentIdName -> aliceAmulets.asJava - ).asJava, - new ScopedAccount( - ttAdminParty.toProtoPrimitive, - basicAccount(aliceParty), - ) -> Map - .empty[String, java.util.List[holdingv2.Holding.ContractId]] - .asJava, // alice has no USDC here yet - ).asJava - ), - java.util.List.of( - new TSA_AllocationFactory_AllocateV2( - new ChoiceCall[AllocationFactory_Allocate]( - new metadatav1.AnyContract.ContractId( - amuletAllocationFactory.factoryId.contractId - ), - amuletAllocationFactory.args, - ) - ), - new TSA_AllocationFactory_AllocateV2( - new ChoiceCall[AllocationFactory_Allocate]( - new metadatav1.AnyContract.ContractId(tokenRulesId.contractId), - new allocationinstructionv2.AllocationFactory_Allocate( - aliceAllocationRequest.contract.payload.settlement, - usdcSpec, - aliceAllocationRequest.contract.payload.requestedAt, - java.util.List.of(), - new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), - java.util.List.of(aliceParty.toProtoPrimitive), - ), - ) + val usdcContext = registry.getContext(Seq.empty) + val aliceAllocateUpdate = batchingUtility + .exerciseBatchingUtility_ExecuteBatch( + new HoldingMap( + Map( + new ScopedAccount( + dsoParty.toProtoPrimitive, + basicAccount(aliceParty), + ) -> Map[String, java.util.List[holdingv2.Holding.ContractId]]( + amuletInstrumentIdName -> aliceAmulets.asJava + ).asJava, + new ScopedAccount( + ttAdminParty.toProtoPrimitive, + basicAccount(aliceParty), + ) -> Map + .empty[String, java.util.List[holdingv2.Holding.ContractId]] + .asJava, // alice has no USDC here yet + ).asJava ), - new TSA_AllocationRequest_AcceptV2( - new ChoiceCall[AllocationRequest_Accept]( - new metadatav1.AnyContract.ContractId( - aliceAllocationRequest.contract.contractId.contractId - ), - new AllocationRequest_Accept( - java.util.List.of(aliceParty.toProtoPrimitive), - amuletAllocationFactory.args.extraArgs, - ), - ) + java.util.List.of( + new TSA_AllocationFactory_AllocateV2( + new ChoiceCall[AllocationFactory_Allocate]( + new metadatav1.AnyContract.ContractId( + amuletAllocationFactory.factoryId.contractId + ), + amuletAllocationFactory.args, + ) + ), + new TSA_AllocationFactory_AllocateV2( + new ChoiceCall[AllocationFactory_Allocate]( + new metadatav1.AnyContract.ContractId(tokenRulesId.contractId), + new allocationinstructionv2.AllocationFactory_Allocate( + aliceAllocationRequest.contract.payload.settlement, + usdcSpec, + aliceAllocationRequest.contract.payload.requestedAt, + java.util.List.of(), + new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), + java.util.List.of(aliceParty.toProtoPrimitive), + ), + ) + ), + new TSA_AllocationRequest_AcceptV2( + new ChoiceCall[AllocationRequest_Accept]( + new metadatav1.AnyContract.ContractId( + aliceAllocationRequest.contract.contractId.contractId + ), + new AllocationRequest_Accept( + java.util.List.of(aliceParty.toProtoPrimitive), + amuletAllocationFactory.args.extraArgs, + ), + ) + ), ), - ), - true, - ) - val aliceAllocateTx = - aliceValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.commands - .submitJava( - userId = aliceValidatorBackend.config.ledgerApiUser, - actAs = Seq(aliceParty), - readAs = Seq(aliceParty), - commands = aliceAllocateUpdate.commands().asScala.toSeq, - disclosedContracts = - amuletAllocationFactory.disclosedContracts ++ usdcContext.disclosedContracts, + true, ) + val aliceAllocateTx = + aliceValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.commands + .submitJava( + userId = aliceValidatorBackend.config.ledgerApiUser, + actAs = Seq(aliceParty), + readAs = Seq(aliceParty), + commands = aliceAllocateUpdate.commands().asScala.toSeq, + disclosedContracts = + amuletAllocationFactory.disclosedContracts ++ usdcContext.disclosedContracts, + ) - val aliceAllocationCids = SpliceLedgerConnection - .decodeExerciseResult( - aliceAllocateUpdate, - aliceAllocateTx, - ) - .exerciseResult - .actionResults - .asScala - .map { - case _: TSAR_AllocationRequest_AcceptV2Result => None - case v: TSAR_AllocationInstructionResultV2 => - v.allocationInstructionResultValue.output match { - case completed: AllocationInstructionResult_Completed => - Some(completed.allocationCid) - case other => fail(s"Expected AllocationInstructionResult_Completed but got $other") - } - case other => - fail(s"Expected TSAR_AllocationResultV2 but got $other") - } - .collect { case Some(cid) => cid } + val aliceAllocationCids = SpliceLedgerConnection + .decodeExerciseResult( + aliceAllocateUpdate, + aliceAllocateTx, + ) + .exerciseResult + .actionResults + .asScala + .map { + case _: TSAR_AllocationRequest_AcceptV2Result => None + case v: TSAR_AllocationInstructionResultV2 => + v.allocationInstructionResultValue.output match { + case completed: AllocationInstructionResult_Completed => + Some(completed.allocationCid) + case other => + fail(s"Expected AllocationInstructionResult_Completed but got $other") + } + case other => + fail(s"Expected TSAR_AllocationResultV2 but got $other") + } + .collect { case Some(cid) => cid } - (aliceAllocationCids, aliceAllocateTx) + (aliceAllocationCids, aliceAllocateTx) + } } val (bobAllocationCids, bobAllocateTx) = clue( "Bob uses the BatchingUtilityV2 to accept the request and create two allocations in a single tx" ) { - val batchingUtility = batchingUtilityIds(bobParty) - val amuletSpec = bobAllocationRequest.contract.payload.allocations.asScala - .filter(_.admin == dsoParty.toProtoPrimitive) - .loneElement - val usdcSpec = bobAllocationRequest.contract.payload.allocations.asScala - .filter(_.admin == ttAdminParty.toProtoPrimitive) - .loneElement - val amuletAllocationFactory = sv1ScanBackend.getAllocationFactoryV2( - new allocationinstructionv2.AllocationFactory_Allocate( - bobAllocationRequest.contract.payload.settlement, - amuletSpec, - bobAllocationRequest.contract.payload.requestedAt, - java.util.List.of(), // bob has no amulets - emptyExtraArgs, - java.util.List.of(bobParty.toProtoPrimitive), + // Same UpdateExternalPartyConfigStateTrigger/LOCAL_VERDICT_INACTIVE_CONTRACTS logic + // as with alice's usage of BatchingUtilityV2 above. + eventuallySucceeds() { + val batchingUtility = batchingUtilityIds(bobParty) + val amuletSpec = bobAllocationRequest.contract.payload.allocations.asScala + .filter(_.admin == dsoParty.toProtoPrimitive) + .loneElement + val usdcSpec = bobAllocationRequest.contract.payload.allocations.asScala + .filter(_.admin == ttAdminParty.toProtoPrimitive) + .loneElement + val amuletAllocationFactory = sv1ScanBackend.getAllocationFactoryV2( + new allocationinstructionv2.AllocationFactory_Allocate( + bobAllocationRequest.contract.payload.settlement, + amuletSpec, + bobAllocationRequest.contract.payload.requestedAt, + java.util.List.of(), // bob has no amulets + emptyExtraArgs, + java.util.List.of(bobParty.toProtoPrimitive), + ) ) - ) - val bobUsdcHoldings = getHoldings(bobParty, bobValidatorBackend) - .map(_.contractId) - .map(id => new holdingv2.Holding.ContractId(id)) - val usdcContext = registry.getContext( - bobUsdcHoldings - ) - val bobAllocateUpdate = batchingUtility - .exerciseBatchingUtility_ExecuteBatch( - new HoldingMap( - Map( - new ScopedAccount( - dsoParty.toProtoPrimitive, - basicAccount(bobParty), - ) -> Map[String, java.util.List[holdingv2.Holding.ContractId]]().asJava, - new ScopedAccount( - ttAdminParty.toProtoPrimitive, - basicAccount(bobParty), - ) -> Map[String, java.util.List[holdingv2.Holding.ContractId]]( - usdcInstrumentName -> bobUsdcHoldings.asJava - ).asJava, // alice has no USDC here yet - ).asJava - ), - java.util.List.of( - new TSA_AllocationFactory_AllocateV2( - new ChoiceCall[AllocationFactory_Allocate]( - new metadatav1.AnyContract.ContractId( - amuletAllocationFactory.factoryId.contractId - ), - amuletAllocationFactory.args, - ) - ), - new TSA_AllocationFactory_AllocateV2( - new ChoiceCall[AllocationFactory_Allocate]( - new metadatav1.AnyContract.ContractId(tokenRulesId.contractId), - new allocationinstructionv2.AllocationFactory_Allocate( - bobAllocationRequest.contract.payload.settlement, - usdcSpec, - bobAllocationRequest.contract.payload.requestedAt, - java.util.List.of(), - new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), - java.util.List.of(bobParty.toProtoPrimitive), - ), - ) + val bobUsdcHoldings = getHoldings(bobParty, bobValidatorBackend) + .map(_.contractId) + .map(id => new holdingv2.Holding.ContractId(id)) + val usdcContext = registry.getContext( + bobUsdcHoldings + ) + val bobAllocateUpdate = batchingUtility + .exerciseBatchingUtility_ExecuteBatch( + new HoldingMap( + Map( + new ScopedAccount( + dsoParty.toProtoPrimitive, + basicAccount(bobParty), + ) -> Map[String, java.util.List[holdingv2.Holding.ContractId]]().asJava, + new ScopedAccount( + ttAdminParty.toProtoPrimitive, + basicAccount(bobParty), + ) -> Map[String, java.util.List[holdingv2.Holding.ContractId]]( + usdcInstrumentName -> bobUsdcHoldings.asJava + ).asJava, // alice has no USDC here yet + ).asJava ), - new TSA_AllocationRequest_AcceptV2( - new ChoiceCall[AllocationRequest_Accept]( - new metadatav1.AnyContract.ContractId( - bobAllocationRequest.contract.contractId.contractId - ), - new AllocationRequest_Accept( - java.util.List.of(bobParty.toProtoPrimitive), - new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), - ), - ) + java.util.List.of( + new TSA_AllocationFactory_AllocateV2( + new ChoiceCall[AllocationFactory_Allocate]( + new metadatav1.AnyContract.ContractId( + amuletAllocationFactory.factoryId.contractId + ), + amuletAllocationFactory.args, + ) + ), + new TSA_AllocationFactory_AllocateV2( + new ChoiceCall[AllocationFactory_Allocate]( + new metadatav1.AnyContract.ContractId(tokenRulesId.contractId), + new allocationinstructionv2.AllocationFactory_Allocate( + bobAllocationRequest.contract.payload.settlement, + usdcSpec, + bobAllocationRequest.contract.payload.requestedAt, + java.util.List.of(), + new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), + java.util.List.of(bobParty.toProtoPrimitive), + ), + ) + ), + new TSA_AllocationRequest_AcceptV2( + new ChoiceCall[AllocationRequest_Accept]( + new metadatav1.AnyContract.ContractId( + bobAllocationRequest.contract.contractId.contractId + ), + new AllocationRequest_Accept( + java.util.List.of(bobParty.toProtoPrimitive), + new metadatav1.ExtraArgs(usdcContext.choiceContext, emptyMetadata), + ), + ) + ), ), - ), - true, - ) - val bobAllocateTx = - bobValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.commands - .submitJava( - userId = bobValidatorBackend.config.ledgerApiUser, - actAs = Seq(bobParty), - readAs = Seq(bobParty), - commands = bobAllocateUpdate.commands().asScala.toSeq, - disclosedContracts = - amuletAllocationFactory.disclosedContracts ++ usdcContext.disclosedContracts, + true, ) + val bobAllocateTx = + bobValidatorBackend.participantClientWithAdminToken.ledger_api_extensions.commands + .submitJava( + userId = bobValidatorBackend.config.ledgerApiUser, + actAs = Seq(bobParty), + readAs = Seq(bobParty), + commands = bobAllocateUpdate.commands().asScala.toSeq, + disclosedContracts = + amuletAllocationFactory.disclosedContracts ++ usdcContext.disclosedContracts, + ) - val bobAllocationCids = SpliceLedgerConnection - .decodeExerciseResult( - bobAllocateUpdate, - bobAllocateTx, - ) - .exerciseResult - .actionResults - .asScala - .map { - case _: TSAR_AllocationRequest_AcceptV2Result => None - case v: TSAR_AllocationInstructionResultV2 => - v.allocationInstructionResultValue.output match { - case completed: AllocationInstructionResult_Completed => - Some(completed.allocationCid) - case other => - fail(s"Expected AllocationInstructionResult_Completed but got $other") - } - case other => - fail(s"Expected TSAR_AllocationResultV2 but got $other") - } - .collect { case Some(cid) => cid } + val bobAllocationCids = SpliceLedgerConnection + .decodeExerciseResult( + bobAllocateUpdate, + bobAllocateTx, + ) + .exerciseResult + .actionResults + .asScala + .map { + case _: TSAR_AllocationRequest_AcceptV2Result => None + case v: TSAR_AllocationInstructionResultV2 => + v.allocationInstructionResultValue.output match { + case completed: AllocationInstructionResult_Completed => + Some(completed.allocationCid) + case other => + fail(s"Expected AllocationInstructionResult_Completed but got $other") + } + case other => + fail(s"Expected TSAR_AllocationResultV2 but got $other") + } + .collect { case Some(cid) => cid } - (bobAllocationCids, bobAllocateTx) + (bobAllocationCids, bobAllocateTx) + } } val (settleTradeTx, _) = actAndCheck( "Venue settles the trade", { - // UpdateExternalPartyConfigStateTrigger might run concurrently and cause a LOCAL_VERDICT_INACTIVE_CONTRACTS - // because of the ExternalPartyConfigState being updated. - // In the real world, we expect the venue to also just retry re-fetching all contexts + // Same UpdateExternalPartyConfigStateTrigger/LOCAL_VERDICT_INACTIVE_CONTRACTS logic + // as with alice's usage of BatchingUtilityV2 above. eventuallySucceeds() { val allAllocations = { venueValidator.participantClientWithAdminToken.ledger_api.state.acs.of_party( From 99dd0783b3b31c3d1dc75877cd948f2f82ab35b3 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Wed, 12 Aug 2026 15:31:00 +0200 Subject: [PATCH 231/329] Auto-ignore party when no amulet version is vetted (#6747) Signed-off-by: Julien Tinguely --- ...reUnresponsivePartiesIntegrationTest.scala | 2 +- ...MinimalVettedPackagesIntegrationTest.scala | 91 +++++++++++---- ...hedMultiDomainExpiredContractTrigger.scala | 21 +++- .../DsoDelegateBasedAutomationService.scala | 23 ++-- .../ExpireRewardCouponV2Trigger.scala | 19 ++- .../ExpireRewardCouponsTrigger.scala | 7 +- .../ExpireTransferPreapprovalsTrigger.scala | 29 +---- .../ExpiredAmuletAllocationTrigger.scala | 7 +- .../ExpiredAmuletAllocationV2Trigger.scala | 7 +- ...iredAmuletTransferInstructionTrigger.scala | 7 +- .../delegatebased/ExpiredAmuletTrigger.scala | 7 +- .../ExpiredAnsEntryTrigger.scala | 29 +---- .../ExpiredAnsSubscriptionTrigger.scala | 34 ++---- .../ExpiredLockedAmuletTrigger.scala | 7 +- .../FeaturedAppActivityMarkerTrigger.scala | 16 ++- .../IgnoredAmuletVersionGuard.scala | 70 ----------- .../IgnoredUnavailablePartiesGuard.scala | 110 ++++++++++++++++++ test-full-class-names.log | 1 + 18 files changed, 273 insertions(+), 214 deletions(-) delete mode 100644 apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala create mode 100644 apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredUnavailablePartiesGuard.scala diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AutoIgnoreUnresponsivePartiesIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AutoIgnoreUnresponsivePartiesIntegrationTest.scala index 7386391182..2a4f8aea60 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AutoIgnoreUnresponsivePartiesIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/AutoIgnoreUnresponsivePartiesIntegrationTest.scala @@ -191,7 +191,7 @@ class AutoIgnoreUnresponsivePartiesIntegrationTest )( "Alice is added to the ignored parties store after mediator timeout", _ => { - sv1Backend.dsoDelegateBasedAutomation.expiredAmuletIgnoredPartiesStore.getAll should contain( + sv1Backend.dsoDelegateBasedAutomation.unavailablePartiesStore.getAll should contain( aliceParty ) }, diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ExpiryWithMinimalVettedPackagesIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ExpiryWithMinimalVettedPackagesIntegrationTest.scala index b513cf43f0..5211596688 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ExpiryWithMinimalVettedPackagesIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ExpiryWithMinimalVettedPackagesIntegrationTest.scala @@ -7,9 +7,9 @@ import com.digitalasset.canton.config.CantonRequireTypes.InstanceName import com.digitalasset.canton.config.NonNegativeFiniteDuration import com.digitalasset.canton.discard.Implicits.DiscardOps import com.digitalasset.canton.logging.SuppressionRule -import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.topology.transaction.ParticipantPermission -import com.digitalasset.daml.lf.data.Ref.{PackageName, PackageVersion} +import com.digitalasset.canton.topology.{ForceFlag, ForceFlags, PartyId} +import com.digitalasset.daml.lf.data.Ref.{PackageId, PackageName, PackageVersion} import org.lfdecentralizedtrust.splice.codegen.java.da.time.types.RelTime import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet.{ AppRewardCoupon, @@ -18,13 +18,7 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.amulet.{ import org.lfdecentralizedtrust.splice.codegen.java.splice.amuletrules.TransferPreapproval import org.lfdecentralizedtrust.splice.codegen.java.splice.ans.{AnsEntry, AnsEntryContext} import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.payment.{PaymentAmount, Unit} -import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.subscriptions.{ - Subscription, - SubscriptionData, - SubscriptionIdleState, - SubscriptionPayData, - SubscriptionRequest, -} +import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.subscriptions.* import org.lfdecentralizedtrust.splice.config.ConfigTransforms import org.lfdecentralizedtrust.splice.config.ConfigTransforms.{ ConfigurableApp, @@ -37,23 +31,14 @@ import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.{ SpliceTestConsoleEnvironment, } import org.lfdecentralizedtrust.splice.store.db.DbMultiDomainAcsStore -import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.{ - AdvanceOpenMiningRoundTrigger, - ExpireRewardCouponsTrigger, - ExpireTransferPreapprovalsTrigger, - ExpiredAmuletTrigger, - ExpiredAnsEntryTrigger, - ExpiredAnsSubscriptionTrigger, - ExpiredLockedAmuletTrigger, - FeaturedAppActivityMarkerTrigger, - UpdateExternalPartyConfigStateTrigger, -} +import org.lfdecentralizedtrust.splice.sv.automation.delegatebased.* import org.lfdecentralizedtrust.splice.util.* +import org.lfdecentralizedtrust.splice.validator.automation.ValidatorPackageVettingTrigger import org.lfdecentralizedtrust.splice.wallet.automation.SubscriptionReadyForPaymentTrigger import org.slf4j.event.Level -import scala.concurrent.duration.* import java.time.Duration +import scala.concurrent.duration.* abstract class ExpiryWithMinimalVettedPackagesIntegrationTestBase extends IntegrationTestWithIsolatedEnvironment @@ -111,6 +96,8 @@ abstract class ExpiryWithMinimalVettedPackagesIntegrationTestBase .withPausedTrigger[ExpireTransferPreapprovalsTrigger] .withPausedTrigger[ExpiredAnsEntryTrigger] .withPausedTrigger[ExpiredAnsSubscriptionTrigger] + .withPausedTrigger[ExpiredAmuletTrigger] + .withPausedTrigger[ExpiredLockedAmuletTrigger] )(c) ) .addConfigTransforms((_, c) => @@ -369,7 +356,7 @@ class ExpiryWithIgnoredAmuletVersionIntegrationTest )( s"All dust contracts remain because alice's preferred version is in ignoredAmuletVersions", _ => { - sv1Backend.dsoDelegateBasedAutomation.expiredAmuletIgnoredPartiesStore.getAll should + sv1Backend.dsoDelegateBasedAutomation.unavailablePartiesStore.getAll should contain(alice) aliceWalletClient.list().amulets should have length 2L withClue "amulets" @@ -403,3 +390,63 @@ class ExpiryWithIgnoredAmuletVersionIntegrationTest ) } } + +/** Tests that expiry triggers ignore parties whose participant has no vetted amulet. + * Only Amulet contracts are covered in this test, as the ignore logic is shared across expiry triggers. + */ +class ExpiryWithNoVettedAmuletVersionIntegrationTest + extends ExpiryWithMinimalVettedPackagesIntegrationTestBase { + + "Amulet expiry ignores parties with no vetted amulet version" in { implicit env => + val alice = setupAliceWithDustAmulets() + + clue("Alice unvets every amulet version") { + aliceValidatorBackend.validatorAutomation + .trigger[ValidatorPackageVettingTrigger] + .pause() + .futureValue + aliceValidatorBackend.participantClient.topology.vetted_packages.propose_delta( + aliceValidatorBackend.participantClient.id, + store = decentralizedSynchronizerId, + removes = DarResources.amulet.all.map(p => PackageId.assertFromString(p.packageId)), + force = ForceFlags(ForceFlag.AllowUnvettedDependencies), + ) + eventually() { + val vetted = getVettedPackageIds( + aliceValidatorBackend.appState.participantAdminConnection, + decentralizedSynchronizerId, + ).toSet + DarResources.amulet.all.foreach(p => vetted should not contain p.packageId) + } + } + + loggerFactory.assertLogsSeq( + SuppressionRule.forLogger[ExpiredAmuletTrigger] && SuppressionRule.Level(Level.WARN) + )( + actAndCheck(timeUntilSuccess = 60.seconds)( + "Advance 4 rounds and resume the amulet expiry trigger", { + (1 to 4).foreach(_ => advanceRoundsByOneTickViaAutomation()) + updateExternalPartyConfigStatesViaAutomation() + updateExternalPartyConfigStatesViaAutomation() + env.svs.local.foreach( + _.dsoDelegateBasedAutomation.trigger[ExpiredAmuletTrigger].resume() + ) + }, + )( + "Alice is ignored and her dust amulets are not expired", + _ => { + val ignored = sv1Backend.dsoDelegateBasedAutomation.unavailablePartiesStore.getAll + ignored should contain(alice) + ignored should not contain dsoParty + aliceWalletClient.list().amulets should have length 2L withClue "dust amulets" + }, + ), + entries => + forAtLeast(1, entries) { entry => + entry.warningMessage should include("No vetted Amulet version") + entry.warningMessage should include(alice.uid.identifier.str) + entry.warningMessage should include("ignoring 1 parties") + }, + ) + } +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/BatchedMultiDomainExpiredContractTrigger.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/BatchedMultiDomainExpiredContractTrigger.scala index 26da174abc..e2ec3530cf 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/BatchedMultiDomainExpiredContractTrigger.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/automation/BatchedMultiDomainExpiredContractTrigger.scala @@ -8,14 +8,14 @@ import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.tracing.TraceContext -import com.digitalasset.canton.util.ShowUtil.* import com.digitalasset.daml.lf.data.Ref.PackageVersion import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import org.lfdecentralizedtrust.splice.environment.{PackageIdResolver, PackageVettingLookupService} import org.lfdecentralizedtrust.splice.store.MultiDomainAcsStore.ContractState -import org.lfdecentralizedtrust.splice.store.{MultiDomainAcsStore, PageLimit} +import org.lfdecentralizedtrust.splice.store.{IgnoredPartiesStore, MultiDomainAcsStore, PageLimit} import org.lfdecentralizedtrust.splice.util.{AssignedContract, Contract} +import com.digitalasset.canton.discard.Implicits.DiscardOps import scala.concurrent.{ExecutionContext, Future} @@ -44,6 +44,14 @@ abstract class BatchedMultiDomainExpiredContractTrigger[ import BatchedMultiDomainExpiredContractTrigger.Batch + protected val ignoredPartiesStore: IgnoredPartiesStore + + protected def ignorePartiesWithoutVettedAmulet( + informees: Set[PartyId], + contractIds: Seq[String], + logAsWarning: Boolean, + )(implicit tc: TraceContext): String + override final protected def listReadyTasks(now: CantonTimestamp, limit: Int)(implicit tc: TraceContext ): Future[Seq[Batch[TCid, T]]] = @@ -67,9 +75,12 @@ abstract class BatchedMultiDomainExpiredContractTrigger[ Batch(pkg, version, contracts, stakeholders) } case (None, contracts) => - logger.warn( - show"No vetted $pkg version for ${contracts.flatten.map { _.contractId.contractId }}" - ) + val stakeholders = contracts.flatten.flatMap(c => getStakeholders(c.payload)).toSet + ignorePartiesWithoutVettedAmulet( + stakeholders, + contracts.flatten.map(_.contractId.contractId), + logAsWarning = true, + ).discard Seq.empty } } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala index e161567339..d7ce2ce925 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/DsoDelegateBasedAutomationService.scala @@ -45,7 +45,7 @@ class DsoDelegateBasedAutomationService( : org.lfdecentralizedtrust.splice.sv.automation.DsoDelegateBasedAutomationService.type = DsoDelegateBasedAutomationService - val expiredAmuletIgnoredPartiesStore = new IgnoredPartiesStore( + val unavailablePartiesStore = new IgnoredPartiesStore( triggerContext.config.ignoredPartyIds ) @@ -68,7 +68,7 @@ class DsoDelegateBasedAutomationService( config, triggerContext, svTaskContext, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, ) ) registerTrigger( @@ -76,7 +76,7 @@ class DsoDelegateBasedAutomationService( config, triggerContext, svTaskContext, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, ) ) registerTrigger( @@ -85,7 +85,7 @@ class DsoDelegateBasedAutomationService( clock, triggerContext, svTaskContext, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, ) ) registerTrigger( @@ -94,7 +94,7 @@ class DsoDelegateBasedAutomationService( clock, triggerContext, svTaskContext, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, ) ) registerTrigger( @@ -103,7 +103,7 @@ class DsoDelegateBasedAutomationService( clock, triggerContext, svTaskContext, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, ) ) registerTrigger(new ExpiredSvOnboardingRequestTrigger(triggerContext, svTaskContext)) @@ -118,7 +118,7 @@ class DsoDelegateBasedAutomationService( new ExpireRewardCouponsTrigger( triggerContext, svTaskContext, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, config, ) ) @@ -129,7 +129,7 @@ class DsoDelegateBasedAutomationService( triggerContext, svTaskContext, config, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, ) ) registerTrigger( @@ -137,7 +137,7 @@ class DsoDelegateBasedAutomationService( triggerContext, svTaskContext, config, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, ) ) registerTrigger( @@ -145,7 +145,7 @@ class DsoDelegateBasedAutomationService( triggerContext, svTaskContext, config, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, ) ) registerTrigger(new TerminatedSubscriptionTrigger(triggerContext, svTaskContext)) @@ -163,7 +163,7 @@ class DsoDelegateBasedAutomationService( triggerContext, svTaskContext, config, - expiredAmuletIgnoredPartiesStore, + unavailablePartiesStore, ) ) @@ -200,6 +200,7 @@ class DsoDelegateBasedAutomationService( config, triggerContext, svTaskContext, + unavailablePartiesStore, ) ) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponV2Trigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponV2Trigger.scala index 9c1ec54266..837e02f79f 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponV2Trigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponV2Trigger.scala @@ -11,9 +11,10 @@ import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer import scala.concurrent.{ExecutionContext, Future} -import ExpireRewardCouponV2Trigger.{Task, Coupon, CouponCid, getStakeholders} +import ExpireRewardCouponV2Trigger.{Coupon, CouponCid, Task, getStakeholders} import org.lfdecentralizedtrust.splice.environment.{DarResources, PackageIdResolver} import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders @@ -21,9 +22,10 @@ import scala.jdk.CollectionConverters.* import scala.jdk.OptionConverters.* class ExpireRewardCouponV2Trigger( - svConfig: SvAppBackendConfig, + override protected val svConfig: SvAppBackendConfig, override protected val context: TriggerContext, override protected val svTaskContext: SvTaskBasedTrigger.Context, + override protected val ignoredPartiesStore: IgnoredPartiesStore, )(implicit override val ec: ExecutionContext, mat: Materializer, @@ -37,11 +39,21 @@ class ExpireRewardCouponV2Trigger( PackageIdResolver.Package.SpliceAmulet, getStakeholders, ) - with SvTaskBasedTrigger[Task] { + with SvTaskBasedTrigger[Task] + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext + ): Future[TaskOutcome] = + completeUnlessAmuletVersionIgnored( + task.work.vettedVersion.toString, + task.work.stakeholders, + ignoreUnresponsiveParties = true, + )(completeExpiryTaskAsDsoDelegate(task, controller)) + + private def completeExpiryTaskAsDsoDelegate(task: Task, controller: String)(implicit + tc: TraceContext ): Future[TaskOutcome] = { val expiredCoupons = task.work.expiredContracts // The batch is already split by the amulet version so we skip the whole batch. @@ -85,6 +97,7 @@ class ExpireRewardCouponV2Trigger( } yield TaskSuccess(s"archived ${expiredCoupons.size} expired reward coupons v2") } } + } object ExpireRewardCouponV2Trigger extends ContractStakeholders[splice.amulet.RewardCouponV2] { diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala index 51e3f2388d..4248c82c54 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireRewardCouponsTrigger.scala @@ -53,7 +53,7 @@ class ExpireRewardCouponsTrigger( tracer: Tracer, ) extends PollingParallelTaskExecutionTrigger[Task] with SvTaskBasedTrigger[Task] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore override protected def retrieveTasks()(implicit @@ -163,11 +163,10 @@ class ExpireRewardCouponsTrigger( ValidatorLivenessActivityRecords.getInformeesFromContracts( task.batch.validatorLivenessActivityRecords ) - completeWithIgnoredAmuletVersionCheck( + completeUnlessAmuletVersionIgnored( task.vettedAmuletVersion.toString, informees, - store.dsoPartyId, - enableUnresponsivePartiesAutoIgnore = true, + ignoreUnresponsiveParties = true, )(completeExpiryTaskAsDsoDelegate(task, controller)) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireTransferPreapprovalsTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireTransferPreapprovalsTrigger.scala index d5d6d620c5..ea84f04c59 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireTransferPreapprovalsTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpireTransferPreapprovalsTrigger.scala @@ -16,7 +16,6 @@ import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders import java.util.Optional import scala.concurrent.{ExecutionContext, Future} import ExpireTransferPreapprovalsTrigger.{Task, getStakeholders} -import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore class ExpireTransferPreapprovalsTrigger( @@ -40,33 +39,17 @@ class ExpireTransferPreapprovalsTrigger( TransferPreapproval.ContractId, TransferPreapproval, ]]] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext - ): Future[TaskOutcome] = { - val stakeholders = getStakeholders(task.work.payload).toSet - svTaskContext.vettingLookupService - .lookupVettingState(stakeholders.toSeq, PackageIdResolver.Package.SpliceAmulet) - .flatMap { - case Some(vettedVersion) => - completeWithIgnoredAmuletVersionCheck( - vettedVersion.toString, - stakeholders, - store.key.dsoParty, - enableUnresponsivePartiesAutoIgnore = true, - )(completeExpiryTaskAsDsoDelegate(task, controller)) - case None => - Future.successful( - TaskSuccess( - s"No vetted SpliceAmulet version for stakeholders $stakeholders of " + - s"TransferPreapproval ${task.work.contractId}, skipping." - ) - ) - } - } + ): Future[TaskOutcome] = + completeWithVettedAmuletVersion( + getStakeholders(task.work.payload).toSet, + Seq(task.work.contractId.contractId), + )(completeExpiryTaskAsDsoDelegate(task, controller)) private def completeExpiryTaskAsDsoDelegate( task: Task, diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala index 216c7878e8..038caae7ae 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationTrigger.scala @@ -45,18 +45,17 @@ class ExpiredAmuletAllocationTrigger( getStakeholders, ) with SvTaskBasedTrigger[Task] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - completeWithIgnoredAmuletVersionCheck( + completeUnlessAmuletVersionIgnored( task.work.vettedVersion.toString, task.work.stakeholders, - store.key.dsoParty, - enableUnresponsivePartiesAutoIgnore = true, + ignoreUnresponsiveParties = true, )(completeExpiryTaskAsDsoDelegate(task, controller)) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala index ce54bffef5..ed3b73c8a9 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletAllocationV2Trigger.scala @@ -45,7 +45,7 @@ class ExpiredAmuletAllocationV2Trigger( ExpiredAmuletAllocationV2Trigger.getStakeholders, ) with SvTaskBasedTrigger[ExpiredAmuletAllocationV2Trigger.Task] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore @@ -55,11 +55,10 @@ class ExpiredAmuletAllocationV2Trigger( )(implicit tc: TraceContext ): Future[TaskOutcome] = { - completeWithIgnoredAmuletVersionCheck( + completeUnlessAmuletVersionIgnored( task.work.vettedVersion.toString, task.work.stakeholders, - store.key.dsoParty, - enableUnresponsivePartiesAutoIgnore = true, + ignoreUnresponsiveParties = true, )(completeExpiryTaskAsDsoDelegate(task, controller)) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala index 89416b70ad..d50197a9cd 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTransferInstructionTrigger.scala @@ -45,18 +45,17 @@ class ExpiredAmuletTransferInstructionTrigger( getStakeholders, ) with SvTaskBasedTrigger[Task] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - completeWithIgnoredAmuletVersionCheck( + completeUnlessAmuletVersionIgnored( task.work.vettedVersion.toString, task.work.stakeholders, - store.key.dsoParty, - enableUnresponsivePartiesAutoIgnore = true, + ignoreUnresponsiveParties = true, )(completeExpiryTaskAsDsoDelegate(task, controller)) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala index 8f849a073c..32a1f131de 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAmuletTrigger.scala @@ -43,17 +43,16 @@ class ExpiredAmuletTrigger( getStakeholders, ) with SvTaskBasedTrigger[Task] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - completeWithIgnoredAmuletVersionCheck( + completeUnlessAmuletVersionIgnored( task.work.vettedVersion.toString, task.work.stakeholders, - store.key.dsoParty, - enableUnresponsivePartiesAutoIgnore = true, + ignoreUnresponsiveParties = true, )(completeExpiryTaskAsDsoDelegate(task, controller)) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsEntryTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsEntryTrigger.scala index 27b0e00115..3cb66cc1c1 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsEntryTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsEntryTrigger.scala @@ -10,7 +10,6 @@ import org.lfdecentralizedtrust.splice.util.AssignedContract import com.digitalasset.canton.tracing.TraceContext import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer -import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.util.ContractStakeholders @@ -41,33 +40,17 @@ class ExpiredAnsEntryTrigger( splice.ans.AnsEntry.ContractId, splice.ans.AnsEntry, ]]] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext - ): Future[TaskOutcome] = { - val stakeholders = getStakeholders(task.work.payload).toSet - svTaskContext.vettingLookupService - .lookupVettingState(stakeholders.toSeq, PackageIdResolver.Package.SpliceAmulet) - .flatMap { - case Some(vettedVersion) => - completeWithIgnoredAmuletVersionCheck( - vettedVersion.toString, - stakeholders, - store.key.dsoParty, - enableUnresponsivePartiesAutoIgnore = true, - )(completeExpiryTaskAsDsoDelegate(task, controller)) - case None => - Future.successful( - TaskSuccess( - s"No vetted SpliceAmulet version for stakeholders $stakeholders of " + - s"AnsEntry ${task.work.contractId}, skipping." - ) - ) - } - } + ): Future[TaskOutcome] = + completeWithVettedAmuletVersion( + getStakeholders(task.work.payload).toSet, + Seq(task.work.contractId.contractId), + )(completeExpiryTaskAsDsoDelegate(task, controller)) private def completeExpiryTaskAsDsoDelegate( task: Task, diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsSubscriptionTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsSubscriptionTrigger.scala index 815b3ed3ec..8b6e009a08 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsSubscriptionTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredAnsSubscriptionTrigger.scala @@ -16,7 +16,6 @@ import io.opentelemetry.api.trace.Tracer import org.lfdecentralizedtrust.splice.codegen.java.splice.ans as ansCodegen import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.subscriptions as subsCodegen import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.subscriptions.SubscriptionIdleState_ExpireSubscription -import org.lfdecentralizedtrust.splice.environment.PackageIdResolver import org.lfdecentralizedtrust.splice.store.{IgnoredPartiesStore, PageLimit} import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig import org.lfdecentralizedtrust.splice.sv.store.SvDsoStore @@ -39,7 +38,7 @@ class ExpiredAnsSubscriptionTrigger( tracer: Tracer, ) extends ScheduledTaskTrigger[SvDsoStore.IdleAnsSubscription] with SvTaskBasedTrigger[ScheduledTaskTrigger.ReadyTask[SvDsoStore.IdleAnsSubscription]] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore override protected def listReadyTasks(now: CantonTimestamp, limit: Int)(implicit @@ -47,30 +46,13 @@ class ExpiredAnsSubscriptionTrigger( ): Future[Seq[SvDsoStore.IdleAnsSubscription]] = store.listExpiredAnsSubscriptions(now, PageLimit.tryCreate(limit), Some(ignoredPartiesStore)) - override protected def completeTaskAsDsoDelegate( - task: Task, - controller: String, - )(implicit tc: TraceContext): Future[TaskOutcome] = { - val stakeholders = getStakeholders(task.work.state.payload).toSet - svTaskContext.vettingLookupService - .lookupVettingState(stakeholders.toSeq, PackageIdResolver.Package.SpliceAmulet) - .flatMap { - case Some(vettedVersion) => - completeWithIgnoredAmuletVersionCheck( - vettedVersion.toString, - stakeholders, - store.key.dsoParty, - enableUnresponsivePartiesAutoIgnore = true, - )(completeExpiryTaskAsDsoDelegate(task, controller)) - case None => - Future.successful( - TaskSuccess( - s"No vetted SpliceAmulet version for stakeholders $stakeholders of " + - s"ANS subscription ${task.work.state.contractId}, skipping." - ) - ) - } - } + override protected def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit + tc: TraceContext + ): Future[TaskOutcome] = + completeWithVettedAmuletVersion( + getStakeholders(task.work.state.payload).toSet, + Seq(task.work.state.contractId.contractId), + )(completeExpiryTaskAsDsoDelegate(task, controller)) private def completeExpiryTaskAsDsoDelegate( task: Task, diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala index 0ea2f6b28a..c01e110be4 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/ExpiredLockedAmuletTrigger.scala @@ -43,17 +43,16 @@ class ExpiredLockedAmuletTrigger( getStakeholders, ) with SvTaskBasedTrigger[Task] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val store = svTaskContext.dsoStore override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - completeWithIgnoredAmuletVersionCheck( + completeUnlessAmuletVersionIgnored( task.work.vettedVersion.toString, task.work.stakeholders, - store.key.dsoParty, - enableUnresponsivePartiesAutoIgnore = true, + ignoreUnresponsiveParties = true, )(completeExpiryTaskAsDsoDelegate(task, controller)) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala index 8502130f8d..ddcbfa2bff 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/FeaturedAppActivityMarkerTrigger.scala @@ -28,9 +28,10 @@ import scala.jdk.OptionConverters.* import FeaturedAppActivityMarkerTrigger.{ CrossVersionBatch, Task, - getStakeholders, getInformeesFromContracts, + getStakeholders, } +import com.digitalasset.canton.discard.Implicits.DiscardOps import org.lfdecentralizedtrust.splice.store.AppStoreWithIngestion.SpliceLedgerConnectionPriority import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig @@ -51,7 +52,7 @@ class FeaturedAppActivityMarkerTrigger( // This is a polling trigger as we usually expect to be able to batch together the conversion ) extends PollingParallelTaskExecutionTrigger[Task] with SvTaskBasedTrigger[Task] - with IgnoredAmuletVersionGuard { + with IgnoredUnavailablePartiesGuard { private val rng: Random = new Random() @@ -108,7 +109,11 @@ class FeaturedAppActivityMarkerTrigger( ) } case (None, markers) => - logger.warn(show"No vetted amulet version for $markers") + ignorePartiesWithoutVettedAmulet( + getInformeesFromContracts(markers.flatten), + markers.flatten.map(_.contractId.contractId), + logAsWarning = true, + ).discard Seq.empty } @@ -200,12 +205,11 @@ class FeaturedAppActivityMarkerTrigger( override def completeTaskAsDsoDelegate(task: Task, controller: String)(implicit tc: TraceContext ): Future[TaskOutcome] = { - completeWithIgnoredAmuletVersionCheck( + completeUnlessAmuletVersionIgnored( task.vettedAmuletVersion.toString, task.informees, - store.key.dsoParty, // ignoring a party would mean their featured app activity markers do not get converted into rewards - enableUnresponsivePartiesAutoIgnore = false, + ignoreUnresponsiveParties = false, )(completeExpiryTaskAsDsoDelegate(task, controller)) } diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala deleted file mode 100644 index eb99c31ed8..0000000000 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredAmuletVersionGuard.scala +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package org.lfdecentralizedtrust.splice.sv.automation.delegatebased - -import com.digitalasset.base.error.utils.ErrorDetails -import com.digitalasset.canton.topology.PartyId -import io.grpc.StatusRuntimeException -import io.grpc.protobuf.StatusProto -import org.lfdecentralizedtrust.splice.automation.{TaskOutcome, TaskSuccess} -import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore -import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig -import org.lfdecentralizedtrust.splice.util.UnresponsiveParties - -import scala.concurrent.{ExecutionContext, Future} - -trait IgnoredAmuletVersionGuard { - protected def svConfig: SvAppBackendConfig - protected def ignoredPartiesStore: IgnoredPartiesStore - protected def svTaskContext: SvTaskBasedTrigger.Context - - protected def completeWithIgnoredAmuletVersionCheck( - vettedVersion: String, - stakeholders: Set[PartyId], - dsoParty: PartyId, - enableUnresponsivePartiesAutoIgnore: Boolean, - )( - fallback: => Future[TaskOutcome] - )(implicit ec: ExecutionContext): Future[TaskOutcome] = { - // ensure we do not ignore the DSO party itself, even if it is unresponsive - val expiredOwners = stakeholders - dsoParty - if ( - svConfig.allIgnoredAmuletVersions.contains(vettedVersion) && - svConfig.parameters.enabledFeatures.ignorePartyIdWithIgnoredAmulet - ) { - ignoredPartiesStore.addAll(expiredOwners) - Future.successful( - TaskSuccess( - s"Skipped batch with ignored version $vettedVersion: added ${expiredOwners.size} parties to ignore list: $expiredOwners" - ) - ) - } else { - val enableNaiveUnresponsivePartiesAutoIgnore = - svConfig.parameters.enabledFeatures.naiveUnresponsivePartiesAutoIgnore && enableUnresponsivePartiesAutoIgnore - fallback.recoverWith { - case ex: StatusRuntimeException if enableNaiveUnresponsivePartiesAutoIgnore => - extractUnresponsiveParties(ex) match { - case parties if parties.nonEmpty => - val partiesToIgnore = parties - svTaskContext.dsoStore.key.dsoParty.partyId - ignoredPartiesStore.addAll(partiesToIgnore) - Future.successful( - TaskSuccess( - s"Batch failed due to unresponsive parties, added ${partiesToIgnore.size} to ignore list: $partiesToIgnore" - ) - ) - case _ => Future.failed(ex) - } - } - } - } - - private def extractUnresponsiveParties(ex: StatusRuntimeException): Set[PartyId] = { - val statusProto = StatusProto.fromThrowable(ex) - val errorDetails = ErrorDetails.from(statusProto) - errorDetails - .collectFirst { case UnresponsiveParties(parties) => parties } - .getOrElse(Set.empty) - } - -} diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredUnavailablePartiesGuard.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredUnavailablePartiesGuard.scala new file mode 100644 index 0000000000..b6f001e9b0 --- /dev/null +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/delegatebased/IgnoredUnavailablePartiesGuard.scala @@ -0,0 +1,110 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.sv.automation.delegatebased + +import com.digitalasset.base.error.utils.ErrorDetails +import com.digitalasset.canton.logging.NamedLogging +import com.digitalasset.canton.topology.PartyId +import com.digitalasset.canton.tracing.TraceContext +import io.grpc.StatusRuntimeException +import io.grpc.protobuf.StatusProto +import org.lfdecentralizedtrust.splice.automation.{TaskOutcome, TaskSuccess} +import org.lfdecentralizedtrust.splice.store.IgnoredPartiesStore +import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig +import org.lfdecentralizedtrust.splice.util.UnresponsiveParties +import org.lfdecentralizedtrust.splice.environment.PackageIdResolver +import scala.concurrent.{ExecutionContext, Future} + +trait IgnoredUnavailablePartiesGuard extends NamedLogging { + protected def svConfig: SvAppBackendConfig + protected def ignoredPartiesStore: IgnoredPartiesStore + protected def svTaskContext: SvTaskBasedTrigger.Context + + protected def completeUnlessAmuletVersionIgnored( + vettedVersion: String, + stakeholders: Set[PartyId], + ignoreUnresponsiveParties: Boolean, + )(task: => Future[TaskOutcome])(implicit ec: ExecutionContext): Future[TaskOutcome] = + if ( + svConfig.allIgnoredAmuletVersions.contains(vettedVersion) && + svConfig.parameters.enabledFeatures.ignorePartyIdWithIgnoredAmulet + ) { + val toIgnore = withoutDsoParty(stakeholders) + ignoredPartiesStore.addAll(toIgnore) + Future.successful( + TaskSuccess( + s"Skipped batch with ignored version $vettedVersion: added ${toIgnore.size} parties to ignore list: $toIgnore" + ) + ) + } else { + task.recoverWith(recoverUnresponsiveParties(ignoreUnresponsiveParties)) + } + + protected def completeWithVettedAmuletVersion( + stakeholders: Set[PartyId], + contractIds: Seq[String], + ignoreUnresponsiveParties: Boolean = true, + )(task: => Future[TaskOutcome])(implicit + ec: ExecutionContext, + tc: TraceContext, + ): Future[TaskOutcome] = + svTaskContext.vettingLookupService + .lookupVettingState(stakeholders.toSeq, PackageIdResolver.Package.SpliceAmulet) + .flatMap { + case Some(vettedVersion) => + completeUnlessAmuletVersionIgnored( + vettedVersion.toString, + stakeholders, + ignoreUnresponsiveParties, + )(task) + case None => + Future.successful( + TaskSuccess(ignorePartiesWithoutVettedAmulet(stakeholders, contractIds)) + ) + } + + protected def ignorePartiesWithoutVettedAmulet( + informees: Set[PartyId], + contractIds: Seq[String], + logAsWarning: Boolean = false, + )(implicit tc: TraceContext): String = { + val toIgnore = withoutDsoParty(informees) + ignoredPartiesStore.addAll(toIgnore) + val msg = + s"No vetted Amulet version for $contractIds; ignoring ${toIgnore.size} parties: $toIgnore" + if (logAsWarning) logger.warn(msg) + msg + } + + private def recoverUnresponsiveParties( + enabled: Boolean + ): PartialFunction[Throwable, Future[TaskOutcome]] = { + case ex: StatusRuntimeException + if enabled && svConfig.parameters.enabledFeatures.naiveUnresponsivePartiesAutoIgnore => + val toIgnore = withoutDsoParty(extractUnresponsiveParties(ex)) + if (toIgnore.isEmpty) { + Future.failed(ex) + } else { + ignoredPartiesStore.addAll(toIgnore) + Future.successful( + TaskSuccess( + s"Batch failed due to unresponsive parties, added ${toIgnore.size} to ignore list: $toIgnore" + ) + ) + } + } + + // never ignore the DSO party itself: it is a stakeholder on every DSO contract + private def withoutDsoParty(parties: Set[PartyId]): Set[PartyId] = + parties - svTaskContext.dsoStore.key.dsoParty + + private def extractUnresponsiveParties(ex: StatusRuntimeException): Set[PartyId] = { + val statusProto = StatusProto.fromThrowable(ex) + val errorDetails = ErrorDetails.from(statusProto) + errorDetails + .collectFirst { case UnresponsiveParties(parties) => parties } + .getOrElse(Set.empty) + } + +} diff --git a/test-full-class-names.log b/test-full-class-names.log index c4ad4ee597..38f35e559a 100644 --- a/test-full-class-names.log +++ b/test-full-class-names.log @@ -20,6 +20,7 @@ org.lfdecentralizedtrust.splice.integration.tests.DirectoryPeriodicBackupIntegra org.lfdecentralizedtrust.splice.integration.tests.DistributedDomainIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.DowngradeSvPackagesIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.ExpiryWithIgnoredAmuletVersionIntegrationTest +org.lfdecentralizedtrust.splice.integration.tests.ExpiryWithNoVettedAmuletVersionIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.ExternalPartySetupProposalIntegrationTest org.lfdecentralizedtrust.splice.integration.tests.ExternallySignedPartyOnboardingTest org.lfdecentralizedtrust.splice.integration.tests.FeaturedAppActivityMarkerIntegrationTest From 8a071e2c48dd22aa061823306bf4fa344c62dadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oriol=20Mu=C3=B1oz?= Date: Wed, 12 Aug 2026 17:24:18 +0200 Subject: [PATCH 232/329] Reapply "[ci] Bump the development-dependencies group across 1 directory with 8 updates" (#6757) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit 905ead3cf829334795e830a2b7a00d7faa4099ea. --------- Signed-off-by: Oriol Muñoz --- .../canton-network/tsconfig.eslint.json | 1 + cluster/pulumi/circleci/tsconfig.eslint.json | 1 + cluster/pulumi/cluster/tsconfig.eslint.json | 1 + .../common-sv/src/approvedIdentities.ts | 2 +- cluster/pulumi/common-sv/tsconfig.eslint.json | 1 + .../common-validator/tsconfig.eslint.json | 1 + cluster/pulumi/common/package.json | 2 +- cluster/pulumi/common/src/postgres.ts | 6 +- cluster/pulumi/common/src/serviceAccount.ts | 3 +- cluster/pulumi/common/tsconfig.eslint.json | 1 + .../pulumi/deployment/tsconfig.eslint.json | 1 + cluster/pulumi/eslint.config.mjs | 2 +- cluster/pulumi/gcp/tsconfig.eslint.json | 1 + cluster/pulumi/gha/tsconfig.eslint.json | 1 + cluster/pulumi/infra/src/cloudArmor.ts | 4 +- cluster/pulumi/infra/tsconfig.eslint.json | 1 + .../multi-validator/tsconfig.eslint.json | 1 + .../pulumi/observability/tsconfig.eslint.json | 1 + cluster/pulumi/operator/tsconfig.eslint.json | 1 + cluster/pulumi/package-lock.json | 470 ++++++++++++++---- cluster/pulumi/package.json | 12 +- cluster/pulumi/policies/tsconfig.eslint.json | 1 + cluster/pulumi/splitwell/tsconfig.eslint.json | 1 + cluster/pulumi/sv-canton/tsconfig.eslint.json | 1 + .../pulumi/sv-runbook/tsconfig.eslint.json | 1 + cluster/pulumi/sv/tsconfig.eslint.json | 1 + cluster/pulumi/tsconfig.eslint.json | 5 + .../validator-runbook/tsconfig.eslint.json | 1 + .../pulumi/validator1/tsconfig.eslint.json | 1 + 29 files changed, 422 insertions(+), 104 deletions(-) create mode 100644 cluster/pulumi/canton-network/tsconfig.eslint.json create mode 100644 cluster/pulumi/circleci/tsconfig.eslint.json create mode 100644 cluster/pulumi/cluster/tsconfig.eslint.json create mode 100644 cluster/pulumi/common-sv/tsconfig.eslint.json create mode 100644 cluster/pulumi/common-validator/tsconfig.eslint.json create mode 100644 cluster/pulumi/common/tsconfig.eslint.json create mode 100644 cluster/pulumi/deployment/tsconfig.eslint.json create mode 100644 cluster/pulumi/gcp/tsconfig.eslint.json create mode 100644 cluster/pulumi/gha/tsconfig.eslint.json create mode 100644 cluster/pulumi/infra/tsconfig.eslint.json create mode 100644 cluster/pulumi/multi-validator/tsconfig.eslint.json create mode 100644 cluster/pulumi/observability/tsconfig.eslint.json create mode 100644 cluster/pulumi/operator/tsconfig.eslint.json create mode 100644 cluster/pulumi/policies/tsconfig.eslint.json create mode 100644 cluster/pulumi/splitwell/tsconfig.eslint.json create mode 100644 cluster/pulumi/sv-canton/tsconfig.eslint.json create mode 100644 cluster/pulumi/sv-runbook/tsconfig.eslint.json create mode 100644 cluster/pulumi/sv/tsconfig.eslint.json create mode 100644 cluster/pulumi/tsconfig.eslint.json create mode 100644 cluster/pulumi/validator-runbook/tsconfig.eslint.json create mode 100644 cluster/pulumi/validator1/tsconfig.eslint.json diff --git a/cluster/pulumi/canton-network/tsconfig.eslint.json b/cluster/pulumi/canton-network/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/canton-network/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/circleci/tsconfig.eslint.json b/cluster/pulumi/circleci/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/circleci/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/cluster/tsconfig.eslint.json b/cluster/pulumi/cluster/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/cluster/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/common-sv/src/approvedIdentities.ts b/cluster/pulumi/common-sv/src/approvedIdentities.ts index cf5a1bbb10..178baa6169 100644 --- a/cluster/pulumi/common-sv/src/approvedIdentities.ts +++ b/cluster/pulumi/common-sv/src/approvedIdentities.ts @@ -61,7 +61,7 @@ export function approvedSvIdentities(): ApprovedSvIdentity[] { const rawFromFile = approvedSvIdentitiesFromFile(); const fromFile: ApprovedSvIdentity[] = rawFromFile.map(identity => ({ name: identity.name, - rewardWeightBps: parseInt(String(identity.rewardWeightBps).replace('_', ''), 10), + rewardWeightBps: parseInt(String(identity.rewardWeightBps).replaceAll('_', ''), 10), publicKey: identity.publicKey, })); const fromConfig = approvedSvIdentitiesFromConfig(); diff --git a/cluster/pulumi/common-sv/tsconfig.eslint.json b/cluster/pulumi/common-sv/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/common-sv/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/common-validator/tsconfig.eslint.json b/cluster/pulumi/common-validator/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/common-validator/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/common/package.json b/cluster/pulumi/common/package.json index c8891effcc..3caf421b9f 100644 --- a/cluster/pulumi/common/package.json +++ b/cluster/pulumi/common/package.json @@ -35,7 +35,7 @@ "devDependencies": { "@jest/globals": "^30.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.17.24", + "@types/lodash": "^4.17.25", "@types/ws": "^8.18.1", "dedent": "^1.7.2" } diff --git a/cluster/pulumi/common/src/postgres.ts b/cluster/pulumi/common/src/postgres.ts index e86c3fabd8..798f6a3ec8 100644 --- a/cluster/pulumi/common/src/postgres.ts +++ b/cluster/pulumi/common/src/postgres.ts @@ -509,8 +509,7 @@ export class SplicePostgres extends pulumi.ComponentResource implements Postgres instanceName: string, installPassword: (parent: Resource) => k8s.core.v1.Secret, splicePostgresHelmMigrationConfig: - | SplicePostgresMigrateConfig - | SplicePostgresDockerImageConfig, + SplicePostgresMigrateConfig | SplicePostgresDockerImageConfig, values?: LegacyChartValues, overrideDbSizeFromValues?: boolean, disableProtection?: boolean, @@ -926,8 +925,7 @@ export function installSplicePostgres( instanceName, parent => installPasswordWithParent(parent, xns, instanceName, secretName), splicePostgresHelmMigrationConfig as - | SplicePostgresMigrateConfig - | SplicePostgresDockerImageConfig, + SplicePostgresMigrateConfig | SplicePostgresDockerImageConfig, chartValues, overrideDbSizeFromValues, opts.disableProtection, diff --git a/cluster/pulumi/common/src/serviceAccount.ts b/cluster/pulumi/common/src/serviceAccount.ts index 62803263c8..d23c386728 100644 --- a/cluster/pulumi/common/src/serviceAccount.ts +++ b/cluster/pulumi/common/src/serviceAccount.ts @@ -4,8 +4,7 @@ import * as gcp from '@pulumi/gcp'; import * as pulumi from '@pulumi/pulumi'; type Role = - | string - | { id: string; condition: { title: string; description: string; expression: string } }; + string | { id: string; condition: { title: string; description: string; expression: string } }; const roleToPulumiName = (role: Role): string => { if (typeof role === 'string') { diff --git a/cluster/pulumi/common/tsconfig.eslint.json b/cluster/pulumi/common/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/common/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/deployment/tsconfig.eslint.json b/cluster/pulumi/deployment/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/deployment/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/eslint.config.mjs b/cluster/pulumi/eslint.config.mjs index d5a0756535..8717187764 100644 --- a/cluster/pulumi/eslint.config.mjs +++ b/cluster/pulumi/eslint.config.mjs @@ -32,7 +32,7 @@ export default defineConfig([{ sourceType: "script", parserOptions: { - project: ["./tsconfig.json"], + project: ["./tsconfig.eslint.json"], }, }, diff --git a/cluster/pulumi/gcp/tsconfig.eslint.json b/cluster/pulumi/gcp/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/gcp/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/gha/tsconfig.eslint.json b/cluster/pulumi/gha/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/gha/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/infra/src/cloudArmor.ts b/cluster/pulumi/infra/src/cloudArmor.ts index 2ab7f7e286..82fda8ef65 100644 --- a/cluster/pulumi/infra/src/cloudArmor.ts +++ b/cluster/pulumi/infra/src/cloudArmor.ts @@ -76,7 +76,9 @@ export function configureCloudArmorPolicy( // Step 2: Add predefined WAF rules if (cac.predefinedWafRules && cac.predefinedWafRules.length > 0) { - addPredefinedWafRules(/*securityPolicy, args.predefinedWafRules, cac.allRulesPreviewOnly, ruleOpts*/); + addPredefinedWafRules( + /*securityPolicy, args.predefinedWafRules, cac.allRulesPreviewOnly, ruleOpts*/ + ); } // Step 3: Add IP whitelisting rules diff --git a/cluster/pulumi/infra/tsconfig.eslint.json b/cluster/pulumi/infra/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/infra/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/multi-validator/tsconfig.eslint.json b/cluster/pulumi/multi-validator/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/multi-validator/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/observability/tsconfig.eslint.json b/cluster/pulumi/observability/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/observability/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/operator/tsconfig.eslint.json b/cluster/pulumi/operator/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/operator/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/package-lock.json b/cluster/pulumi/package-lock.json index 829177c65a..8f668a304e 100644 --- a/cluster/pulumi/package-lock.json +++ b/cluster/pulumi/package-lock.json @@ -35,20 +35,20 @@ "commander": "^14.0.3" }, "devDependencies": { - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", "@jest/globals": "^30.4.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/request": "^2.48.13", - "@typescript-eslint/eslint-plugin": "^8.61.1", + "@typescript-eslint/eslint-plugin": "^8.65.0", "@typescript-eslint/parser": "^8.57.2", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-promise": "^7.3.0", "jest": "^30.4.2", - "minimatch": "10.2.5", - "prettier": "^3.8.4", - "ts-jest": "^29.4.11", + "minimatch": "10.2.6", + "prettier": "^3.9.6", + "ts-jest": "^29.4.12", "typescript": "^5.9.3" } }, @@ -108,7 +108,7 @@ "devDependencies": { "@jest/globals": "^30.4.1", "@types/js-yaml": "^4.0.5", - "@types/lodash": "^4.17.24", + "@types/lodash": "^4.17.25", "@types/ws": "^8.18.1", "dedent": "^1.7.2" } @@ -1013,9 +1013,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.6.tgz", + "integrity": "sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==", "dev": true, "license": "MIT", "dependencies": { @@ -1025,7 +1025,7 @@ "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", + "js-yaml": "^4.3.0", "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, @@ -1061,9 +1061,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", "dev": true, "license": "MIT", "engines": { @@ -3581,9 +3581,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.25.tgz", + "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", "dev": true, "license": "MIT" }, @@ -3736,17 +3736,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", - "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz", + "integrity": "sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/type-utils": "8.61.1", - "@typescript-eslint/utils": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/type-utils": "8.65.0", + "@typescript-eslint/utils": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -3759,7 +3759,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.61.1", + "@typescript-eslint/parser": "^8.65.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -3775,16 +3775,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", - "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", + "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3" }, "engines": { @@ -3799,15 +3799,64 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/parser/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-eslint/project-service": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", - "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.61.1", - "@typescript-eslint/types": "^8.61.1", + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", "debug": "^4.4.3" }, "engines": { @@ -3822,14 +3871,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", - "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", + "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1" + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3839,10 +3888,24 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/scope-manager/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", - "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", "dev": true, "license": "MIT", "engines": { @@ -3857,15 +3920,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", - "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz", + "integrity": "sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1", - "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0", + "@typescript-eslint/utils": "8.65.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -3881,10 +3944,91 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/types": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", - "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", "dev": true, "license": "MIT", "engines": { @@ -3896,16 +4040,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", - "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.61.1", - "@typescript-eslint/tsconfig-utils": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/visitor-keys": "8.61.1", + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -3923,17 +4067,48 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@typescript-eslint/utils": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", - "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", + "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.61.1", - "@typescript-eslint/types": "8.61.1", - "@typescript-eslint/typescript-estree": "8.61.1" + "@typescript-eslint/scope-manager": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/typescript-estree": "8.65.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3947,14 +4122,95 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/project-service": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", + "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.65.0", + "@typescript-eslint/types": "^8.65.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", + "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", + "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.65.0", + "@typescript-eslint/tsconfig-utils": "8.65.0", + "@typescript-eslint/types": "8.65.0", + "@typescript-eslint/visitor-keys": "8.65.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.61.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", - "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", + "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/types": "8.65.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3965,6 +4221,20 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", @@ -6146,6 +6416,20 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, "node_modules/eslint/node_modules/brace-expansion": { "version": "1.1.16", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", @@ -8616,9 +8900,19 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -8969,12 +9263,12 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -9974,9 +10268,9 @@ } }, "node_modules/prettier": { - "version": "3.8.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", - "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", + "version": "3.9.6", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", "dev": true, "license": "MIT", "bin": { @@ -10425,9 +10719,9 @@ } }, "node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11230,9 +11524,9 @@ } }, "node_modules/ts-jest": { - "version": "29.4.11", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.11.tgz", - "integrity": "sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==", + "version": "29.4.12", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.12.tgz", + "integrity": "sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==", "dev": true, "license": "MIT", "dependencies": { @@ -11242,7 +11536,7 @@ "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.8.0", + "semver": "^7.8.5", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, diff --git a/cluster/pulumi/package.json b/cluster/pulumi/package.json index f6e4c34262..1ac82f9ff8 100644 --- a/cluster/pulumi/package.json +++ b/cluster/pulumi/package.json @@ -3,20 +3,20 @@ "version": "1.0.0", "main": "src/index.ts", "devDependencies": { - "@eslint/eslintrc": "^3.3.5", + "@eslint/eslintrc": "^3.3.6", "@jest/globals": "^30.4.1", "@trivago/prettier-plugin-sort-imports": "^6.0.2", "@types/request": "^2.48.13", - "@typescript-eslint/eslint-plugin": "^8.61.1", + "@typescript-eslint/eslint-plugin": "^8.65.0", "@typescript-eslint/parser": "^8.57.2", - "@eslint/js": "9.39.4", + "@eslint/js": "9.39.5", "eslint-config-prettier": "10.1.8", "eslint-plugin-import": "^2.32.0", "eslint-plugin-promise": "^7.3.0", "jest": "^30.4.2", - "minimatch": "10.2.5", - "prettier": "^3.8.4", - "ts-jest": "^29.4.11", + "minimatch": "10.2.6", + "prettier": "^3.9.6", + "ts-jest": "^29.4.12", "typescript": "^5.9.3" }, "scripts": { diff --git a/cluster/pulumi/policies/tsconfig.eslint.json b/cluster/pulumi/policies/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/policies/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/splitwell/tsconfig.eslint.json b/cluster/pulumi/splitwell/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/splitwell/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/sv-canton/tsconfig.eslint.json b/cluster/pulumi/sv-canton/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/sv-canton/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/sv-runbook/tsconfig.eslint.json b/cluster/pulumi/sv-runbook/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/sv-runbook/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/sv/tsconfig.eslint.json b/cluster/pulumi/sv/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/sv/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/tsconfig.eslint.json b/cluster/pulumi/tsconfig.eslint.json new file mode 100644 index 0000000000..843ee98bc1 --- /dev/null +++ b/cluster/pulumi/tsconfig.eslint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["*.ts"], + "exclude": [] +} diff --git a/cluster/pulumi/validator-runbook/tsconfig.eslint.json b/cluster/pulumi/validator-runbook/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/validator-runbook/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} diff --git a/cluster/pulumi/validator1/tsconfig.eslint.json b/cluster/pulumi/validator1/tsconfig.eslint.json new file mode 100644 index 0000000000..f88dce88fd --- /dev/null +++ b/cluster/pulumi/validator1/tsconfig.eslint.json @@ -0,0 +1 @@ +{"extends": "./tsconfig.json"} From 96235c3984d349549efebcd6a402758a7b475b4d Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Thu, 13 Aug 2026 08:45:30 +0200 Subject: [PATCH 233/329] Bump canton version (#6765) Signed-off-by: Julien Tinguely --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index bd940930ca..a8c4d80f46 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.13-snapshot.20260811.19159.0.va77f0cc3", - "oss_sha256": "sha256:1j0m0q3b83k6z5ay9qmdvijvswh29bppxsr8w2mc8kck8prx3sqs", - "canton_base_image_sha256": "sha256:103a57fccc1ccb7edf12f75b6382d205df82e6ebe2574c6b5e63db8fefc621da", - "canton_participant_image_sha256": "sha256:74e3c1d7cc1f19f9f10261e932613217245f55be774c78eb65c8470da887902d", - "canton_mediator_image_sha256": "sha256:79728083e2e01074a7d2c740ea5db26c12d808702c37e6500198d649683ea9f4", - "canton_sequencer_image_sha256": "sha256:dcfbf19a240d89df7aa33b0403e84e46eb0e3e1b77b82fc97d0c430e3be53da4" + "version": "3.5.13-snapshot.20260812.19162.0.vd36e0b8a", + "oss_sha256": "sha256:0q53rqa47rrky4kyzzrli7a9h0rb3a68lfaxiq09qnbs1ysn3vvd", + "canton_base_image_sha256": "sha256:c024c49eda2cc580f2a2406be98254a81fad6036c89d0e2952c8d598da0b50b5", + "canton_participant_image_sha256": "sha256:da32c4477366eb07ca467ece29d7675125928212be28e6f0417af49167450a3d", + "canton_mediator_image_sha256": "sha256:b3ea4be76e4a04a095b1368e0a7bfe88aa059c7c0c3c53a50db51e2ce8275443", + "canton_sequencer_image_sha256": "sha256:753a850ec4ada8f729e31d01bec4d4f6715e4ea2790da720e19833c65a58fa80" } From c57f1686ef468810613346f05f239524c7e338da Mon Sep 17 00:00:00 2001 From: Martin Florian Date: Thu, 13 Aug 2026 10:25:58 +0200 Subject: [PATCH 234/329] Auto-enable session signing keys on KMS participants (#6778) Part of #5135 Signed-off-by: Martin Florian --- .../pack/examples/sv-helm/kms-participant-aws-values.yaml | 5 +++++ .../pack/examples/sv-helm/kms-participant-gcp-values.yaml | 5 +++++ cluster/expected/sv/expected.json | 4 ++++ cluster/expected/validator1/expected.json | 4 ++++ 4 files changed, 18 insertions(+) diff --git a/apps/app/src/pack/examples/sv-helm/kms-participant-aws-values.yaml b/apps/app/src/pack/examples/sv-helm/kms-participant-aws-values.yaml index 30ddb0440e..9a8b9d01d0 100644 --- a/apps/app/src/pack/examples/sv-helm/kms-participant-aws-values.yaml +++ b/apps/app/src/pack/examples/sv-helm/kms-participant-aws-values.yaml @@ -23,3 +23,8 @@ additionalEnvVars: secretKeyRef: name: aws-credentials key: secretAccessKey + # Session signing keys reduce KMS load and cost on KMS-backed participants by signing + # most messages with short-lived in-memory keys instead of calling the KMS each time. + # This config setting only works on KMS participants. + - name: ADDITIONAL_CONFIG_SESSION_SIGNING_KEYS + value: canton.participants.participant.crypto.session-signing-keys.enabled = true diff --git a/apps/app/src/pack/examples/sv-helm/kms-participant-gcp-values.yaml b/apps/app/src/pack/examples/sv-helm/kms-participant-gcp-values.yaml index b8598761d2..ea4b871a90 100644 --- a/apps/app/src/pack/examples/sv-helm/kms-participant-gcp-values.yaml +++ b/apps/app/src/pack/examples/sv-helm/kms-participant-gcp-values.yaml @@ -19,6 +19,11 @@ kms: additionalEnvVars: - name: GOOGLE_APPLICATION_CREDENTIALS value: "/app/gcp-credentials.json" + # Session signing keys reduce KMS load and cost on KMS-backed participants by signing + # most messages with short-lived in-memory keys instead of calling the KMS each time. + # This config setting only works on KMS participants. + - name: ADDITIONAL_CONFIG_SESSION_SIGNING_KEYS + value: canton.participants.participant.crypto.session-signing-keys.enabled = true extraVolumeMounts: - name: gcp-credentials mountPath: "/app/gcp-credentials.json" diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index 357082cc3f..7f996c98fa 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -5137,6 +5137,10 @@ "name": "GOOGLE_APPLICATION_CREDENTIALS", "value": "/app/gcp-credentials.json" }, + { + "name": "ADDITIONAL_CONFIG_SESSION_SIGNING_KEYS", + "value": "canton.participants.participant.crypto.session-signing-keys.enabled = true" + }, { "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", "value": "# Ignore missing ACS commitment and commitment mismatches\ncanton.participants.participant.parameters.stores.safe-to-prune-commitment-state = \"all\"\n" diff --git a/cluster/expected/validator1/expected.json b/cluster/expected/validator1/expected.json index a44642038f..df7f996b96 100644 --- a/cluster/expected/validator1/expected.json +++ b/cluster/expected/validator1/expected.json @@ -707,6 +707,10 @@ "name": "GOOGLE_APPLICATION_CREDENTIALS", "value": "/app/gcp-credentials.json" }, + { + "name": "ADDITIONAL_CONFIG_SESSION_SIGNING_KEYS", + "value": "canton.participants.participant.crypto.session-signing-keys.enabled = true" + }, { "name": "ADDITIONAL_CONFIG_PARTICIPANT_PRUNING", "value": "# Ignore missing ACS commitment and commitment mismatches\ncanton.participants.participant.parameters.stores.safe-to-prune-commitment-state = \"all\"\n" From 2e45f99eea1f18813c2fe8927557ec44e05a6402 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Thu, 13 Aug 2026 17:06:13 +0200 Subject: [PATCH 235/329] Bump canton version to 3.5.13 (#6781) Signed-off-by: Julien Tinguely Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- apps/app/src/test/resources/include/sequencers.conf | 1 + .../images/canton-sequencer/additional-config.conf | 1 + nix/canton-sources.json | 12 ++++++------ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/app/src/test/resources/include/sequencers.conf b/apps/app/src/test/resources/include/sequencers.conf index 2156a6a98d..85858a350d 100644 --- a/apps/app/src/test/resources/include/sequencers.conf +++ b/apps/app/src/test/resources/include/sequencers.conf @@ -36,6 +36,7 @@ _sequencer_reference_template { storage = ${_shared.storage} storage.config.properties.databaseName = "sequencer_driver" consensus-empty-block-creation-timeout = 500.milliseconds + output-fetch-how-many-recipients = 2 } type = reference block { diff --git a/cluster/images/canton-sequencer/additional-config.conf b/cluster/images/canton-sequencer/additional-config.conf index bbdf489770..2f65136564 100644 --- a/cluster/images/canton-sequencer/additional-config.conf +++ b/cluster/images/canton-sequencer/additional-config.conf @@ -10,6 +10,7 @@ canton.sequencers.sequencer { } sequencer { config.consensus-empty-block-creation-timeout = 500.milliseconds + config.output-fetch-how-many-recipients = 2 block.throughput-cap.messages { confirmation-request.strict = false topology.strict = false diff --git a/nix/canton-sources.json b/nix/canton-sources.json index a8c4d80f46..f3aee487e2 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.13-snapshot.20260812.19162.0.vd36e0b8a", - "oss_sha256": "sha256:0q53rqa47rrky4kyzzrli7a9h0rb3a68lfaxiq09qnbs1ysn3vvd", - "canton_base_image_sha256": "sha256:c024c49eda2cc580f2a2406be98254a81fad6036c89d0e2952c8d598da0b50b5", - "canton_participant_image_sha256": "sha256:da32c4477366eb07ca467ece29d7675125928212be28e6f0417af49167450a3d", - "canton_mediator_image_sha256": "sha256:b3ea4be76e4a04a095b1368e0a7bfe88aa059c7c0c3c53a50db51e2ce8275443", - "canton_sequencer_image_sha256": "sha256:753a850ec4ada8f729e31d01bec4d4f6715e4ea2790da720e19833c65a58fa80" + "version": "3.5.13", + "oss_sha256": "sha256:0fqh6zxcbmlamb5c0yqsgsdipll7x7vwlyib57bxgscya5wwwa1f", + "canton_base_image_sha256": "sha256:0c4fb100f48245ff06c3f86686003c04db6575e43214ac69bf8e2cbd5fd5aa32", + "canton_participant_image_sha256": "sha256:dd4b434fab29b40ac9278ac1d9d194683f9c36d9bca551c5618075ec19a1776c", + "canton_mediator_image_sha256": "sha256:59a2f423ed0292d33514e450bdcba59b0b50410d93b04a9f62751f18765e1c16", + "canton_sequencer_image_sha256": "sha256:e8ef032c530b078093b0e45d83e5133e308eefc40728ed0b6d424538f3f95768" } From e6db4632965691cd835771d79dbfeafb040f0ff0 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Thu, 13 Aug 2026 17:15:16 +0200 Subject: [PATCH 236/329] Clear upcoming release notes (#6779) Signed-off-by: Julien Tinguely --- docs/src/release_notes_upcoming.rst | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 17d5625652..dc39b70538 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -5,28 +5,4 @@ .. NOTE: add your upcoming release notes below this line. They are included in the `release_notes.rst`. -.. release-notes:: Upcoming - - - SV App - - - The SV app now reconciles the ``setBalanceRequestSubmissionWindowSize`` traffic control parameter of the global synchronizer against a new SV app config value ``set-balance-request-submission-window-size``, which defaults to Canton's current default of 2 minutes. - This parameter defines the time window used to compute the max sequencing time of traffic purchase (top-up) requests. - Canton lowered its default from 4 minutes to 2 minutes (see the `Canton 3.5.1 release notes `_). - Networks bootstrapped on an older version (DevNet, TestNet, MainNet) still use the old value for this parameter. - By upgrading to this version, SVs agree to change this parameter to 2 minutes (unless they override the new SV app config value). - The change takes effect once a sufficient number of SVs have upgraded. - - - The governance UI no longer stops an SV from casting or changing its vote once a - proposal's target effective time has passed. Votes are now accepted for as long as the - vote request is open, matching what the ledger allows. This makes it possible to reject a - proposal whose action fails to execute and which would otherwise remain in flight - indefinitely. - - - Network banner now always shows. The network name is derived from the scan node's public URL: - MainNet/TestNet/DevNet/ScratchNet from the cluster subdomain, LocalNet for localhost, and a - capitalized fallback otherwise (Unknown Network when no scan URL is available). - - - SV UI - - - During the creation of ``AmuletRules_SetConfig`` proposal, for the ``rewardConfig`` - config field form improve the field descriptions and use drop-downs. +release-notes:: Upcoming From b20f80d3c7fac2870b03a88945425862d2727ecd Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Thu, 13 Aug 2026 12:10:07 -0400 Subject: [PATCH 237/329] Upload protobuf encoding of snapshot/updates to bulk storage. (#6602) Signed-off-by: Matt Dziuban --- .../tests/ScanTimeBasedIntegrationTest.scala | 11 +- .../splice/store/HistoryMetrics.scala | 8 +- .../scan/admin/http/ScanHttpEncodings.scala | 11 +- .../scan/config/ScanStorageConfig.scala | 23 ++ .../scan/store/bulk/BulkStorageReader.scala | 44 ++- .../bulk/MultiEncodingBulkStorageFlow.scala | 49 +++ .../bulk/SingleAcsSnapshotBulkStorage.scala | 73 ++--- .../UpdateHistorySegmentBulkStorage.scala | 65 ++-- ...shotBulkStorageCommitFromStagingTest.scala | 31 +- ...sSnapshotBulkStorageWriterFromDbTest.scala | 134 ++++---- .../bulk/UpdateHistoryBulkStorageTest.scala | 285 ++++++++---------- 11 files changed, 435 insertions(+), 299 deletions(-) create mode 100644 apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/MultiEncodingBulkStorageFlow.scala diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala index dbd17c3a86..9dfb43d4b9 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala @@ -22,7 +22,7 @@ import org.lfdecentralizedtrust.splice.http.v0.definitions.DamlValueEncoding.mem import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.IntegrationTestWithIsolatedEnvironment import org.lfdecentralizedtrust.splice.scan.admin.http.CompactJsonScanHttpEncodings -import org.lfdecentralizedtrust.splice.scan.config.BulkStorageConfig +import org.lfdecentralizedtrust.splice.scan.config.{BulkStorageConfig, ScanStorageConfig} import org.lfdecentralizedtrust.splice.scan.config.ScanStorageConfigs.scanStorageConfigV1 import org.lfdecentralizedtrust.splice.store.{HasS3Mock, S3BucketConnectionForTests} import org.lfdecentralizedtrust.splice.store.UpdateHistory.BackfillingState @@ -446,7 +446,8 @@ class ScanTimeBasedIntegrationTest val endTime = getLedgerTime val lastMidnight = endTime.toInstant.truncatedTo(ChronoUnit.DAYS); val nextMidnight = lastMidnight.plus(1, ChronoUnit.DAYS) - val expectedAcsSnapshotKey = s"$lastMidnight~$nextMidnight/ACS_0.zstd" + val expectedAcsSnapshotKey = + s"$lastMidnight~$nextMidnight/${ScanStorageConfig.Encoding.CompactJson.storageKey("ACS", 0)}" val committedBucketConnection = new S3BucketConnectionForTests(s3ConfigMock("committed"), loggerFactory) @@ -474,7 +475,11 @@ class ScanTimeBasedIntegrationTest // at last midnight committedS3Objs .map(_.key()) - .filter(_.endsWith(s"~$lastMidnight/updates_0.zstd")) should not be empty + .filter( + _.endsWith( + s"~$lastMidnight/${ScanStorageConfig.Encoding.CompactJson.storageKey("updates", 0)}" + ) + ) should not be empty // Compare bulk storage data to hot storage data from scan // TODO(#4788): for now, bulk storage still uses v0, so we use that here as well diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/HistoryMetrics.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/HistoryMetrics.scala index 1e4193c680..73f314d99f 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/HistoryMetrics.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/HistoryMetrics.scala @@ -438,11 +438,11 @@ class HistoryMetrics(metricsFactory: LabeledMetricsFactory)(implicit ) )(metricsContext) - def incAcsSnapshotObjects(): Unit = - objectsCount.inc()(MetricsContext("object_type" -> "ACS_snapshots")) + def incAcsSnapshotObjects(encoding: String): Unit = + objectsCount.inc()(MetricsContext("object_type" -> "ACS_snapshots", "encoding" -> encoding)) - def incUpdateObjects(): Unit = - objectsCount.inc()(MetricsContext("object_type" -> "updates")) + def incUpdateObjects(encoding: String): Unit = + objectsCount.inc()(MetricsContext("object_type" -> "updates", "encoding" -> encoding)) def incUpdatesCount(count: Int): Unit = updatesCount.inc(count.toLong)(MetricsContext.Empty) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/ScanHttpEncodings.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/ScanHttpEncodings.scala index 3c43d3695a..e8d9b20c5f 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/ScanHttpEncodings.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/ScanHttpEncodings.scala @@ -636,6 +636,12 @@ object ScanHttpEncodings { ) } + def fromDamlValueEncoding(encoding: definitions.DamlValueEncoding): ScanHttpEncodings = + encoding match { + case definitions.DamlValueEncoding.members.CompactJson => CompactJsonScanHttpEncodings() + case definitions.DamlValueEncoding.members.ProtobufJson => ProtobufJsonScanHttpEncodings + } + def encodeUpdate( update: TreeUpdateWithMigrationId, encoding: definitions.DamlValueEncoding, @@ -656,10 +662,7 @@ object ScanHttpEncodings { externalTransactionHashThresholdTime, ) } - val encodings: ScanHttpEncodings = encoding match { - case definitions.DamlValueEncoding.members.CompactJson => CompactJsonScanHttpEncodings() - case definitions.DamlValueEncoding.members.ProtobufJson => ProtobufJsonScanHttpEncodings - } + val encodings: ScanHttpEncodings = fromDamlValueEncoding(encoding) // v0 always returns the update ids as `#` prefixed,as that's the way they were encoded in canton. v1 returns it without the `#` encodings.lapiToHttpUpdate( update2, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanStorageConfig.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanStorageConfig.scala index 9eaa7a0e10..1655bc8eee 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanStorageConfig.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanStorageConfig.scala @@ -3,7 +3,9 @@ package org.lfdecentralizedtrust.splice.scan.config +import cats.data.NonEmptyList import com.digitalasset.canton.data.CantonTimestamp +import org.lfdecentralizedtrust.splice.http.v0.definitions import org.lfdecentralizedtrust.splice.scan.store.AcsSnapshotStore.{ AcsSnapshot, IncrementalAcsSnapshot, @@ -11,6 +13,7 @@ import org.lfdecentralizedtrust.splice.scan.store.AcsSnapshotStore.{ import java.time.{Duration, Instant, ZoneOffset} import java.time.temporal.{ChronoField, ChronoUnit} +import scala.util.matching.Regex /** Note that these configurations must be kept consistent between SVs, * so they are not configured via a local config file in Scan. Instead, they must be voted on. @@ -136,6 +139,26 @@ case class ScanStorageConfig( } +object ScanStorageConfig { + sealed abstract class Encoding( + val key: String, + val damlValueEncoding: definitions.DamlValueEncoding, + ) { + final def storageKey(prefix: String, index: Int): String = s"${prefix}_${key}_$index.zstd" + + final def storageKeyRegex(prefix: String): Regex = + (".*" + Regex.quote(prefix) + "_" + Regex.quote(key) + "_\\d+\\.zstd").r + } + object Encoding { + case object CompactJson + extends Encoding("compact_json", definitions.DamlValueEncoding.CompactJson) + case object ProtobufJson + extends Encoding("protobuf_json", definitions.DamlValueEncoding.ProtobufJson) + + lazy val all: NonEmptyList[Encoding] = NonEmptyList.of[Encoding](CompactJson, ProtobufJson) + } +} + object ScanStorageConfigs { val scanStorageConfigV1 = ScanStorageConfig( dbAcsSnapshotPeriodHours = 3, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageReader.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageReader.scala index de1653fb35..1ce642d492 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageReader.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageReader.scala @@ -3,6 +3,7 @@ package org.lfdecentralizedtrust.splice.scan.store.bulk +import cats.data.NonEmptyList import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.tracing.TraceContext @@ -28,7 +29,9 @@ class BulkStorageReader( extends NamedLogging { def getCommittedObjectsForAcsSnapshotAtOrBefore( - atOrBeforeTimestamp: CantonTimestamp + atOrBeforeTimestamp: CantonTimestamp, + storageEncodings: NonEmptyList[ScanStorageConfig.Encoding] = + NonEmptyList.one(ScanStorageConfig.Encoding.CompactJson), )(implicit tc: TraceContext, ec: ExecutionContext): Future[AcsSnapshotObjects] = { for { snapshotTs <- @@ -45,7 +48,12 @@ class BulkStorageReader( ts.timestamp case Some(ts) => storageConfig.computeBulkSnapshotTimeAtOrBefore(atOrBeforeTimestamp) } - objects <- getAcsSnapshotObjects(snapshotTs, committedS3Connection, storageConfig) + objects <- getAcsSnapshotObjects( + snapshotTs, + committedS3Connection, + storageConfig, + storageEncodings, + ) } yield { objects } @@ -99,18 +107,26 @@ class BulkStorageReader( def getStagingObjectsForAcsSnapshotAt( timestamp: CantonTimestamp ): Future[AcsSnapshotObjects] = { - getAcsSnapshotObjects(timestamp, stagingS3Connection, storageConfig) + getAcsSnapshotObjects( + timestamp, + stagingS3Connection, + storageConfig, + ScanStorageConfig.Encoding.all, + ) } def getStagingObjectsForUpdateHistorySegment( segment: UpdatesSegment - ): Future[UpdateHistoryObjectsResponse] = getUpdateObjectsInSegment(segment, stagingS3Connection) + ): Future[UpdateHistoryObjectsResponse] = + getUpdateObjectsInSegment(segment, stagingS3Connection, ScanStorageConfig.Encoding.all) def getCommittedUpdatesBetweenDates( afterRecordTime: CantonTimestamp, atOrBeforeRecordTime: CantonTimestamp, limit: PageLimit, nextPageTokenO: Option[String], + storageEncodings: NonEmptyList[ScanStorageConfig.Encoding] = + NonEmptyList.one(ScanStorageConfig.Encoding.CompactJson), )(implicit tc: TraceContext, ec: ExecutionContext): Future[UpdateHistoryObjectsResponse] = getUpdatesBetweenDatesFromBucket( afterRecordTime, @@ -119,6 +135,7 @@ class BulkStorageReader( nextPageTokenO, committedS3Connection, updateHistoryCommittedProgress.readLatestProcessedSegment, + storageEncodings, ) def getStagingSegmentStartingAt( @@ -158,10 +175,18 @@ class BulkStorageReader( ) } + /** Checks if a key in bulk storage matches one of the given [[storageEncodings]]. */ + private def keyMatchesStorageEncodings( + prefix: String, + storageEncodings: NonEmptyList[ScanStorageConfig.Encoding], + )(key: String): Boolean = + storageEncodings.exists(_.storageKeyRegex(prefix).matches(key)) + private def getAcsSnapshotObjects( timestamp: CantonTimestamp, s3Connection: S3BucketConnection, storageConfig: ScanStorageConfig, + storageEncodings: NonEmptyList[ScanStorageConfig.Encoding], ): Future[AcsSnapshotObjects] = { for { objects <- s3Connection @@ -170,7 +195,7 @@ class BulkStorageReader( // (hence the HardLimit, just as a safety precaution). .listObjects( storageConfig.findSegmentFolderPrefixByStartTimestamp(timestamp), - _.matches(".*ACS_\\d+\\.zstd"), + keyMatchesStorageEncodings("ACS", storageEncodings), HardLimit.tryCreate(Limit.DefaultMaxPageSize), ) objectsWithChecksums <- s3Connection.getChecksums(objects) @@ -201,6 +226,7 @@ class BulkStorageReader( nextPageTokenO: Option[String], s3Connection: S3BucketConnection, readLatestProcessedSegment: => Future[Option[UpdatesSegment]], + storageEncodings: NonEmptyList[ScanStorageConfig.Encoding], )(implicit tc: TraceContext, ec: ExecutionContext): Future[UpdateHistoryObjectsResponse] = { def isFolderInRange(folder: String): Boolean = { @@ -299,7 +325,7 @@ class BulkStorageReader( if (folderLimit <= 0) { Future.successful((folderAcc, folderLimit)) } else { - getUpdateObjectsInFolder(s3Connection, folder).map { folderObjs => + getUpdateObjectsInFolder(s3Connection, folder, storageEncodings).map { folderObjs => if (folderObjs.size > folderLimit) { // Folder would exceed the limit; omit it entirely (and stop adding more by making the limit 0) if (folderAcc.isEmpty) { @@ -338,12 +364,13 @@ class BulkStorageReader( private def getUpdateObjectsInSegment( segment: UpdatesSegment, s3Connection: S3BucketConnection, + storageEncodings: NonEmptyList[ScanStorageConfig.Encoding], ): Future[UpdateHistoryObjectsResponse] = { val folder = storageConfig.getSegmentFolder( segment.fromTimestamp.timestamp, Some(segment.toTimestamp.timestamp), ) - getUpdateObjectsInFolder(s3Connection, folder) + getUpdateObjectsInFolder(s3Connection, folder, storageEncodings) .flatMap(s3Connection.getChecksums) .map { objectsWithChecksums => if (objectsWithChecksums.isEmpty) { @@ -366,9 +393,10 @@ class BulkStorageReader( private def getUpdateObjectsInFolder( s3Connection: S3BucketConnection, folder: String, + storageEncodings: NonEmptyList[ScanStorageConfig.Encoding], ): Future[Seq[String]] = s3Connection.listObjects( prefix = folder, - _.matches(".*updates_\\d+\\.zstd"), + keyMatchesStorageEncodings("updates", storageEncodings), HardLimit.tryCreate(Limit.DefaultMaxPageSize), ) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/MultiEncodingBulkStorageFlow.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/MultiEncodingBulkStorageFlow.scala new file mode 100644 index 0000000000..9e1342e4c1 --- /dev/null +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/MultiEncodingBulkStorageFlow.scala @@ -0,0 +1,49 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.scan.store.bulk + +import org.apache.pekko.NotUsed +import org.apache.pekko.stream.FlowShape +import org.apache.pekko.stream.scaladsl.{Broadcast, Flow, GraphDSL, Merge} +import org.apache.pekko.util.ByteString +import org.lfdecentralizedtrust.splice.scan.config.ScanStorageConfig + +/** Uploads every chunk of a bulk storage dump once per [[ScanStorageConfig.Encoding]]. + * + * Each upstream chunk is broadcast to one branch per encoding, where it's encoded, uploaded via + * the given [[uploadFlow]] (an [[S3ZstdObjects]] flow), and counted in the metrics. The emitted + * object keys of all branches are merged into a single downstream output. + * + * Note that the `Merge` is not eager, so the resulting flow only completes once every branch has + * uploaded all of its objects. Callers can therefore rely on total completion before advancing a + * progress marker. + */ +object MultiEncodingBulkStorageFlow { + private lazy val encodings = ScanStorageConfig.Encoding.all.toList + private lazy val numEncodings = encodings.length + + def apply[A]( + encode: (A, ScanStorageConfig.Encoding) => ByteString, + uploadFlow: ScanStorageConfig.Encoding => Flow[ByteString, String, ?], + incObjects: ScanStorageConfig.Encoding => Unit, + ): Flow[A, String, NotUsed] = { + Flow.fromGraph(GraphDSL.create() { implicit b => + import GraphDSL.Implicits.* + + val broadcast = b.add(Broadcast[A](numEncodings)) + val merge = b.add(Merge[String](numEncodings)) + + encodings.zipWithIndex.foreach { case (encoding, i) => + val branch = Flow[A] + .map(encode(_, encoding)) + .via(uploadFlow(encoding)) + .wireTap(_ => incObjects(encoding)) + + broadcast.out(i) ~> branch ~> merge.in(i) + } + + FlowShape(broadcast.in, merge.out) + }) + } +} diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/SingleAcsSnapshotBulkStorage.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/SingleAcsSnapshotBulkStorage.scala index fb514a6dce..bddfca8da7 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/SingleAcsSnapshotBulkStorage.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/SingleAcsSnapshotBulkStorage.scala @@ -8,7 +8,7 @@ import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.tracing.TraceContext import org.apache.pekko.stream.scaladsl.{Flow, Source} import org.apache.pekko.util.ByteString -import org.lfdecentralizedtrust.splice.scan.admin.http.CompactJsonScanHttpEncodings +import org.lfdecentralizedtrust.splice.scan.admin.http.ScanHttpEncodings import org.lfdecentralizedtrust.splice.scan.store.AcsSnapshotStore import org.lfdecentralizedtrust.splice.store.{ HistoryMetrics, @@ -16,6 +16,7 @@ import org.lfdecentralizedtrust.splice.store.{ S3BucketConnection, TimestampWithMigrationId, } +import org.lfdecentralizedtrust.splice.store.events.SpliceCreatedEvent import scala.concurrent.Future import io.circe.syntax.* @@ -46,15 +47,10 @@ class SingleAcsSnapshotBulkStorage( )(implicit tc: TraceContext, ec: ExecutionContext) extends NamedLogging { - case class AcsSnapshotChunk( - chunkBytes: ByteString, - numContracts: Int, - ) - private def getAcsSnapshotChunk( timestamp: TimestampWithMigrationId, after: Option[Long], - ): Future[(Position, AcsSnapshotChunk)] = { + ): Future[(Position, Vector[SpliceCreatedEvent])] = { for { snapshot <- acsSnapshotStore.queryAcsSnapshot( timestamp.migrationId, @@ -64,22 +60,27 @@ class SingleAcsSnapshotBulkStorage( Seq.empty, Seq.empty, ) - } yield { - val encoded = snapshot.createdEventsInPage.map(event => - CompactJsonScanHttpEncodings() - .javaToHttpActiveContract(event.eventId, event.recordTime, event.event) - ) - val contractsStr = encoded.map(_.asJson.noSpacesSortKeys).mkString("\n") + "\n" - val contractsBytes = ByteString(contractsStr.getBytes(StandardCharsets.UTF_8)) - logger.debug( - s"Read ${encoded.length} contracts from ACS, to a bytestring of size ${contractsBytes.length} bytes" - ) - ( - snapshot.afterToken.fold(End: Position)(Index(_)), - AcsSnapshotChunk(contractsBytes, encoded.length), - ) - } + } yield ( + snapshot.afterToken.fold(End: Position)(Index(_)), + snapshot.createdEventsInPage, + ) + + } + private def encodeEvents( + events: Vector[SpliceCreatedEvent], + encoding: ScanStorageConfig.Encoding, + ): ByteString = { + val encodings = ScanHttpEncodings.fromDamlValueEncoding(encoding.damlValueEncoding) + val encoded = events.map(event => + encodings.javaToHttpActiveContract(event.eventId, event.recordTime, event.event) + ) + val contractsStr = encoded.map(_.asJson.noSpacesSortKeys).mkString("\n") + "\n" + val contractsBytes = ByteString(contractsStr.getBytes(StandardCharsets.UTF_8)) + logger.debug( + s"Read ${encoded.length} contracts from ACS, to a bytestring of size ${contractsBytes.length} bytes, with encoding ${encoding.key}" + ) + contractsBytes } private def getSource: Source[Seq[String], NotUsed] = { @@ -89,22 +90,26 @@ class SingleAcsSnapshotBulkStorage( case Index(i) => getAcsSnapshotChunk(timestamp, Some(i)).map(Some(_)) case End => Future.successful(None) } - .map(chunk => { - historyMetrics.BulkStorage.incContractsCount(chunk.numContracts) - chunk.chunkBytes + .map(events => { + historyMetrics.BulkStorage.incContractsCount(events.length) + events }) .via( - S3ZstdObjects( - storageConfig, - appConfig, - s3Connection, - { objIdx => - s"${storageConfig.getSegmentFolder(timestamp.timestamp, None)}/ACS_$objIdx.zstd" - }, - loggerFactory, + MultiEncodingBulkStorageFlow( + encodeEvents, + encoding => + S3ZstdObjects( + storageConfig, + appConfig, + s3Connection, + objIdx => + s"${storageConfig.getSegmentFolder(timestamp.timestamp, None)}/${encoding + .storageKey("ACS", objIdx)}", + loggerFactory, + ), + encoding => historyMetrics.BulkStorage.incAcsSnapshotObjects(encoding.key), ) ) - .wireTap(_ => historyMetrics.BulkStorage.incAcsSnapshotObjects()) .fold(Seq.empty[String])(_ :+ _) } } diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistorySegmentBulkStorage.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistorySegmentBulkStorage.scala index bd0782c85a..ea2561d8d4 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistorySegmentBulkStorage.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistorySegmentBulkStorage.scala @@ -3,6 +3,7 @@ package org.lfdecentralizedtrust.splice.scan.store.bulk +import cats.data.NonEmptyList import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.tracing.TraceContext import org.apache.pekko.NotUsed @@ -10,7 +11,6 @@ import org.apache.pekko.stream.scaladsl.{Flow, Source} import org.lfdecentralizedtrust.splice.scan.config.{BulkStorageConfig, ScanStorageConfig} import org.apache.pekko.util.ByteString import org.apache.pekko.pattern.after -import org.lfdecentralizedtrust.splice.http.v0.definitions import org.lfdecentralizedtrust.splice.scan.admin.http.{ScanHttpEncodings, ScanJsonSupport} import org.lfdecentralizedtrust.splice.store.{ HistoryMetrics, @@ -56,14 +56,11 @@ class UpdateHistorySegmentBulkStorage( )(implicit tc: TraceContext, ec: ExecutionContext) extends NamedLogging { - case class UpdatesChunk( - updateBytes: ByteString, - numUpdates: Int, - ) - private def getUpdatesChunk( afterTs: TimestampWithMigrationId - )(implicit actorSystem: ActorSystem): Future[Option[(TimestampWithMigrationId, UpdatesChunk)]] = { + )(implicit + actorSystem: ActorSystem + ): Future[Option[(TimestampWithMigrationId, Seq[TreeUpdateWithMigrationId])]] = { for { updates <- updateHistory.getUpdatesWithoutImportUpdates( Some((afterTs.migrationId, afterTs.timestamp)), @@ -85,7 +82,6 @@ class UpdateHistorySegmentBulkStorage( s"Adding ${updatesInSegment.length} updates, between record time ${updatesInSegment.headOption .map(_.update.update.recordTime)} and ${updatesInSegment.lastOption.map(_.update.update.recordTime)}" ) - val updatesBytes: ByteString = encodeUpdates(updatesInSegment) val last = updatesInSegment.lastOption.getOrElse( throw new RuntimeException("Unexpected failure") ) @@ -93,7 +89,7 @@ class UpdateHistorySegmentBulkStorage( Some( ( TimestampWithMigrationId(last.update.update.recordTime, last.migrationId), - UpdatesChunk(updatesBytes, updatesInSegment.length), + updatesInSegment, ) ) ) @@ -113,7 +109,7 @@ class UpdateHistorySegmentBulkStorage( appConfig.updatesPollingInterval.underlying, actorSystem.scheduler, ) { - Future.successful(Some((afterTs, UpdatesChunk(ByteString.empty, 0)))) + Future.successful(Some((afterTs, Nil))) } } } yield { @@ -121,11 +117,14 @@ class UpdateHistorySegmentBulkStorage( } } - private def encodeUpdates(updates: Seq[TreeUpdateWithMigrationId]) = { - val encoded = updates.map(update => + private def encodeUpdates( + updates: NonEmptyList[TreeUpdateWithMigrationId], + encoding: ScanStorageConfig.Encoding, + ): ByteString = { + val encoded = updates.toList.map(update => ScanHttpEncodings.encodeUpdateV2( update, - definitions.DamlValueEncoding.CompactJson, + encoding.damlValueEncoding, ScanHttpEncodings.V1, ) ) @@ -139,8 +138,7 @@ class UpdateHistorySegmentBulkStorage( .mkString("\n") + "\n" val updatesBytes = ByteString(updatesStr.getBytes(StandardCharsets.UTF_8)) logger.debug( - s"Read and encoded ${encoded.length} updates from DB, to a bytestring of size ${updatesBytes.length} bytes. Timestamps are ${updates.headOption - .map(_.update.update.recordTime)} to ${updates.lastOption.map(_.update.update.recordTime)}" + s"Read and encoded ${encoded.length} updates from DB, to a bytestring of size ${updatesBytes.length} bytes, with encoding ${encoding.key}. Timestamps are ${updates.head.update.update.recordTime} to ${updates.last.update.update.recordTime}" ) updatesBytes } @@ -150,30 +148,35 @@ class UpdateHistorySegmentBulkStorage( ): Source[Seq[String], NotUsed] = { Source .unfoldAsync(segment.fromTimestamp)(ts => getUpdatesChunk(ts)) - .map(chunk => { - historyMetrics.BulkStorage.incUpdatesCount(chunk.numUpdates) - chunk.updateBytes + .map(updates => { + historyMetrics.BulkStorage.incUpdatesCount(updates.length) + updates }) .via( - // We use lazyFlow, so that in the case where no updates are emitted, we don't instantiate the S3ZstdObjects at all, - // since it assumes that it gets at least one chunk to write. - Flow.lazyFlow(() => - S3ZstdObjects( - storageConfig, - appConfig, - s3Connection, - { objIdx => - s"${storageConfig.getSegmentFolder(segment.fromTimestamp.timestamp, Some(segment.toTimestamp.timestamp))}/updates_$objIdx.zstd" - }, - loggerFactory, - ) + MultiEncodingBulkStorageFlow( + (updates, encoding) => + NonEmptyList.fromFoldable(updates).fold(ByteString.empty)(encodeUpdates(_, encoding)), + encoding => + // We use lazyFlow, so that in the case where no updates are emitted, we don't instantiate the S3ZstdObjects at all, + // since it assumes that it gets at least one chunk to write. + Flow.lazyFlow(() => + S3ZstdObjects( + storageConfig, + appConfig, + s3Connection, + objIdx => + s"${storageConfig.getSegmentFolder(segment.fromTimestamp.timestamp, Some(segment.toTimestamp.timestamp))}/${encoding + .storageKey("updates", objIdx)}", + loggerFactory, + ) + ), + encoding => historyMetrics.BulkStorage.incUpdateObjects(encoding.key), ) ) .orElse(Source.lazySource { () => logger.warn(s"No updates found in segment ${segment.fromTimestamp}-${segment.toTimestamp}") Source.empty }) - .wireTap(_ => historyMetrics.BulkStorage.incUpdateObjects()) .fold(Seq.empty[String])(_ :+ _) } } diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStagingTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStagingTest.scala index 44adcd701e..69d660309e 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStagingTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStagingTest.scala @@ -137,21 +137,25 @@ class AcsSnapshotBulkStorageCommitFromStagingTest objectCount: Int, ): Unit = { (0 until objectCount).foreach { i => - stagingConnection - .createObject( - s"${bulkStorageTestConfig.getSegmentFolder(ts(day), None)}/ACS_$i.zstd", - s"dummy acs snapshot at ${ts(day)} (object $i)".getBytes, - ) - .futureValue + ScanStorageConfig.Encoding.all.toList.foreach { encoding => + stagingConnection + .createObject( + s"${bulkStorageTestConfig.getSegmentFolder(ts(day), None)}/${encoding.storageKey("ACS", i)}", + s"dummy acs snapshot at ${ts(day)} (object $i)".getBytes, + ) + .futureValue + } } } def assertCommittedObjectsForSnapshot(day: Int, expectedCount: Int): Assertion = { - val expectedKeys = (0 until expectedCount).map { i => - s"${bulkStorageTestConfig.getSegmentFolder(ts(day), None)}/ACS_$i.zstd" + val expectedKeys = (0 until expectedCount).flatMap { i => + ScanStorageConfig.Encoding.all.toList.map { encoding => + s"${bulkStorageTestConfig.getSegmentFolder(ts(day), None)}/${encoding.storageKey("ACS", i)}" + } } reader - .getCommittedObjectsForAcsSnapshotAtOrBefore(ts(day)) + .getCommittedObjectsForAcsSnapshotAtOrBefore(ts(day), ScanStorageConfig.Encoding.all) .futureValue .objects .map(_.key) should contain theSameElementsAs @@ -215,7 +219,8 @@ class AcsSnapshotBulkStorageCommitFromStagingTest committedConnection .copyObject( "staging", - s"${bulkStorageTestConfig.getSegmentFolder(ts(4), None)}/ACS_0.zstd", + s"${bulkStorageTestConfig.getSegmentFolder(ts(4), None)}/${ScanStorageConfig.Encoding.CompactJson + .storageKey("ACS", 0)}", ) .futureValue @@ -236,12 +241,14 @@ class AcsSnapshotBulkStorageCommitFromStagingTest committedConnection .copyObject( "staging", - s"${bulkStorageTestConfig.getSegmentFolder(ts(5), None)}/ACS_$i.zstd", + s"${bulkStorageTestConfig + .getSegmentFolder(ts(5), None)}/${ScanStorageConfig.Encoding.CompactJson.storageKey("ACS", i)}", ) .futureValue stagingConnection .deleteObject( - s"${bulkStorageTestConfig.getSegmentFolder(ts(5), None)}/ACS_$i.zstd" + s"${bulkStorageTestConfig + .getSegmentFolder(ts(5), None)}/${ScanStorageConfig.Encoding.CompactJson.storageKey("ACS", i)}" ) .futureValue } diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageWriterFromDbTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageWriterFromDbTest.scala index e700ce4a2d..a79ad57c8d 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageWriterFromDbTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageWriterFromDbTest.scala @@ -23,7 +23,11 @@ import org.apache.pekko.stream.scaladsl.{Sink, Source} import org.lfdecentralizedtrust.splice.config.AutomationConfig import org.lfdecentralizedtrust.splice.environment.{DarResources, RetryProvider, SpliceMetrics} import org.lfdecentralizedtrust.splice.http.v0.definitions as httpApi -import org.lfdecentralizedtrust.splice.scan.admin.http.CompactJsonScanHttpEncodings +import org.lfdecentralizedtrust.splice.scan.admin.http.{ + CompactJsonScanHttpEncodings, + ProtobufJsonScanHttpEncodings, + ScanHttpEncodings, +} import org.lfdecentralizedtrust.splice.scan.config.{BulkStorageConfig, ScanStorageConfig} import org.lfdecentralizedtrust.splice.scan.store.{ AcsSnapshotStore, @@ -50,6 +54,7 @@ import org.slf4j.event.Level import java.time.Instant import java.time.temporal.ChronoUnit +import java.util.concurrent.ConcurrentHashMap import scala.concurrent.{ExecutionContext, Future} import scala.jdk.CollectionConverters.* import scala.concurrent.duration.* @@ -106,49 +111,80 @@ class AcsSnapshotBulkStorageWriterFromDbTest ) .map(_.createdEventsInPage) } yield { - val objectKeys = s3Objects.contents.asScala.map(_.key()).sorted - objectKeys should have length 7 - objectKeys.foreach( - _ should startWith("2026-01-02T00:00:00Z~2026-01-03T00:00:00Z/ACS_") - ) - val objectCountMetrics = metricsFactory.metrics.counters.get( - SpliceMetrics.MetricsPrefix :+ "history" :+ "bulk-storage" :+ "object-count" - ) - val numObjectsFromMetric = objectCountMetrics.value - .get(MetricsContext.Empty) - .value - .markers - .get(MetricsContext("object_type" -> "ACS_snapshots")) - .value - .get() - numObjectsFromMetric shouldBe 7 + def checkEncoding(encoding: ScanStorageConfig.Encoding) = { + /* We hard-code the expected digests to enforce that the persisted data format does not change. + These values must not be modified unless there is a conscious decision to change the persisted format, + with a migration plan for how to apply it consistently across SVs. */ + val (encodings, expectedDigests): (ScanHttpEncodings, Seq[String]) = + encoding match { + case ScanStorageConfig.Encoding.CompactJson => + ( + new CompactJsonScanHttpEncodings(identity, identity), + Seq( + "n6CV6dF9zpleq66YiXmCG96hw1BBakp1I8JjC5lf5n0=", + "bJDalSmiVKCk9QSc6sAWdahJNZQqVn51WmkFbQI6wkA=", + "noZU+He8HnCM38MujtpEle4NNGwnE7wN8z96V+HTdK0=", + "rwak+Y4JcInTiEa2yUKf8rjO3RD7ay/D2hmQG4BAa54=", + "YM7SNxHrU3xYyNOjgEqowitAvgsiX1f7tq0pCaD/OhQ=", + "Mb3D2ZOVQclMwuYEqLuTKhGqnUHCio6K61FBTXgt5Vs=", + "+5iW2M9Vz5y9sCtEWyrS3m+EUqnD50dXRVIMQAMSgBY=", + ), + ) + case ScanStorageConfig.Encoding.ProtobufJson => + ( + ProtobufJsonScanHttpEncodings, + Seq( + "NDdxcBFCRqz5hXHXqJkD9qqzc1C9t0PWZgHq2F9xsUA=", + "hluFPPWS1V2djExfppU+aiPqYx18s/qxZe83nFjthV0=", + "5D3k/XWBw/OhN5wus7XMuesHKhQwlktDFMFcO9lAI5g=", + "sov3P/ekZ1CRQUPYVMcA7tn2yO4EV+XYtlWc5NF2FP0=", + "tYYPDCgkg/s+dbJD9i6kxBHiMIKq2RB/D0+Fc5gzlAI=", + "CjQBhKAQ+KU7it/OCAkyDtKLNHJmWu2nsU4x1TcT+us=", + "ZonY8bJ5NA2b1Y/gOU2eeUF6lVsQqijKWDjP8kKWvhU=", + "G7eAcDOxoCsxpC9Qwo61IZFUFD0sZnqPf3/dolF9nXQ=", + ), + ) + } + val objectKeys = s3Objects.contents.asScala + .map(_.key()) + .sorted + .filter( + encoding.storageKeyRegex("ACS").matches + ) + objectKeys should have length expectedDigests.length.toLong + objectKeys.foreach( + _ should startWith(s"2026-01-02T00:00:00Z~2026-01-03T00:00:00Z/ACS_${encoding.key}") + ) + val objectCountMetrics = metricsFactory.metrics.counters.get( + SpliceMetrics.MetricsPrefix :+ "history" :+ "bulk-storage" :+ "object-count" + ) + val numObjectsFromMetric = objectCountMetrics.value + .get(MetricsContext.Empty) + .value + .markers + .get(MetricsContext("object_type" -> "ACS_snapshots", "encoding" -> encoding.key)) + .value + .get() + numObjectsFromMetric shouldBe expectedDigests.length - val allContractsFromS3 = objectKeys.flatMap( - readUncompressAndDecode( - bucketConnection, - io.circe.parser.decode[httpApi.ActiveContract], + val allContractsFromS3 = objectKeys.flatMap( + readUncompressAndDecode( + bucketConnection, + io.circe.parser.decode[httpApi.ActiveContract], + ) ) - ) - allContracts.map(c => - new CompactJsonScanHttpEncodings(identity, identity) - .javaToHttpActiveContract(c.eventId, c.recordTime, c.event) - ) should contain theSameElementsInOrderAs allContractsFromS3 + allContracts.map(c => + encodings.javaToHttpActiveContract(c.eventId, c.recordTime, c.event) + ) should contain theSameElementsInOrderAs allContractsFromS3 - /* We hard-code the expected digests to enforce that the persisted data format does not change. - These values must not be modified unless there is a conscious decision to change the persisted format, - with a migration plan for how to apply it consistently across SVs. */ - bucketConnection - .getChecksums(objectKeys.toSeq) - .futureValue - .map(_.checksum) should contain theSameElementsInOrderAs Seq( - "n6CV6dF9zpleq66YiXmCG96hw1BBakp1I8JjC5lf5n0=", - "bJDalSmiVKCk9QSc6sAWdahJNZQqVn51WmkFbQI6wkA=", - "noZU+He8HnCM38MujtpEle4NNGwnE7wN8z96V+HTdK0=", - "rwak+Y4JcInTiEa2yUKf8rjO3RD7ay/D2hmQG4BAa54=", - "YM7SNxHrU3xYyNOjgEqowitAvgsiX1f7tq0pCaD/OhQ=", - "Mb3D2ZOVQclMwuYEqLuTKhGqnUHCio6K61FBTXgt5Vs=", - "+5iW2M9Vz5y9sCtEWyrS3m+EUqnD50dXRVIMQAMSgBY=", - ) + bucketConnection + .getChecksums(objectKeys.toSeq) + .futureValue + .map(_.checksum) should contain theSameElementsInOrderAs expectedDigests + } + + checkEncoding(ScanStorageConfig.Encoding.CompactJson) + checkEncoding(ScanStorageConfig.Encoding.ProtobufJson) } } @@ -223,7 +259,8 @@ class AcsSnapshotBulkStorageWriterFromDbTest reader.getCommittedObjectsForAcsSnapshotAtOrBefore(queryTs).futureValue getObjectsResult.objects.map(_.key) should contain theSameElementsInOrderAs (0 until expectedNumObjects).map(i => - s"$expectedTs~${expectedTs.add(1.days)}/ACS_$i.zstd" + s"$expectedTs~${expectedTs + .add(1.days)}/${ScanStorageConfig.Encoding.CompactJson.storageKey("ACS", i)}" ) getObjectsResult.objects.map(_.checksum).foreach { // We test elsewhere that computed and persisted checksums are correct, so here we just check that they are present and not empty @@ -403,19 +440,12 @@ class AcsSnapshotBulkStorageWriterFromDbTest bucketConnection: S3BucketConnection ): S3BucketConnection = { val s3BucketConnectionWithErrors = Mockito.spy(bucketConnection) - var failureCount = 0 + val failedKeys = ConcurrentHashMap.newKeySet[String]() val _ = doAnswer { (invocation: InvocationOnMock) => val args = invocation.getArguments args.toList match { - case (key: String) :: _ if key.endsWith("2.zstd") => - if (failureCount < 1) { - failureCount += 1 - throw new RuntimeException(s"Simulated S3 error (#$failureCount)") - } else { - failureCount = 0 - logger.debug(s"No Simulated S3 error, resetting failureCount to 0") - invocation.callRealMethod().asInstanceOf[s3BucketConnectionWithErrors.AppendWriteObject] - } + case (key: String) :: _ if key.endsWith("2.zstd") && failedKeys.add(key) => + throw new RuntimeException(s"Simulated S3 error for $key") case _ => invocation.callRealMethod().asInstanceOf[s3BucketConnectionWithErrors.AppendWriteObject] } diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageTest.scala index 62ddcd750e..7e400aed5d 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageTest.scala @@ -25,7 +25,11 @@ import org.lfdecentralizedtrust.splice.config.AutomationConfig import org.lfdecentralizedtrust.splice.environment.{DarResources, RetryProvider, SpliceMetrics} import org.lfdecentralizedtrust.splice.environment.ledger.api.TransactionTreeUpdate import org.lfdecentralizedtrust.splice.http.v0.definitions.UpdateHistoryItemV2 -import org.lfdecentralizedtrust.splice.scan.admin.http.CompactJsonScanHttpEncodings +import org.lfdecentralizedtrust.splice.scan.admin.http.{ + CompactJsonScanHttpEncodings, + ProtobufJsonScanHttpEncodings, + ScanHttpEncodings, +} import org.lfdecentralizedtrust.splice.scan.config.{BulkStorageConfig, ScanStorageConfig} import org.lfdecentralizedtrust.splice.scan.store.{ScanKeyValueProvider, ScanKeyValueStore} import org.lfdecentralizedtrust.splice.store.UpdateHistory.UpdateHistoryResponse @@ -104,22 +108,37 @@ class UpdateHistoryBulkStorageTest "Ingest 1000 more events. Now the last timestamp will be beyond the segment, so the source will complete and emit the object keys" ) { mockStore.mockIngestion(1000) - probe.expectNext(20.seconds) should contain theSameElementsInOrderAs Seq( - "1970-01-01T00:00:00.100Z~1970-01-01T00:00:02.300Z/updates_0.zstd", - "1970-01-01T00:00:00.100Z~1970-01-01T00:00:02.300Z/updates_1.zstd", + val expectedKeys = ScanStorageConfig.Encoding.all.toList.flatMap(e => + Seq( + s"1970-01-01T00:00:00.100Z~1970-01-01T00:00:02.300Z/${e.storageKey("updates", 0)}", + s"1970-01-01T00:00:00.100Z~1970-01-01T00:00:02.300Z/${e.storageKey("updates", 1)}", + ) + ) + val actualKeys = probe.expectNext(20.seconds) + def filterKeys(keys: Seq[String], encoding: ScanStorageConfig.Encoding) = + keys.filter(encoding.storageKeyRegex("updates").matches) + actualKeys should contain theSameElementsAs expectedKeys + // Confirm encoding-specific keys are in the correct order + ScanStorageConfig.Encoding.all.toList.foreach(e => + filterKeys(actualKeys, e) should contain theSameElementsInOrderAs filterKeys( + expectedKeys, + e, + ) ) probe.expectComplete() val objectCountMetrics = metricsFactory.metrics.counters .get(SpliceMetrics.MetricsPrefix :+ "history" :+ "bulk-storage" :+ "object-count") .value - val numObjectsFromMetric = objectCountMetrics - .get(MetricsContext.Empty) - .value - .markers - .get(MetricsContext("object_type" -> "updates")) - .value - .get() - numObjectsFromMetric shouldBe 2 + def numObjectsFromMetric(encoding: ScanStorageConfig.Encoding): Long = + objectCountMetrics + .get(MetricsContext.Empty) + .value + .markers + .get(MetricsContext("object_type" -> "updates", "encoding" -> encoding.key)) + .value + .get() + numObjectsFromMetric(ScanStorageConfig.Encoding.CompactJson) shouldBe 2 + numObjectsFromMetric(ScanStorageConfig.Encoding.ProtobufJson) shouldBe 2 } clue("Check that the dumped content is correct") { @@ -134,27 +153,50 @@ class UpdateHistoryBulkStorageTest update.update.update.recordTime <= toTimestamp ) } yield { - val objectKeys = s3Objects.contents.asScala.map(_.key()).sorted - objectKeys should have length 2 - s3Objects.contents().get(0).size().toInt should be >= maxFileSize.toInt - val allUpdatesFromS3 = objectKeys.flatMap( - readUncompressAndDecode(bucketConnection, io.circe.parser.decode[UpdateHistoryItemV2]) - ) - allUpdatesFromS3.length shouldBe segmentUpdates.length - allUpdatesFromS3 - .map( - new CompactJsonScanHttpEncodings(identity, identity).httpToLapiUpdate - ) should contain theSameElementsInOrderAs segmentUpdates - /* We hard-code the expected digests to enforce that the persisted data format does not change. - These values must not be modified unless there is a conscious decision to change the persisted format, - with a migration plan for how to apply it consistently across SVs. */ - bucketConnection - .getChecksums(objectKeys.toSeq) - .futureValue - .map(_.checksum) should contain theSameElementsInOrderAs Seq( - "MM+DyxPP6UgpAaSCsm99j4ZAtYIK3TIrPmxFyodBrQQ=", - "2oWb5Um18xwnJTMkC4yilyrcsUADYoxtV7toJi29VsI=", - ) + def checkEncoding(encoding: ScanStorageConfig.Encoding) = { + /* We hard-code the expected digests to enforce that the persisted data format does not change. + These values must not be modified unless there is a conscious decision to change the persisted format, + with a migration plan for how to apply it consistently across SVs. */ + val (encodings, expectedDigests): (ScanHttpEncodings, Seq[String]) = encoding match { + case ScanStorageConfig.Encoding.CompactJson => + ( + new CompactJsonScanHttpEncodings(identity, identity), + Seq( + "MM+DyxPP6UgpAaSCsm99j4ZAtYIK3TIrPmxFyodBrQQ=", + "2oWb5Um18xwnJTMkC4yilyrcsUADYoxtV7toJi29VsI=", + ), + ) + case ScanStorageConfig.Encoding.ProtobufJson => + ( + ProtobufJsonScanHttpEncodings, + Seq( + "9QrYwnzkSce+GIh82uzY+1JHv4ukYC+llD0Idx1GDio=", + "pCOz8MG6Zoxup4NGnzBx48kFPm582cWn+GxWSZFyq+E=", + ), + ) + } + + val filteredS3Objects = s3Objects.contents.asScala + .filter(o => encoding.storageKeyRegex("updates").matches(o.key())) + val objectKeys = filteredS3Objects.map(_.key()).sorted + objectKeys should have length expectedDigests.length.toLong + filteredS3Objects(0).size().toInt should be >= maxFileSize.toInt + val allUpdatesFromS3 = objectKeys.flatMap( + readUncompressAndDecode(bucketConnection, io.circe.parser.decode[UpdateHistoryItemV2]) + ) + allUpdatesFromS3.length shouldBe segmentUpdates.length + allUpdatesFromS3 + .map( + encodings.httpToLapiUpdate + ) should contain theSameElementsInOrderAs segmentUpdates + bucketConnection + .getChecksums(objectKeys.toSeq) + .futureValue + .map(_.checksum) should contain theSameElementsInOrderAs expectedDigests + } + + checkEncoding(ScanStorageConfig.Encoding.CompactJson) + checkEncoding(ScanStorageConfig.Encoding.ProtobufJson) } } } @@ -382,28 +424,28 @@ class UpdateHistoryBulkStorageTest loggerFactory, ) - val d20u0 = "2015-10-20T00:00:00Z~2015-10-21T00:00:00Z/updates_0.zstd" - val d20u1 = "2015-10-20T00:00:00Z~2015-10-21T00:00:00Z/updates_1.zstd" - val d21u0 = "2015-10-21T00:00:00Z~2015-10-22T00:00:00Z/updates_0.zstd" - val d21u1 = "2015-10-21T00:00:00Z~2015-10-22T00:00:00Z/updates_1.zstd" - val d22u0 = "2015-10-22T00:00:00Z~2015-10-23T00:00:00Z/updates_0.zstd" - val d22u1 = "2015-10-22T00:00:00Z~2015-10-23T00:00:00Z/updates_1.zstd" - val d23u0 = "2015-10-23T00:00:00Z~2015-10-24T00:00:00Z/updates_0.zstd" - val d23u1 = "2015-10-23T00:00:00Z~2015-10-24T00:00:00Z/updates_1.zstd" - val d24u0 = "2015-10-24T00:00:00Z~2015-10-25T00:00:00Z/updates_0.zstd" - val d24u1 = "2015-10-24T00:00:00Z~2015-10-25T00:00:00Z/updates_1.zstd" - val allObjs = Seq( - d20u0, - d20u1, - d21u0, - d21u1, - d22u0, - d22u1, - d23u0, - d23u1, - d24u0, - d24u1, - ) + def makeObjectKeys(dates: String, prefix: String = "updates"): Seq[String] = + ScanStorageConfig.Encoding.all.toList.flatMap { encoding => + Seq(0, 1).map { i => + s"${dates}/${encoding.storageKey(prefix, i)}" + } + } + + def getCommitted(start: String, end: String, limit: Int, nextPageTokenO: Option[String]) = + reader.getCommittedUpdatesBetweenDates( + CantonTimestamp.tryFromInstant(Instant.parse(start)), + CantonTimestamp.tryFromInstant(Instant.parse(end)), + PageLimit.tryCreate(limit), + nextPageTokenO, + ScanStorageConfig.Encoding.all, + ) + + val d20 = makeObjectKeys("2015-10-20T00:00:00Z~2015-10-21T00:00:00Z") + val d21 = makeObjectKeys("2015-10-21T00:00:00Z~2015-10-22T00:00:00Z") + val d22 = makeObjectKeys("2015-10-22T00:00:00Z~2015-10-23T00:00:00Z") + val d23 = makeObjectKeys("2015-10-23T00:00:00Z~2015-10-24T00:00:00Z") + val d24 = makeObjectKeys("2015-10-24T00:00:00Z~2015-10-25T00:00:00Z") + val allObjs = d20 ++ d21 ++ d22 ++ d23 ++ d24 Future .sequence(allObjs.map { bucketConnection.createObject(_) @@ -411,108 +453,58 @@ class UpdateHistoryBulkStorageTest .futureValue // A wider range than the data - val res1 = reader - .getCommittedUpdatesBetweenDates( - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-10T00:00:00Z")), - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-30T00:00:00Z")), - PageLimit.tryCreate(10), - None, - ) - .futureValue - res1.objects.map(_.key) should contain theSameElementsInOrderAs Seq( - d20u0, - d20u1, - d21u0, - d21u1, - d22u0, - d22u1, - d23u0, - d23u1, - ) + val res1 = getCommitted("2015-10-10T00:00:00Z", "2015-10-30T00:00:00Z", 20, None).futureValue + res1.objects.map(_.key) should contain theSameElementsInOrderAs d20 ++ d21 ++ d22 ++ d23 res1.nextPageTokenO shouldBe Some("2015-10-23T00:00:00Z~2015-10-24T00:00:00Z/") - val res1b = reader - .getCommittedUpdatesBetweenDates( - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-10T00:00:00Z")), - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-30T00:00:00Z")), - PageLimit.tryCreate(10), - res1.nextPageTokenO, - ) - .futureValue + val res1b = getCommitted( + "2015-10-10T00:00:00Z", + "2015-10-30T00:00:00Z", + 20, + res1.nextPageTokenO, + ).futureValue res1b.objects.map(_.key) shouldBe empty res1b.nextPageTokenO shouldBe Some("2015-10-23T00:00:00Z~2015-10-24T00:00:00Z/") // A smaller range within the data - val res2 = reader - .getCommittedUpdatesBetweenDates( - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-21T16:00:00Z")), - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-21T16:00:05Z")), - PageLimit.tryCreate(10), - None, - ) - .futureValue - res2.objects.map(_.key) should contain theSameElementsInOrderAs Seq(d21u0, d21u1) + val res2 = getCommitted("2015-10-21T16:00:00Z", "2015-10-21T16:00:05Z", 20, None).futureValue + res2.objects.map(_.key) should contain theSameElementsInOrderAs d21 res2.nextPageTokenO shouldBe None // pagination - val res3 = reader - .getCommittedUpdatesBetweenDates( - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-01T12:00:00Z")), - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-21T16:00:05Z")), - PageLimit.tryCreate( - 3 - ), // on purpose 3 even though we expect only 2 back (since the response is always full days of updates) - None, - ) - .futureValue - res3.objects.map(_.key) should contain theSameElementsInOrderAs Seq(d20u0, d20u1) + val res3 = getCommitted( + "2015-10-01T12:00:00Z", + "2015-10-21T16:00:05Z", + 5, // on purpose 5 even though we expect only 4 back (since the response is always full days of updates) + None, + ).futureValue + res3.objects.map(_.key) should contain theSameElementsInOrderAs d20 res3.nextPageTokenO shouldBe Some("2015-10-20T00:00:00Z~2015-10-21T00:00:00Z/") - val res3b = reader - .getCommittedUpdatesBetweenDates( - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-01T12:00:00Z")), - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-21T16:00:05Z")), - PageLimit.tryCreate(3), - res3.nextPageTokenO, - ) - .futureValue - res3b.objects.map(_.key) should contain theSameElementsInOrderAs Seq(d21u0, d21u1) + val res3b = getCommitted( + "2015-10-01T12:00:00Z", + "2015-10-21T16:00:05Z", + 5, + res3.nextPageTokenO, + ).futureValue + res3b.objects.map(_.key) should contain theSameElementsInOrderAs d21 res3b.nextPageTokenO shouldBe None // exact match with start and end of segments - val res4 = reader - .getCommittedUpdatesBetweenDates( - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-21T00:00:00Z")), - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-23T00:00:00Z")), - PageLimit.tryCreate(4), - None, - ) - .futureValue + val res4 = getCommitted("2015-10-21T00:00:00Z", "2015-10-23T00:00:00Z", 8, None).futureValue res4.objects - .map(_.key) should contain theSameElementsInOrderAs Seq(d21u0, d21u1, d22u0, d22u1) + .map(_.key) should contain theSameElementsInOrderAs d21 ++ d22 res4.nextPageTokenO shouldBe None // limit too low for first folder - val ex = reader - .getCommittedUpdatesBetweenDates( - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-21T00:00:00Z")), - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-23T00:00:00Z")), - PageLimit.tryCreate(1), - None, - ) - .failed - .futureValue + val ex = + getCommitted("2015-10-21T00:00:00Z", "2015-10-23T00:00:00Z", 3, None).failed.futureValue ex shouldBe a[StatusRuntimeException] ex.asInstanceOf[StatusRuntimeException] .getStatus .getCode shouldBe io.grpc.Status.Code.INVALID_ARGUMENT // Test handling an empty segment: Simulate no updates in 2015-10-25 to 2015-10-26 - val d26u0 = "2015-10-26T00:00:00Z~2015-10-27T00:00:00Z/updates_0.zstd" - val d26u1 = "2015-10-26T00:00:00Z~2015-10-27T00:00:00Z/updates_1.zstd" - val moreObjs = Seq( - "2015-10-25T00:00:00Z~2015-10-26T00:00:00Z/ACS_0.zstd", - d26u0, - d26u1, - ) + val d26 = makeObjectKeys("2015-10-26T00:00:00Z~2015-10-27T00:00:00Z") + val moreObjs = makeObjectKeys("2015-10-25T00:00:00Z~2015-10-26T00:00:00Z", "ACS") ++ d26 Future .sequence(moreObjs.map { bucketConnection.createObject(_) @@ -546,25 +538,16 @@ class UpdateHistoryBulkStorageTest ) ) // Query up to the middle of the empty segment - val res5 = reader - .getCommittedUpdatesBetweenDates( - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-20T00:00:00Z")), - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-25T12:00:00Z")), - PageLimit.tryCreate(20), - None, - ) - .futureValue + val res5 = getCommitted("2015-10-20T00:00:00Z", "2015-10-25T12:00:00Z", 20, None).futureValue // First response contains all data, but with a next page token res5.objects.map(_.key) should contain theSameElementsInOrderAs allObjs res5.nextPageTokenO shouldBe Some("2015-10-24T00:00:00Z~2015-10-25T00:00:00Z/") - val res5b = reader - .getCommittedUpdatesBetweenDates( - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-21T00:00:00Z")), - CantonTimestamp.tryFromInstant(Instant.parse("2015-10-25T12:00:00Z")), - PageLimit.tryCreate(20), - res5.nextPageTokenO, - ) - .futureValue + val res5b = getCommitted( + "2015-10-21T00:00:00Z", + "2015-10-25T12:00:00Z", + 20, + res5.nextPageTokenO, + ).futureValue // Second page should be empty, with no nextPageToken res5b.objects.map(_.key) shouldBe empty res5b.nextPageTokenO shouldBe None From d1ab22ec8ff9cfa0362cfe1f2decf66dd16ad5f8 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Thu, 13 Aug 2026 19:29:58 +0200 Subject: [PATCH 238/329] Bump versions after release (#6783) Signed-off-by: Julien Tinguely --- LATEST_RELEASE | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LATEST_RELEASE b/LATEST_RELEASE index 39e898a4f9..7486fdbc50 100644 --- a/LATEST_RELEASE +++ b/LATEST_RELEASE @@ -1 +1 @@ -0.7.1 +0.7.2 diff --git a/VERSION b/VERSION index 7486fdbc50..f38fc5393f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.2 +0.7.3 From f56f841ab40e4474f376ac1d7b1f3f8eb7cf0b33 Mon Sep 17 00:00:00 2001 From: kajalshah-da Date: Thu, 13 Aug 2026 15:06:11 -0400 Subject: [PATCH 239/329] added a new data stream and code to create datatransfer jobs to load prod tables (#6542) * added a new data stream and code to create datatransfer jobs to load prod tables Signed-off-by: Kajal Shah * Added code to stop and start datastreams from config and intergrated PR comments Signed-off-by: Kajal Shah * added index file changes Signed-off-by: Kajal Shah * Addressed PR comments and refactured the code Signed-off-by: Kajal Shah * made extra edits to bigquery.ts Signed-off-by: Kajal Shah * added new boundary condition per PR comments Signed-off-by: Kajal Shah * restored index and envr.envrs file Signed-off-by: Kajal Shah * removed the watermark update criteria as it would always fail being less than the crrent value Signed-off-by: Kajal Shah * Addresed PR comments Signed-off-by: Kajal Shah * formatting Signed-off-by: Kajal Shah * formatting and deleting extra coments Signed-off-by: Kajal Shah * Apply suggestions from code review Co-authored-by: Stephen Compall Signed-off-by: kajalshah-da Signed-off-by: Kajal Shah * minor edits Signed-off-by: Kajal Shah * minor edits- removed IAM function Signed-off-by: Kajal Shah * Merged with main and fixed all diffs[ci] Signed-off-by: Kajal Shah * added partition in publication per issue #6730 Signed-off-by: Kajal Shah * added partition in publication per issue #6730 [ci] Signed-off-by: Kajal Shah * update expected Signed-off-by: Kajal Shah --------- Signed-off-by: Kajal Shah Signed-off-by: kajalshah-da Co-authored-by: Stephen Compall --- cluster/expected/canton-network/expected.json | 10 +- .../canton-network/bigquery-cloudsql.sh | 4 +- cluster/pulumi/common-sv/src/bigQuery.ts | 540 ++++++++++++++++-- cluster/pulumi/common-sv/src/config.ts | 5 - .../pulumi/common-sv/src/hourly_append.sql | 99 ++++ .../pulumi/common-sv/src/singleSvConfig.ts | 28 +- 6 files changed, 608 insertions(+), 78 deletions(-) create mode 100644 cluster/pulumi/common-sv/src/hourly_append.sql diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index a08db70466..344c44581f 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -165,7 +165,8 @@ "deleteContentsOnDestroy": true, "friendlyName": "mock_da2_scan Dataset", "labels": { - "cluster": "mock" + "cluster": "mock", + "datastream_id": "legacy" }, "location": "europe-west6" }, @@ -187,8 +188,8 @@ "custom": true, "id": "", "inputs": { - "create": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' create-pub-rep-slot \\\n --private-network-project=\"test-project\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n ", - "delete": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' delete-pub-rep-slot \\\n --private-network-project=\"test-project\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n " + "create": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' create-pub-rep-slot \\\n --private-network-project=\"undefined\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n ", + "delete": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' delete-pub-rep-slot \\\n --private-network-project=\"undefined\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n " }, "name": "sv-1-bqdatastream-pub-replicate-slots", "provider": "", @@ -333,7 +334,8 @@ }, "displayName": "sv-1-scan-update-history", "labels": { - "cluster": "mock" + "cluster": "mock", + "datastream_id": "legacy" }, "location": "europe-west6", "sourceConfig": { diff --git a/cluster/pulumi/canton-network/bigquery-cloudsql.sh b/cluster/pulumi/canton-network/bigquery-cloudsql.sh index 5603a654c2..3d655ff464 100755 --- a/cluster/pulumi/canton-network/bigquery-cloudsql.sh +++ b/cluster/pulumi/canton-network/bigquery-cloudsql.sh @@ -200,9 +200,11 @@ case "$SUBCOMMAND" in IF EXISTS (SELECT 1 FROM pg_publication WHERE pubname = '$PUBLICATION_NAME') THEN ALTER PUBLICATION $PUBLICATION_NAME SET TABLE $TABLES_TO_REPLICATE_JOINED; + ELSE CREATE PUBLICATION $PUBLICATION_NAME - FOR TABLE $TABLES_TO_REPLICATE_JOINED; + FOR TABLE $TABLES_TO_REPLICATE_JOINED + WITH (publish_via_partition_root = true); END IF; END \$\$; COMMIT; -- otherwise fails with "cannot create logical replication slot diff --git a/cluster/pulumi/common-sv/src/bigQuery.ts b/cluster/pulumi/common-sv/src/bigQuery.ts index 0d4c1c84d3..b45c294d12 100644 --- a/cluster/pulumi/common-sv/src/bigQuery.ts +++ b/cluster/pulumi/common-sv/src/bigQuery.ts @@ -4,7 +4,9 @@ import * as command from '@pulumi/command'; import * as gcp from '@pulumi/gcp'; import * as k8s from '@pulumi/kubernetes'; import * as pulumi from '@pulumi/pulumi'; +import * as fs from 'fs'; import * as ip from 'ip'; +import * as path from 'path'; import { InstalledHelmChart, installPostgresPasswordSecret, @@ -25,10 +27,13 @@ import { commandScriptPath, } from '@canton-network/splice-pulumi-common/src/utils'; -interface ScanBigQueryConfig { - dataset: string; - prefix: string; -} +import { ScanBigQueryConfig } from './singleSvConfig'; + +// ============================================================================ +// PIPELINE CONFIGURATION & TYPES +// ============================================================================ + +const THREE_DAYS_MS = 3 * 24 * 60 * 60 * 1000; interface PostgresPassword { contents: pulumi.Output; @@ -37,18 +42,64 @@ interface PostgresPassword { const dbPort = 5432; const replicatorUserName = 'bqdatastream'; + +// Remove legacy datastream configuration once migration to stag-prod pipeline is verified. +// issue: https://github.com/canton-network/splice/issues/6656 +// TODO (#6656) Remove legacy datastream configuration once migration to stag-prod pipeline is verified + const replicationSlotName = 'update_history_datastream_r_slot'; const publicationName = 'update_history_datastream_pub'; -// what tables from Scan to replicate to BigQuery -const tablesToReplicate = [ - 'update_history_creates', - 'update_history_exercises', - 'scan_verdict_store', - 'scan_verdict_transaction_view_store', - 'app_activity_record_store', -]; + +// Stream 2 (Stag-Prod) CDC Replication Configuration +const replicationSlotNameStagProd = 'update_history_datastream_stag_prod_r_slot'; +const publicationNameStagProd = 'update_history_datastream_stag_prod_pub'; + const flywayMigrationToWaitFor = 'V068__app_activity_record_meta.sql'; +// ============================================================================ +// SINGLE SOURCE OF TRUTH: REPLICATED TABLE CONFIGURATION +// ============================================================================ +// what tables from Scan to replicate to BigQuery +interface ReplicatedTableConfig { + primaryKey: string; + datePartitionColumn: string; + timeType: 'micros' | 'datastream_metadata'; +} + +const replicatedTables: Record = { + update_history_creates: { + primaryKey: 'row_id', + datePartitionColumn: 'record_time', + timeType: 'micros', + }, + update_history_exercises: { + primaryKey: 'row_id', + datePartitionColumn: 'record_time', + timeType: 'micros', + }, + scan_verdict_store: { + primaryKey: 'row_id', + datePartitionColumn: 'record_time', + timeType: 'micros', + }, + scan_verdict_transaction_view_store: { + primaryKey: 'verdict_row_id, view_id', + datePartitionColumn: 'source_timestamp', + timeType: 'datastream_metadata', + }, + app_activity_record_store: { + primaryKey: 'row_id', + datePartitionColumn: 'record_time', + timeType: 'micros', + }, +}; + +const tablesToReplicate = Object.keys(replicatedTables); + +// ============================================================================ +// HELPER FUNCTIONS +// ============================================================================ + function cloudsdkComputeRegion() { return config.requireEnv('CLOUDSDK_COMPUTE_REGION'); } @@ -56,7 +107,6 @@ function cloudsdkComputeRegion() { function pickDatastreamPeeringCidr(): string { const baseCidr = config.requireEnv('GCP_MASTER_IPV4_CIDR'); const baseSubnet = ip.cidrSubnet(baseCidr); - // assert GCP_MASTER_IPV4_CIDR is a /28 CIDR if (baseSubnet.subnetMaskLength !== 28) { throw new Error(`Expected a /28 CIDR, but got ${baseCidr}`); @@ -134,13 +184,18 @@ iptables-save }); } +// ============================================================================ +// DATASTREAM PIPELINE DEFINITIONS +// ============================================================================ + function installDatastream( namespace: ExactNamespace, databaseInstance: gcp.sql.DatabaseInstance, source: gcp.datastream.ConnectionProfile, destination: gcp.datastream.ConnectionProfile, bigQueryDataset: gcp.bigquery.Dataset, - pubRepSlots: pulumi.Resource + pubRepSlots: pulumi.Resource, + desiredState: 'RUNNING' | 'PAUSED' ): gcp.datastream.Stream { const streamName = `${namespace.logicalName}-scan-update-history`; const schemaName = scanAppDatabaseName(namespace); @@ -150,7 +205,7 @@ function installDatastream( location: cloudsdkComputeRegion(), streamId: streamName, displayName: streamName, - desiredState: 'RUNNING', + desiredState: desiredState, sourceConfig: { postgresqlSourceConfig: { includeObjects: { @@ -180,26 +235,105 @@ function installDatastream( backfillAll: {}, labels: { cluster: CLUSTER_BASENAME, + datastream_id: 'legacy', }, }, { dependsOn: [databaseInstance, source, destination, bigQueryDataset, pubRepSlots] } ); } +function installDatastream_stag_prod( + namespace: ExactNamespace, + databaseInstance: gcp.sql.DatabaseInstance, + source: gcp.datastream.ConnectionProfile, + destination: gcp.datastream.ConnectionProfile, + bigQueryDataset: gcp.bigquery.Dataset, + pubRepSlots: pulumi.Resource, + desiredState: 'RUNNING' | 'PAUSED' +): gcp.datastream.Stream { + const streamName = `${CLUSTER_BASENAME}-${namespace.logicalName}-stag-production-datastream`; + const schemaName = scanAppDatabaseName(namespace); + return new gcp.datastream.Stream( + streamName, + { + location: cloudsdkComputeRegion(), + streamId: streamName, + displayName: streamName, + desiredState: desiredState, + sourceConfig: { + postgresqlSourceConfig: { + includeObjects: { + postgresqlSchemas: [ + { + schema: schemaName, + postgresqlTables: tablesToReplicate.map(table => ({ table })), + }, + ], + }, + publication: publicationNameStagProd, + replicationSlot: replicationSlotNameStagProd, + }, + sourceConnectionProfile: source.name, + }, + destinationConfig: { + bigqueryDestinationConfig: { + singleTargetDataset: { + datasetId: pulumi.interpolate`projects/${bigQueryDataset.project}/datasets/${bigQueryDataset.datasetId}`, + }, + dataFreshness: '0s', + appendOnly: {}, + }, + destinationConnectionProfile: destination.name, + }, + backfillAll: {}, + ruleSets: tablesToReplicate.map(tableName => ({ + objectFilter: { + sourceObjectIdentifier: { + postgresqlIdentifier: { + schema: schemaName, + table: tableName, + }, + }, + }, + customizationRules: [ + { + bigqueryPartitioning: { + ingestionTimePartition: { + partitioningTimeGranularity: 'PARTITIONING_TIME_GRANULARITY_HOUR', + }, + }, + }, + ], + })), + labels: { + cluster: CLUSTER_BASENAME, + datastream_id: 'stag_prod', + }, + }, + { + dependsOn: [databaseInstance, source, destination, bigQueryDataset, pubRepSlots], + } + ); +} + +// ============================================================================ +// BIGQUERY DATASET CREATION +// ============================================================================ + function installBigqueryDataset(scanBigQuery: ScanBigQueryConfig): gcp.bigquery.Dataset { return new gcp.bigquery.Dataset(scanBigQuery.dataset, { datasetId: scanBigQuery.dataset, friendlyName: `${scanBigQuery.dataset} Dataset`, location: cloudsdkComputeRegion(), - deleteContentsOnDestroy: true, + deleteContentsOnDestroy: true, //retaining old value // TODO (DACH-NY/canton-network-internal#343) reduce time travel window from 7-day default to 2 days if // it makes a cost difference labels: { cluster: CLUSTER_BASENAME, + datastream_id: 'legacy', }, }); } - /* TODO (DACH-NY/canton-network-internal#341) remove this comment when enabled on all relevant clusters If you see an error like this gcp:datastream:ConnectionProfile (sv-4-scan-bq-cxn): @@ -215,6 +349,125 @@ you have to manually enable the API as described for that cluster. - done for da-cn-ci-2 */ +function installBigqueryStagingDataset(scanBigQuery: ScanBigQueryConfig): gcp.bigquery.Dataset { + return new gcp.bigquery.Dataset(`${scanBigQuery.dataset}-staging`, { + datasetId: `${scanBigQuery.dataset}_staging`, + friendlyName: `${scanBigQuery.dataset} Staging Dataset`, + location: cloudsdkComputeRegion(), + deleteContentsOnDestroy: true, + defaultTableExpirationMs: THREE_DAYS_MS, + labels: { + cluster: CLUSTER_BASENAME, + datastream_id: 'stag_prod', + }, + }); +} + +function installBigqueryProdDataset(scanBigQuery: ScanBigQueryConfig): gcp.bigquery.Dataset { + return new gcp.bigquery.Dataset(`${scanBigQuery.dataset}-prod`, { + datasetId: `${scanBigQuery.dataset}_prod`, + friendlyName: `${scanBigQuery.dataset} Production Dataset`, + location: cloudsdkComputeRegion(), + deleteContentsOnDestroy: true, + labels: { + cluster: CLUSTER_BASENAME, + }, + }); +} + +// ============================================================================ +// HOURLY DEDUPLICATION & SCHEDULED QUERIES +// ============================================================================ + +const rawSqlTemplate = fs.readFileSync(path.join(__dirname, 'hourly_append.sql'), 'utf8'); + +function installHourlyScheduledQueries( + namespace: ExactNamespace, + stagingDataset: gcp.bigquery.Dataset, + prodDataset: gcp.bigquery.Dataset +) { + const currentProject = gcp.organizations.getProjectOutput({}); + const projectId = currentProject.apply(p => p.projectId!); + const schemaName = scanAppDatabaseName(namespace); + + const transferServiceAgentPermission = new gcp.projects.IAMMember('bq-transfer-token-creator', { + project: projectId, + role: 'roles/iam.serviceAccountTokenCreator', + member: currentProject.apply( + p => `serviceAccount:service-${p.number}@gcp-sa-bigquerydatatransfer.iam.gserviceaccount.com` + ), + }); + + Object.entries(replicatedTables).forEach(([tableName, tableConfig]) => { + const primaryKeyExpr = tableConfig.primaryKey; + const colName = tableConfig.datePartitionColumn; + + let recordTimestampExpr: string; + if (tableConfig.timeType === 'micros') { + recordTimestampExpr = `TIMESTAMP_MICROS(staging.${colName})`; + } else if (tableConfig.timeType === 'datastream_metadata') { + recordTimestampExpr = `TIMESTAMP_MILLIS(staging.datastream_metadata.source_timestamp)`; + } else { + const unreachable: never = tableConfig.timeType; + throw new Error(`impossible time config: ${unreachable}`); + } + + const recordDateExpr = `CAST(${recordTimestampExpr} AS DATE)`; + + const procedureBody = pulumi + .all([projectId, prodDataset.datasetId, stagingDataset.datasetId]) + .apply(([proj, prodDs, stagingDs]) => { + const prodTable = `\`${proj}.${prodDs}.${tableName}\``; + const stagingTable = `\`${proj}.${stagingDs}.${schemaName}_${tableName}\``; + const watermarksTable = `\`${proj}.${prodDs}.pipeline_watermarks\``; + const prodInfoSchema = `\`${proj}.${prodDs}.INFORMATION_SCHEMA.TABLES\``; + + return rawSqlTemplate + .replaceAll('{{tableName}}', tableName) + .replaceAll('{{schemaName}}', schemaName) + .replaceAll('{{primaryKeyExpr}}', primaryKeyExpr) + .replaceAll('{{recordTimestampExpr}}', recordTimestampExpr) + .replaceAll('{{recordDateExpr}}', recordDateExpr) + .replaceAll('{{prodTable}}', prodTable) + .replaceAll('{{stagingTable}}', stagingTable) + .replaceAll('{{watermarksTable}}', watermarksTable) + .replaceAll('{{prodInfoSchema}}', prodInfoSchema); + }); + + const routineId = `sp_append_${tableName}`; + + const appendRoutine = new gcp.bigquery.Routine(`${tableName}-append-routine`, { + datasetId: prodDataset.datasetId, + routineId: routineId, + routineType: 'PROCEDURE', + language: 'SQL', + definitionBody: procedureBody, + }); + + new gcp.bigquery.DataTransferConfig( + `${CLUSTER_BASENAME}_${tableName}-hourly-append`, + { + displayName: `${CLUSTER_BASENAME}_${tableName} Hourly Append Pipeline`, + location: cloudsdkComputeRegion(), + serviceAccountName: pulumi.interpolate`bigquery@${projectId}.iam.gserviceaccount.com`, + dataSourceId: 'scheduled_query', + schedule: 'every 1 hours from 00:07 to 23:07', + + params: { + query: pulumi.interpolate`CALL \`${projectId}.${prodDataset.datasetId}.${routineId}\`();`, + }, + }, + { + dependsOn: [transferServiceAgentPermission, appendRoutine], + } + ); + }); +} + +// ============================================================================ +// CONNECTION PROFILES & NETWORKING +// ============================================================================ + function installBigqueryConnectionProfile( namespace: ExactNamespace, bigQuery: gcp.bigquery.Dataset, @@ -236,10 +489,30 @@ function installBigqueryConnectionProfile( ); } +function installBigqueryStagingConnectionProfile( + namespace: ExactNamespace, + bigQuery: gcp.bigquery.Dataset, + pcc: gcp.datastream.PrivateConnection +): gcp.datastream.ConnectionProfile { + const profileName = `${namespace.logicalName}-scan-bq-staging-cxn`; + return new gcp.datastream.ConnectionProfile( + profileName, + { + connectionProfileId: profileName, + displayName: profileName, + location: cloudsdkComputeRegion(), + bigqueryProfile: {}, + labels: { + cluster: CLUSTER_BASENAME, + }, + }, + { dependsOn: [bigQuery, pcc] } + ); +} + function scanAppDatabaseName(namespace: ExactNamespace): string { return `scan_${namespace.logicalName.replace(/-/g, '_')}`; } - function installPostgresConnectionProfile( namespace: ExactNamespace, databaseInstance: gcp.sql.DatabaseInstance, @@ -319,9 +592,11 @@ function installDatastreamToNatVmFirewallRule( }); } +// ============================================================================ +// POSTGRESQL AUTHENTICATION & REPLICATION SLOT PROVISIONING +// ============================================================================ // TODO (DACH-NY/canton-network-internal#342) if we disable default egress rule, we need another firewall // rule for Nat VM -> Postgres - function installReplicatorPassword(namespace: ExactNamespace): PostgresPassword { const secretName = `${namespace.logicalName}-${replicatorUserName}-passwd`; const password = generatePassword(`cn-apps-pg-${replicatorUserName}-passwd`, { @@ -372,43 +647,141 @@ function createPublicationAndReplicationSlots( namespace: ExactNamespace, databaseInstance: gcp.sql.DatabaseInstance, replicatorUser: gcp.sql.User, - scan: InstalledHelmChart | undefined -) { + scan: InstalledHelmChart | undefined, + enableLegacy: boolean, + enableStagProd: boolean +): { + slot1?: command.local.Command; + slot2?: command.local.Command; +} { + // --------------------------------------------------------------------------- + // 1. Shared Environment & Project Setup + // --------------------------------------------------------------------------- + const dbName = scanAppDatabaseName(namespace); const schemaName = dbName; - const path = commandScriptPath('cluster/pulumi/canton-network/bigquery-cloudsql.sh'); - const scriptArgs = pulumi.interpolate`\\ - --private-network-project="${gcp.organizations.getProjectOutput({}).apply(proj => proj.name)}" \\ - --compute-region="${cloudsdkComputeRegion()}" \\ - --service-account-email="${databaseInstance.serviceAccountEmailAddress}" \\ - --schema-name="${schemaName}" \\ - --tables-to-replicate-joined="${tablesToReplicate.join(', ')}" \\ - --postgres-user-name="${defaultUserName}" \\ - --publication-name="${publicationName}" \\ - --replication-slot-name="${replicationSlotName}" \\ - --replicator-user-name="${replicatorUserName}" \\ - --postgres-instance-name="${databaseInstance.name}" \\ - --scan-app-database-name="${scanAppDatabaseName(namespace)}" \\ - --flyway-migration-to-wait-for="${flywayMigrationToWaitFor}" \\ - `; - return new command.local.Command( - `${namespace.logicalName}-${replicatorUserName}-pub-replicate-slots`, - { - create: pulumi.interpolate`'${path}' create-pub-rep-slot ${scriptArgs}`, - delete: pulumi.interpolate`'${path}' delete-pub-rep-slot ${scriptArgs}`, - }, - { - dependsOn: [databaseInstance, replicatorUser, ...(scan !== undefined ? [scan] : [])], - deleteBeforeReplace: true, - } - ); + const scriptPath = commandScriptPath('cluster/pulumi/canton-network/bigquery-cloudsql.sh'); + + const projectId = gcp.organizations.getProjectOutput({}).apply(proj => proj.projectId); + + const commonDependencies = [scan, databaseInstance, replicatorUser]; + + // --------------------------------------------------------------------------- + // 2. Base Arguments Split (Matches Stored Deployment Ordering & Formatting) + // --------------------------------------------------------------------------- + + // Prefix arguments (Arguments 1–6) + const baseArgsPrefix: pulumi.Input[] = [ + pulumi.interpolate`--private-network-project="${projectId}"`, + pulumi.interpolate`--compute-region="${cloudsdkComputeRegion()}"`, + pulumi.interpolate`--service-account-email="${databaseInstance.serviceAccountEmailAddress}"`, + pulumi.interpolate`--schema-name="${schemaName}"`, + pulumi.interpolate`--tables-to-replicate-joined="${tablesToReplicate.join(', ')}"`, + pulumi.interpolate`--postgres-user-name="${defaultUserName}"`, + ]; + + // Suffix arguments (Arguments 9–12) + const baseArgsSuffix: pulumi.Input[] = [ + pulumi.interpolate`--replicator-user-name="${replicatorUserName}"`, + pulumi.interpolate`--postgres-instance-name="${databaseInstance.name}"`, + pulumi.interpolate`--scan-app-database-name="${dbName}"`, + pulumi.interpolate`--flyway-migration-to-wait-for="${flywayMigrationToWaitFor}"`, + ]; + + const buildScriptCommand = ( + action: string, + slotArgs: pulumi.Input[] + ): pulumi.Output => { + const allArgs = [...baseArgsPrefix, ...slotArgs, ...baseArgsSuffix]; + + return pulumi.all(allArgs).apply(args => { + const formattedArgs = args.join(' \\\n '); + return `'${scriptPath}' ${action} \\\n ${formattedArgs} \\\n `; + }); + }; + + // --------------------------------------------------------------------------- + // 3. Legacy Datastream Slot (Slot 1) + // --------------------------------------------------------------------------- + + let slot1: command.local.Command | undefined; + + if (enableLegacy) { + const slot1Args: pulumi.Input[] = [ + pulumi.interpolate`--publication-name="${publicationName}"`, + pulumi.interpolate`--replication-slot-name="${replicationSlotName}"`, + ]; + + slot1 = new command.local.Command( + `${namespace.logicalName}-${replicatorUserName}-pub-replicate-slots`, + { + create: buildScriptCommand('create-pub-rep-slot', slot1Args), + delete: buildScriptCommand('delete-pub-rep-slot', slot1Args), + }, + { + dependsOn: [databaseInstance, replicatorUser, ...(scan !== undefined ? [scan] : [])], + deleteBeforeReplace: true, + } + ); + } + + // --------------------------------------------------------------------------- + // 4. Stag-Prod Datastream Slot (Slot 2) + // --------------------------------------------------------------------------- + + let slot2: command.local.Command | undefined; + + if (enableStagProd) { + const slot2Args: pulumi.Input[] = [ + pulumi.interpolate`--publication-name="${publicationNameStagProd}"`, + pulumi.interpolate`--replication-slot-name="${replicationSlotNameStagProd}"`, + ]; + + slot2 = new command.local.Command( + `${namespace.logicalName}-${replicatorUserName}-pub-replicate-slot-2`, + { + create: buildScriptCommand('create-pub-rep-slot', slot2Args), + delete: buildScriptCommand('delete-pub-rep-slot', slot2Args), + }, + { + dependsOn: [databaseInstance, replicatorUser, ...(scan !== undefined ? [scan] : [])], + deleteBeforeReplace: true, + } + ); + } + + // --------------------------------------------------------------------------- + // 5. Return Created Slots + // --------------------------------------------------------------------------- + + return { + slot1, + slot2, + }; } +// ============================================================================ +// MAIN ENTRYPOINT +// ============================================================================ export async function configureScanBigQuery({ namespace, bigQueryConfig, scanReference, }: ScanBigQueryArgs): Promise { + // Use config file to determine which datastreams to enable and their desired states + + const { + enableLegacyDatastream, + enableStagProdDatastream, + legacyDesiredState, + stagProdDesiredState, + } = bigQueryConfig; + + if (!enableLegacyDatastream && !enableStagProdDatastream) { + throw new Error( + 'configureScanBigQuery was called, but both legacy and stag-prod Datastreams are disabled.' + ); + } const zone = getCloudSdkZone(); const [databaseInstance, scanChart] = await (async () => { switch (scanReference.type) { @@ -418,18 +791,21 @@ export async function configureScanBigQuery({ return [await getScanDb(scanReference.databaseInstanceNamePrefix, zone), undefined]; } })(); + const passwordSecret = installReplicatorPassword(namespace); - const pubRepSlots = createPublicationAndReplicationSlots( + const slots = createPublicationAndReplicationSlots( namespace, databaseInstance, createPostgresReplicatorUser(namespace, databaseInstance, passwordSecret), - scanChart + scanChart, + enableLegacyDatastream, + enableStagProdDatastream ); const natVm = installNatVm(namespace, zone, databaseInstance); - const dataset = installBigqueryDataset(bigQueryConfig); const pcc = installPrivateConnectivityConfiguration(namespace); - const destinationProfile = installBigqueryConnectionProfile(namespace, dataset, pcc); + installDatastreamToNatVmFirewallRule(namespace, pcc, natVm); + const sourceProfile = installPostgresConnectionProfile( namespace, databaseInstance, @@ -438,18 +814,60 @@ export async function configureScanBigQuery({ pcc, passwordSecret ); - installDatastreamToNatVmFirewallRule(namespace, pcc, natVm); - installDatastream( - namespace, - databaseInstance, - sourceProfile, - destinationProfile, - dataset, - pubRepSlots - ); + + let legacyDataset: gcp.bigquery.Dataset | undefined; + let stagingDataset: gcp.bigquery.Dataset | undefined; + let prodDataset: gcp.bigquery.Dataset | undefined; + + if (enableLegacyDatastream && slots.slot1) { + legacyDataset = installBigqueryDataset(bigQueryConfig); + const legacyDestinationProfile = installBigqueryConnectionProfile( + namespace, + legacyDataset, + pcc + ); + + installDatastream( + namespace, + databaseInstance, + sourceProfile, + legacyDestinationProfile, + legacyDataset, + slots.slot1, + legacyDesiredState + ); + } + + if (enableStagProdDatastream && slots.slot2) { + stagingDataset = installBigqueryStagingDataset(bigQueryConfig); + prodDataset = installBigqueryProdDataset(bigQueryConfig); + const stagingDestinationProfile = installBigqueryStagingConnectionProfile( + namespace, + stagingDataset, + pcc + ); + + installDatastream_stag_prod( + namespace, + databaseInstance, + sourceProfile, + stagingDestinationProfile, + stagingDataset, + slots.slot2, + stagProdDesiredState + ); + + installHourlyScheduledQueries(namespace, stagingDataset, prodDataset); + } + // TODO (DACH-NY/canton-network-internal#6451) not sure if this function needs to return anything, + // but we need to return something to satisfy the ScanBigQuery type. + // For now, we return the primary dataset's ID, which is either legacy, staging, or prod, whichever is defined first. + // we should consider removing the return datasetId if it's not needed + + const primaryDataset = legacyDataset ?? stagingDataset ?? prodDataset; return { - datasetId: dataset.id, + datasetId: primaryDataset!.id, }; } diff --git a/cluster/pulumi/common-sv/src/config.ts b/cluster/pulumi/common-sv/src/config.ts index 5022305712..2e1b728bbc 100644 --- a/cluster/pulumi/common-sv/src/config.ts +++ b/cluster/pulumi/common-sv/src/config.ts @@ -41,11 +41,6 @@ export type SvOnboarding = sponsorScanUrl: string; }; -export interface ScanBigQueryConfig { - dataset: string; - prefix: string; -} - export interface StaticSvConfigBasic { nodeName: string; ingressName: string; diff --git a/cluster/pulumi/common-sv/src/hourly_append.sql b/cluster/pulumi/common-sv/src/hourly_append.sql new file mode 100644 index 0000000000..c1c0bab71f --- /dev/null +++ b/cluster/pulumi/common-sv/src/hourly_append.sql @@ -0,0 +1,99 @@ +BEGIN + DECLARE current_watermark_micros INT64; + DECLARE current_watermark_ts TIMESTAMP; + DECLARE max_available_timestamp TIMESTAMP; + DECLARE max_closed_timestamp TIMESTAMP; + + -- 1. Check if production table exists; if not, create shell table + IF NOT EXISTS ( + SELECT 1 + FROM {{prodInfoSchema}} + WHERE table_name = '{{tableName}}' + ) THEN + CREATE TABLE {{prodTable}} + PARTITION BY record_date AS + SELECT + *, + {{recordDateExpr}} AS record_date + FROM {{stagingTable}} AS staging + WHERE 1 = 0; + END IF; + + -- 2. Create central watermark tracking table using INT64 (microseconds) + CREATE TABLE IF NOT EXISTS {{watermarksTable}} ( + table_name STRING, + last_watermark_time INT64 + ); + + -- 3. Initialize watermark to epoch zero (0 micros) if missing + IF NOT EXISTS ( + SELECT 1 + FROM {{watermarksTable}} + WHERE table_name = '{{tableName}}' + ) THEN + INSERT INTO {{watermarksTable}} + VALUES ('{{tableName}}', 0); + END IF; + + -- 4. Get current scalar watermark in INT64 micros and convert to TIMESTAMP + SET current_watermark_micros = ( + SELECT MAX(last_watermark_time) + FROM {{watermarksTable}} + WHERE table_name = '{{tableName}}' + ); + + SET current_watermark_ts = TIMESTAMP_MICROS(current_watermark_micros); + + -- 5. Fetch max available timestamp from staging WITH PARTITION PRUNING + SET max_available_timestamp = COALESCE( + ( + SELECT MAX({{recordTimestampExpr}}) + FROM {{stagingTable}} AS staging + WHERE ( + staging._PARTITIONTIME >= TIMESTAMP_SUB(current_watermark_ts, INTERVAL 3 HOUR) + OR staging._PARTITIONTIME IS NULL + ) + ), + -- Fall back to current_watermark_ts if no new data arrived in the last 3 hours + current_watermark_ts + ); + + -- Subtract 1 hour to safely close the ingestion window + SET max_closed_timestamp = TIMESTAMP_SUB(max_available_timestamp, INTERVAL 1 HOUR); + + -- 6. Filter incremental data with Primary Key Deduplication & Partition Pruning + CREATE TEMP TABLE temp_incremental AS ( + SELECT + staging.*, + {{recordDateExpr}} AS record_date + FROM {{stagingTable}} AS staging + WHERE ( + -- Cost control: Scan 3-hour partition window + staging._PARTITIONTIME >= TIMESTAMP_SUB(current_watermark_ts, INTERVAL 3 HOUR) + -- Streaming buffer protection for recent arrivals + OR staging._PARTITIONTIME IS NULL + ) + AND {{recordTimestampExpr}} > current_watermark_ts + AND {{recordTimestampExpr}} <= max_closed_timestamp + -- Primary Key Deduplication + QUALIFY ROW_NUMBER() OVER ( + PARTITION BY {{primaryKeyExpr}} + ORDER BY + staging.datastream_metadata.source_timestamp DESC, + staging.datastream_metadata.change_sequence_number DESC + ) = 1 + ); + + -- 7. Perform append and update watermark if valid new records exist + IF EXISTS (SELECT 1 FROM temp_incremental) THEN + + INSERT INTO {{prodTable}} + SELECT * FROM temp_incremental; + + UPDATE {{watermarksTable}} + SET last_watermark_time = UNIX_MICROS(max_closed_timestamp) + WHERE table_name = '{{tableName}}' + AND last_watermark_time < UNIX_MICROS(max_closed_timestamp); + + END IF; +END; \ No newline at end of file diff --git a/cluster/pulumi/common-sv/src/singleSvConfig.ts b/cluster/pulumi/common-sv/src/singleSvConfig.ts index 78e3389678..fc4d720b57 100644 --- a/cluster/pulumi/common-sv/src/singleSvConfig.ts +++ b/cluster/pulumi/common-sv/src/singleSvConfig.ts @@ -93,22 +93,36 @@ const SvAppConfigSchema = z const BulkStorageConfigSchema = z.object({ enabled: z.boolean(), }); + export type BulkStorageConfig = z.infer; + +// 1. Extract ScanBigQueryConfigSchema to validate all Datastream settings. +// All new fields are optional to ensure existing deployments do not fail parsing. +export const ScanBigQueryConfigSchema = z + .object({ + dataset: z.string(), + prefix: z.string(), + functionsDataset: z.string().optional(), + enableLegacyDatastream: z.boolean().default(true), + enableStagProdDatastream: z.boolean().default(false), + legacyDesiredState: z.enum(['RUNNING', 'PAUSED']).default('RUNNING'), + stagProdDesiredState: z.enum(['RUNNING', 'PAUSED']).default('RUNNING'), + }) + .strict(); // Keeps strict mode safe now that all known fields are explicitly defined + +// 2. Single source of truth: infer the TypeScript type directly from the Zod schema +export type ScanBigQueryConfig = z.infer; +// 3. Update ScanAppConfigSchema to reference the extracted sub-schema const ScanAppConfigSchema = z .object({ - bigQuery: z - .object({ - dataset: z.string(), - prefix: z.string(), - functionsDataset: z.string().optional(), - }) - .optional(), + bigQuery: ScanBigQueryConfigSchema.optional(), bulkStorage: BulkStorageConfigSchema.optional(), additionalEnvVars: z.array(EnvVarConfigSchema).default([]), additionalJvmOptions: z.string().optional(), resources: K8sResourceSchema, }) .strict(); + const SvValidatorAppConfigSchema = z .object({ walletUser: z.string().optional(), From 26a1fc5cde9ef972f91fbfa257a0d80b998721a1 Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Fri, 14 Aug 2026 02:55:42 -0400 Subject: [PATCH 240/329] Remove Scan app `enableAppActivityRecordAndTrafficIngestion` and `serveAppActivityRecordsAndTraffic` config options (#6643) This removes the `enableAppActivityRecordAndTrafficIngestion` and `serveAppActivityRecordsAndTraffic` config options and the optionality proliferation that they resulted in. --------- Signed-off-by: Matt Dziuban --- .../DistributedDomainIntegrationTest.scala | 2 +- .../ManualSignatureIntegrationTest.scala | 7 - ...RewardsSvAppTimeBasedIntegrationTest.scala | 2 +- .../splice/scan/ScanApp.scala | 114 ++++----- .../splice/scan/ScanSynchronizerNode.scala | 2 +- .../scan/admin/http/HttpScanHandler.scala | 235 ++++++++---------- .../automation/ScanAutomationService.scala | 9 +- .../ScanVerdictAutomationService.scala | 10 +- .../ScanVerdictIngestionService.scala | 61 ++--- .../splice/scan/config/ScanAppConfig.scala | 2 - .../splice/scan/store/ScanEventStore.scala | 5 +- .../scan/store/db/DbScanVerdictStore.scala | 24 +- .../store/DbAppActivityRecordStoreTest.scala | 2 +- .../scan/store/ScanEventStoreTest.scala | 15 +- docs/src/release_notes_upcoming.rst | 7 + 15 files changed, 214 insertions(+), 283 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DistributedDomainIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DistributedDomainIntegrationTest.scala index 40b9e4d6f5..78281baaf3 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DistributedDomainIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/DistributedDomainIntegrationTest.scala @@ -117,7 +117,7 @@ class DistributedDomainIntegrationTest // Check that things work for external validators clue("Alice can tap") { - onboardWalletUser(aliceWalletClient, aliceValidatorBackend) + eventuallySucceeds()(onboardWalletUser(aliceWalletClient, aliceValidatorBackend)) aliceWalletClient.tap(1000) } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ManualSignatureIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ManualSignatureIntegrationTest.scala index 9c6ec90f0f..60d50c974e 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ManualSignatureIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ManualSignatureIntegrationTest.scala @@ -6,7 +6,6 @@ import com.digitalasset.canton.crypto.SigningPublicKey import com.digitalasset.canton.topology.Namespace import com.digitalasset.canton.topology.admin.grpc.TopologyStoreId import com.digitalasset.canton.topology.store.TimeQuery -import org.lfdecentralizedtrust.splice.config.ConfigTransforms.updateAllScanAppConfigs import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.IntegrationTest import org.lfdecentralizedtrust.splice.util.WalletTestUtil @@ -25,12 +24,6 @@ class ManualSignatureIntegrationTest .withManualStart .withoutAliceValidatorConnectingToSplitwell .withSequencerConnectionsFromScanDisabled() - .addConfigTransforms((_, config) => - updateAllScanAppConfigs((_, config) => - // Sequencer is returning TRAFFIC_CONTROL_DISABLED when looking up traffic summaries. - config.copy(enableAppActivityRecordAndTrafficIngestion = false) - )(config) - ) } "synchronizer" should { diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsSvAppTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsSvAppTimeBasedIntegrationTest.scala index b88a5c992d..6872258e32 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsSvAppTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TrafficBasedRewardsSvAppTimeBasedIntegrationTest.scala @@ -227,7 +227,7 @@ class TrafficBasedRewardsSvAppTimeBasedIntegrationTest clue("CalculateRewardsV2 contracts are also visible in scan rewards reference store") { eventually() { - val v2s = sv1ScanBackend.appState.rewardsReferenceStoreO.value + val v2s = sv1ScanBackend.appState.rewardsReferenceStore .listActiveCalculateRewardsV2() .futureValue v2s.map(c => diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala index 0eaada7f91..da68f46e44 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala @@ -144,16 +144,12 @@ class ScanApp( nodeMetrics.grpcClientMetrics, retryProvider, ), - if (config.enableAppActivityRecordAndTrafficIngestion) { - Some( - new SequencerTrafficClient( - syncConfig.sequencer, - retryProvider, - nodeMetrics.grpcClientMetrics, - loggerFactory, - ) - ) - } else None, + new SequencerTrafficClient( + syncConfig.sequencer, + retryProvider, + nodeMetrics.grpcClientMetrics, + loggerFactory, + ), ) override def initialize( @@ -273,30 +269,22 @@ class ScanApp( loggerFactory, ) ) - // Conditionally create traffic summary ingestion dependencies - appActivityRecordStoreO = - if (config.enableAppActivityRecordAndTrafficIngestion) { - Some( - new DbAppActivityRecordStore( - storage, - updateHistory, - DbAppActivityRecordStore.IngestionVersions( - AppActivityComputation.ActivityIngestionCodeVersion, - config.activityIngestionUserVersion.fold(0)(_.toInt), - ), - config.isFirstSv, - loggerFactory, - ) - ) - } else None - appRewardsStoreO = appActivityRecordStoreO.map(appActivityRecordStore => - new DbScanAppRewardsStore( - storage, - updateHistory, - appActivityRecordStore, - config.rewardMintingAllowanceTolerance, - loggerFactory, - ) + appActivityRecordStore = new DbAppActivityRecordStore( + storage, + updateHistory, + DbAppActivityRecordStore.IngestionVersions( + AppActivityComputation.ActivityIngestionCodeVersion, + config.activityIngestionUserVersion.fold(0)(_.toInt), + ), + config.isFirstSv, + loggerFactory, + ) + appRewardsStore = new DbScanAppRewardsStore( + storage, + updateHistory, + appActivityRecordStore, + config.rewardMintingAllowanceTolerance, + loggerFactory, ) synchronizerId <- retryProvider.getValueWithRetries( @@ -319,8 +307,8 @@ class ScanApp( loggerFactory, store, updateHistory, - appRewardsStoreO, - appActivityRecordStoreO, + appRewardsStore, + appActivityRecordStore, storage, acsSnapshotStore, serviceUserPrimaryParty, @@ -331,7 +319,7 @@ class ScanApp( scanVerdictStore = DbScanVerdictStore( storage, updateHistory, - appActivityRecordStoreO, + appActivityRecordStore, loggerFactory, )(ec) scanEventStore = new ScanEventStore( @@ -360,25 +348,24 @@ class ScanApp( dsoParty, config.spliceInstanceNames.nameServiceNameAcronym.toLowerCase(), ) - rewardsReferenceStoreO = - if (config.enableAppActivityRecordAndTrafficIngestion) { - val rewardsStore = ScanRewardsReferenceStore( - key = ScanRewardsReferenceStore.Key( - dsoParty = dsoParty, - synchronizerId = synchronizerId, - ), - storage, - loggerFactory, - retryProvider, - domainMigrationId, - participantId, - config.automation.ingestion, - config.parameters.defaultLimit, - ) - automation.registerRewardsReferenceStoreIngestion(rewardsStore) - automation.registerRewardComputationTrigger(rewardsStore) - Some(rewardsStore) - } else None + rewardsReferenceStore = { + val rewardsStore = ScanRewardsReferenceStore( + key = ScanRewardsReferenceStore.Key( + dsoParty = dsoParty, + synchronizerId = synchronizerId, + ), + storage, + loggerFactory, + retryProvider, + domainMigrationId, + participantId, + config.automation.ingestion, + config.parameters.defaultLimit, + ) + automation.registerRewardsReferenceStoreIngestion(rewardsStore) + automation.registerRewardComputationTrigger(rewardsStore) + rewardsStore + } verdictAutomation = new ScanVerdictAutomationService( config, syncNodes, @@ -390,7 +377,7 @@ class ScanApp( domainMigrationId, synchronizerId, nodeMetrics.verdictIngestion, - rewardsReferenceStoreO, + rewardsReferenceStore, ) scanHandler = new HttpScanHandler( serviceUserPrimaryParty, @@ -400,15 +387,14 @@ class ScanApp( syncService, automation, updateHistory, - appRewardsStoreO, - appActivityRecordStoreO, + appRewardsStore, + appActivityRecordStore, acsSnapshotStore, scanEventStore, bulkStorage.map(_.reader), dsoAnsResolver, config.miningRoundsCacheTimeToLiveOverride, config.enableForcedAcsSnapshots, - config.serveAppActivityRecordsAndTraffic, clock, loggerFactory, packageVersionSupport, @@ -548,7 +534,7 @@ class ScanApp( bulkStorage, verdictAutomation, scanEventStore, - rewardsReferenceStoreO, + rewardsReferenceStore, loggerFactory.getTracedLogger(ScanApp.State.getClass), timeouts, bftSequencersWithAdminConnections.map(_._1), @@ -622,7 +608,7 @@ object ScanApp { bulkStorage: Option[BulkStorage], verdictAutomation: ScanVerdictAutomationService, eventStore: ScanEventStore, - rewardsReferenceStoreO: Option[ScanRewardsReferenceStore], + rewardsReferenceStore: ScanRewardsReferenceStore, logger: TracedLogger, timeouts: ProcessingTimeout, bftSequencersAdminConnections: Seq[SequencerAdminConnection], @@ -643,9 +629,7 @@ object ScanApp { automation, verdictAutomation, store, - ) ++ - rewardsReferenceStoreO.toList ++ - Seq( + rewardsReferenceStore, storage, synchronizerNodes.current, participantAdminConnection, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanSynchronizerNode.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanSynchronizerNode.scala index a118f25ccd..eaff50ea3c 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanSynchronizerNode.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanSynchronizerNode.scala @@ -8,7 +8,7 @@ import org.lfdecentralizedtrust.splice.scan.sequencer.SequencerTrafficClient final class ScanSynchronizerNode( override val sequencerAdminConnection: SequencerAdminConnection, - val sequencerTrafficClient: Option[SequencerTrafficClient], + val sequencerTrafficClient: SequencerTrafficClient, ) extends SynchronizerNode(sequencerAdminConnection) with AutoCloseable { override def close(): Unit = { diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala index 14e2066ec2..1fe648f179 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala @@ -147,15 +147,14 @@ class HttpScanHandler( synchronizerNodeService: SynchronizerNodeService[ScanSynchronizerNode], protected val storeWithIngestion: AppStoreWithIngestion[ScanStore], updateHistory: UpdateHistory, - appRewardsStoreO: Option[DbScanAppRewardsStore], - appActivityStoreO: Option[AppActivityStore], + appRewardsStore: DbScanAppRewardsStore, + appActivityStore: AppActivityStore, snapshotStore: AcsSnapshotStore, eventStore: ScanEventStore, bulkStorage: Option[BulkStorageReader], dsoAnsResolver: DsoAnsResolver, miningRoundsCacheTimeToLiveOverride: Option[NonNegativeFiniteDuration], enableForcedAcsSnapshots: Boolean, - serveAppActivityRecordsAndTraffic: Boolean, clock: Clock, protected val loggerFactory: NamedLoggerFactory, protected val packageVersionSupport: PackageVersionSupport, @@ -831,14 +830,11 @@ class HttpScanHandler( case Some((verdictWithViewsO, updateO)) => val verdictRowIdO = verdictWithViewsO.map { case (v, _) => v.rowId } for { - appActivityRecordO <- - if (serveAppActivityRecordsAndTraffic) - verdictRowIdO match { - case Some(rowId) => - eventStore.getAppActivityRecords(Seq(rowId)).map(_.get(rowId)) - case None => Future.successful(None) - } - else Future.successful(None) + appActivityRecordO <- verdictRowIdO match { + case Some(rowId) => + eventStore.getAppActivityRecords(Seq(rowId)).map(_.get(rowId)) + case None => Future.successful(None) + } } yield { val encodedUpdateV2 = updateO .map( @@ -853,12 +849,9 @@ class HttpScanHandler( val verdictEncoded = verdictWithViewsO.map { case (v, views) => ScanHttpEncodings.encodeVerdict(v, views) } - val trafficSummaryEncoded = - if (serveAppActivityRecordsAndTraffic) - verdictWithViewsO.flatMap { case (v, _) => - v.trafficSummaryO.map(ScanHttpEncodings.encodeTrafficSummary) - } - else None + val trafficSummaryEncoded = verdictWithViewsO.flatMap { case (v, _) => + v.trafficSummaryO.map(ScanHttpEncodings.encodeTrafficSummary) + } val appActivityRecordEncoded = appActivityRecordO.map( ScanHttpEncodings.encodeAppActivityRecord ) @@ -915,9 +908,7 @@ class HttpScanHandler( verdictRowIds = events.flatMap { case (verdictWithViewsO, _) => verdictWithViewsO.map { case (v, _) => v.rowId } } - appActivityRecordMap <- - if (serveAppActivityRecordsAndTraffic) eventStore.getAppActivityRecords(verdictRowIds) - else Future.successful(Map.empty[Long, eventStore.AppActivityRecordT]) + appActivityRecordMap <- eventStore.getAppActivityRecords(verdictRowIds) } yield events.map { case (verdictWithViewsO, updateO) => val encodedUpdateV2 = updateO .map( @@ -932,12 +923,9 @@ class HttpScanHandler( val verdictEncoded = verdictWithViewsO.map { case (v, views) => ScanHttpEncodings.encodeVerdict(v, views) } - val trafficSummaryEncoded = - if (serveAppActivityRecordsAndTraffic) - verdictWithViewsO.flatMap { case (v, _) => - v.trafficSummaryO.map(ScanHttpEncodings.encodeTrafficSummary) - } - else None + val trafficSummaryEncoded = verdictWithViewsO.flatMap { case (v, _) => + v.trafficSummaryO.map(ScanHttpEncodings.encodeTrafficSummary) + } val appActivityRecordEncoded = verdictWithViewsO.flatMap { case (v, _) => appActivityRecordMap.get(v.rowId).map(ScanHttpEncodings.encodeAppActivityRecord) } @@ -2706,23 +2694,14 @@ class HttpScanHandler( ] = { implicit val tc = extracted withSpan(s"$workflowId.getRewardAccountingEarliestAvailableRound") { _ => _ => - appActivityStoreO match { - case Some(appActivityStore) => - appActivityStore.earliestRoundWithCompleteAppActivity().map { - case Some(round) => - ScanResource.GetRewardAccountingEarliestAvailableRoundResponse.OK( - definitions.GetRewardAccountingEarliestAvailableRoundResponse(round) - ) - case None => - ScanResource.GetRewardAccountingEarliestAvailableRoundResponse.NotFound( - ErrorResponse("No reward accounting data available yet") - ) - } + appActivityStore.earliestRoundWithCompleteAppActivity().map { + case Some(round) => + ScanResource.GetRewardAccountingEarliestAvailableRoundResponse.OK( + definitions.GetRewardAccountingEarliestAvailableRoundResponse(round) + ) case None => - Future.successful( - ScanResource.GetRewardAccountingEarliestAvailableRoundResponse.NotFound( - ErrorResponse("Reward accounting is not enabled") - ) + ScanResource.GetRewardAccountingEarliestAvailableRoundResponse.NotFound( + ErrorResponse("No reward accounting data available yet") ) } } @@ -2745,44 +2724,38 @@ class HttpScanHandler( ) ) withSpan(s"$workflowId.getRewardAccountingActivityTotals") { _ => _ => - (appRewardsStoreO, appActivityStoreO) match { - case (Some(appRewardsStore), Some(appActivityStore)) => - appRewardsStore.getAppActivityRoundTotalByRound(roundNumber).flatMap { - case Some(activityTotal) => - appRewardsStore.getAppRewardRoundTotalByRound(roundNumber).map { - case Some(rewardTotal) => - ScanResource.GetRewardAccountingActivityTotalsResponse.OK( - definitions.GetRewardAccountingActivityTotalsResponse( - definitions.RewardAccountingActivityTotalsOk( - status = "Ok", - roundNumber = activityTotal.roundNumber, - totalAppActivityWeight = activityTotal.totalRoundAppActivityWeight, - activePartiesCount = activityTotal.activeAppProviderPartiesCount, - activityRecordsCount = activityTotal.activityRecordsCount, - totalAppRewardMintingAllowance = - rewardTotal.totalAppRewardMintingAllowance.toString, - totalAppRewardThresholded = rewardTotal.totalAppRewardThresholded.toString, - totalAppRewardUnclaimed = rewardTotal.totalAppRewardUnclaimed.toString, - rewardedAppProviderPartiesCount = - rewardTotal.rewardedAppProviderPartiesCount, - ) - ) + appRewardsStore.getAppActivityRoundTotalByRound(roundNumber).flatMap { + case Some(activityTotal) => + appRewardsStore.getAppRewardRoundTotalByRound(roundNumber).map { + case Some(rewardTotal) => + ScanResource.GetRewardAccountingActivityTotalsResponse.OK( + definitions.GetRewardAccountingActivityTotalsResponse( + definitions.RewardAccountingActivityTotalsOk( + status = "Ok", + roundNumber = activityTotal.roundNumber, + totalAppActivityWeight = activityTotal.totalRoundAppActivityWeight, + activePartiesCount = activityTotal.activeAppProviderPartiesCount, + activityRecordsCount = activityTotal.activityRecordsCount, + totalAppRewardMintingAllowance = + rewardTotal.totalAppRewardMintingAllowance.toString, + totalAppRewardThresholded = rewardTotal.totalAppRewardThresholded.toString, + totalAppRewardUnclaimed = rewardTotal.totalAppRewardUnclaimed.toString, + rewardedAppProviderPartiesCount = rewardTotal.rewardedAppProviderPartiesCount, ) - case None => - // We should never hit this, as both activity totals and round - // totals are added in a single DB Tx - undetermined - } + ) + ) case None => - appActivityStore.earliestIngestedRound().map { - case Some(earliestIngested) if roundNumber <= earliestIngested => - cannotProvide - case _ => - undetermined - } + // We should never hit this, as both activity totals and round + // totals are added in a single DB Tx + undetermined + } + case None => + appActivityStore.earliestIngestedRound().map { + case Some(earliestIngested) if roundNumber <= earliestIngested => + cannotProvide + case _ => + undetermined } - case _ => - Future.successful(cannotProvide) } } } @@ -2804,31 +2777,26 @@ class HttpScanHandler( ) ) withSpan(s"$workflowId.getRewardAccountingRootHash") { _ => _ => - (appRewardsStoreO, appActivityStoreO) match { - case (Some(appRewardsStore), Some(appActivityStore)) => - appRewardsStore.getAppRewardRootHashByRound(roundNumber).flatMap { - case Some(rootHash) => - Future.successful( - ScanResource.GetRewardAccountingRootHashResponse.OK( - definitions.GetRewardAccountingRootHashResponse( - definitions.RewardAccountingRootHashOk( - status = "Ok", - roundNumber = rootHash.roundNumber, - rootHash = rootHash.rootHash.toHex, - ) - ) + appRewardsStore.getAppRewardRootHashByRound(roundNumber).flatMap { + case Some(rootHash) => + Future.successful( + ScanResource.GetRewardAccountingRootHashResponse.OK( + definitions.GetRewardAccountingRootHashResponse( + definitions.RewardAccountingRootHashOk( + status = "Ok", + roundNumber = rootHash.roundNumber, + rootHash = rootHash.rootHash.toHex, ) ) - case None => - appActivityStore.earliestIngestedRound().map { - case Some(earliestIngested) if roundNumber <= earliestIngested => - cannotProvide - case _ => - undetermined - } + ) + ) + case None => + appActivityStore.earliestIngestedRound().map { + case Some(earliestIngested) if roundNumber <= earliestIngested => + cannotProvide + case _ => + undetermined } - case _ => - Future.successful(cannotProvide) } } } @@ -2840,50 +2808,41 @@ class HttpScanHandler( ] = { implicit val tc = extracted withSpan(s"$workflowId.getRewardAccountingBatch") { _ => _ => - appRewardsStoreO match { - case None => - Future.successful( + appRewardsStore + .lookupBatchByHash(roundNumber, DbScanAppRewardsStore.RewardHash.fromHex(batchHash)) + .map { + case None => ScanResource.GetRewardAccountingBatchResponse.NotFound( - ErrorResponse("Reward accounting is not enabled on this node") + ErrorResponse( + s"Batch not (yet) found for round $roundNumber with hash $batchHash" + ) ) - ) - case Some(appRewardsStore) => - appRewardsStore - .lookupBatchByHash(roundNumber, DbScanAppRewardsStore.RewardHash.fromHex(batchHash)) - .map { - case None => - ScanResource.GetRewardAccountingBatchResponse.NotFound( - ErrorResponse( - s"Batch not (yet) found for round $roundNumber with hash $batchHash" - ) + case Some(batch: DbScanAppRewardsStore.BatchOfBatches) => + ScanResource.GetRewardAccountingBatchResponse.OK( + definitions.GetRewardAccountingBatchResponse( + definitions.RewardAccountingBatchOfBatches( + batchType = "BatchOfBatches", + childHashes = batch.childHashes.map(_.toHex).toVector, ) - case Some(batch: DbScanAppRewardsStore.BatchOfBatches) => - ScanResource.GetRewardAccountingBatchResponse.OK( - definitions.GetRewardAccountingBatchResponse( - definitions.RewardAccountingBatchOfBatches( - batchType = "BatchOfBatches", - childHashes = batch.childHashes.map(_.toHex).toVector, - ) - ) - ) - case Some(batch: DbScanAppRewardsStore.BatchOfMintingAllowances) => - ScanResource.GetRewardAccountingBatchResponse.OK( - definitions.GetRewardAccountingBatchResponse( - definitions.RewardAccountingBatchOfMintingAllowances( - batchType = "BatchOfMintingAllowances", - mintingAllowances = batch.allowances - .map(a => - definitions.RewardAccountingMintingAllowance( - provider = a.provider, - amount = a.amount.toString, - ) - ) - .toVector, + ) + ) + case Some(batch: DbScanAppRewardsStore.BatchOfMintingAllowances) => + ScanResource.GetRewardAccountingBatchResponse.OK( + definitions.GetRewardAccountingBatchResponse( + definitions.RewardAccountingBatchOfMintingAllowances( + batchType = "BatchOfMintingAllowances", + mintingAllowances = batch.allowances + .map(a => + definitions.RewardAccountingMintingAllowance( + provider = a.provider, + amount = a.amount.toString, + ) ) - ) + .toVector, ) - } - } + ) + ) + } } } } diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanAutomationService.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanAutomationService.scala index e46e2bafab..6bf147e2db 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanAutomationService.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanAutomationService.scala @@ -47,8 +47,8 @@ class ScanAutomationService( protected val loggerFactory: NamedLoggerFactory, store: ScanStore, val updateHistory: UpdateHistory, - appRewardsStoreO: Option[DbScanAppRewardsStore], - appActivityStoreO: Option[AppActivityStore], + appRewardsStore: DbScanAppRewardsStore, + appActivityStore: AppActivityStore, storage: DbStorage, snapshotStore: AcsSnapshotStore, svParty: PartyId, @@ -79,10 +79,7 @@ class ScanAutomationService( def registerRewardComputationTrigger( rewardsReferenceStore: ScanRewardsReferenceStore ): Unit = - for { - appRewardsStore <- appRewardsStoreO - appActivityStore <- appActivityStoreO - } registerTrigger( + registerTrigger( new RewardComputationTrigger( appRewardsStore, appActivityStore, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanVerdictAutomationService.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanVerdictAutomationService.scala index 4341f3e5fe..d40da689ce 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanVerdictAutomationService.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanVerdictAutomationService.scala @@ -34,7 +34,7 @@ class ScanVerdictAutomationService( migrationId: Long, synchronizerId: SynchronizerId, ingestionMetrics: ScanMediatorVerdictIngestionMetrics, - rewardsReferenceStoreO: Option[ScanRewardsReferenceStore], + rewardsReferenceStore: ScanRewardsReferenceStore, )(implicit ec: ExecutionContextExecutor, mat: Materializer, @@ -49,10 +49,8 @@ class ScanVerdictAutomationService( override def companion: AutomationServiceCompanion = ScanVerdictAutomationService - private val appActivityComputationO: Option[AppActivityComputation] = - rewardsReferenceStoreO.map { store => - new AppActivityComputation(store, loggerFactory) - } + private val appActivityComputation: AppActivityComputation = + new AppActivityComputation(rewardsReferenceStore, loggerFactory) registerService( new ScanVerdictIngestionService( @@ -63,7 +61,7 @@ class ScanVerdictAutomationService( migrationId = migrationId, synchronizerId = synchronizerId, ingestionMetrics = ingestionMetrics, - appActivityComputationO = appActivityComputationO, + appActivityComputation = appActivityComputation, backoffClock = triggerContext.pollingClock, retryProvider = triggerContext.retryProvider, loggerFactory = triggerContext.loggerFactory, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanVerdictIngestionService.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanVerdictIngestionService.scala index ee476d12a8..5a8da6401b 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanVerdictIngestionService.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanVerdictIngestionService.scala @@ -74,7 +74,7 @@ class ScanVerdictIngestionService( migrationId: Long, synchronizerId: SynchronizerId, ingestionMetrics: ScanMediatorVerdictIngestionMetrics, - appActivityComputationO: Option[AppActivityComputation], + appActivityComputation: AppActivityComputation, backoffClock: Clock, override protected val retryProvider: RetryProvider, override protected val loggerFactory: NamedLoggerFactory, @@ -107,10 +107,7 @@ class ScanVerdictIngestionService( private def waitForStores(): Future[Unit] = for { _ <- store.waitUntilInitialized - _ <- appActivityComputationO match { - case Some(appActivityComputation) => appActivityComputation.waitUntilInitialized - case None => Future.unit - } + _ <- appActivityComputation.waitUntilInitialized } yield () /** When starting a fresh stream, the record time from which to start streaming */ @@ -130,7 +127,7 @@ class ScanVerdictIngestionService( streamVerdictsAndBatchWithTraffic( ingestionStart, currentMediatorClient, - synchronizerNodes.current.sequencerTrafficClient, + Some(synchronizerNodes.current.sequencerTrafficClient), ) val completedWithCompleteF = Promise[Option[v30.VerdictsResponse.Complete]]() val source = currentSource @@ -157,7 +154,7 @@ class ScanVerdictIngestionService( streamVerdictsAndBatchWithTraffic( successorIngestionStart, successorMediatorClient, - synchronizerNodes.successor.flatMap(_.sequencerTrafficClient), + synchronizerNodes.successor.map(_.sequencerTrafficClient), ) .mapMaterializedValue(_ => NotUsed) case None => @@ -243,30 +240,27 @@ class ScanVerdictIngestionService( // Compute app activity records (before DB transaction). // Records have verdictRowId = DUMMY_VERDICT_ROW_ID // the store resolves actual row_ids during insertion. - (appActivityRecords, firstActiveRoundO, lastArchivedRoundO) <- - appActivityComputationO match { - case Some(appActivityComputation) => - val recordTimes = - verdicts.map(v => CantonTimestamp.tryFromProtoTimestamp(v.getRecordTime)) - for { - records <- appActivityComputation.computeActivities(summariesWithVerdicts).map { - _.flatMap { case (summary, _, recordO) => - recordO.map(summary.sequencingTime -> _) - } - } - firstActiveRoundO <- recordTimes.minOption match { - case Some(minRecordTime) => - appActivityComputation.lookupActiveOpenMiningRound(minRecordTime) - case None => Future.successful(None) - } - lastArchivedRoundO <- recordTimes.maxOption match { - case Some(maxRecordTime) => - appActivityComputation.lookupLatestArchivedOpenMiningRound(maxRecordTime) - case None => Future.successful(None) - } - } yield (records, firstActiveRoundO, lastArchivedRoundO) - case None => Future.successful((Seq.empty, None, None)) - } + (appActivityRecords, firstActiveRoundO, lastArchivedRoundO) <- { + val recordTimes = + verdicts.map(v => CantonTimestamp.tryFromProtoTimestamp(v.getRecordTime)) + for { + records <- appActivityComputation.computeActivities(summariesWithVerdicts).map { + _.flatMap { case (summary, _, recordO) => + recordO.map(summary.sequencingTime -> _) + } + } + firstActiveRoundO <- recordTimes.minOption match { + case Some(minRecordTime) => + appActivityComputation.lookupActiveOpenMiningRound(minRecordTime) + case None => Future.successful(None) + } + lastArchivedRoundO <- recordTimes.maxOption match { + case Some(maxRecordTime) => + appActivityComputation.lookupLatestArchivedOpenMiningRound(maxRecordTime) + case None => Future.successful(None) + } + } yield (records, firstActiveRoundO, lastArchivedRoundO) + } _ <- ensureVerdictsHaveTrafficSummaries(verdicts, summaryByTime) _ <- store.insertVerdictsWithAppActivityRecords( @@ -362,10 +356,7 @@ class ScanVerdictIngestionService( verdicts: Seq[v30.Verdict], summaryByTime: Map[CantonTimestamp, DbScanVerdictStore.TrafficSummaryT], )(implicit tc: TraceContext): Future[Unit] = - (store.appActivityRecordStoreO match { - case None => Future.successful(None) - case Some(s) => s.startedIngestingAt - }).map { startO => + store.appActivityRecordStore.startedIngestingAt.map { startO => val missingTimes = ScanVerdictIngestionService.findMissingTrafficSummaries( verdicts.map(v => CantonTimestamp.tryFromProtoTimestamp(v.getRecordTime)), summaryByTime.keySet, diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanAppConfig.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanAppConfig.scala index d9c87180c5..163d51e289 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanAppConfig.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanAppConfig.scala @@ -61,8 +61,6 @@ case class ScanAppBackendConfig( synchronizerNodes: ScanSynchronizerNodesConfig, override val automation: AutomationConfig = AutomationConfig(), mediatorVerdictIngestion: MediatorVerdictIngestionConfig = MediatorVerdictIngestionConfig(), - enableAppActivityRecordAndTrafficIngestion: Boolean = true, - serveAppActivityRecordsAndTraffic: Boolean = true, isFirstSv: Boolean = false, // Max rounding error tolerated wrt actual total of minting allowances // and the per-round minting allowance from the CC whitepaper. diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanEventStore.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanEventStore.scala index b2bc6d8b23..4729fce4ef 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanEventStore.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/ScanEventStore.scala @@ -125,10 +125,7 @@ class ScanEventStore( def getAppActivityRecords(verdictRowIds: Seq[Long])(implicit tc: TraceContext ): Future[Map[Long, AppActivityRecordT]] = - verdictStore.appActivityRecordStoreO match { - case Some(store) => store.getRecordsByVerdictRowIds(verdictRowIds) - case None => Future.successful(Map.empty) - } + verdictStore.appActivityRecordStore.getRecordsByVerdictRowIds(verdictRowIds) // Get values from in-memory refs, fallsback to DB read private def resolveCurrentMigrationCap( diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbScanVerdictStore.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbScanVerdictStore.scala index 38b7f2e6a1..f14fca20d1 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbScanVerdictStore.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/db/DbScanVerdictStore.scala @@ -237,13 +237,13 @@ object DbScanVerdictStore { def apply( storage: com.digitalasset.canton.resource.DbStorage, updateHistory: UpdateHistory, - appActivityRecordStoreO: Option[DbAppActivityRecordStore], + appActivityRecordStore: DbAppActivityRecordStore, loggerFactory: NamedLoggerFactory, )(implicit ec: ExecutionContext): DbScanVerdictStore = new DbScanVerdictStore( storage, updateHistory, - appActivityRecordStoreO, + appActivityRecordStore, loggerFactory, ) } @@ -251,7 +251,7 @@ object DbScanVerdictStore { class DbScanVerdictStore( storage: DbStorage, updateHistory: UpdateHistory, - val appActivityRecordStoreO: Option[DbAppActivityRecordStore], + val appActivityRecordStore: DbAppActivityRecordStore, override protected val loggerFactory: NamedLoggerFactory, )(implicit ec: ExecutionContext @@ -556,17 +556,13 @@ class DbScanVerdictStore( firstActiveRoundO: Option[Long], lastArchivedRoundO: Option[Long], )(implicit tc: TraceContext): DBIO[Unit] = - appActivityRecordStoreO match { - case None => DBIO.successful(()) - case Some(s) => - s.insertAppActivityRecordsDBIO( - items, - firstRecordTimeMicros, - hasTrafficSummaries, - firstActiveRoundO, - lastArchivedRoundO, - ) - } + appActivityRecordStore.insertAppActivityRecordsDBIO( + items, + firstRecordTimeMicros, + hasTrafficSummaries, + firstActiveRoundO, + lastArchivedRoundO, + ) private def afterFilters( afterO: Option[(Long, CantonTimestamp)], diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbAppActivityRecordStoreTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbAppActivityRecordStoreTest.scala index da458938d1..7d14f778c4 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbAppActivityRecordStoreTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/DbAppActivityRecordStoreTest.scala @@ -1143,7 +1143,7 @@ class DbAppActivityRecordStoreTest val verdictStore = new DbScanVerdictStore( storage.underlying, updateHistory, - Some(appStore), + appStore, loggerFactory, ) (appStore, verdictStore) diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/ScanEventStoreTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/ScanEventStoreTest.scala index 9a0f061b3f..de8dc884af 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/ScanEventStoreTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/ScanEventStoreTest.scala @@ -11,7 +11,7 @@ import org.lfdecentralizedtrust.splice.store.{ StoreTestBase, UpdateHistory, } -import org.lfdecentralizedtrust.splice.scan.store.db.DbScanVerdictStore +import org.lfdecentralizedtrust.splice.scan.store.db.{DbAppActivityRecordStore, DbScanVerdictStore} import org.lfdecentralizedtrust.splice.scan.store.db.DbScanVerdictStore.{TrafficSummaryT, EnvelopeT} import org.lfdecentralizedtrust.splice.store.db.SplicePostgresTest import com.digitalasset.canton.resource.DbStorage @@ -880,7 +880,18 @@ class ScanEventStoreTest extends StoreTestBase with HasExecutionContext with Spl } private def newVerdictStore(updateHistory: UpdateHistory) = - new DbScanVerdictStore(storage.underlying, updateHistory, None, loggerFactory) + new DbScanVerdictStore( + storage.underlying, + updateHistory, + new DbAppActivityRecordStore( + storage.underlying, + updateHistory, + DbAppActivityRecordStore.IngestionVersions(1, 0), + false, + loggerFactory, + ), + loggerFactory, + ) private def insertUpdate( updateHistory: UpdateHistory, diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index dc39b70538..0cebf148c0 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -6,3 +6,10 @@ .. NOTE: add your upcoming release notes below this line. They are included in the `release_notes.rst`. release-notes:: Upcoming + + - Scan app + + - The ``enable-app-activity-record-and-traffic-ingestion`` and + ``serve-app-activity-records-and-traffic`` configuration options have been removed. App + activity records and sequencer traffic are now always ingested, and app activity is + always computed and served on the corresponding HTTP endpoints. From 429825f355658d46f30dda24c3a4dc8a992590dc Mon Sep 17 00:00:00 2001 From: Matt Dziuban Date: Mon, 17 Aug 2026 02:34:40 -0400 Subject: [PATCH 241/329] Re-enable `SplitwellUpgradeIntegrationTest` (#6748) * Re-enable `SplitwellUpgradeIntegrationTest`. Signed-off-by: Matt Dziuban * Ensure DAR is uploaded and multi-synchronizer feature flag is enabled. Signed-off-by: Matt Dziuban * Remove unneeded log assertion. Signed-off-by: Matt Dziuban * Re-enable `SplitwellUpgradeFrontendIntegrationTest` as well. Signed-off-by: Matt Dziuban * Move `splitwellUpgradeSynchronizerId` to after Alice's participant connects to that synchronizer. This fixes an issue where the `balance update and invite contracts follow group, which follows installs` test would fail when run in isolation because Alice hadn't connected to the splitwell upgrade synchronizer. When the suite was run in full, earlier tests ensured the pre-condition was met, but this prevents devs from running a command like `testOnly ... -- -z "balance update"`. Signed-off-by: Matt Dziuban --------- Signed-off-by: Matt Dziuban --- .../integration/tests/SplitwellTestUtil.scala | 10 ++++-- ...itwellUpgradeFrontendIntegrationTest.scala | 3 -- .../SplitwellUpgradeIntegrationTest.scala | 31 +++---------------- 3 files changed, 12 insertions(+), 32 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellTestUtil.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellTestUtil.scala index 5a1bb670ec..1dbce9df33 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellTestUtil.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellTestUtil.scala @@ -2,6 +2,7 @@ package org.lfdecentralizedtrust.splice.util import com.digitalasset.canton.SynchronizerAlias import com.digitalasset.canton.admin.api.client.data.GrpcSequencerConnection +import com.digitalasset.canton.integration.util.MultiSynchronizerFeatureFlag import com.digitalasset.canton.topology.PartyId import org.lfdecentralizedtrust.splice.codegen.java.splice.splitwell as splitwellCodegen import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.payment as walletCodegen @@ -42,8 +43,13 @@ trait SplitwellTestUtil extends TestCommon with WalletTestUtil with TimeTestUtil actAndCheck( timeUntilSuccess = 40.seconds )( - "Connect splitwell upgrade domain", - participant.synchronizers.connect(splitwellUpgradeAlias, url), + "Connect splitwell upgrade domain", { + participant.synchronizers.connect(splitwellUpgradeAlias, url) + participant.upload_dar_unless_exists(splitwellDarPath) + participant.synchronizers.list_connected().foreach { sync => + MultiSynchronizerFeatureFlag.enable(Seq(participant), sync.synchronizerId) + } + }, )( s"Wait for splitwell upgrade domain to be connected for party $ensurePartyIsOnNewDomain", _ => { diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeFrontendIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeFrontendIntegrationTest.scala index a46c72f5ee..a1b3a30b9a 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeFrontendIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeFrontendIntegrationTest.scala @@ -13,10 +13,7 @@ import org.lfdecentralizedtrust.splice.util.{ WalletTestUtil, } import SplitwellUpgradeFrontendIntegrationTest.* -import org.scalatest.Ignore -// TODO(DACH-NY/canton-network-internal#1834) Reenable once we sorted out the reassignment issues -@Ignore class SplitwellUpgradeFrontendIntegrationTest extends FrontendIntegrationTest(aliceSplitwellFE, bobSplitwellFE) with FrontendLoginUtil diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeIntegrationTest.scala index c59b765738..18e7604812 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeIntegrationTest.scala @@ -8,16 +8,12 @@ import org.lfdecentralizedtrust.splice.console.SplitwellAppClientReference import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.IntegrationTest import SpliceTests.BracketSynchronous.* -import com.digitalasset.canton.logging.SuppressingLogger.LogEntryOptionality import org.lfdecentralizedtrust.splice.util.{MultiDomainTestUtil, SplitwellTestUtil, WalletTestUtil} import com.digitalasset.canton.topology.{PartyId, SynchronizerId} -import org.scalatest.Ignore import scala.concurrent.duration.DurationInt import scala.util.Try -// TODO(#2703) Reenable or delete -@Ignore class SplitwellUpgradeIntegrationTest extends IntegrationTest with MultiDomainTestUtil @@ -61,27 +57,7 @@ class SplitwellUpgradeIntegrationTest def createInstalls(splitwells: SplitwellAppClientReference*) = for { splitwell <- splitwells } eventually() { - loggerFactory - .assertLogsUnorderedOptionalFromResult[Try[Unit]]( - Try(splitwell.createInstallRequests()), - { r => - if (r.isFailure) { - Seq( - ( - LogEntryOptionality.Required, - log => - log.errorMessage should include( - "Not all informee are on the specified domainID: splitwellUpgrade" - ), - ) - ) - } else { - Seq.empty - } - }, - ) - .toEither - .valueOr(fail(_)) + Try(splitwell.createInstallRequests()).toEither.valueOr(fail(_)) } def twoInstalls(alice: PartyId, install: splitwellCodegen.SplitwellInstall.Contract)(implicit @@ -152,8 +128,6 @@ class SplitwellUpgradeIntegrationTest val acceptedInvite = bobSplitwellClient.acceptInvite(invite) val splitwellSynchronizerId = aliceValidatorBackend.participantClient.synchronizers.id_of(splitwellAlias).logical - val splitwellUpgradeSynchronizerId = - aliceValidatorBackend.participantClient.synchronizers.id_of(splitwellUpgradeAlias).logical eventually() { val contractDomains = @@ -175,6 +149,9 @@ class SplitwellUpgradeIntegrationTest connectSplitwellUpgradeDomain(aliceValidatorBackend.participantClient, alice), disconnectSplitwellUpgradeDomain(aliceValidatorBackend.participantClient), ) { + val splitwellUpgradeSynchronizerId = + aliceValidatorBackend.participantClient.synchronizers.id_of(splitwellUpgradeAlias).logical + bracket( connectSplitwellUpgradeDomain(bobValidatorBackend.participantClient, bob), disconnectSplitwellUpgradeDomain(bobValidatorBackend.participantClient), From 62ebf821f1199b2c9b99c03658967ee45ffa94db Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Mon, 17 Aug 2026 09:01:46 +0200 Subject: [PATCH 242/329] Bump version to 0.7.4 and latest release to 0.7.3 (#6795) [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- LATEST_RELEASE | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LATEST_RELEASE b/LATEST_RELEASE index 7486fdbc50..f38fc5393f 100644 --- a/LATEST_RELEASE +++ b/LATEST_RELEASE @@ -1 +1 @@ -0.7.2 +0.7.3 diff --git a/VERSION b/VERSION index f38fc5393f..0a1ffad4b4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.3 +0.7.4 From bd2f5f26997c38de6b62474a1250b1102deabe36 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Mon, 17 Aug 2026 09:15:15 +0200 Subject: [PATCH 243/329] Fix buffer usage metric in jvm dashboard (#6796) [static] Not sure if this was ever right but it doesn't work atm. Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- cluster/expected/observability/expected.json | 2 +- .../grafana-dashboards/jvm/jvm.json | 56 +++++++++++-------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 7ec78fdef3..b8c6d06eb1 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -269,7 +269,7 @@ "apiVersion": "v1", "data": { "executor_service.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"target\": {\n \"limit\": 100,\n \"matchAny\": false,\n \"tags\": [],\n \"type\": \"dashboard\"\n },\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 1171,\n \"links\": [],\n \"liveNow\": false,\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 21,\n \"panels\": [],\n \"title\": \"Runtime Metrics\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 23,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.95, sum by(namespace, pod, container, name, component, participant) (rate(daml_executor_runtime_duration_seconds{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{pod}} - {{container}} - {{name}} / {{component}} {{participant}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Task Run Duration (p95}\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 1\n },\n \"id\": 24,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without() (histogram_avg(rate(daml_executor_runtime_duration_seconds{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Task Run Duration (average}\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 28,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.95, sum by(namespace, pod, container, name, component, participant) (rate(daml_executor_runtime_idle_duration_seconds{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{pod}} - {{container}} - {{name}} / {{component}} {{participant}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Task Idle Duration (p95}\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 9\n },\n \"id\": 29,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without() (histogram_avg(rate(daml_executor_runtime_idle_duration_seconds{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Task Idle Duration (average}\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"tasks / s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 17\n },\n \"id\": 26,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without($group_without) (rate(daml_executor_runtime_submitted_total{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval]))\\n\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Submitted Tasks\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"tasks / s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 17\n },\n \"id\": 30,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without($group_without) (rate(daml_executor_runtime_completed_total{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval]))\\n\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Completed Tasks\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"none\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 24,\n \"x\": 0,\n \"y\": 30\n },\n \"id\": 27,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without($group_without) (daml_executor_runtime_running{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"})\\n\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Running Tasks\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 42\n },\n \"id\": 6,\n \"panels\": [],\n \"title\": \"Common Metrics\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 6,\n \"x\": 0,\n \"y\": 43\n },\n \"id\": 19,\n \"options\": {\n \"code\": {\n \"language\": \"plaintext\",\n \"showLineNumbers\": false,\n \"showMiniMap\": false\n },\n \"content\": \"# Common Executor Service Metrics\\n\\nExecutor Service Metrics that are common between thread pools and fork join pools.\\nThese metrics are outsourced from the Executor Service itself.\",\n \"mode\": \"markdown\"\n },\n \"pluginVersion\": \"11.1.0\",\n \"title\": \"Info\",\n \"type\": \"text\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 49\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_executor_pool_size{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}\",\n \"legendFormat\": \"{{pod}} -- {{name}} -- {{type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Pool Size\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"axisSoftMin\": 0,\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 58\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": false\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_executor_threads_active{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}\",\n \"legendFormat\": \"{{pod}} -- {{name}} -- {{type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Active Threads\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"axisSoftMin\": 0,\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 67\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": false\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_executor_tasks_queued{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}\",\n \"legendFormat\": \"{{pod}} -- {{name}} -- {{type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Queued Tasks\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 76\n },\n \"id\": 8,\n \"panels\": [],\n \"title\": \"Thread Pool Metrics\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 77\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_executor_pool_core{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}\",\n \"legendFormat\": \"Core - {{pod}} -- {{name}} -- {{type}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_executor_pool_max{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}\",\n \"hide\": false,\n \"legendFormat\": \"Max - {{pod}} -- {{name}} -- {{type}}\",\n \"range\": true,\n \"refId\": \"B\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_executor_pool_largest{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}\",\n \"hide\": false,\n \"legendFormat\": \"Largest - {{pod}} -- {{name}} -- {{type}}\",\n \"range\": true,\n \"refId\": \"C\"\n }\n ],\n \"title\": \"Pool Sizes\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"tasks/s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 86\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without($group_without) (rate(daml_executor_tasks_submitted{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Tasks Submitted\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"tasks/s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 95\n },\n \"id\": 31,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without($group_without) (rate(daml_executor_tasks_completed{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval]))\",\n \"hide\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Tasks Completed\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 104\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_executor_tasks_queue_remaining{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}\",\n \"legendFormat\": \"Remaining - {{pod}} -- {{name}} -- {{type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Remaining Queue Capacity\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 113\n },\n \"id\": 13,\n \"panels\": [],\n \"title\": \"Fork Join Metrics\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"axisSoftMin\": 0,\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 114\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": false\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_executor_threads_running{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}\",\n \"legendFormat\": \"{{pod}} -- {{name}} -- {{type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Running Threads\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"tasks/s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 123\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without($group_without) (rate(daml_executor_tasks_stolen{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Stolen Tasks\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"tasks/s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 132\n },\n \"id\": 16,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum without($group_without) (rate(daml_executor_tasks_executing_queued{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Task Queuing Rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \" tasks\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 141\n },\n \"id\": 17,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"daml_executor_tasks_executing_queued{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\", container=\\\"$container\\\", name=~\\\"$name\\\"}\",\n \"hide\": false,\n \"legendFormat\": \"Queued total - {{pod}} -- {{name}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Queued Tasks\",\n \"type\": \"timeseries\"\n }\n ],\n \"refresh\": false,\n \"schemaVersion\": 39,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"selected\": false,\n \"text\": \"Prometheus\",\n \"value\": \"prometheus\"\n },\n \"hide\": 0,\n \"includeAll\": false,\n \"multi\": false,\n \"name\": \"datasource\",\n \"options\": [],\n \"query\": \"prometheus\",\n \"queryValue\": \"\",\n \"refresh\": 1,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"type\": \"datasource\"\n },\n {\n \"current\": {\n \"selected\": false,\n \"text\": \"sv-1\",\n \"value\": \"sv-1\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"definition\": \"label_values(daml_executor_pool_size, namespace)\",\n \"hide\": 0,\n \"includeAll\": false,\n \"multi\": false,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(daml_executor_pool_size, namespace)\",\n \"refId\": \"StandardVariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 0,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"definition\": \"label_values(daml_executor_pool_size{namespace=\\\"$namespace\\\"}, pod)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"pod\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(daml_executor_pool_size{namespace=\\\"$namespace\\\"}, pod)\",\n \"refId\": \"StandardVariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 0,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"isNone\": true,\n \"selected\": false,\n \"text\": \"None\",\n \"value\": \"\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"definition\": \"label_values(daml_executor_pool_size{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\"}, container)\",\n \"hide\": 0,\n \"includeAll\": false,\n \"multi\": false,\n \"name\": \"container\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(daml_executor_pool_size{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\"}, container)\",\n \"refId\": \"StandardVariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 0,\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"definition\": \"label_values(daml_executor_pool_size{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\",container=\\\"$container\\\"}, name)\",\n \"hide\": 0,\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"name\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(daml_executor_pool_size{namespace=\\\"$namespace\\\", pod=~\\\"$pod\\\",container=\\\"$container\\\"}, name)\",\n \"refId\": \"StandardVariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 0,\n \"type\": \"query\"\n },\n {\n \"hide\": 2,\n \"name\": \"group_without\",\n \"query\": \"endpoint, instance,daml_version,job,namespace,container,canton_version\",\n \"skipUrlSync\": false,\n \"type\": \"constant\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-3h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"JVM Executor Services\",\n \"uid\": \"AYtzKz2Vz\",\n \"version\": 2,\n \"weekStart\": \"\"\n}\n", - "jvm.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"target\": {\n \"limit\": 100,\n \"matchAny\": false,\n \"tags\": [],\n \"type\": \"dashboard\"\n },\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 9858,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 16,\n \"panels\": [],\n \"title\": \"CPU\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 6,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 17,\n \"options\": {\n \"minVizHeight\": 75,\n \"minVizWidth\": 75,\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showThresholdLabels\": false,\n \"showThresholdMarkers\": true,\n \"sizing\": \"auto\"\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"jvm_cpu_count{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"{{job}}\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Count\",\n \"type\": \"gauge\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"percentunit\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 18,\n \"x\": 6,\n \"y\": 1\n },\n \"id\": 19,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"jvm_cpu_recent_utilization_ratio{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"CPU Utilization\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 24,\n \"x\": 0,\n \"y\": 10\n },\n \"id\": 18,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(container, pod) (rate(jvm_cpu_time_seconds_total{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{pod}} {{container}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"CPU Usage\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 22\n },\n \"id\": 14,\n \"panels\": [],\n \"title\": \"Threads\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 24,\n \"x\": 0,\n \"y\": 23\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"jvm_thread_count{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{job}} - daemon {{jvm_thread_daemon}} - {{jvm_thread_state}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Threads\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 37\n },\n \"id\": 4,\n \"panels\": [],\n \"title\": \"Garbage Collection\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(gc, container, pod, jvm_gc_name, jvm_gc_action, namespace) (histogram_count(rate(jvm_gc_duration_seconds{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}} {{jvm_gc_name}} {{jvm_gc_action}} {{pod}} {{container}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"GC Runs\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 38\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(jvm_gc_action, jvm_gc_name, container, pod, namespace) (histogram_sum(rate(jvm_gc_duration_seconds{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"rate {{namespace}} {{jvm_gc_name}} {{jvm_gc_action}} {{pod}} {{container}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(jvm_gc_action, jvm_gc_name, container, pod, namespace) (histogram_avg(rate(jvm_gc_duration_seconds{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}[$__rate_interval])))\",\n \"hide\": false,\n \"legendFormat\": \"avg {{namespace}} {{jvm_gc_name}} {{jvm_gc_action}} {{pod}} {{container}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"GC Timing\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 51\n },\n \"id\": 7,\n \"panels\": [],\n \"title\": \"Memory\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 52\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_memory_used_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_memory_pool_name}} - {{jvm_memory_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Memory Used\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 52\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_memory_limit_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_memory_pool_name}} - {{jvm_memory_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Memory Limit\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 65\n },\n \"id\": 13,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_memory_used_after_last_gc_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_memory_pool_name}} - {{jvm_memory_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Memory Used After Last GC \",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 65\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_memory_committed_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_memory_pool_name}} - {{jvm_memory_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Memory Committed \",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 78\n },\n \"id\": 21,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_buffer_memory_usage_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_buffer_pool_name}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Buffer Memory Usage\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 78\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_buffer_memory_limit_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_buffer_pool_name}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Buffer Memory Limit\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"\",\n \"schemaVersion\": 41,\n \"tags\": [\n \"jvm\"\n ],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"Prometheus\",\n \"value\": \"prometheus\"\n },\n \"includeAll\": false,\n \"name\": \"datasource\",\n \"options\": [],\n \"query\": \"prometheus\",\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"datasource\"\n },\n {\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(jvm_cpu_count,namespace)\",\n \"description\": \"K8s namespace\",\n \"includeAll\": true,\n \"label\": \"Namespace\",\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(jvm_cpu_count,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": [\n \"All\"\n ],\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\"},service)\",\n \"description\": \"Splice app\",\n \"includeAll\": true,\n \"label\": \"App\",\n \"multi\": true,\n \"name\": \"app\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\"},service)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"sort\": 1,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\", service=~\\\"$app\\\"},pod)\",\n \"description\": \"K8s pod\",\n \"includeAll\": true,\n \"label\": \"Pod\",\n \"multi\": true,\n \"name\": \"pod\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\", service=~\\\"$app\\\"},pod)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"sort\": 1,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\", service=~\\\"$app\\\"},container)\",\n \"description\": \"\",\n \"includeAll\": true,\n \"label\": \"Container\",\n \"multi\": true,\n \"name\": \"container\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\", service=~\\\"$app\\\"},container)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"sort\": 1,\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-30m\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"JVM Metrics\",\n \"uid\": \"rgt7PhA4z\",\n \"version\": 1\n}\n" + "jvm.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"target\": {\n \"limit\": 100,\n \"matchAny\": false,\n \"tags\": [],\n \"type\": \"dashboard\"\n },\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 16,\n \"panels\": [],\n \"title\": \"CPU\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 6,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 17,\n \"options\": {\n \"minVizHeight\": 75,\n \"minVizWidth\": 75,\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showThresholdLabels\": false,\n \"showThresholdMarkers\": true,\n \"sizing\": \"auto\"\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"jvm_cpu_count{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"{{job}}\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Count\",\n \"type\": \"gauge\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"percentunit\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 18,\n \"x\": 6,\n \"y\": 1\n },\n \"id\": 19,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"jvm_cpu_recent_utilization_ratio{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"CPU Utilization\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 24,\n \"x\": 0,\n \"y\": 10\n },\n \"id\": 18,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(container, pod) (rate(jvm_cpu_time_seconds_total{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{pod}} {{container}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"CPU Usage\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 22\n },\n \"id\": 14,\n \"panels\": [],\n \"title\": \"Threads\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 14,\n \"w\": 24,\n \"x\": 0,\n \"y\": 23\n },\n \"id\": 15,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"jvm_thread_count{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{job}} - daemon {{jvm_thread_daemon}} - {{jvm_thread_state}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Threads\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 37\n },\n \"id\": 4,\n \"panels\": [],\n \"title\": \"Garbage Collection\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 38\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(gc, container, pod, jvm_gc_name, jvm_gc_action, namespace) (histogram_count(rate(jvm_gc_duration_seconds{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"{{namespace}} {{jvm_gc_name}} {{jvm_gc_action}} {{pod}} {{container}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"GC Runs\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 38\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(jvm_gc_action, jvm_gc_name, container, pod, namespace) (histogram_sum(rate(jvm_gc_duration_seconds{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"rate {{namespace}} {{jvm_gc_name}} {{jvm_gc_action}} {{pod}} {{container}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(jvm_gc_action, jvm_gc_name, container, pod, namespace) (histogram_avg(rate(jvm_gc_duration_seconds{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}[$__rate_interval])))\",\n \"legendFormat\": \"avg {{namespace}} {{jvm_gc_name}} {{jvm_gc_action}} {{pod}} {{container}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"GC Timing\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 51\n },\n \"id\": 7,\n \"panels\": [],\n \"title\": \"Memory\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 52\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_memory_used_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_memory_pool_name}} - {{jvm_memory_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Memory Used\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 52\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_memory_limit_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_memory_pool_name}} - {{jvm_memory_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Memory Limit\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 65\n },\n \"id\": 13,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_memory_used_after_last_gc_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_memory_pool_name}} - {{jvm_memory_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Memory Used After Last GC \",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 65\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_memory_committed_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_memory_pool_name}} - {{jvm_memory_type}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Memory Committed \",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 0,\n \"y\": 78\n },\n \"id\": 21,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_buffer_memory_used_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_buffer_pool_name}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Buffer Memory Usage\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"bytes\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 12,\n \"x\": 12,\n \"y\": 78\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${datasource}\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"jvm_buffer_memory_limit_bytes{namespace=~\\\"$namespace\\\", pod=~\\\"$pod\\\", container=~\\\"$container\\\", service=~\\\"$app\\\"}\",\n \"legendFormat\": \"{{pod}} {{container}} - {{jvm_buffer_pool_name}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Buffer Memory Limit\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"\",\n \"schemaVersion\": 42,\n \"tags\": [\n \"jvm\"\n ],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"Prometheus\",\n \"value\": \"prometheus\"\n },\n \"includeAll\": false,\n \"name\": \"datasource\",\n \"options\": [],\n \"query\": \"prometheus\",\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"datasource\"\n },\n {\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(jvm_cpu_count,namespace)\",\n \"description\": \"K8s namespace\",\n \"includeAll\": true,\n \"label\": \"Namespace\",\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(jvm_cpu_count,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\"},service)\",\n \"description\": \"Splice app\",\n \"includeAll\": true,\n \"label\": \"App\",\n \"multi\": true,\n \"name\": \"app\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\"},service)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"sort\": 1,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\", service=~\\\"$app\\\"},pod)\",\n \"description\": \"K8s pod\",\n \"includeAll\": true,\n \"label\": \"Pod\",\n \"multi\": true,\n \"name\": \"pod\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\", service=~\\\"$app\\\"},pod)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"sort\": 1,\n \"type\": \"query\"\n },\n {\n \"allValue\": \".*\",\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\", service=~\\\"$app\\\"},container)\",\n \"description\": \"\",\n \"includeAll\": true,\n \"label\": \"Container\",\n \"multi\": true,\n \"name\": \"container\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(jvm_cpu_count{namespace=~\\\"$namespace\\\", service=~\\\"$app\\\"},container)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"sort\": 1,\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-30m\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"JVM Metrics\",\n \"uid\": \"rgt7PhA4z\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n" }, "kind": "ConfigMap", "metadata": { diff --git a/cluster/pulumi/observability/grafana-dashboards/jvm/jvm.json b/cluster/pulumi/observability/grafana-dashboards/jvm/jvm.json index d1ebc8fc65..d9927705ad 100644 --- a/cluster/pulumi/observability/grafana-dashboards/jvm/jvm.json +++ b/cluster/pulumi/observability/grafana-dashboards/jvm/jvm.json @@ -24,7 +24,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": 9858, "links": [], "panels": [ { @@ -89,7 +88,7 @@ "showThresholdMarkers": true, "sizing": "auto" }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -146,6 +145,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -193,7 +193,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -250,6 +250,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -297,7 +298,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -361,6 +362,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -407,7 +409,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -476,6 +478,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -522,7 +525,7 @@ "sort": "none" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -573,6 +576,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -620,7 +624,7 @@ "sort": "none" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -640,7 +644,6 @@ }, "editorMode": "code", "expr": "sum by(jvm_gc_action, jvm_gc_name, container, pod, namespace) (histogram_avg(rate(jvm_gc_duration_seconds{namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", service=~\"$app\"}[$__rate_interval])))", - "hide": false, "legendFormat": "avg {{namespace}} {{jvm_gc_name}} {{jvm_gc_action}} {{pod}} {{container}}", "range": true, "refId": "B" @@ -696,6 +699,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -743,7 +747,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -794,6 +798,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -841,7 +846,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -892,6 +897,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -939,7 +945,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -990,6 +996,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -1037,7 +1044,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -1088,6 +1095,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -1135,7 +1143,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -1143,7 +1151,7 @@ "uid": "${datasource}" }, "editorMode": "code", - "expr": "jvm_buffer_memory_usage_bytes{namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", service=~\"$app\"}", + "expr": "jvm_buffer_memory_used_bytes{namespace=~\"$namespace\", pod=~\"$pod\", container=~\"$container\", service=~\"$app\"}", "legendFormat": "{{pod}} {{container}} - {{jvm_buffer_pool_name}}", "range": true, "refId": "A" @@ -1186,6 +1194,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -1233,7 +1242,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -1253,7 +1262,7 @@ ], "preload": false, "refresh": "", - "schemaVersion": 41, + "schemaVersion": 42, "tags": [ "jvm" ], @@ -1299,14 +1308,13 @@ }, "refresh": 2, "regex": "", + "regexApplyTo": "value", "type": "query" }, { "allValue": ".*", "current": { - "text": [ - "All" - ], + "text": "All", "value": [ "$__all" ] @@ -1329,6 +1337,7 @@ }, "refresh": 2, "regex": "", + "regexApplyTo": "value", "sort": 1, "type": "query" }, @@ -1358,6 +1367,7 @@ }, "refresh": 2, "regex": "", + "regexApplyTo": "value", "sort": 1, "type": "query" }, @@ -1365,9 +1375,7 @@ "allValue": ".*", "current": { "text": "All", - "value": [ - "$__all" - ] + "value": "$__all" }, "datasource": { "type": "prometheus", @@ -1387,6 +1395,7 @@ }, "refresh": 2, "regex": "", + "regexApplyTo": "value", "sort": 1, "type": "query" } @@ -1400,5 +1409,6 @@ "timezone": "", "title": "JVM Metrics", "uid": "rgt7PhA4z", - "version": 1 + "version": 1, + "weekStart": "" } From 65c0224eff5265106ff8d6cb10bdccc9b9a91ce4 Mon Sep 17 00:00:00 2001 From: Itai Segall Date: Mon, 17 Aug 2026 06:01:35 -0400 Subject: [PATCH 244/329] [ci] fully retire artifactory from splice (#6498) Addresses #6448 (the splice side) --------- Signed-off-by: Itai Segall --- .envrc.vars | 2 +- .github/workflows/build.yml | 2 -- TROUBLESHOOTING.md | 1 - cluster/expected/canton-network/expected.json | 2 +- .../expected/multi-validator/expected.json | 2 +- cluster/expected/operator/expected.json | 2 +- cluster/expected/splitwell/expected.json | 2 +- cluster/expected/sv-canton/expected.json | 36 +++++++++---------- cluster/expected/sv-runbook/expected.json | 4 +-- cluster/expected/sv/expected.json | 10 +++--- .../expected/validator-runbook/expected.json | 4 +-- cluster/expected/validator1/expected.json | 2 +- cluster/images/cometbft/Dockerfile | 4 +-- .../images/splice-test-cometbft/Dockerfile | 4 +-- cluster/pulumi/common/src/dockerConfig.ts | 9 +---- .../pulumi/common/src/dump-config-common.ts | 9 ----- cluster/pulumi/gha/src/github.ts | 22 ------------ cluster/pulumi/sv-runbook/dump-config.ts | 5 --- 18 files changed, 38 insertions(+), 84 deletions(-) diff --git a/.envrc.vars b/.envrc.vars index 69864a4ec1..70783df4dd 100644 --- a/.envrc.vars +++ b/.envrc.vars @@ -105,7 +105,7 @@ export SPLICE_OAUTH_SV_TEST_CLIENT_ID_VALIDATOR=bUfFRpl2tEfZBB7wzIo9iRNGTj8wMeIn export USE_GKE_GCLOUD_AUTH_PLUGIN=true # CometBFT settings -export COMETBFT_DOCKER_IMAGE="digitalasset-canton-enterprise-docker.jfrog.io/cometbft-canton-network:${COMETBFT_RELEASE_VERSION}" +export COMETBFT_DOCKER_IMAGE="europe-docker.pkg.dev/da-images/public/docker/cometbft-canton-network:${COMETBFT_RELEASE_VERSION}" #Test containers config ## Speed up runs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c230aacda..5dac6847c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -163,8 +163,6 @@ jobs: scala_test_with_cometbft: uses: ./.github/workflows/build.scala_test_with_cometbft.yml - # skip for external contributors (fork PRs) as we pull the cometbft image from artifactory. - if: github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name with: runs_on: self-hosted-k8s-medium test_names_file: "test-cometbft-full-class-names.log" diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 092381848d..95bdd015ac 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -155,5 +155,4 @@ If you see **`Environment variable VERSION must be set`** or **`locale.Error: un ### Additional notes - Always run SBT commands from a terminal with `direnv` enabled to ensure the `nix` environment is correctly set up. -- If JFrog access issues persist, confirm with your team lead that all necessary team members have been added to the `splice-developers` group. - For persistent issues, consult the Splice repository’s documentation or raise an issue in the repository for further assistance. diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index 344c44581f..ed10ee29e4 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -66,7 +66,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" diff --git a/cluster/expected/multi-validator/expected.json b/cluster/expected/multi-validator/expected.json index 73a63b17af..a652179e7e 100644 --- a/cluster/expected/multi-validator/expected.json +++ b/cluster/expected/multi-validator/expected.json @@ -4767,7 +4767,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" diff --git a/cluster/expected/operator/expected.json b/cluster/expected/operator/expected.json index c0b849ffea..f475e0c7cd 100644 --- a/cluster/expected/operator/expected.json +++ b/cluster/expected/operator/expected.json @@ -501,7 +501,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" diff --git a/cluster/expected/splitwell/expected.json b/cluster/expected/splitwell/expected.json index d978b51f48..afabb893bc 100644 --- a/cluster/expected/splitwell/expected.json +++ b/cluster/expected/splitwell/expected.json @@ -517,7 +517,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" diff --git a/cluster/expected/sv-canton/expected.json b/cluster/expected/sv-canton/expected.json index 28ed6af695..439ac79415 100644 --- a/cluster/expected/sv-canton/expected.json +++ b/cluster/expected/sv-canton/expected.json @@ -4278,7 +4278,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -4320,7 +4320,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -4362,7 +4362,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -4404,7 +4404,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -4446,7 +4446,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -4488,7 +4488,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -7860,7 +7860,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -7902,7 +7902,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -7944,7 +7944,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -7986,7 +7986,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -8028,7 +8028,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -8070,7 +8070,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -10742,7 +10742,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -10784,7 +10784,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -10826,7 +10826,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -10868,7 +10868,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -10910,7 +10910,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -10952,7 +10952,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index 253a40b837..f08c1f770e 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -32,7 +32,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -1599,7 +1599,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index 7f996c98fa..c44cc1ed10 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -2502,7 +2502,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -4408,7 +4408,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -4807,7 +4807,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -6709,7 +6709,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -7177,7 +7177,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" diff --git a/cluster/expected/validator-runbook/expected.json b/cluster/expected/validator-runbook/expected.json index 2d29b242f6..9a9c59dce5 100644 --- a/cluster/expected/validator-runbook/expected.json +++ b/cluster/expected/validator-runbook/expected.json @@ -32,7 +32,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" @@ -404,7 +404,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" diff --git a/cluster/expected/validator1/expected.json b/cluster/expected/validator1/expected.json index df7f996b96..900e4880f3 100644 --- a/cluster/expected/validator1/expected.json +++ b/cluster/expected/validator1/expected.json @@ -553,7 +553,7 @@ "stringData": { "4dabf18193072939515e22adb298388d": "1b47061264138c4ac30d75fd1eb44270", "value": { - ".dockerconfigjson": "{\"auths\":{\"digitalasset-canton-enterprise-docker.jfrog.io\":{\"auth\":\"YXJ0X3VzZXI6czNjcjN0\",\"username\":\"art_user\",\"password\":\"s3cr3t\"},\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" + ".dockerconfigjson": "{\"auths\":{\"us-central1-docker.pkg.dev\":{\"auth\":\"X2pzb25fa2V5OnsidHlwZSI6InNlcnZpY2VfYWNjb3VudCIsInByb2plY3RfaWQiOiJmYWtlLXByb2plY3QiLCJwcml2YXRlX2tleV9pZCI6ImZha2VfaWQiLCJwcml2YXRlX2tleSI6Ii0tLS0tQkVHSU4gUFJJVkFURSBLRVktLS0tLVxuZmFrZVxuLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLVxuIiwiY2xpZW50X2VtYWlsIjoiZmFrZUBmYWtlLXByb2plY3QuaWFtLmdzZXJ2aWNlYWNjb3VudC5jb20iLCJjbGllbnRfaWQiOiJmYWtlLWNsaWVudC1pZCIsImF1dGhfdXJpIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tL28vb2F1dGgyL2F1dGgiLCJ0b2tlbl91cmkiOiJodHRwczovL29hdXRoMi5nb29nbGVhcGlzLmNvbS90b2tlbiIsImF1dGhfcHJvdmlkZXJfeDUwOV9jZXJ0X3VybCI6Imh0dHBzOi8vd3d3Lmdvb2dsZWFwaXMuY29tL29hdXRoMi92MS9jZXJ0cyIsImNsaWVudF94NTA5X2NlcnRfdXJsIjoiaHR0cHM6Ly93d3cuZ29vZ2xlYXBpcy5jb20vcm9ib3QvdjEvbWV0YWRhdGEveDUwOS9mYWtlJTQwZmFrZS1wcm9qZWN0LmlhbS5nc2VydmljZWFjY291bnQuY29tIiwidW5pdmVyc2VfZG9tYWluIjoiZ29vZ2xlYXBpcy5jb20ifQ==\",\"username\":\"_json_key\",\"password\":\"{\\\"type\\\":\\\"service_account\\\",\\\"project_id\\\":\\\"fake-project\\\",\\\"private_key_id\\\":\\\"fake_id\\\",\\\"private_key\\\":\\\"-----BEGIN PRIVATE KEY-----\\\\nfake\\\\n-----END PRIVATE KEY-----\\\\n\\\",\\\"client_email\\\":\\\"fake@fake-project.iam.gserviceaccount.com\\\",\\\"client_id\\\":\\\"fake-client-id\\\",\\\"auth_uri\\\":\\\"https://accounts.google.com/o/oauth2/auth\\\",\\\"token_uri\\\":\\\"https://oauth2.googleapis.com/token\\\",\\\"auth_provider_x509_cert_url\\\":\\\"https://www.googleapis.com/oauth2/v1/certs\\\",\\\"client_x509_cert_url\\\":\\\"https://www.googleapis.com/robot/v1/metadata/x509/fake%40fake-project.iam.gserviceaccount.com\\\",\\\"universe_domain\\\":\\\"googleapis.com\\\"}\"}}}" } }, "type": "kubernetes.io/dockerconfigjson" diff --git a/cluster/images/cometbft/Dockerfile b/cluster/images/cometbft/Dockerfile index 22b79437ab..635a5301c2 100644 --- a/cluster/images/cometbft/Dockerfile +++ b/cluster/images/cometbft/Dockerfile @@ -3,10 +3,10 @@ ARG cometbft_version ARG cometbft_sha -FROM digitalasset-canton-enterprise-docker.jfrog.io/cometbft-canton-network:$cometbft_version@sha256:$cometbft_sha +FROM europe-docker.pkg.dev/da-images/public/docker/cometbft-canton-network:$cometbft_version@sha256:$cometbft_sha ARG cometbft_version -LABEL org.opencontainers.image.base.name="digitalasset-canton-enterprise-docker.jfrog.io/cometbft-canton-network:$cometbft_version" +LABEL org.opencontainers.image.base.name="europe-docker.pkg.dev/da-images/public/docker/cometbft-canton-network:$cometbft_version" COPY configure-state-sync.sh /cometbft/ RUN chmod +x /cometbft/configure-state-sync.sh diff --git a/cluster/images/splice-test-cometbft/Dockerfile b/cluster/images/splice-test-cometbft/Dockerfile index 5e6fcbf8cd..b3a8c1e4c2 100644 --- a/cluster/images/splice-test-cometbft/Dockerfile +++ b/cluster/images/splice-test-cometbft/Dockerfile @@ -1,10 +1,10 @@ # tag should match the version in nix/cometbft-driver-sources.json ARG cometbft_version ARG cometbft_sha -FROM digitalasset-canton-enterprise-docker.jfrog.io/cometbft-canton-network:$cometbft_version@sha256:$cometbft_sha +FROM europe-docker.pkg.dev/da-images/public/docker/cometbft-canton-network:$cometbft_version@sha256:$cometbft_sha ARG cometbft_version -LABEL org.opencontainers.image.base.name="digitalasset-canton-enterprise-docker.jfrog.io/cometbft-canton-network:$cometbft_version" +LABEL org.opencontainers.image.base.name="europe-docker.pkg.dev/da-images/public/docker/cometbft-canton-network:$cometbft_version" # Copy the configuration files COPY configs / diff --git a/cluster/pulumi/common/src/dockerConfig.ts b/cluster/pulumi/common/src/dockerConfig.ts index bd17145059..92e5128782 100644 --- a/cluster/pulumi/common/src/dockerConfig.ts +++ b/cluster/pulumi/common/src/dockerConfig.ts @@ -17,21 +17,14 @@ export class DockerConfig { private jsonConfig: pulumi.Output; private constructor() { - const jfrogCreds = DockerConfig.fetchCredentialsFromSecret('artifactory-keys'); const googleCreds = DockerConfig.fetchGoogleCredentialsFromSecret( 'us-central1-artifact-reader-key' ); - this.jsonConfig = pulumi.all([jfrogCreds, googleCreds]).apply(([jfrog, google]) => { - const artifactoryAuth = DockerConfig.toAuthField(jfrog); + this.jsonConfig = googleCreds.apply(google => { const googleAuth = DockerConfig.toAuthField(google); const conf = Buffer.from( JSON.stringify({ auths: { - 'digitalasset-canton-enterprise-docker.jfrog.io': { - auth: artifactoryAuth, - username: jfrog.username, - password: jfrog.password, - }, 'us-central1-docker.pkg.dev': { auth: googleAuth, username: google.username, diff --git a/cluster/pulumi/common/src/dump-config-common.ts b/cluster/pulumi/common/src/dump-config-common.ts index ad80aafe42..0fd783a4b2 100644 --- a/cluster/pulumi/common/src/dump-config-common.ts +++ b/cluster/pulumi/common/src/dump-config-common.ts @@ -320,15 +320,6 @@ export async function initDumpConfig({ ...args.inputs, secretData, }; - } else if (args.inputs.secret == 'artifactory-keys') { - const secretData = JSON.stringify({ - username: 'art_user', - password: 's3cr3t', - }); - return { - ...args.inputs, - secretData, - }; } else if (args.inputs.secret == 'us-central1-artifact-reader-key') { const secretData = JSON.stringify({ type: 'service_account', diff --git a/cluster/pulumi/gha/src/github.ts b/cluster/pulumi/gha/src/github.ts index ea7d33d673..a8fb74956f 100644 --- a/cluster/pulumi/gha/src/github.ts +++ b/cluster/pulumi/gha/src/github.ts @@ -51,28 +51,6 @@ export function installGithubRepo(repo: string): void { owner: ghaConfig.githubOrg, }); - // A bit ugly that we reuse this straight from DockerConfig, but we plan to - // retire artifactory altogether soon, so we don't bother cleaning this up. - const creds = DockerConfig.fetchCredentialsFromSecret('artifactory-keys'); - new github.ActionsVariable( - `artifactory-user-${repo}`, - { - repository: repo, - variableName: 'ARTIFACTORY_USER', - value: creds.apply(creds => creds.username), - }, - { provider: orgProvider } - ); - new github.ActionsSecret( - `artifactory-password-${repo}`, - { - repository: repo, - secretName: 'ARTIFACTORY_PASSWORD', - value: creds.apply(creds => creds.password), - }, - { provider: orgProvider } - ); - const auth0TestsManagementApi = getSecretVersionOutput({ secret: 'auth0-tests-management-api' }); new github.ActionsSecret( `auth0-tests-management-api-client-id-${repo}`, diff --git a/cluster/pulumi/sv-runbook/dump-config.ts b/cluster/pulumi/sv-runbook/dump-config.ts index eabc59091e..5cf462114e 100644 --- a/cluster/pulumi/sv-runbook/dump-config.ts +++ b/cluster/pulumi/sv-runbook/dump-config.ts @@ -9,11 +9,6 @@ import { async function main() { await initDumpConfig(); - /* eslint-disable no-process-env */ - process.env.ARTIFACTORY_USER = 'artie'; - /* eslint-disable no-process-env */ - process.env.ARTIFACTORY_PASSWORD = 's3cr3t'; - const installNode = await import('./src/installNode'); const secrets = new SecretsFixtureMap(); // Need to import this directly to avoid initializing any configs before the mocks are initialized From be0af6ea8315ecd7ea020ff6f25f21c1b70a9241 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Perek?= Date: Mon, 17 Aug 2026 12:33:53 +0200 Subject: [PATCH 245/329] Expose vote creation metrics (#6780) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Perek --- .../splice/metrics/MetricsDocs.scala | 2 + .../automation/SvDsoAutomationService.scala | 7 ++ .../VoteRequestMetricsTrigger.scala | 113 ++++++++++++++++++ .../VoteRequestMetricsTriggerTest.scala | 35 ++++++ docs/src/release_notes_upcoming.rst | 7 ++ test-full-class-names-non-integration.log | 1 + 6 files changed, 165 insertions(+) create mode 100644 apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/VoteRequestMetricsTrigger.scala create mode 100644 apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/sv/automation/VoteRequestMetricsTriggerTest.scala diff --git a/apps/metrics-docs/src/main/scala/org/lfdecentralizedtrust/splice/metrics/MetricsDocs.scala b/apps/metrics-docs/src/main/scala/org/lfdecentralizedtrust/splice/metrics/MetricsDocs.scala index b3c708f35c..ba957e162b 100644 --- a/apps/metrics-docs/src/main/scala/org/lfdecentralizedtrust/splice/metrics/MetricsDocs.scala +++ b/apps/metrics-docs/src/main/scala/org/lfdecentralizedtrust/splice/metrics/MetricsDocs.scala @@ -22,6 +22,7 @@ import org.lfdecentralizedtrust.splice.sv.automation.{ AmuletPriceMetricsTrigger, ReportSvStatusMetricsExportTrigger, RewardMetricsTrigger, + VoteRequestMetricsTrigger, } import org.lfdecentralizedtrust.splice.sv.store.db.DbSvDsoStoreMetrics import org.lfdecentralizedtrust.splice.store.{HistoryMetrics, StoreMetrics} @@ -112,6 +113,7 @@ object MetricsDocs { ) new AmuletPriceMetricsTrigger.AmuletPriceMetrics(generator) new RewardMetricsTrigger.RewardMetrics(generator) + new VoteRequestMetricsTrigger.VoteRequestMetrics(generator) new ProcessRewardsTriggerBase.ProcessRewardsMetrics(generator, true) new CalculateRewardsTriggerBase.CalculateRewardsMetrics(generator, true) new SummarizingMiningRoundTrigger.SummarizingMiningRoundMetrics(generator) diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/SvDsoAutomationService.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/SvDsoAutomationService.scala index 356bc9ffd2..bbc32e5252 100644 --- a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/SvDsoAutomationService.scala +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/SvDsoAutomationService.scala @@ -597,6 +597,12 @@ class SvDsoAutomationService( dsoStore, ) ) + registerTrigger( + new VoteRequestMetricsTrigger( + triggerContext, + dsoStore, + ) + ) registerTrigger( new RewardMetricsTrigger( triggerContext, @@ -765,6 +771,7 @@ object SvDsoAutomationService extends AutomationServiceCompanion { aTrigger[FollowAmuletConversionRateFeedTrigger], aTrigger[CopyVotesTrigger], aTrigger[AmuletPriceMetricsTrigger], + aTrigger[VoteRequestMetricsTrigger], aTrigger[RewardMetricsTrigger], aTrigger[CreateBootstrapExternalPartyConfigStateInstructionTrigger], aTrigger[LsuTrigger], diff --git a/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/VoteRequestMetricsTrigger.scala b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/VoteRequestMetricsTrigger.scala new file mode 100644 index 0000000000..c7f761dcb2 --- /dev/null +++ b/apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/automation/VoteRequestMetricsTrigger.scala @@ -0,0 +1,113 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.sv.automation + +import com.daml.metrics.api.MetricHandle.{Gauge, LabeledMetricsFactory} +import com.daml.metrics.api.MetricQualification.Saturation +import com.daml.metrics.api.{MetricInfo, MetricName, MetricsContext} +import com.digitalasset.canton.lifecycle.{AsyncOrSyncCloseable, SyncCloseable} +import com.digitalasset.canton.tracing.TraceContext +import io.opentelemetry.api.trace.Tracer +import org.apache.pekko.stream.Materializer +import org.lfdecentralizedtrust.splice.automation.{PollingTrigger, TriggerContext} +import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.VoteRequest +import org.lfdecentralizedtrust.splice.environment.SpliceMetrics +import org.lfdecentralizedtrust.splice.store.PageLimit +import org.lfdecentralizedtrust.splice.sv.automation.VoteRequestMetricsTrigger.{ + VoteRequestMetrics, + countByState, +} +import org.lfdecentralizedtrust.splice.sv.store.SvDsoStore +import org.lfdecentralizedtrust.splice.util.Contract + +import scala.concurrent.{ExecutionContext, Future} +import scala.jdk.CollectionConverters.* + +class VoteRequestMetricsTrigger( + override protected val context: TriggerContext, + dsoStore: SvDsoStore, +)(implicit + override val ec: ExecutionContext, + override val tracer: Tracer, + val mat: Materializer, +) extends PollingTrigger { + + private val voteRequestMetrics = new VoteRequestMetrics(context.metricsFactory) + private val svParty = dsoStore.key.svParty.toProtoPrimitive + + override def performWorkIfAvailable()(implicit traceContext: TraceContext): Future[Boolean] = + for { + voteRequests <- dsoStore.listVoteRequests() + readyToCloseContracts <- dsoStore.listVoteRequestsReadyToBeClosed( + context.clock.now, + PageLimit.Max, + )(traceContext) + } yield { + val counts = countByState( + voteRequests, + readyToCloseContracts.map(_.contractId).toSet, + svParty, + ) + voteRequestMetrics.actionNeeded.updateValue(counts.actionNeeded) + voteRequestMetrics.inProgress.updateValue(counts.inProgress) + voteRequestMetrics.readyToClose.updateValue(counts.readyToClose) + false + } + + override def closeAsync(): Seq[AsyncOrSyncCloseable] = super + .closeAsync() + .appended(SyncCloseable("vote request metrics", voteRequestMetrics.close())) +} + +object VoteRequestMetricsTrigger { + + case class VoteRequestCounts(actionNeeded: Long, inProgress: Long, readyToClose: Long) + + def countByState( + voteRequests: Seq[Contract[VoteRequest.ContractId, VoteRequest]], + readyToCloseCids: Set[VoteRequest.ContractId], + svParty: String, + ): VoteRequestCounts = { + val (readyToClose, open) = + voteRequests.partition(request => readyToCloseCids.contains(request.contractId)) + val (inProgress, actionNeeded) = + open.partition(_.payload.votes.values().asScala.exists(_.sv == svParty)) + VoteRequestCounts( + actionNeeded = actionNeeded.size.toLong, + inProgress = inProgress.size.toLong, + readyToClose = readyToClose.size.toLong, + ) + } + + case class VoteRequestMetrics(metricsFactory: LabeledMetricsFactory) extends AutoCloseable { + + private val name: MetricName = + SpliceMetrics.MetricsPrefix :+ "sv_vote_requests" :+ "active" + + private def stateGauge(state: String): Gauge[Long] = + metricsFactory.gauge( + MetricInfo( + name, + "The number of active vote requests, split by their state relative to this SV", + Saturation, + "The state label is one of: " + + "action_needed (the request is open for voting and this SV has not voted yet), " + + "in_progress (the request is open for voting and this SV has voted), " + + "ready_to_close (the request fulfills the conditions for the closing automation " + + "to close it, e.g. its voting deadline has passed).", + ), + 0L, + )(MetricsContext.Empty.withExtraLabels("state" -> state)) + + val actionNeeded: Gauge[Long] = stateGauge("action_needed") + val inProgress: Gauge[Long] = stateGauge("in_progress") + val readyToClose: Gauge[Long] = stateGauge("ready_to_close") + + override def close(): Unit = { + actionNeeded.close() + inProgress.close() + readyToClose.close() + } + } +} diff --git a/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/sv/automation/VoteRequestMetricsTriggerTest.scala b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/sv/automation/VoteRequestMetricsTriggerTest.scala new file mode 100644 index 0000000000..49a8d07bec --- /dev/null +++ b/apps/sv/src/test/scala/org/lfdecentralizedtrust/splice/sv/automation/VoteRequestMetricsTriggerTest.scala @@ -0,0 +1,35 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.sv.automation + +import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.{Reason, Vote} +import org.lfdecentralizedtrust.splice.store.StoreTestBase +import org.lfdecentralizedtrust.splice.sv.automation.VoteRequestMetricsTrigger.VoteRequestCounts + +import java.util.Optional + +class VoteRequestMetricsTriggerTest extends StoreTestBase { + + "countByState" should { + "partition vote requests by their state relative to the SV" in { + val sv = userParty(1) + val otherSv = userParty(2) + def vote(svParty: com.digitalasset.canton.topology.PartyId): Vote = + new Vote(svParty.toProtoPrimitive, true, new Reason("", ""), Optional.empty()) + + val notVoted = voteRequest(requester = otherSv, votes = Seq(vote(otherSv))) + val voted = voteRequest(requester = otherSv, votes = Seq(vote(otherSv), vote(sv))) + val ownRequest = voteRequest(requester = sv, votes = Seq(vote(sv))) + // ready to close counts as such regardless of whether the SV has voted + val readyVoted = voteRequest(requester = sv, votes = Seq(vote(sv))) + val readyNotVoted = voteRequest(requester = otherSv, votes = Seq(vote(otherSv))) + + VoteRequestMetricsTrigger.countByState( + Seq(notVoted, voted, ownRequest, readyVoted, readyNotVoted), + Set(readyVoted.contractId, readyNotVoted.contractId), + sv.toProtoPrimitive, + ) shouldBe VoteRequestCounts(actionNeeded = 1, inProgress = 2, readyToClose = 2) + } + } +} diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 0cebf148c0..f623c41987 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -13,3 +13,10 @@ release-notes:: Upcoming ``serve-app-activity-records-and-traffic`` configuration options have been removed. App activity records and sequencer traffic are now always ingested, and app activity is always computed and served on the corresponding HTTP endpoints. + + - SV App + + - The SV app now exposes a ``splice.sv_vote_requests.active`` metric counting the active + vote requests by their state relative to the SV (``action_needed``, ``in_progress``, + ``ready_to_close``), allowing SV operators to alert on vote proposals that require + their vote. diff --git a/test-full-class-names-non-integration.log b/test-full-class-names-non-integration.log index 374d4d63f4..3c16adfde2 100644 --- a/test-full-class-names-non-integration.log +++ b/test-full-class-names-non-integration.log @@ -47,6 +47,7 @@ org.lfdecentralizedtrust.splice.store.db.DbSvSvStoreTest org.lfdecentralizedtrust.splice.store.db.DbTcsStoreTest org.lfdecentralizedtrust.splice.store.db.DbUserWalletStoreTest org.lfdecentralizedtrust.splice.store.db.SpliceStorageMultiLockTest +org.lfdecentralizedtrust.splice.sv.automation.VoteRequestMetricsTriggerTest org.lfdecentralizedtrust.splice.sv.cometbft.CometBftNodeTest org.lfdecentralizedtrust.splice.sv.cometbft.CometBftRequestSignerTest org.lfdecentralizedtrust.splice.sv.onboarding.SequencerBftPeerReconcilerSpec From a1eaeea57820abc9d5c20142628d6540538a0350 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Mon, 17 Aug 2026 12:52:52 +0200 Subject: [PATCH 246/329] apply the envoy flow control to the sequencer bft endpoint (#6784) * apply the envoy flow control to the sequencer bft endpoint [static] Signed-off-by: Nicu Reut --- cluster/expected/infra/expected.json | 125 +++++++++++++++++---------- cluster/pulumi/infra/src/istio.ts | 93 ++++++++++---------- 2 files changed, 121 insertions(+), 97 deletions(-) diff --git a/cluster/expected/infra/expected.json b/cluster/expected/infra/expected.json index 2a019811d7..67ee7f62fa 100644 --- a/cluster/expected/infra/expected.json +++ b/cluster/expected/infra/expected.json @@ -2314,6 +2314,35 @@ } } } + }, + { + "applyTo": "CLUSTER", + "match": { + "cluster": { + "portNumber": 5010 + } + }, + "patch": { + "operation": "MERGE", + "value": { + "typed_extension_protocol_options": { + "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": { + "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions", + "use_downstream_protocol_config": { + "http2_protocol_options": { + "connection_keepalive": { + "interval": "30s", + "timeout": "5s" + }, + "initial_connection_window_size": 52428800, + "initial_stream_window_size": 524288 + }, + "http_protocol_options": {} + } + } + } + } + } } ] } @@ -2373,13 +2402,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2407,13 +2436,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2441,13 +2470,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2475,13 +2504,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2545,13 +2574,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2579,13 +2608,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2613,13 +2642,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2647,13 +2676,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2681,13 +2710,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2715,13 +2744,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2749,13 +2778,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { @@ -2783,13 +2812,13 @@ "trafficPolicy": { "connectionPool": { "http": { - "http1MaxPendingRequests": 10000, - "http2MaxRequests": 10000, - "maxConcurrentStreams": 10000, + "http1MaxPendingRequests": 20000, + "http2MaxRequests": 20000, + "maxConcurrentStreams": 20000, "maxRequestsPerConnection": 0 }, "tcp": { - "maxConnections": 10000 + "maxConnections": 20000 } }, "loadBalancer": { diff --git a/cluster/pulumi/infra/src/istio.ts b/cluster/pulumi/infra/src/istio.ts index e6179dd52a..3e9ecbb17a 100644 --- a/cluster/pulumi/infra/src/istio.ts +++ b/cluster/pulumi/infra/src/istio.ts @@ -840,13 +840,13 @@ function configureSequencerHighPerformanceGrpcDestinationRule( }, connectionPool: { http: { - http1MaxPendingRequests: 10000, - http2MaxRequests: 10000, - maxConcurrentStreams: 10000, + http1MaxPendingRequests: 20000, + http2MaxRequests: 20000, + maxConcurrentStreams: 20000, maxRequestsPerConnection: 0, }, tcp: { - maxConnections: 10000, + maxConnections: 20000, }, }, }, @@ -854,6 +854,10 @@ function configureSequencerHighPerformanceGrpcDestinationRule( }); } +// Ports of the http2 servers that we apply the upstream flow control config to: +// the sequencer public API and the sequencer BFT P2P API. +const sequencerFlowControlUpstreamPorts = [5008, 5010]; + // Istio proxies lots of client connections over relatively few connections. If one of the client connections gets stuck // (e.g. because the client died) buffers will fill up and eventually istio will stop sending connection-level window updates // to the sequencer and trigger netty flow control. This surfaces as requests that send back response headers but then nothing else until the client times out. @@ -866,6 +870,40 @@ function configureSequencerHighPerformanceGrpcDestinationRule( function configureSequencerFlowControl( ingressNs: k8s.core.v1.Namespace ): k8s.apiextensions.CustomResource { + const http2ProtocolOptions = { + initial_stream_window_size: infraConfig.istio.sequencerFlowControl.initialStreamWindowSize, + initial_connection_window_size: + infraConfig.istio.sequencerFlowControl.initialConnectionWindowSize, + connection_keepalive: { + interval: '30s', + timeout: '5s', + }, + }; + // istio -> upstream (aka sequencer) + const upstreamPatch = (portNumber: number) => ({ + applyTo: 'CLUSTER', + match: { + cluster: { + portNumber, + // Ideally we would just apply it everywhere. But doing it without this portNumber breaks http1 configs. In theory there is `auto_config` which should do the right thing but then it doesn't apply it at all anymore. + // So for now we just apply it to the sequencer ports (public API and BFT P2P) which are the only externally exposed http2 servers so the only things where this really should matter in practice. + }, + }, + patch: { + operation: 'MERGE', + value: { + typed_extension_protocol_options: { + 'envoy.extensions.upstreams.http.v3.HttpProtocolOptions': { + '@type': 'type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions', + use_downstream_protocol_config: { + http_protocol_options: {}, + http2_protocol_options: http2ProtocolOptions, + }, + }, + }, + }, + }, + }); return new k8s.apiextensions.CustomResource('sequencer-flow-control', { apiVersion: 'networking.istio.io/v1alpha3', kind: 'EnvoyFilter', @@ -894,55 +932,12 @@ function configureSequencerFlowControl( typed_config: { '@type': 'type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager', - http2_protocol_options: { - initial_stream_window_size: - infraConfig.istio.sequencerFlowControl.initialStreamWindowSize, - initial_connection_window_size: - infraConfig.istio.sequencerFlowControl.initialConnectionWindowSize, - connection_keepalive: { - interval: '30s', - timeout: '5s', - }, - }, - }, - }, - }, - }, - { - // istio -> upstream (aka sequencer) - applyTo: 'CLUSTER', - match: { - cluster: { - portNumber: 5008, - // Ideally we would just apply it everywhere. But doing it without this portNumber breaks http1 configs. In theory there is `auto_config` which should do the right thing but then it doesn't apply it at all anymore. - // So for now we just apply it to the sequencer which is the only externally exposed http2 server so the only thing where this really should matter in practice. - }, - }, - patch: { - operation: 'MERGE', - value: { - typed_extension_protocol_options: { - 'envoy.extensions.upstreams.http.v3.HttpProtocolOptions': { - '@type': - 'type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions', - use_downstream_protocol_config: { - http_protocol_options: {}, - http2_protocol_options: { - initial_stream_window_size: - infraConfig.istio.sequencerFlowControl.initialStreamWindowSize, - initial_connection_window_size: - infraConfig.istio.sequencerFlowControl.initialConnectionWindowSize, - connection_keepalive: { - interval: '30s', - timeout: '5s', - }, - }, - }, - }, + http2_protocol_options: http2ProtocolOptions, }, }, }, }, + ...sequencerFlowControlUpstreamPorts.map(upstreamPatch), ], }, }); From ec3d767c3e7585c9e8930b63a17d618e7e24749f Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Mon, 17 Aug 2026 14:03:40 +0200 Subject: [PATCH 247/329] Extend splice rate limits (#6764) * Extend splice rate limits Valid for scan and sv app Add global rate limiter (previosly we rate limited only each individual operation), which is enabled by default. Add the ability to rate limit also per ip for each oepration (disabled by default). The global rate limiter has this option enabled by default. Extend the rate limiter to check a longer interval (60s). The previous behavior was checking only the last 1s, this is still in place and works as a burst limiter, allowing for shorter burts but the longer interval enforces a lower limit for the configured interval. [ci] Signed-off-by: Nicu Reut --- .../splice/config/SpliceConfig.scala | 24 +- .../test/resources/include/scans/_scan.conf | 9 +- .../src/test/resources/include/svs/_sv.conf | 9 +- .../tests/ScanIntegrationTest.scala | 7 +- ...dCliTestDataTimeBasedIntegrationTest.scala | 6 +- .../concurrent/BurstyRateLimiterFactory.java | 27 + .../splice/config/RateLimitersConfig.scala | 36 +- .../config/SpliceParametersConfig.scala | 6 +- .../splice/http/ClientIpDirectives.scala | 64 ++ .../splice/http/HttpRateLimiter.scala | 148 ++++- .../splice/util/SpliceRateLimiter.scala | 226 ++++++- .../splice/http/HttpRateLimiterTest.scala | 554 ++++++++++++++++++ .../splice/util/SpliceRateLimiterTest.scala | 183 +++++- cluster/images/scan-app/app.conf | 14 +- cluster/images/sv-app/app.conf | 14 +- docs/src/release_notes_upcoming.rst | 34 ++ test-full-class-names-non-integration.log | 1 + 17 files changed, 1304 insertions(+), 58 deletions(-) create mode 100644 apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java create mode 100644 apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala create mode 100644 apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala index 57f81e1ce7..7d76d69eb7 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala @@ -33,7 +33,11 @@ import org.lfdecentralizedtrust.splice.splitwell.config.{ import org.lfdecentralizedtrust.splice.sv.config.* import org.lfdecentralizedtrust.splice.sv.SvAppClientConfig import org.lfdecentralizedtrust.splice.sv.config.SvOnboardingConfig.FoundDso -import org.lfdecentralizedtrust.splice.util.{Codec, SpliceRateLimitConfig} +import org.lfdecentralizedtrust.splice.util.{ + Codec, + PerAttributeRateLimitConfig, + SpliceRateLimitConfig, +} import org.lfdecentralizedtrust.splice.validator.config.* import org.lfdecentralizedtrust.splice.wallet.config.{ AppRewardBeneficiaryConfig, @@ -427,10 +431,15 @@ object SpliceConfig { deriveReader[SpliceCachingConfigs] implicit val spliceParametersConfig: ConfigReader[SpliceParametersConfig] = deriveReader[SpliceParametersConfig] + implicit val spliceRateLimiterSimpleConfig: ConfigReader[SpliceRateLimitConfig.Simple] = + deriveReader[SpliceRateLimitConfig.Simple] + implicit val clientIpRateLimitConfig: ConfigReader[PerAttributeRateLimitConfig] = + deriveReader[PerAttributeRateLimitConfig] + implicit val spliceRateLimiterWithPerClientIpConfig + : ConfigReader[SpliceRateLimitConfig.WithPerClientIp] = + deriveReader[SpliceRateLimitConfig.WithPerClientIp] implicit val rateLimitersConfig: ConfigReader[RateLimitersConfig] = deriveReader[RateLimitersConfig] - implicit val spliceRateLimiterConfig: ConfigReader[SpliceRateLimitConfig] = - deriveReader[SpliceRateLimitConfig] implicit val enabledFeaturesConfigReader: ConfigReader[EnabledFeaturesConfig] = deriveReader[EnabledFeaturesConfig] implicit val splicePostgresConfigReader: ConfigReader[SplicePostgresConfig] = @@ -948,10 +957,15 @@ object SpliceConfig { implicit val spliceParametersConfig: ConfigWriter[SpliceParametersConfig] = deriveWriter[SpliceParametersConfig] + implicit val spliceRateLimiterSimpleConfig: ConfigWriter[SpliceRateLimitConfig.Simple] = + deriveWriter[SpliceRateLimitConfig.Simple] + implicit val clientIpRateLimitConfig: ConfigWriter[PerAttributeRateLimitConfig] = + deriveWriter[PerAttributeRateLimitConfig] + implicit val spliceRateLimiterWithPerClientIpConfig + : ConfigWriter[SpliceRateLimitConfig.WithPerClientIp] = + deriveWriter[SpliceRateLimitConfig.WithPerClientIp] implicit val rateLimitersConfig: ConfigWriter[RateLimitersConfig] = deriveWriter[RateLimitersConfig] - implicit val spliceRateLimiterConfig: ConfigWriter[SpliceRateLimitConfig] = - deriveWriter[SpliceRateLimitConfig] implicit val enabledFeaturesConfigWriter: ConfigWriter[EnabledFeaturesConfig] = deriveWriter[EnabledFeaturesConfig] diff --git a/apps/app/src/test/resources/include/scans/_scan.conf b/apps/app/src/test/resources/include/scans/_scan.conf index 147faf2366..a937c58c69 100644 --- a/apps/app/src/test/resources/include/scans/_scan.conf +++ b/apps/app/src/test/resources/include/scans/_scan.conf @@ -31,8 +31,13 @@ getAcsSnapshot = 1 minute } rate-limiting { - default { - rate-per-second = 200 + # the global limiter caps the total request rate across all operations; disabled in + # tests so that it doesn't interfere with the per-operation limits exercised here. + global { + enabled = false + per-client-ip { + enabled = false + } } rate-limiters { getAcsSnapshot.rate-per-second = 2 diff --git a/apps/app/src/test/resources/include/svs/_sv.conf b/apps/app/src/test/resources/include/svs/_sv.conf index a08a5c59c4..54433a96a5 100644 --- a/apps/app/src/test/resources/include/svs/_sv.conf +++ b/apps/app/src/test/resources/include/svs/_sv.conf @@ -64,8 +64,13 @@ onboardSvPartyMigrationAuthorize = 5 minutes } rate-limiting { - default { - rate-per-second = 200 + # the global limiter caps the total request rate across all operations; disabled in + # tests so that it doesn't interfere with the per-operation limits exercised here. + global { + enabled = false + per-client-ip { + enabled = false + } } rate-limiters { prepareValidatorOnboarding.rate-per-second = 2 diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala index a934e1469c..0bbcb18f67 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala @@ -76,9 +76,10 @@ class ScanIntegrationTest // used for the rate limit test rateLimiting = config.parameters.rateLimiting.copy( rateLimiters = - config.parameters.rateLimiting.rateLimiters + ("listAnsEntries" -> SpliceRateLimitConfig( - ratePerSecond = 5 - )) + config.parameters.rateLimiting.rateLimiters + ("listAnsEntries" -> SpliceRateLimitConfig + .WithPerClientIp( + ratePerSecond = 5 + )) ), ), ) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala index a086e43182..bd8adda202 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala @@ -144,7 +144,11 @@ class TokenStandardCliTestDataTimeBasedIntegrationTest updateAllScanAppConfigs_(config => config.copy(parameters = config.parameters.copy(rateLimiting = - RateLimitersConfig(SpliceRateLimitConfig(enabled = false, 1), Map.empty) + RateLimitersConfig( + default = SpliceRateLimitConfig.WithPerClientIp(enabled = false, 1), + rateLimiters = Map.empty, + global = SpliceRateLimitConfig.WithPerClientIp(enabled = false, 1), + ) ) ) )(config) diff --git a/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java b/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java new file mode 100644 index 0000000000..cd94bd5790 --- /dev/null +++ b/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java @@ -0,0 +1,27 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.google.common.util.concurrent; + +/** + * Shim that constructs a Guava {@link RateLimiter} backed by {@code SmoothBursty} with a custom + * maximum burst duration. It lives in this package because the relevant {@code + * SmoothRateLimiter.SmoothBursty} constructor is package-private. + */ +public final class BurstyRateLimiterFactory { + + private BurstyRateLimiterFactory() { + } + + /** + * Creates a bursty {@link RateLimiter} that sustains {@code permitsPerSecond} on average while + * allowing bursts of up to {@code permitsPerSecond * maxBurstSeconds} permits after idle periods. + */ + public static RateLimiter create(double permitsPerSecond, double maxBurstSeconds) { + RateLimiter rateLimiter = + new SmoothRateLimiter.SmoothBursty( + RateLimiter.SleepingStopwatch.createFromSystemTimer(), maxBurstSeconds); + rateLimiter.setRate(permitsPerSecond); + return rateLimiter; + } +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala index 971de2ae5f..115652ace6 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala @@ -3,11 +3,39 @@ package org.lfdecentralizedtrust.splice.config -import org.lfdecentralizedtrust.splice.util.SpliceRateLimitConfig +import org.lfdecentralizedtrust.splice.util.{PerAttributeRateLimitConfig, SpliceRateLimitConfig} case class RateLimitersConfig( - default: SpliceRateLimitConfig, - rateLimiters: Map[String, SpliceRateLimitConfig], + /** Overall rate limiter applied per operation. Used when there is no operation-specific override + * in `rateLimiters`. The embedded `perClientIp` limiter is disabled by default; enable it to + * additionally limit per client IP. + */ + default: SpliceRateLimitConfig.WithPerClientIp, + /** Per-operation overrides of the overall `default` rate limiter. */ + rateLimiters: Map[String, SpliceRateLimitConfig.WithPerClientIp], + global: SpliceRateLimitConfig.WithPerClientIp = RateLimitersConfig.DefaultGlobal, + /** Name of the HTTP header set by a trusted reverse proxy that carries the real client IP. This header must be set - and any + * client-provided value overwritten - by infrastructure the client cannot bypass, otherwise it + * can be spoofed. When present and parseable as an IP literal it takes precedence over the + * client-controlled `X-Forwarded-For`/`X-Real-Ip` headers. Set to an empty string to disable + * trusting a proxy header and only rely on `X-Forwarded-For`/`X-Real-Ip`/the remote address. + */ + trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, ) { - def forRateLimiter(name: String): SpliceRateLimitConfig = rateLimiters.getOrElse(name, default) + def forRateLimiter(name: String): SpliceRateLimitConfig.WithPerClientIp = + rateLimiters.getOrElse(name, default) +} + +object RateLimitersConfig { + + /** Header set by the Envoy sidecar/ingress (Istio) to the trusted external client address that + * Envoy computes from its trusted-hops configuration. + */ + val DefaultTrustedClientIpHeader: String = "x-envoy-external-address" + + private val DefaultGlobal: SpliceRateLimitConfig.WithPerClientIp = + SpliceRateLimitConfig.WithPerClientIp( + ratePerSecond = 200, + perClientIp = PerAttributeRateLimitConfig(), + ) } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala index 42c842df85..a26b3e1a03 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala @@ -17,8 +17,10 @@ final case class SpliceParametersConfig( // Do not define any defaults on the class containing the `SpliceParametersConfig` as they'll be overwritten. // Do it instead on the app.conf file in `cluster/images/${the_app}/app.conf` customTimeouts: Map[String, NonNegativeFiniteDuration] = Map.empty, - rateLimiting: RateLimitersConfig = - RateLimitersConfig(SpliceRateLimitConfig(enabled = true, ratePerSecond = 200), Map.empty), + rateLimiting: RateLimitersConfig = RateLimitersConfig( + SpliceRateLimitConfig.WithPerClientIp(ratePerSecond = 200), + Map.empty, + ), // Configuration for the circuit breaker for ledger API command submissions. circuitBreakers: CircuitBreakersConfig = CircuitBreakersConfig(), enabledFeatures: EnabledFeaturesConfig = EnabledFeaturesConfig(), diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala new file mode 100644 index 0000000000..c4f620f484 --- /dev/null +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala @@ -0,0 +1,64 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.http + +import org.apache.pekko.http.scaladsl.model.headers.{`X-Forwarded-For`, `X-Real-Ip`} +import org.apache.pekko.http.scaladsl.model.RemoteAddress +import org.apache.pekko.http.scaladsl.server.Directive1 +import org.apache.pekko.http.scaladsl.server.Directives.* + +object ClientIpDirectives { + + /** Extracts the address of the client the request originated from, if it can be determined. + * + * The address is taken from the first of the following sources that yields an address: + * 1. the `trustedClientIpHeader` (if configured and parseable as an IP literal), which is set + * by a trusted reverse proxy and hence cannot be spoofed by the client, + * 1. the client-controlled `X-Forwarded-For` header, + * 1. the client-controlled `X-Real-Ip` header, + * + * @param trustedClientIpHeader + * name of the header set by a trusted reverse proxy, matched case-insensitively. An empty name + * disables trusting a proxy header. + */ + def extractClientIp(trustedClientIpHeader: String): Directive1[Option[RemoteAddress]] = + firstDefined( + trustedClientIp(trustedClientIpHeader), + forwardedForClientIp, + realIpClientIp, + ) + + private def trustedClientIp(headerName: String): Directive1[Option[RemoteAddress]] = { + val trimmedHeaderName = headerName.trim + if (trimmedHeaderName.isEmpty) provide(None) + else + // matched case-insensitively (and locale independently) as the configured header name is not + // required to be lowercase + optionalHeaderValueByName(trimmedHeaderName).map(_.flatMap(parseIpLiteral)) + } + + private val forwardedForClientIp: Directive1[Option[RemoteAddress]] = { + optionalHeaderValuePF { case `X-Forwarded-For`(Seq(address, _*)) => address } + } + + private val realIpClientIp: Directive1[Option[RemoteAddress]] = + optionalHeaderValuePF { case `X-Real-Ip`(address) => address } + + /** The value of the first directive that extracts a defined value, [[None]] if there is none. */ + private def firstDefined[A]( + directives: Directive1[Option[A]]* + ): Directive1[Option[A]] = + directives.foldRight(provide(Option.empty[A])) { (directive, fallback) => + directive.flatMap { + case defined @ Some(_) => provide(defined) + case None => fallback + } + } + + private def parseIpLiteral(value: String): Option[RemoteAddress] = + `X-Real-Ip` + .parseFromValueString(value.trim) + .toOption + .map(_.address) +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala index 2dc1c8ad1f..520150944f 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala @@ -6,11 +6,16 @@ package org.lfdecentralizedtrust.splice.http import com.daml.metrics.api.MetricHandle.LabeledMetricsFactory import com.daml.metrics.api.MetricsContext import com.digitalasset.canton.logging.TracedLogger -import org.apache.pekko.http.scaladsl.model.{HttpEntity, StatusCodes} -import org.apache.pekko.http.scaladsl.server.Directive0 +import org.apache.pekko.http.scaladsl.model.{HttpEntity, RemoteAddress, StatusCodes} +import org.apache.pekko.http.scaladsl.server.{Directive0, Directive1} import org.lfdecentralizedtrust.splice.config.RateLimitersConfig -import org.lfdecentralizedtrust.splice.util.{SpliceRateLimitMetrics, SpliceRateLimiter} +import org.lfdecentralizedtrust.splice.util.{ + PerAttributeRateLimiter, + SpliceRateLimiter, + SpliceRateLimitMetrics, +} +import java.net.{Inet6Address, InetAddress} import java.time.Instant class HttpRateLimiter( @@ -19,12 +24,20 @@ class HttpRateLimiter( logger: TracedLogger, ) extends AutoCloseable { - // need to cache it as the pekko reoutes get evaluated for each request - private val rateLimiters = scala.collection.concurrent.TrieMap[String, SpliceRateLimiter]() + // need to cache it as the pekko routes get evaluated for each request + // keyed by (service, operation) as the same operation name can be used by multiple services + private val rateLimiters = + scala.collection.concurrent.TrieMap[ + (String, String), + (SpliceRateLimiter, PerAttributeRateLimiter), + ]() private val metrics = scala.collection.concurrent.TrieMap[String, SpliceRateLimitMetrics]() - def withRateLimit(service: String)(operation: String): Directive0 = { - val rateLimiterMetrics = metrics.getOrElseUpdate( + private val trustedClientIpHeader: String = + config.trustedClientIpHeader.trim + + private def metricsFor(service: String): SpliceRateLimitMetrics = + metrics.getOrElseUpdate( service, SpliceRateLimitMetrics(metricsFactory, logger)( MetricsContext( @@ -32,22 +45,79 @@ class HttpRateLimiter( ) ), ) - val rateLimiter = rateLimiters.getOrElseUpdate( - operation, + + // the rate limiter has a cold start, to avoid the first request being rejected + // we enforce the rate limit only after 1 second + private def enforceAfter = Instant.now().plusSeconds(1) + + private val globalRateLimiter: (SpliceRateLimiter, PerAttributeRateLimiter) = { + val globalMetrics = metricsFor(HttpRateLimiter.GlobalService) + ( new SpliceRateLimiter( - operation, - config.forRateLimiter(operation), - rateLimiterMetrics, - // the rate limiter has a cold start, to avoid the first request being rejected - // we enforce the rate limit only after 1 second - Instant.now().plusSeconds(1), + HttpRateLimiter.GlobalLimiter, + config.global, + globalMetrics, + enforceAfter, + ), + new PerAttributeRateLimiter( + HttpRateLimiter.GlobalLimiter, + HttpRateLimiter.ClientIpAttribute, + config.global, + config.global.perClientIp, + globalMetrics, + enforceAfter, + logger, ), ) + } + + private def operationRateLimiter( + service: String, + operation: String, + ): (SpliceRateLimiter, PerAttributeRateLimiter) = + rateLimiters.getOrElseUpdate( + (service, operation), { + val rateLimiterMetrics = metricsFor(service) + val operationConfig = config.forRateLimiter(operation) + ( + new SpliceRateLimiter( + operation, + operationConfig, + rateLimiterMetrics, + enforceAfter, + ), + new PerAttributeRateLimiter( + operation, + HttpRateLimiter.ClientIpAttribute, + operationConfig, + operationConfig.perClientIp, + rateLimiterMetrics, + enforceAfter, + logger, + ), + ) + }, + ) + + def withRateLimit(service: String)(operation: String): Directive0 = { + val (globalLimiter, globalClientIpLimiter) = globalRateLimiter + val (operationLimiter, operationClientIpLimiter) = operationRateLimiter(service, operation) import org.apache.pekko.http.scaladsl.server.Directives.* - extractRequestContext.flatMap { _ => - if (rateLimiter.markRun()) { + HttpRateLimiter.extractClientIpKey(trustedClientIpHeader).flatMap { clientIp => + // The per client IP limiters are checked first (and `&&` short-circuits) so that a request + // rejected because of its own client IP does not consume budget from the shared overall + // limiters. Otherwise a single abusive client could exhaust the overall limits and thereby + // deny service to all other clients. + // Within each of those two groups the narrower per operation limiter is checked before the + // global one, so that a request rejected for its operation does not consume global budget. + val allowed = + operationClientIpLimiter.markRun(clientIp) && + globalClientIpLimiter.markRun(clientIp) && + operationLimiter.markRun() && + globalLimiter.markRun() + if (allowed) { pass } else { complete( @@ -62,3 +132,47 @@ class HttpRateLimiter( def close(): Unit = metrics.view.values.foreach(_.close()) } + +object HttpRateLimiter { + + private val ClientIpAttribute = "client_ip" + + private[splice] val GlobalLimiter = "global" + private[splice] val GlobalService = "global" + + private[splice] def extractClientIpKey( + trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader + ): Directive1[Option[String]] = + ClientIpDirectives + .extractClientIp(trustedClientIpHeader) + .map(_.collect { case RemoteAddress.IP(ip, _) => rateLimitKey(ip) }) + + /** Single clients are typically assigned a whole IPv6 /64 (or larger) network, so limiting per + * full IPv6 address would allow a single client to trivially bypass the per client IP limit. + * IPv6 addresses are therefore grouped by their /64 prefix, IPv4 addresses are used as is. + */ + private def rateLimitKey(address: InetAddress): String = address match { + case ipv6: Inet6Address => + val bytes = ipv6.getAddress + ipv4Mapped(bytes) match { + // connections accepted on a dual stack socket can report IPv4 clients as IPv4-mapped IPv6 + // addresses (e.g. ::ffff:192.0.2.1), those must use the same key as the plain IPv4 address + case Some(ipv4) => ipv4.getHostAddress + case None => + // zero out the lower 64 bits (the interface identifier), keeping the /64 network prefix + // note that this also drops any scope/zone id, which is not meaningful for rate limiting + val prefix = bytes.take(8) ++ Array.fill[Byte](8)(0) + s"${InetAddress.getByAddress(prefix).getHostAddress}/64" + } + case ip => ip.getHostAddress + } + + /** ::ffff:0:0/96, see [[https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2]] */ + private val Ipv4MappedPrefix: Seq[Byte] = + Seq.fill[Byte](10)(0) ++ Seq[Byte](0xff.toByte, 0xff.toByte) + + private def ipv4Mapped(bytes: Array[Byte]): Option[InetAddress] = + Option.when(bytes.length == 16 && bytes.startsWith(Ipv4MappedPrefix))( + InetAddress.getByAddress(bytes.drop(12)) + ) +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala index 6d6f66019d..6e41c4eab5 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala @@ -3,22 +3,31 @@ package org.lfdecentralizedtrust.splice.util +import com.daml.metrics.CacheMetrics import com.daml.metrics.api.MetricHandle.LabeledMetricsFactory import com.daml.metrics.api.MetricQualification.Saturation import com.daml.metrics.api.{MetricHandle, MetricInfo, MetricsContext} +import com.digitalasset.canton.caching.{CaffeineCache, ConcurrentCache} import com.digitalasset.canton.discard.Implicits.DiscardOps import com.digitalasset.canton.lifecycle.LifeCycle import com.digitalasset.canton.logging.TracedLogger -import com.google.common.util.concurrent.RateLimiter +import com.digitalasset.canton.tracing.TraceContext +import com.github.benmanes.caffeine.cache.{Caffeine, RemovalCause, RemovalListener} +import com.google.common.util.concurrent.{BurstyRateLimiterFactory, RateLimiter} import org.lfdecentralizedtrust.splice.environment.SpliceMetrics -import java.time.Instant +import java.time.{Duration, Instant} import java.util import java.util.Collections +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicLong import scala.concurrent.Future import scala.jdk.CollectionConverters.CollectionHasAsScala -case class SpliceRateLimitMetrics(otelFactory: LabeledMetricsFactory, logger: TracedLogger)(implicit +case class SpliceRateLimitMetrics( + otelFactory: LabeledMetricsFactory, + private val logger: TracedLogger, +)(implicit mc: MetricsContext ) extends AutoCloseable { @@ -53,39 +62,122 @@ case class SpliceRateLimitMetrics(otelFactory: LabeledMetricsFactory, logger: Tr } -case class SpliceRateLimitConfig( +sealed trait SpliceRateLimitConfig { + + def enabled: Boolean + + def ratePerSecond: Double + + def sustainedRatePerSecond: Option[Double] + + def sustainedWindowSeconds: Long +} + +object SpliceRateLimitConfig { + + final case class Simple( + enabled: Boolean = true, + ratePerSecond: Double, + sustainedRatePerSecond: Option[Double] = None, + sustainedWindowSeconds: Long = SpliceRateLimiter.DefaultSustainedWindowSeconds, + ) extends SpliceRateLimitConfig + + final case class WithPerClientIp( + enabled: Boolean = true, + ratePerSecond: Double, + sustainedRatePerSecond: Option[Double] = None, + sustainedWindowSeconds: Long = SpliceRateLimiter.DefaultSustainedWindowSeconds, + perClientIp: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig.Disabled, + ) extends SpliceRateLimitConfig + + def apply( + enabled: Boolean = true, + ratePerSecond: Double, + sustainedRatePerSecond: Option[Double] = None, + sustainedWindowSeconds: Long = SpliceRateLimiter.DefaultSustainedWindowSeconds, + ): Simple = + Simple(enabled, ratePerSecond, sustainedRatePerSecond, sustainedWindowSeconds) +} + +case class PerAttributeRateLimitConfig( enabled: Boolean = true, - ratePerSecond: Double, -) + limit: SpliceRateLimitConfig.Simple = PerAttributeRateLimitConfig.DefaultLimit, + maxAttributeValues: Long = 10000, +) { + + def rateLimitFor(overall: SpliceRateLimitConfig): SpliceRateLimitConfig.Simple = + limit.copy(enabled = enabled && limit.enabled && overall.enabled) +} +object PerAttributeRateLimitConfig { + val DefaultLimit: SpliceRateLimitConfig.Simple = SpliceRateLimitConfig(ratePerSecond = 10) + val Disabled: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig(enabled = false) +} + +object SpliceRateLimiter { + + val GlobalLimiterType = "global" + val PerAttributeLimiterType = "per-attribute" + val UnknownAttributeLimiterType = "unknown-attribute" + + val DefaultSustainedWindowSeconds: Long = 60 + + private[util] def sustainedWindow(config: SpliceRateLimitConfig): Duration = + Duration.ofSeconds(Math.max(1L, config.sustainedWindowSeconds)) +} + +// noinspection UnstableApiUsage class SpliceRateLimiter( name: String, config: SpliceRateLimitConfig, metrics: SpliceRateLimitMetrics, enforceAfter: Instant = Instant.now(), + limiterType: String = SpliceRateLimiter.GlobalLimiterType, + extraLabels: Map[String, String] = Map.empty, + // must be disabled for the per-attribute limiters as they'd all report the same value + // and would explode the number of registered gauges + reportMaxLimit: Boolean = true, ) { - // noinspection UnstableApiUsage - private val limiter = RateLimiter.create(config.ratePerSecond) - // lazy to ensure metrics get registered only if the limiter is actually used - private lazy val rateLimiter = { - metrics - .recordMaxLimit(config.ratePerSecond)( - MetricsContext("limiter" -> name) + private val metricsContext = MetricsContext( + extraLabels ++ Map("limiter" -> name, "limiter_type" -> limiterType) + ) + + // The limiters are created eagerly so that they are already "warm" (i.e. have accumulated their + // burst budget) by the time the limit starts being enforced. They are only created for enabled + // limiters: a disabled limiter is never consulted and its configured rate might not even be a + // valid guava rate (e.g. 0). + // enforces the per-second burst limit (checked over a 1s window) + private val limiter: Option[RateLimiter] = + Option.when(config.enabled)(RateLimiter.create(config.ratePerSecond)) + // enforces the sustained limit over the sustained window, while still allowing bursts within its budget. + private val sustainedLimiter: Option[RateLimiter] = + Option + .when(config.enabled)(config.sustainedRatePerSecond) + .flatten + .map( + BurstyRateLimiterFactory + .create(_, SpliceRateLimiter.sustainedWindow(config).toSeconds.toDouble) ) + // lazy to ensure metrics get registered only if the limiter is actually used + private lazy val rateLimiter: Option[RateLimiter] = { + if (reportMaxLimit) { + metrics + .recordMaxLimit(config.ratePerSecond)(metricsContext) + } limiter } def markRun(): Boolean = { if (config.enabled && Instant.now().isAfter(enforceAfter)) { - val canRun = rateLimiter.tryAcquire() + val canRun = rateLimiter.forall(_.tryAcquire()) && sustainedLimiter.forall(_.tryAcquire()) if (canRun) { metrics.meter.mark()( - MetricsContext("result" -> "accepted", "limiter" -> name) + metricsContext.merge(MetricsContext("result" -> "accepted")) ) } else { metrics.meter.mark()( - MetricsContext("result" -> "rejected", "limiter" -> name) + metricsContext.merge(MetricsContext("result" -> "rejected")) ) } canRun @@ -105,3 +197,105 @@ class SpliceRateLimiter( } } + +class PerAttributeRateLimiter( + name: String, + attribute: String, + config: SpliceRateLimitConfig, + attributeConfig: PerAttributeRateLimitConfig, + metrics: SpliceRateLimitMetrics, + enforceAfter: Instant = Instant.now(), + logger: TracedLogger, +) { + + private val perAttributeConfig = attributeConfig.rateLimitFor(config) + private val isEnabled = perAttributeConfig.enabled && perAttributeConfig.ratePerSecond > 0 + private val attributeLabel = Map("limiter_attribute" -> attribute) + + // evictions by size can happen for every single request (e.g. when a large number of distinct + // attribute values is seen), so the warning is throttled to avoid flooding the logs + private val lastSizeEvictionWarning = + new AtomicLong(System.nanoTime() - PerAttributeRateLimiter.EvictionWarningIntervalNanos) + + private val evictionListener: RemovalListener[String, SpliceRateLimiter] = + (key: String, _: SpliceRateLimiter, cause: RemovalCause) => { + if (cause == RemovalCause.SIZE) { + implicit val tc: TraceContext = TraceContext.empty + val message = + s"Rate limiter cache for $name (attribute '$attribute') exceeded its maximum size of " + + s"${attributeConfig.maxAttributeValues}; evicting the rate limiter for attribute value '$key'. " + + "Its rate limiting state is lost. Consider increasing max-attribute-values." + val now = System.nanoTime() + val last = lastSizeEvictionWarning.get() + if ( + now - last >= PerAttributeRateLimiter.EvictionWarningIntervalNanos && lastSizeEvictionWarning + .compareAndSet(last, now) + ) { + logger.warn(message) + } else { + logger.debug(message) + } + } + } + + // lazy so that neither the cache nor its metrics are created if the limiter is disabled + private lazy val cache: ConcurrentCache[String, SpliceRateLimiter] = CaffeineCache[ + String, + SpliceRateLimiter, + ]( + Caffeine + .newBuilder() + .maximumSize(attributeConfig.maxAttributeValues) + // Evict limiters that have not been used for a full sustained rate limiting window (the bucket + // size of the interval rate limiter): after that time an idle limiter would have refilled its + // budget anyway, so dropping it does not change the enforced rate. + .expireAfterAccess(SpliceRateLimiter.sustainedWindow(perAttributeConfig)) + .evictionListener(evictionListener), + Some(new CacheMetrics(s"$name-$attribute-rate-limiter", metrics.otelFactory)), + ) + + private lazy val defaultRateLimiter = new SpliceRateLimiter( + name, + perAttributeConfig, + metrics, + enforceAfter, + limiterType = SpliceRateLimiter.UnknownAttributeLimiterType, + extraLabels = attributeLabel, + ) + + private lazy val reportedMaxLimit: Unit = + metrics.recordMaxLimit(perAttributeConfig.ratePerSecond)( + MetricsContext( + attributeLabel ++ Map( + "limiter" -> name, + "limiter_type" -> SpliceRateLimiter.PerAttributeLimiterType, + ) + ) + ) + + def markRun(attributeValue: Option[String]): Boolean = + if (isEnabled) attributeValue.fold(defaultRateLimiter)(limiterFor).markRun() + else true + + private def limiterFor(attributeValue: String): SpliceRateLimiter = { + reportedMaxLimit + cache.getOrAcquire( + attributeValue, + (_: String) => + new SpliceRateLimiter( + name, + perAttributeConfig, + metrics, + enforceAfter, + limiterType = SpliceRateLimiter.PerAttributeLimiterType, + extraLabels = attributeLabel, + reportMaxLimit = false, + ), + ) + } +} + +object PerAttributeRateLimiter { + + private val EvictionWarningIntervalNanos: Long = TimeUnit.MINUTES.toNanos(1) +} diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala new file mode 100644 index 0000000000..410bf7e4d1 --- /dev/null +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala @@ -0,0 +1,554 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.http + +import com.daml.metrics.api.testing.InMemoryMetricsFactory +import com.digitalasset.canton.BaseTest +import com.digitalasset.canton.concurrent.Threading +import org.apache.pekko.http.scaladsl.model.headers.{RawHeader, `X-Forwarded-For`, `X-Real-Ip`} +import org.apache.pekko.http.scaladsl.model.{ + AttributeKeys, + HttpRequest, + RemoteAddress, + StatusCode, + StatusCodes, +} +import org.apache.pekko.http.scaladsl.server.Directives.* +import org.apache.pekko.http.scaladsl.server.Route +import org.apache.pekko.http.scaladsl.testkit.ScalatestRouteTest +import org.lfdecentralizedtrust.splice.config.RateLimitersConfig +import org.lfdecentralizedtrust.splice.util.{ + PerAttributeRateLimitConfig, + SpliceRateLimitConfig, + SpliceRateLimiter, +} +import org.scalatest.wordspec.AnyWordSpec + +import java.net.{Inet6Address, InetAddress} + +class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteTest { + + "clientIp" should { + + "prefer the trusted X-Envoy-External-Address over spoofable headers" in { + clientIp( + HttpRequest() + .withHeaders( + RawHeader("X-Envoy-External-Address", "4.4.4.4"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), + ) + .withAttributes( + Map( + AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3")) + ) + ) + ) should be(Some("4.4.4.4")) + } + + "ignore a non-IP X-Envoy-External-Address and fall back to the next header" in { + clientIp( + HttpRequest() + .withHeaders( + RawHeader("X-Envoy-External-Address", "evil.example.com"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + ) + ) should be(Some("1.1.1.1")) + } + + "use a configurable trusted proxy header" in { + clientIp( + HttpRequest() + .withHeaders( + RawHeader("X-Trusted-Client-Ip", "4.4.4.4"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + ), + trustedClientIpHeader = "x-trusted-client-ip", + ) should be(Some("4.4.4.4")) + } + + "match the trusted proxy header case-insensitively" in { + clientIp( + HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "4.4.4.4")), + trustedClientIpHeader = "X-Envoy-External-Address", + ) should be(Some("4.4.4.4")) + } + + "not trust any proxy header when the trusted header is disabled" in { + clientIp( + HttpRequest().withHeaders( + RawHeader("X-Envoy-External-Address", "4.4.4.4"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + ), + trustedClientIpHeader = "", + ) should be(Some("1.1.1.1")) + } + + "prefer X-Forwarded-For" in { + clientIp( + HttpRequest() + .withHeaders( + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), + ) + .withAttributes( + Map( + AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3")) + ) + ) + ) should be(Some("1.1.1.1")) + } + + "fall back to X-Real-Ip" in { + clientIp( + HttpRequest().withHeaders(`X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2")))) + ) should be(Some("2.2.2.2")) + } + + "not use the remote address of the transport connection" in { + // the remote address is not exposed by the server, so it must not be relied upon + clientIp( + HttpRequest().withAttributes( + Map(AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3"))) + ) + ) should be(None) + } + + "return None if no IP can be determined" in { + clientIp(HttpRequest()) should be(None) + clientIp( + HttpRequest().withHeaders(RawHeader("X-Forwarded-For", "not-an-ip")) + ) should be(None) + } + } + + "the client IP used for rate limiting" should { + + "use the full address for IPv4 clients" in { + clientIpOf("1.2.3.4") should be(Some("1.2.3.4")) + } + + "group IPv6 clients by their /64 prefix" in { + // the lower 64 bits (the interface identifier) are freely chosen by the client + clientIpOf("2001:db8:0:1:1:2:3:4") should be(Some("2001:db8:0:1:0:0:0:0/64")) + clientIpOf("2001:db8:0:1:ffff:ffff:ffff:ffff") should be( + clientIpOf("2001:db8:0:1:1:2:3:4") + ) + clientIpOf("2001:db8:0:1::") should be(clientIpOf("2001:db8:0:1:1:2:3:4")) + } + + "not group IPv6 clients of different /64 networks" in { + clientIpOf("2001:db8:0:2:1:2:3:4") should not be clientIpOf("2001:db8:0:1:1:2:3:4") + clientIpOf("2001:db9:0:1:1:2:3:4") should not be clientIpOf("2001:db8:0:1:1:2:3:4") + } + + "ignore the zone id of IPv6 addresses" in { + val scoped = Inet6Address.getByAddress( + null, + InetAddress.getByName("fe80::1:2:3:4").getAddress, + 7, + ) + // sanity check that the zone id is part of the address representation + scoped.getHostAddress should be("fe80:0:0:0:1:2:3:4%7") + clientIpOf(scoped) should be(Some("fe80:0:0:0:0:0:0:0/64")) + } + + "use the IPv4 address for IPv4-mapped IPv6 clients" in { + // dual stack sockets can report IPv4 clients as ::ffff:a.b.c.d, those must not end up in a + // single /64 bucket shared by all IPv4 clients + val ipv4Mapped = Inet6Address.getByAddress( + null, + Array[Byte](0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff.toByte, 0xff.toByte, 1, 2, 3, 4), + 0, + ) + ipv4Mapped shouldBe a[Inet6Address] + clientIpOf(ipv4Mapped) should be(Some("1.2.3.4")) + clientIpOf(ipv4Mapped) should be(clientIpOf("1.2.3.4")) + clientIpOf( + Inet6Address.getByAddress( + null, + Array[Byte](0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff.toByte, 0xff.toByte, 4, 3, 2, 1), + 0, + ) + ) should not be clientIpOf(ipv4Mapped) + } + + "apply the same grouping to all client IP sources" in { + val expected = Some("2001:db8:0:1:0:0:0:0/64") + val address = RemoteAddress(InetAddress.getByName("2001:db8:0:1:1:2:3:4")) + clientIp( + HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "2001:db8:0:1:1:2:3:4")) + ) should be(expected) + clientIp( + HttpRequest().withHeaders(`X-Forwarded-For`(address)) + ) should be(expected) + clientIp( + HttpRequest().withHeaders(`X-Real-Ip`(address)) + ) should be(expected) + } + } + + "the http rate limiter" should { + + "reject requests of a client IP over the global per client IP limit" in { + // the global per client IP limiter is enabled by default + withRoutes( + globalPerClientIp = perClientIp(1) + )("testOperation") { routes => + val route = routes("testOperation") + val results = (1 to 20).map(_ => call(route, ip = Some("1.1.1.1"))) + // 1 request per second per client IP => the burst gets rejected + results.count(_ == StatusCodes.OK) should be(1) + results.count(_ == StatusCodes.TooManyRequests) should be(19) + } + } + + "not reject requests of other client IPs" in { + withRoutes( + globalPerClientIp = perClientIp(1) + )("testOperation") { routes => + val route = routes("testOperation") + (1 to 20) + .map(_ => call(route, ip = Some("1.1.1.1"))) + .count(_ == StatusCodes.TooManyRequests) should be > 0 + call(route, ip = Some("2.2.2.2")) should be(StatusCodes.OK) + } + } + + "limit IPv6 clients of the same /64 network together" in { + withRoutes( + globalPerClientIp = perClientIp(1) + )("testOperation") { routes => + val route = routes("testOperation") + call(route, ip = Some("2001:db8:0:1:1:2:3:4")) should be(StatusCodes.OK) + // a different address of the same /64 shares the limiter, so it is rejected + call(route, ip = Some("2001:db8:0:1:ffff:ffff:ffff:ffff")) should be( + StatusCodes.TooManyRequests + ) + // a different /64 is a different client + call(route, ip = Some("2001:db8:0:2:1:2:3:4")) should be(StatusCodes.OK) + } + } + + "fall back to the default limiter if no client IP is known" in { + withRoutes( + globalPerClientIp = perClientIp(1) + )("testOperation") { routes => + val route = routes("testOperation") + call(route, ip = None) should be(StatusCodes.OK) + (1 to 20) + .map(_ => call(route, ip = None)) + .count(_ == StatusCodes.TooManyRequests) should be > 0 + // a request with a client IP uses a different limiter + call(route, ip = Some("1.1.1.1")) should be(StatusCodes.OK) + } + } + + "apply the global per client IP limiter across operations" in { + // the same client IP is limited regardless of the operation + withRoutes( + globalPerClientIp = perClientIp(1) + )("operationA", "operationB") { routes => + call(routes("operationA"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) + call(routes("operationB"), ip = Some("1.1.1.1")) should be(StatusCodes.TooManyRequests) + } + } + + "apply the global overall limiter across operations" in { + withRoutes( + global = SpliceRateLimitConfig(ratePerSecond = 1), + globalPerClientIp = PerAttributeRateLimitConfig.Disabled, + )("operationA", "operationB") { routes => + // exhaust the global budget via operationA + (1 to 20).map(_ => call(routes("operationA"), ip = Some("1.1.1.1"))) + // the global limiter ignores the operation and the client IP, so operationB is rejected too + call(routes("operationB"), ip = Some("2.2.2.2")) should be(StatusCodes.TooManyRequests) + } + } + + "not apply the per operation client IP limiter by default" in { + // no per client IP limiting configured for operations => requests from a single IP are only + // bounded by the (high) overall limiters + withRoutes()("testOperation") { routes => + val route = routes("testOperation") + (1 to 20).map(_ => call(route, ip = Some("1.1.1.1"))) should contain only StatusCodes.OK + } + } + + "apply the per operation client IP limiter when enabled for an operation" in { + withRoutes( + perClientIpOverrides = Map("limitedOperation" -> perClientIp(1)) + )("limitedOperation", "otherOperation") { routes => + val results = + (1 to 20).map(_ => call(routes("limitedOperation"), ip = Some("1.1.1.1"))) + results.count(_ == StatusCodes.OK) should be(1) + // a different operation is not affected by the per operation client IP limiter + call(routes("otherOperation"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) + } + } + + "apply the per operation overall limiter" in { + withRoutes( + rateLimiters = Map("limitedOperation" -> SpliceRateLimitConfig(ratePerSecond = 1)) + )("limitedOperation", "otherOperation") { routes => + val results = (1 to 20).map(_ => call(routes("limitedOperation"), ip = Some("1.1.1.1"))) + results.count(_ == StatusCodes.TooManyRequests) should be > 0 + // a different operation uses a separate overall limiter and is not affected + call(routes("otherOperation"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) + } + } + + "use separate per operation limiters for equally named operations of different services" in { + val rateLimiter = new HttpRateLimiter( + RateLimitersConfig( + default = withPerClientIp( + SpliceRateLimitConfig(ratePerSecond = 1), + PerAttributeRateLimitConfig.Disabled, + ), + rateLimiters = Map.empty, + global = withPerClientIp( + SpliceRateLimitConfig(ratePerSecond = 1000), + PerAttributeRateLimitConfig.Disabled, + ), + ), + new InMemoryMetricsFactory(), + loggerFactory.getTracedLogger(classOf[HttpRateLimiterTest]), + ) + try { + val routeV1 = + rateLimiter.withRateLimit("serviceV1")("sharedOperation")(complete(StatusCodes.OK)) + val routeV2 = + rateLimiter.withRateLimit("serviceV2")("sharedOperation")(complete(StatusCodes.OK)) + // the rate limiter only starts enforcing 1 second after it got created + Threading.sleep(1100) + (1 to 20) + .map(_ => call(routeV1, ip = Some("1.1.1.1"))) + .count(_ == StatusCodes.TooManyRequests) should be > 0 + call(routeV2, ip = Some("1.1.1.1")) should be(StatusCodes.OK) + } finally { + rateLimiter.close() + } + } + } + + "the order in which the rate limiters are applied" should { + + // A limiter only records (and thereby only consumes budget for) the requests that actually + // reach it, as the limiters are combined with a short-circuiting `&&`. The tests below send a + // burst of requests that is rejected by one limiter and assert that the limiters which must be + // applied later only saw the requests that were accepted by the rejecting one. + val Burst = 20 + val Rejecting = SpliceRateLimitConfig(ratePerSecond = 1) + // high enough to never reject, so that the recorded requests are exactly the ones that got here + val Downstream = SpliceRateLimitConfig(ratePerSecond = 1000) + val PerAttribute = SpliceRateLimiter.PerAttributeLimiterType + val Overall = SpliceRateLimiter.GlobalLimiterType + + // Sends a burst of requests from a single client IP and returns how many were accepted. + def burst(fixture: HttpRateLimiterTest.Fixture, operation: String): Long = { + val results = (1 to Burst).map(_ => call(fixture(operation), ip = Some("1.1.1.1"))) + results.count(_ == StatusCodes.TooManyRequests) should be > 0 + results.count(_ == StatusCodes.OK).toLong + } + + def onlySawAcceptedRequests( + fixture: HttpRateLimiterTest.Fixture, + accepted: Long, + )(limiters: (String, String)*) = + forEvery(limiters) { case (limiter, limiterType) => + withClue(s"requests seen by the $limiterType limiter '$limiter': ") { + fixture.requestsSeenBy(limiter, limiterType) should be(accepted) + fixture.requestsRejectedBy(limiter, limiterType) should be(0L) + } + } + + "apply the per operation client IP limiter before the overall limiters" in { + withRoutes( + rateLimiters = Map("limitedOperation" -> Downstream), + global = Downstream, + perClientIpOverrides = Map("limitedOperation" -> perClientIp(1)), + )("limitedOperation") { fixture => + val accepted = burst(fixture, "limitedOperation") + onlySawAcceptedRequests(fixture, accepted)( + "limitedOperation" -> Overall, + HttpRateLimiter.GlobalLimiter -> Overall, + ) + } + } + + "apply the global per client IP limiter before the overall limiters" in { + withRoutes( + rateLimiters = Map("limitedOperation" -> Downstream), + global = Downstream, + globalPerClientIp = perClientIp(1), + )("limitedOperation") { fixture => + val accepted = burst(fixture, "limitedOperation") + onlySawAcceptedRequests(fixture, accepted)( + "limitedOperation" -> Overall, + HttpRateLimiter.GlobalLimiter -> Overall, + ) + } + } + + "apply the per operation client IP limiter before the global per client IP limiter" in { + withRoutes( + globalPerClientIp = perClientIp(1000), + perClientIpOverrides = Map("limitedOperation" -> perClientIp(1)), + )("limitedOperation") { fixture => + val accepted = burst(fixture, "limitedOperation") + onlySawAcceptedRequests(fixture, accepted)( + HttpRateLimiter.GlobalLimiter -> PerAttribute + ) + } + } + + "apply the per operation overall limiter before the global overall limiter" in { + withRoutes( + rateLimiters = Map("limitedOperation" -> Rejecting), + global = Downstream, + )("limitedOperation") { fixture => + val accepted = burst(fixture, "limitedOperation") + onlySawAcceptedRequests(fixture, accepted)( + HttpRateLimiter.GlobalLimiter -> Overall + ) + } + } + + "still reject requests that pass the per client IP limiters but exceed an overall limit" in { + withRoutes( + global = SpliceRateLimitConfig(ratePerSecond = 2), + globalPerClientIp = perClientIp(1000), + perClientIpOverrides = Map("testOperation" -> perClientIp(1000)), + )("testOperation") { fixture => + // every request is below both per client IP limits, but the overall global limit applies + val results = (1 to Burst).map(i => call(fixture("testOperation"), ip = Some(s"1.1.1.$i"))) + results.count(_ == StatusCodes.OK) should be < Burst + results.count(_ == StatusCodes.TooManyRequests) should be > 0 + } + } + } + + private def perClientIp(ratePerSecond: Double): PerAttributeRateLimitConfig = + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = ratePerSecond)) + + private def clientIp( + request: HttpRequest, + trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, + ): Option[String] = { + val route = HttpRateLimiter.extractClientIpKey(trustedClientIpHeader) { extracted => + complete(extracted.getOrElse[String](HttpRateLimiterTest.NoClientIp)) + } + request ~> route ~> check { + status should be(StatusCodes.OK) + Some(responseAs[String]).filterNot(_ == HttpRateLimiterTest.NoClientIp) + } + } + + private def clientIpOf(ip: String): Option[String] = + clientIpOf(InetAddress.getByName(ip)) + + private def clientIpOf(ip: InetAddress): Option[String] = + clientIp( + HttpRequest().withHeaders(`X-Forwarded-For`(RemoteAddress(ip))) + ) + + private def call(route: Route, ip: Option[String]): StatusCode = { + val request = ip match { + case Some(value) => + Get("/") ~> addHeader(`X-Forwarded-For`(RemoteAddress(InetAddress.getByName(value)))) + case None => Get("/") + } + request ~> route ~> check(status) + } + + private def withRoutes[A]( + // high enough by default so that only the explicitly configured limiter kicks in + default: SpliceRateLimitConfig = SpliceRateLimitConfig(ratePerSecond = 1000), + rateLimiters: Map[String, SpliceRateLimitConfig] = Map.empty, + global: SpliceRateLimitConfig = SpliceRateLimitConfig(ratePerSecond = 1000), + globalPerClientIp: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig.Disabled, + perClientIpOverrides: Map[String, PerAttributeRateLimitConfig] = Map.empty, + )(operations: String*)(f: HttpRateLimiterTest.Fixture => A): A = { + // Any operation with a per client IP override needs its own overall limiter entry so that the + // embedded per client IP limiter is used instead of the `default` one. + val perOperationConfigs: Map[String, SpliceRateLimitConfig.WithPerClientIp] = + (rateLimiters.keySet ++ perClientIpOverrides.keySet).map { operation => + operation -> withPerClientIp( + rateLimiters.getOrElse(operation, default), + perClientIpOverrides.getOrElse(operation, PerAttributeRateLimitConfig.Disabled), + ) + }.toMap + val metricsFactory = new InMemoryMetricsFactory() + val rateLimiter = new HttpRateLimiter( + RateLimitersConfig( + default = withPerClientIp(default, PerAttributeRateLimitConfig.Disabled), + rateLimiters = perOperationConfigs, + global = withPerClientIp(global, globalPerClientIp), + ), + metricsFactory, + loggerFactory.getTracedLogger(classOf[HttpRateLimiterTest]), + ) + try { + val routes = operations.map { operation => + operation -> rateLimiter.withRateLimit("testService")(operation) { + complete(StatusCodes.OK) + } + }.toMap + // the rate limiter only starts enforcing 1 second after it got created + Threading.sleep(1100) + f(HttpRateLimiterTest.Fixture(routes, metricsFactory)) + } finally { + rateLimiter.close() + } + } + + private def withPerClientIp( + overall: SpliceRateLimitConfig, + perClientIp: PerAttributeRateLimitConfig, + ): SpliceRateLimitConfig.WithPerClientIp = + SpliceRateLimitConfig.WithPerClientIp( + enabled = overall.enabled, + ratePerSecond = overall.ratePerSecond, + sustainedRatePerSecond = overall.sustainedRatePerSecond, + sustainedWindowSeconds = overall.sustainedWindowSeconds, + perClientIp = perClientIp, + ) +} + +object HttpRateLimiterTest { + private val NoClientIp = "" + + /** The routes of the rate limited operations together with the metrics recorded by their rate + * limiters. Requests are only recorded by the limiters they actually reach, which is what allows + * asserting on the order in which the limiters are applied. + */ + private final case class Fixture( + routes: Map[String, Route], + metricsFactory: InMemoryMetricsFactory, + ) { + + def apply(operation: String): Route = routes(operation) + + /** Number of requests recorded by the given limiter, i.e. that were actually evaluated by it. */ + def requestsSeenBy(limiter: String, limiterType: String): Long = + marks(limiter, limiterType, result = None) + + /** Number of requests the given limiter rejected. */ + def requestsRejectedBy(limiter: String, limiterType: String): Long = + marks(limiter, limiterType, result = Some("rejected")) + + private def marks(limiter: String, limiterType: String, result: Option[String]): Long = + metricsFactory.metrics.meters.values + .flatMap(_.values) + .flatMap(_.markers.toSeq) + .collect { + case (context, value) + if context.labels.get("limiter").contains(limiter) && + context.labels.get("limiter_type").contains(limiterType) && + result.forall(expected => context.labels.get("result").contains(expected)) => + value.get() + } + .sum + } +} diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala index f89db4a51c..47f36979e7 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala @@ -1,3 +1,6 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + package org.lfdecentralizedtrust.splice.util import com.daml.metrics.api.MetricsContext @@ -12,6 +15,7 @@ import org.lfdecentralizedtrust.splice.admin.api.client.commands.HttpCommandExce import org.lfdecentralizedtrust.splice.util.SpliceRateLimiterTest.runRateLimited import org.scalatest.wordspec.AnyWordSpecLike +import java.time.Instant import scala.concurrent.Future import scala.concurrent.duration.DurationInt @@ -26,7 +30,7 @@ class SpliceRateLimiterTest "accept requests under limit" in { val elementsToRun = 100 - withRateLimiter { case (rateLimitMetrics, rateLimiter) => + withRateLimiter() { case (rateLimitMetrics, rateLimiter) => runThroughRateLimiter(rateLimiter, 9, elementsToRun).reduce(_ && _) shouldBe true rateLimitMetrics.meter.valueFilteredOnLabels( @@ -43,7 +47,7 @@ class SpliceRateLimiterTest } "reject requests that are over the limit" in { - withRateLimiter { case (rateLimitMetrics, rateLimiter) => + withRateLimiter() { case (rateLimitMetrics, rateLimiter) => val results = runThroughRateLimiter(rateLimiter, 100, 1000) val (accepted, rejected) = results.partition(identity) @@ -76,6 +80,152 @@ class SpliceRateLimiterTest } + "not create any limiter if disabled" in { + // a disabled limiter must not fail even for a rate that guava would reject + withRateLimiter(SpliceRateLimitConfig(enabled = false, ratePerSecond = 0)) { + case (_, rateLimiter) => + Seq.fill(100)(rateLimiter.markRun()) should contain only true + } + } + + } + + "the per attribute rate limiter" should { + + "gate the per attribute limit on the overall limiter being enabled" in { + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 42)) + .rateLimitFor(SpliceRateLimitConfig(ratePerSecond = 100)) + .ratePerSecond should be(42d) + + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 42)) + .rateLimitFor(SpliceRateLimitConfig(enabled = false, ratePerSecond = 100)) + .enabled should be(false) + + PerAttributeRateLimitConfig( + enabled = false, + limit = SpliceRateLimitConfig(ratePerSecond = 42), + ) + .rateLimitFor(SpliceRateLimitConfig(ratePerSecond = 100)) + .enabled should be(false) + + PerAttributeRateLimitConfig.Disabled + .rateLimitFor(SpliceRateLimitConfig(ratePerSecond = 100)) + .enabled should be(false) + } + + "limit each attribute value separately" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 10), + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), + ) { case (_, perAttributeRateLimiter) => + // 1 per second per attribute value, so a burst is rejected after the first request + val ip1 = Seq.fill(20)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) + ip1.count(identity) should be(1) + ip1.count(!_) should be(19) + + // a different attribute value is not affected by the limiter of the first one + perAttributeRateLimiter.markRun(Some("2.2.2.2")) should be(true) + perAttributeRateLimiter.markRun(Some("2.2.2.2")) should be(false) + } + } + + "use a single default limiter if the attribute value is unknown" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 10), + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), + ) { case (metrics, perAttributeRateLimiter) => + val results = Seq.fill(20)(perAttributeRateLimiter.markRun(None)) + results.count(identity) should be(1) + + metrics.meter.valueFilteredOnLabels( + LabelFilter("limiter", "test"), + LabelFilter("limiter_attribute", "test_attribute"), + LabelFilter("limiter_type", SpliceRateLimiter.UnknownAttributeLimiterType), + LabelFilter("result", "rejected"), + ) should be(results.count(!_)) + + // requests with a known attribute value are not affected by the default limiter + perAttributeRateLimiter.markRun(Some("1.1.1.1")) should be(true) + } + } + + "distinguish the metrics of the per attribute limiters" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 10), + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), + ) { case (metrics, perAttributeRateLimiter) => + val results = Seq.fill(20)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) + + metrics.meter.valueFilteredOnLabels( + LabelFilter("limiter", "test"), + LabelFilter("limiter_attribute", "test_attribute"), + LabelFilter("limiter_type", SpliceRateLimiter.PerAttributeLimiterType), + LabelFilter("result", "accepted"), + ) should be(results.count(identity)) + metrics.meter.valueFilteredOnLabels( + LabelFilter("limiter", "test"), + LabelFilter("limiter_attribute", "test_attribute"), + LabelFilter("limiter_type", SpliceRateLimiter.PerAttributeLimiterType), + LabelFilter("result", "rejected"), + ) should be(results.count(!_)) + // no metrics are reported for the default limiter of unknown attribute values + metrics.meter.valuesWithContext.keys + .flatMap(_.labels.get("limiter_type")) + .toSet should be(Set(SpliceRateLimiter.PerAttributeLimiterType)) + } + } + + "not limit anything if disabled" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 10), + PerAttributeRateLimitConfig.Disabled, + ) { case (_, perAttributeRateLimiter) => + Seq.fill(100)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) should contain only true + Seq.fill(100)(perAttributeRateLimiter.markRun(None)) should contain only true + } + } + + "respect the configured rate over time" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 100), + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 10)), + ) { case (_, perAttributeRateLimiter) => + // 10 per second per attribute value + val results = runRateLimited(50, 100) { + if (perAttributeRateLimiter.markRun(Some("1.1.1.1"))) Future.successful(true) + else + Future.failed( + io.grpc.Status.RESOURCE_EXHAUSTED + .withDescription("Rate limit exceeded") + .asRuntimeException() + ) + }.futureValue + // roughly 2 seconds of runtime at 10 permits per second, with some slack + results.count(identity) should (be >= 5 and be <= 40) + results.count(!_) should be > 0 + } + } + + } + + "the rate limiter with a sustained limit" should { + + "throttle to the sustained rate once the burst budget is drained" in { + withRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 1000, sustainedRatePerSecond = Some(10)) + ) { case (_, rateLimiter) => + // The per-second limit is high enough to never reject the throttled input, so the sustained + // limiter (10/s) is the binding constraint over the run. The sustained limiter starts with + // an empty burst budget (Guava SmoothBursty semantics), so throughput tracks the sustained + // rate plus a small initial allowance. + val results = runRateLimited(40, 120) { + rateLimiter.runWithLimit(Future.successful(true)) + }.futureValue + // ~3 seconds of runtime at 10 permits/s, with generous slack + results.count(identity) should (be >= 10 and be <= 60) + results.count(!_) should be > 0 + } + } } private def runThroughRateLimiter( @@ -92,13 +242,38 @@ class SpliceRateLimiterTest } futureValue } - private def withRateLimiter[A](f: (SpliceRateLimitMetrics, SpliceRateLimiter) => A): A = { + private def withRateLimiter[A]( + config: SpliceRateLimitConfig = SpliceRateLimitConfig(enabled = true, ratePerSecond = 10) + )(f: (SpliceRateLimitMetrics, SpliceRateLimiter) => A): A = { val metricsFactory = new InMemoryMetricsFactory() val rateLimitMetrics = SpliceRateLimitMetrics(metricsFactory, logger)(MetricsContext.Empty) val rateLimiter = new SpliceRateLimiter( "test", - SpliceRateLimitConfig(enabled = true, 10), + config, + rateLimitMetrics, + ) + try { + f(rateLimitMetrics, rateLimiter) + } finally { + rateLimitMetrics.close() + } + } + + private def withPerAttributeRateLimiter[A]( + config: SpliceRateLimitConfig, + attributeConfig: PerAttributeRateLimitConfig, + )(f: (SpliceRateLimitMetrics, PerAttributeRateLimiter) => A): A = { + val metricsFactory = new InMemoryMetricsFactory() + val rateLimitMetrics = SpliceRateLimitMetrics(metricsFactory, logger)(MetricsContext.Empty) + val rateLimiter = new PerAttributeRateLimiter( + "test", + "test_attribute", + config, + attributeConfig, rateLimitMetrics, + // no cold start delay in tests + Instant.now().minusSeconds(1), + logger, ) try { f(rateLimitMetrics, rateLimiter) diff --git a/cluster/images/scan-app/app.conf b/cluster/images/scan-app/app.conf index 802305d344..c78b48e7f6 100644 --- a/cluster/images/scan-app/app.conf +++ b/cluster/images/scan-app/app.conf @@ -60,7 +60,19 @@ canton { parameters { rate-limiting { default { - rate-per-second = 200 + rate-per-second = 100 + sustained-rate-per-second = 50 + } + global { + rate-per-second = 400 + sustained-rate-per-second = 200 + per-client-ip { + enabled = true + limit { + rate-per-second = 100 + sustained-rate-per-second = 50 + } + } } rate-limiters { getAcsSnapshot.rate-per-second = 20 diff --git a/cluster/images/sv-app/app.conf b/cluster/images/sv-app/app.conf index 9cd27eda2c..8b407493d4 100644 --- a/cluster/images/sv-app/app.conf +++ b/cluster/images/sv-app/app.conf @@ -107,7 +107,19 @@ canton { } rate-limiting { default { - rate-per-second = 200 + rate-per-second = 20 + sustained-rate-per-second = 10 + } + global { + rate-per-second = 100 + sustained-rate-per-second = 50 + per-client-ip { + enabled = true + limit { + rate-per-second = 20 + sustained-rate-per-second = 10 + } + } } rate-limiters { prepareValidatorOnboarding.rate-per-second = 1 diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index f623c41987..c470d903c2 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -20,3 +20,37 @@ release-notes:: Upcoming vote requests by their state relative to the SV (``action_needed``, ``in_progress``, ``ready_to_close``), allowing SV operators to alert on vote proposals that require their vote. + + - Scan & SV App + + - HTTP rate limiting has been extended with a global rate limiter applied across all + operations, optional per-client-IP rate limiting (enabled by default at the global level), + and an additional sustained rate limit enforced over a longer window on top of the existing + per-second burst limit. The client IP is taken from the trusted, non-spoofable + ``X-Envoy-External-Address`` header set by the Envoy/Istio ingress, falling back to the + client-controlled ``X-Forwarded-For``/ ``X-Real-Ip`` headers and finally the remote + address only for requests that did not pass through the ingress. These can be tuned via + the ``rate-limiting`` config keys. + + .. warning:: + + When per-client-IP rate limiting is enabled, SV operators must ensure that the client IP + used for rate limiting cannot be spoofed. Either configure + ``rate-limiting.trusted-client-ip-header`` to a trusted, non-spoofable header set by + your ingress/proxy (e.g. ``x-envoy-external-address`` for Istio deployments), or ensure + that the ``X-Forwarded-For`` header contains the actual client IP as its first value + and cannot be spoofed by clients. Otherwise, clients may bypass per-client-IP limits or + cause other clients to be throttled by forging these headers. + + - Default rate limits have been adjusted: + + - Scan app: the per-operation burst limit has been lowered from 200 to 100 requests per + second, with a new sustained limit of 50 requests per second. A new global limiter has + also been added, allowing 400 requests per second burst / 200 sustained across all + operations combined, with an embedded per-client-IP limiter allowing 100 requests per + second burst / 50 sustained. + - SV app: the per-operation burst limit has been lowered from 200 to 20 requests per + second, with a new sustained limit of 10 requests per second. A new global limiter has + also been added, allowing 100 requests per second burst / 50 sustained across all + operations combined, with an embedded per-client-IP limiter allowing 20 requests per + second burst / 10 sustained. diff --git a/test-full-class-names-non-integration.log b/test-full-class-names-non-integration.log index 3c16adfde2..b15e928caa 100644 --- a/test-full-class-names-non-integration.log +++ b/test-full-class-names-non-integration.log @@ -11,6 +11,7 @@ org.lfdecentralizedtrust.splice.environment.ActiveContractsRestartTest org.lfdecentralizedtrust.splice.environment.CommandCircuitBreakerTest org.lfdecentralizedtrust.splice.environment.CommandIdDedupTest org.lfdecentralizedtrust.splice.environment.TopologyAwarePackageVersionSupportTest +org.lfdecentralizedtrust.splice.http.HttpRateLimiterTest org.lfdecentralizedtrust.splice.http.InvalidResponseContentTest org.lfdecentralizedtrust.splice.http.NonProxyHostsTest org.lfdecentralizedtrust.splice.http.UrlValidatorTest From 0b6855f2fce554e9c91bcc282b518a22ca0dde7c Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Mon, 17 Aug 2026 13:35:52 -0400 Subject: [PATCH 248/329] Revert "Extend splice rate limits (#6764)" (#6807) This reverts commit ec3d767c3e7585c9e8930b63a17d618e7e24749f. Signed-off-by: Stephen Compall --- .../splice/config/SpliceConfig.scala | 24 +- .../test/resources/include/scans/_scan.conf | 9 +- .../src/test/resources/include/svs/_sv.conf | 9 +- .../tests/ScanIntegrationTest.scala | 7 +- ...dCliTestDataTimeBasedIntegrationTest.scala | 6 +- .../concurrent/BurstyRateLimiterFactory.java | 27 - .../splice/config/RateLimitersConfig.scala | 36 +- .../config/SpliceParametersConfig.scala | 6 +- .../splice/http/ClientIpDirectives.scala | 64 -- .../splice/http/HttpRateLimiter.scala | 148 +---- .../splice/util/SpliceRateLimiter.scala | 226 +------ .../splice/http/HttpRateLimiterTest.scala | 554 ------------------ .../splice/util/SpliceRateLimiterTest.scala | 183 +----- cluster/images/scan-app/app.conf | 14 +- cluster/images/sv-app/app.conf | 14 +- docs/src/release_notes_upcoming.rst | 34 -- test-full-class-names-non-integration.log | 1 - 17 files changed, 58 insertions(+), 1304 deletions(-) delete mode 100644 apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java delete mode 100644 apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala delete mode 100644 apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala index 7d76d69eb7..57f81e1ce7 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala @@ -33,11 +33,7 @@ import org.lfdecentralizedtrust.splice.splitwell.config.{ import org.lfdecentralizedtrust.splice.sv.config.* import org.lfdecentralizedtrust.splice.sv.SvAppClientConfig import org.lfdecentralizedtrust.splice.sv.config.SvOnboardingConfig.FoundDso -import org.lfdecentralizedtrust.splice.util.{ - Codec, - PerAttributeRateLimitConfig, - SpliceRateLimitConfig, -} +import org.lfdecentralizedtrust.splice.util.{Codec, SpliceRateLimitConfig} import org.lfdecentralizedtrust.splice.validator.config.* import org.lfdecentralizedtrust.splice.wallet.config.{ AppRewardBeneficiaryConfig, @@ -431,15 +427,10 @@ object SpliceConfig { deriveReader[SpliceCachingConfigs] implicit val spliceParametersConfig: ConfigReader[SpliceParametersConfig] = deriveReader[SpliceParametersConfig] - implicit val spliceRateLimiterSimpleConfig: ConfigReader[SpliceRateLimitConfig.Simple] = - deriveReader[SpliceRateLimitConfig.Simple] - implicit val clientIpRateLimitConfig: ConfigReader[PerAttributeRateLimitConfig] = - deriveReader[PerAttributeRateLimitConfig] - implicit val spliceRateLimiterWithPerClientIpConfig - : ConfigReader[SpliceRateLimitConfig.WithPerClientIp] = - deriveReader[SpliceRateLimitConfig.WithPerClientIp] implicit val rateLimitersConfig: ConfigReader[RateLimitersConfig] = deriveReader[RateLimitersConfig] + implicit val spliceRateLimiterConfig: ConfigReader[SpliceRateLimitConfig] = + deriveReader[SpliceRateLimitConfig] implicit val enabledFeaturesConfigReader: ConfigReader[EnabledFeaturesConfig] = deriveReader[EnabledFeaturesConfig] implicit val splicePostgresConfigReader: ConfigReader[SplicePostgresConfig] = @@ -957,15 +948,10 @@ object SpliceConfig { implicit val spliceParametersConfig: ConfigWriter[SpliceParametersConfig] = deriveWriter[SpliceParametersConfig] - implicit val spliceRateLimiterSimpleConfig: ConfigWriter[SpliceRateLimitConfig.Simple] = - deriveWriter[SpliceRateLimitConfig.Simple] - implicit val clientIpRateLimitConfig: ConfigWriter[PerAttributeRateLimitConfig] = - deriveWriter[PerAttributeRateLimitConfig] - implicit val spliceRateLimiterWithPerClientIpConfig - : ConfigWriter[SpliceRateLimitConfig.WithPerClientIp] = - deriveWriter[SpliceRateLimitConfig.WithPerClientIp] implicit val rateLimitersConfig: ConfigWriter[RateLimitersConfig] = deriveWriter[RateLimitersConfig] + implicit val spliceRateLimiterConfig: ConfigWriter[SpliceRateLimitConfig] = + deriveWriter[SpliceRateLimitConfig] implicit val enabledFeaturesConfigWriter: ConfigWriter[EnabledFeaturesConfig] = deriveWriter[EnabledFeaturesConfig] diff --git a/apps/app/src/test/resources/include/scans/_scan.conf b/apps/app/src/test/resources/include/scans/_scan.conf index a937c58c69..147faf2366 100644 --- a/apps/app/src/test/resources/include/scans/_scan.conf +++ b/apps/app/src/test/resources/include/scans/_scan.conf @@ -31,13 +31,8 @@ getAcsSnapshot = 1 minute } rate-limiting { - # the global limiter caps the total request rate across all operations; disabled in - # tests so that it doesn't interfere with the per-operation limits exercised here. - global { - enabled = false - per-client-ip { - enabled = false - } + default { + rate-per-second = 200 } rate-limiters { getAcsSnapshot.rate-per-second = 2 diff --git a/apps/app/src/test/resources/include/svs/_sv.conf b/apps/app/src/test/resources/include/svs/_sv.conf index 54433a96a5..a08a5c59c4 100644 --- a/apps/app/src/test/resources/include/svs/_sv.conf +++ b/apps/app/src/test/resources/include/svs/_sv.conf @@ -64,13 +64,8 @@ onboardSvPartyMigrationAuthorize = 5 minutes } rate-limiting { - # the global limiter caps the total request rate across all operations; disabled in - # tests so that it doesn't interfere with the per-operation limits exercised here. - global { - enabled = false - per-client-ip { - enabled = false - } + default { + rate-per-second = 200 } rate-limiters { prepareValidatorOnboarding.rate-per-second = 2 diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala index 0bbcb18f67..a934e1469c 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala @@ -76,10 +76,9 @@ class ScanIntegrationTest // used for the rate limit test rateLimiting = config.parameters.rateLimiting.copy( rateLimiters = - config.parameters.rateLimiting.rateLimiters + ("listAnsEntries" -> SpliceRateLimitConfig - .WithPerClientIp( - ratePerSecond = 5 - )) + config.parameters.rateLimiting.rateLimiters + ("listAnsEntries" -> SpliceRateLimitConfig( + ratePerSecond = 5 + )) ), ), ) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala index bd8adda202..a086e43182 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala @@ -144,11 +144,7 @@ class TokenStandardCliTestDataTimeBasedIntegrationTest updateAllScanAppConfigs_(config => config.copy(parameters = config.parameters.copy(rateLimiting = - RateLimitersConfig( - default = SpliceRateLimitConfig.WithPerClientIp(enabled = false, 1), - rateLimiters = Map.empty, - global = SpliceRateLimitConfig.WithPerClientIp(enabled = false, 1), - ) + RateLimitersConfig(SpliceRateLimitConfig(enabled = false, 1), Map.empty) ) ) )(config) diff --git a/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java b/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java deleted file mode 100644 index cd94bd5790..0000000000 --- a/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.google.common.util.concurrent; - -/** - * Shim that constructs a Guava {@link RateLimiter} backed by {@code SmoothBursty} with a custom - * maximum burst duration. It lives in this package because the relevant {@code - * SmoothRateLimiter.SmoothBursty} constructor is package-private. - */ -public final class BurstyRateLimiterFactory { - - private BurstyRateLimiterFactory() { - } - - /** - * Creates a bursty {@link RateLimiter} that sustains {@code permitsPerSecond} on average while - * allowing bursts of up to {@code permitsPerSecond * maxBurstSeconds} permits after idle periods. - */ - public static RateLimiter create(double permitsPerSecond, double maxBurstSeconds) { - RateLimiter rateLimiter = - new SmoothRateLimiter.SmoothBursty( - RateLimiter.SleepingStopwatch.createFromSystemTimer(), maxBurstSeconds); - rateLimiter.setRate(permitsPerSecond); - return rateLimiter; - } -} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala index 115652ace6..971de2ae5f 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala @@ -3,39 +3,11 @@ package org.lfdecentralizedtrust.splice.config -import org.lfdecentralizedtrust.splice.util.{PerAttributeRateLimitConfig, SpliceRateLimitConfig} +import org.lfdecentralizedtrust.splice.util.SpliceRateLimitConfig case class RateLimitersConfig( - /** Overall rate limiter applied per operation. Used when there is no operation-specific override - * in `rateLimiters`. The embedded `perClientIp` limiter is disabled by default; enable it to - * additionally limit per client IP. - */ - default: SpliceRateLimitConfig.WithPerClientIp, - /** Per-operation overrides of the overall `default` rate limiter. */ - rateLimiters: Map[String, SpliceRateLimitConfig.WithPerClientIp], - global: SpliceRateLimitConfig.WithPerClientIp = RateLimitersConfig.DefaultGlobal, - /** Name of the HTTP header set by a trusted reverse proxy that carries the real client IP. This header must be set - and any - * client-provided value overwritten - by infrastructure the client cannot bypass, otherwise it - * can be spoofed. When present and parseable as an IP literal it takes precedence over the - * client-controlled `X-Forwarded-For`/`X-Real-Ip` headers. Set to an empty string to disable - * trusting a proxy header and only rely on `X-Forwarded-For`/`X-Real-Ip`/the remote address. - */ - trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, + default: SpliceRateLimitConfig, + rateLimiters: Map[String, SpliceRateLimitConfig], ) { - def forRateLimiter(name: String): SpliceRateLimitConfig.WithPerClientIp = - rateLimiters.getOrElse(name, default) -} - -object RateLimitersConfig { - - /** Header set by the Envoy sidecar/ingress (Istio) to the trusted external client address that - * Envoy computes from its trusted-hops configuration. - */ - val DefaultTrustedClientIpHeader: String = "x-envoy-external-address" - - private val DefaultGlobal: SpliceRateLimitConfig.WithPerClientIp = - SpliceRateLimitConfig.WithPerClientIp( - ratePerSecond = 200, - perClientIp = PerAttributeRateLimitConfig(), - ) + def forRateLimiter(name: String): SpliceRateLimitConfig = rateLimiters.getOrElse(name, default) } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala index a26b3e1a03..42c842df85 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala @@ -17,10 +17,8 @@ final case class SpliceParametersConfig( // Do not define any defaults on the class containing the `SpliceParametersConfig` as they'll be overwritten. // Do it instead on the app.conf file in `cluster/images/${the_app}/app.conf` customTimeouts: Map[String, NonNegativeFiniteDuration] = Map.empty, - rateLimiting: RateLimitersConfig = RateLimitersConfig( - SpliceRateLimitConfig.WithPerClientIp(ratePerSecond = 200), - Map.empty, - ), + rateLimiting: RateLimitersConfig = + RateLimitersConfig(SpliceRateLimitConfig(enabled = true, ratePerSecond = 200), Map.empty), // Configuration for the circuit breaker for ledger API command submissions. circuitBreakers: CircuitBreakersConfig = CircuitBreakersConfig(), enabledFeatures: EnabledFeaturesConfig = EnabledFeaturesConfig(), diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala deleted file mode 100644 index c4f620f484..0000000000 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package org.lfdecentralizedtrust.splice.http - -import org.apache.pekko.http.scaladsl.model.headers.{`X-Forwarded-For`, `X-Real-Ip`} -import org.apache.pekko.http.scaladsl.model.RemoteAddress -import org.apache.pekko.http.scaladsl.server.Directive1 -import org.apache.pekko.http.scaladsl.server.Directives.* - -object ClientIpDirectives { - - /** Extracts the address of the client the request originated from, if it can be determined. - * - * The address is taken from the first of the following sources that yields an address: - * 1. the `trustedClientIpHeader` (if configured and parseable as an IP literal), which is set - * by a trusted reverse proxy and hence cannot be spoofed by the client, - * 1. the client-controlled `X-Forwarded-For` header, - * 1. the client-controlled `X-Real-Ip` header, - * - * @param trustedClientIpHeader - * name of the header set by a trusted reverse proxy, matched case-insensitively. An empty name - * disables trusting a proxy header. - */ - def extractClientIp(trustedClientIpHeader: String): Directive1[Option[RemoteAddress]] = - firstDefined( - trustedClientIp(trustedClientIpHeader), - forwardedForClientIp, - realIpClientIp, - ) - - private def trustedClientIp(headerName: String): Directive1[Option[RemoteAddress]] = { - val trimmedHeaderName = headerName.trim - if (trimmedHeaderName.isEmpty) provide(None) - else - // matched case-insensitively (and locale independently) as the configured header name is not - // required to be lowercase - optionalHeaderValueByName(trimmedHeaderName).map(_.flatMap(parseIpLiteral)) - } - - private val forwardedForClientIp: Directive1[Option[RemoteAddress]] = { - optionalHeaderValuePF { case `X-Forwarded-For`(Seq(address, _*)) => address } - } - - private val realIpClientIp: Directive1[Option[RemoteAddress]] = - optionalHeaderValuePF { case `X-Real-Ip`(address) => address } - - /** The value of the first directive that extracts a defined value, [[None]] if there is none. */ - private def firstDefined[A]( - directives: Directive1[Option[A]]* - ): Directive1[Option[A]] = - directives.foldRight(provide(Option.empty[A])) { (directive, fallback) => - directive.flatMap { - case defined @ Some(_) => provide(defined) - case None => fallback - } - } - - private def parseIpLiteral(value: String): Option[RemoteAddress] = - `X-Real-Ip` - .parseFromValueString(value.trim) - .toOption - .map(_.address) -} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala index 520150944f..2dc1c8ad1f 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala @@ -6,16 +6,11 @@ package org.lfdecentralizedtrust.splice.http import com.daml.metrics.api.MetricHandle.LabeledMetricsFactory import com.daml.metrics.api.MetricsContext import com.digitalasset.canton.logging.TracedLogger -import org.apache.pekko.http.scaladsl.model.{HttpEntity, RemoteAddress, StatusCodes} -import org.apache.pekko.http.scaladsl.server.{Directive0, Directive1} +import org.apache.pekko.http.scaladsl.model.{HttpEntity, StatusCodes} +import org.apache.pekko.http.scaladsl.server.Directive0 import org.lfdecentralizedtrust.splice.config.RateLimitersConfig -import org.lfdecentralizedtrust.splice.util.{ - PerAttributeRateLimiter, - SpliceRateLimiter, - SpliceRateLimitMetrics, -} +import org.lfdecentralizedtrust.splice.util.{SpliceRateLimitMetrics, SpliceRateLimiter} -import java.net.{Inet6Address, InetAddress} import java.time.Instant class HttpRateLimiter( @@ -24,20 +19,12 @@ class HttpRateLimiter( logger: TracedLogger, ) extends AutoCloseable { - // need to cache it as the pekko routes get evaluated for each request - // keyed by (service, operation) as the same operation name can be used by multiple services - private val rateLimiters = - scala.collection.concurrent.TrieMap[ - (String, String), - (SpliceRateLimiter, PerAttributeRateLimiter), - ]() + // need to cache it as the pekko reoutes get evaluated for each request + private val rateLimiters = scala.collection.concurrent.TrieMap[String, SpliceRateLimiter]() private val metrics = scala.collection.concurrent.TrieMap[String, SpliceRateLimitMetrics]() - private val trustedClientIpHeader: String = - config.trustedClientIpHeader.trim - - private def metricsFor(service: String): SpliceRateLimitMetrics = - metrics.getOrElseUpdate( + def withRateLimit(service: String)(operation: String): Directive0 = { + val rateLimiterMetrics = metrics.getOrElseUpdate( service, SpliceRateLimitMetrics(metricsFactory, logger)( MetricsContext( @@ -45,79 +32,22 @@ class HttpRateLimiter( ) ), ) - - // the rate limiter has a cold start, to avoid the first request being rejected - // we enforce the rate limit only after 1 second - private def enforceAfter = Instant.now().plusSeconds(1) - - private val globalRateLimiter: (SpliceRateLimiter, PerAttributeRateLimiter) = { - val globalMetrics = metricsFor(HttpRateLimiter.GlobalService) - ( + val rateLimiter = rateLimiters.getOrElseUpdate( + operation, new SpliceRateLimiter( - HttpRateLimiter.GlobalLimiter, - config.global, - globalMetrics, - enforceAfter, - ), - new PerAttributeRateLimiter( - HttpRateLimiter.GlobalLimiter, - HttpRateLimiter.ClientIpAttribute, - config.global, - config.global.perClientIp, - globalMetrics, - enforceAfter, - logger, + operation, + config.forRateLimiter(operation), + rateLimiterMetrics, + // the rate limiter has a cold start, to avoid the first request being rejected + // we enforce the rate limit only after 1 second + Instant.now().plusSeconds(1), ), ) - } - - private def operationRateLimiter( - service: String, - operation: String, - ): (SpliceRateLimiter, PerAttributeRateLimiter) = - rateLimiters.getOrElseUpdate( - (service, operation), { - val rateLimiterMetrics = metricsFor(service) - val operationConfig = config.forRateLimiter(operation) - ( - new SpliceRateLimiter( - operation, - operationConfig, - rateLimiterMetrics, - enforceAfter, - ), - new PerAttributeRateLimiter( - operation, - HttpRateLimiter.ClientIpAttribute, - operationConfig, - operationConfig.perClientIp, - rateLimiterMetrics, - enforceAfter, - logger, - ), - ) - }, - ) - - def withRateLimit(service: String)(operation: String): Directive0 = { - val (globalLimiter, globalClientIpLimiter) = globalRateLimiter - val (operationLimiter, operationClientIpLimiter) = operationRateLimiter(service, operation) import org.apache.pekko.http.scaladsl.server.Directives.* - HttpRateLimiter.extractClientIpKey(trustedClientIpHeader).flatMap { clientIp => - // The per client IP limiters are checked first (and `&&` short-circuits) so that a request - // rejected because of its own client IP does not consume budget from the shared overall - // limiters. Otherwise a single abusive client could exhaust the overall limits and thereby - // deny service to all other clients. - // Within each of those two groups the narrower per operation limiter is checked before the - // global one, so that a request rejected for its operation does not consume global budget. - val allowed = - operationClientIpLimiter.markRun(clientIp) && - globalClientIpLimiter.markRun(clientIp) && - operationLimiter.markRun() && - globalLimiter.markRun() - if (allowed) { + extractRequestContext.flatMap { _ => + if (rateLimiter.markRun()) { pass } else { complete( @@ -132,47 +62,3 @@ class HttpRateLimiter( def close(): Unit = metrics.view.values.foreach(_.close()) } - -object HttpRateLimiter { - - private val ClientIpAttribute = "client_ip" - - private[splice] val GlobalLimiter = "global" - private[splice] val GlobalService = "global" - - private[splice] def extractClientIpKey( - trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader - ): Directive1[Option[String]] = - ClientIpDirectives - .extractClientIp(trustedClientIpHeader) - .map(_.collect { case RemoteAddress.IP(ip, _) => rateLimitKey(ip) }) - - /** Single clients are typically assigned a whole IPv6 /64 (or larger) network, so limiting per - * full IPv6 address would allow a single client to trivially bypass the per client IP limit. - * IPv6 addresses are therefore grouped by their /64 prefix, IPv4 addresses are used as is. - */ - private def rateLimitKey(address: InetAddress): String = address match { - case ipv6: Inet6Address => - val bytes = ipv6.getAddress - ipv4Mapped(bytes) match { - // connections accepted on a dual stack socket can report IPv4 clients as IPv4-mapped IPv6 - // addresses (e.g. ::ffff:192.0.2.1), those must use the same key as the plain IPv4 address - case Some(ipv4) => ipv4.getHostAddress - case None => - // zero out the lower 64 bits (the interface identifier), keeping the /64 network prefix - // note that this also drops any scope/zone id, which is not meaningful for rate limiting - val prefix = bytes.take(8) ++ Array.fill[Byte](8)(0) - s"${InetAddress.getByAddress(prefix).getHostAddress}/64" - } - case ip => ip.getHostAddress - } - - /** ::ffff:0:0/96, see [[https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2]] */ - private val Ipv4MappedPrefix: Seq[Byte] = - Seq.fill[Byte](10)(0) ++ Seq[Byte](0xff.toByte, 0xff.toByte) - - private def ipv4Mapped(bytes: Array[Byte]): Option[InetAddress] = - Option.when(bytes.length == 16 && bytes.startsWith(Ipv4MappedPrefix))( - InetAddress.getByAddress(bytes.drop(12)) - ) -} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala index 6e41c4eab5..6d6f66019d 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala @@ -3,31 +3,22 @@ package org.lfdecentralizedtrust.splice.util -import com.daml.metrics.CacheMetrics import com.daml.metrics.api.MetricHandle.LabeledMetricsFactory import com.daml.metrics.api.MetricQualification.Saturation import com.daml.metrics.api.{MetricHandle, MetricInfo, MetricsContext} -import com.digitalasset.canton.caching.{CaffeineCache, ConcurrentCache} import com.digitalasset.canton.discard.Implicits.DiscardOps import com.digitalasset.canton.lifecycle.LifeCycle import com.digitalasset.canton.logging.TracedLogger -import com.digitalasset.canton.tracing.TraceContext -import com.github.benmanes.caffeine.cache.{Caffeine, RemovalCause, RemovalListener} -import com.google.common.util.concurrent.{BurstyRateLimiterFactory, RateLimiter} +import com.google.common.util.concurrent.RateLimiter import org.lfdecentralizedtrust.splice.environment.SpliceMetrics -import java.time.{Duration, Instant} +import java.time.Instant import java.util import java.util.Collections -import java.util.concurrent.TimeUnit -import java.util.concurrent.atomic.AtomicLong import scala.concurrent.Future import scala.jdk.CollectionConverters.CollectionHasAsScala -case class SpliceRateLimitMetrics( - otelFactory: LabeledMetricsFactory, - private val logger: TracedLogger, -)(implicit +case class SpliceRateLimitMetrics(otelFactory: LabeledMetricsFactory, logger: TracedLogger)(implicit mc: MetricsContext ) extends AutoCloseable { @@ -62,122 +53,39 @@ case class SpliceRateLimitMetrics( } -sealed trait SpliceRateLimitConfig { - - def enabled: Boolean - - def ratePerSecond: Double - - def sustainedRatePerSecond: Option[Double] - - def sustainedWindowSeconds: Long -} - -object SpliceRateLimitConfig { - - final case class Simple( - enabled: Boolean = true, - ratePerSecond: Double, - sustainedRatePerSecond: Option[Double] = None, - sustainedWindowSeconds: Long = SpliceRateLimiter.DefaultSustainedWindowSeconds, - ) extends SpliceRateLimitConfig - - final case class WithPerClientIp( - enabled: Boolean = true, - ratePerSecond: Double, - sustainedRatePerSecond: Option[Double] = None, - sustainedWindowSeconds: Long = SpliceRateLimiter.DefaultSustainedWindowSeconds, - perClientIp: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig.Disabled, - ) extends SpliceRateLimitConfig - - def apply( - enabled: Boolean = true, - ratePerSecond: Double, - sustainedRatePerSecond: Option[Double] = None, - sustainedWindowSeconds: Long = SpliceRateLimiter.DefaultSustainedWindowSeconds, - ): Simple = - Simple(enabled, ratePerSecond, sustainedRatePerSecond, sustainedWindowSeconds) -} - -case class PerAttributeRateLimitConfig( +case class SpliceRateLimitConfig( enabled: Boolean = true, - limit: SpliceRateLimitConfig.Simple = PerAttributeRateLimitConfig.DefaultLimit, - maxAttributeValues: Long = 10000, -) { - - def rateLimitFor(overall: SpliceRateLimitConfig): SpliceRateLimitConfig.Simple = - limit.copy(enabled = enabled && limit.enabled && overall.enabled) -} + ratePerSecond: Double, +) -object PerAttributeRateLimitConfig { - val DefaultLimit: SpliceRateLimitConfig.Simple = SpliceRateLimitConfig(ratePerSecond = 10) - val Disabled: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig(enabled = false) -} - -object SpliceRateLimiter { - - val GlobalLimiterType = "global" - val PerAttributeLimiterType = "per-attribute" - val UnknownAttributeLimiterType = "unknown-attribute" - - val DefaultSustainedWindowSeconds: Long = 60 - - private[util] def sustainedWindow(config: SpliceRateLimitConfig): Duration = - Duration.ofSeconds(Math.max(1L, config.sustainedWindowSeconds)) -} - -// noinspection UnstableApiUsage class SpliceRateLimiter( name: String, config: SpliceRateLimitConfig, metrics: SpliceRateLimitMetrics, enforceAfter: Instant = Instant.now(), - limiterType: String = SpliceRateLimiter.GlobalLimiterType, - extraLabels: Map[String, String] = Map.empty, - // must be disabled for the per-attribute limiters as they'd all report the same value - // and would explode the number of registered gauges - reportMaxLimit: Boolean = true, ) { - private val metricsContext = MetricsContext( - extraLabels ++ Map("limiter" -> name, "limiter_type" -> limiterType) - ) - - // The limiters are created eagerly so that they are already "warm" (i.e. have accumulated their - // burst budget) by the time the limit starts being enforced. They are only created for enabled - // limiters: a disabled limiter is never consulted and its configured rate might not even be a - // valid guava rate (e.g. 0). - // enforces the per-second burst limit (checked over a 1s window) - private val limiter: Option[RateLimiter] = - Option.when(config.enabled)(RateLimiter.create(config.ratePerSecond)) - // enforces the sustained limit over the sustained window, while still allowing bursts within its budget. - private val sustainedLimiter: Option[RateLimiter] = - Option - .when(config.enabled)(config.sustainedRatePerSecond) - .flatten - .map( - BurstyRateLimiterFactory - .create(_, SpliceRateLimiter.sustainedWindow(config).toSeconds.toDouble) - ) + // noinspection UnstableApiUsage + private val limiter = RateLimiter.create(config.ratePerSecond) // lazy to ensure metrics get registered only if the limiter is actually used - private lazy val rateLimiter: Option[RateLimiter] = { - if (reportMaxLimit) { - metrics - .recordMaxLimit(config.ratePerSecond)(metricsContext) - } + private lazy val rateLimiter = { + metrics + .recordMaxLimit(config.ratePerSecond)( + MetricsContext("limiter" -> name) + ) limiter } def markRun(): Boolean = { if (config.enabled && Instant.now().isAfter(enforceAfter)) { - val canRun = rateLimiter.forall(_.tryAcquire()) && sustainedLimiter.forall(_.tryAcquire()) + val canRun = rateLimiter.tryAcquire() if (canRun) { metrics.meter.mark()( - metricsContext.merge(MetricsContext("result" -> "accepted")) + MetricsContext("result" -> "accepted", "limiter" -> name) ) } else { metrics.meter.mark()( - metricsContext.merge(MetricsContext("result" -> "rejected")) + MetricsContext("result" -> "rejected", "limiter" -> name) ) } canRun @@ -197,105 +105,3 @@ class SpliceRateLimiter( } } - -class PerAttributeRateLimiter( - name: String, - attribute: String, - config: SpliceRateLimitConfig, - attributeConfig: PerAttributeRateLimitConfig, - metrics: SpliceRateLimitMetrics, - enforceAfter: Instant = Instant.now(), - logger: TracedLogger, -) { - - private val perAttributeConfig = attributeConfig.rateLimitFor(config) - private val isEnabled = perAttributeConfig.enabled && perAttributeConfig.ratePerSecond > 0 - private val attributeLabel = Map("limiter_attribute" -> attribute) - - // evictions by size can happen for every single request (e.g. when a large number of distinct - // attribute values is seen), so the warning is throttled to avoid flooding the logs - private val lastSizeEvictionWarning = - new AtomicLong(System.nanoTime() - PerAttributeRateLimiter.EvictionWarningIntervalNanos) - - private val evictionListener: RemovalListener[String, SpliceRateLimiter] = - (key: String, _: SpliceRateLimiter, cause: RemovalCause) => { - if (cause == RemovalCause.SIZE) { - implicit val tc: TraceContext = TraceContext.empty - val message = - s"Rate limiter cache for $name (attribute '$attribute') exceeded its maximum size of " + - s"${attributeConfig.maxAttributeValues}; evicting the rate limiter for attribute value '$key'. " + - "Its rate limiting state is lost. Consider increasing max-attribute-values." - val now = System.nanoTime() - val last = lastSizeEvictionWarning.get() - if ( - now - last >= PerAttributeRateLimiter.EvictionWarningIntervalNanos && lastSizeEvictionWarning - .compareAndSet(last, now) - ) { - logger.warn(message) - } else { - logger.debug(message) - } - } - } - - // lazy so that neither the cache nor its metrics are created if the limiter is disabled - private lazy val cache: ConcurrentCache[String, SpliceRateLimiter] = CaffeineCache[ - String, - SpliceRateLimiter, - ]( - Caffeine - .newBuilder() - .maximumSize(attributeConfig.maxAttributeValues) - // Evict limiters that have not been used for a full sustained rate limiting window (the bucket - // size of the interval rate limiter): after that time an idle limiter would have refilled its - // budget anyway, so dropping it does not change the enforced rate. - .expireAfterAccess(SpliceRateLimiter.sustainedWindow(perAttributeConfig)) - .evictionListener(evictionListener), - Some(new CacheMetrics(s"$name-$attribute-rate-limiter", metrics.otelFactory)), - ) - - private lazy val defaultRateLimiter = new SpliceRateLimiter( - name, - perAttributeConfig, - metrics, - enforceAfter, - limiterType = SpliceRateLimiter.UnknownAttributeLimiterType, - extraLabels = attributeLabel, - ) - - private lazy val reportedMaxLimit: Unit = - metrics.recordMaxLimit(perAttributeConfig.ratePerSecond)( - MetricsContext( - attributeLabel ++ Map( - "limiter" -> name, - "limiter_type" -> SpliceRateLimiter.PerAttributeLimiterType, - ) - ) - ) - - def markRun(attributeValue: Option[String]): Boolean = - if (isEnabled) attributeValue.fold(defaultRateLimiter)(limiterFor).markRun() - else true - - private def limiterFor(attributeValue: String): SpliceRateLimiter = { - reportedMaxLimit - cache.getOrAcquire( - attributeValue, - (_: String) => - new SpliceRateLimiter( - name, - perAttributeConfig, - metrics, - enforceAfter, - limiterType = SpliceRateLimiter.PerAttributeLimiterType, - extraLabels = attributeLabel, - reportMaxLimit = false, - ), - ) - } -} - -object PerAttributeRateLimiter { - - private val EvictionWarningIntervalNanos: Long = TimeUnit.MINUTES.toNanos(1) -} diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala deleted file mode 100644 index 410bf7e4d1..0000000000 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala +++ /dev/null @@ -1,554 +0,0 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package org.lfdecentralizedtrust.splice.http - -import com.daml.metrics.api.testing.InMemoryMetricsFactory -import com.digitalasset.canton.BaseTest -import com.digitalasset.canton.concurrent.Threading -import org.apache.pekko.http.scaladsl.model.headers.{RawHeader, `X-Forwarded-For`, `X-Real-Ip`} -import org.apache.pekko.http.scaladsl.model.{ - AttributeKeys, - HttpRequest, - RemoteAddress, - StatusCode, - StatusCodes, -} -import org.apache.pekko.http.scaladsl.server.Directives.* -import org.apache.pekko.http.scaladsl.server.Route -import org.apache.pekko.http.scaladsl.testkit.ScalatestRouteTest -import org.lfdecentralizedtrust.splice.config.RateLimitersConfig -import org.lfdecentralizedtrust.splice.util.{ - PerAttributeRateLimitConfig, - SpliceRateLimitConfig, - SpliceRateLimiter, -} -import org.scalatest.wordspec.AnyWordSpec - -import java.net.{Inet6Address, InetAddress} - -class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteTest { - - "clientIp" should { - - "prefer the trusted X-Envoy-External-Address over spoofable headers" in { - clientIp( - HttpRequest() - .withHeaders( - RawHeader("X-Envoy-External-Address", "4.4.4.4"), - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), - ) - .withAttributes( - Map( - AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3")) - ) - ) - ) should be(Some("4.4.4.4")) - } - - "ignore a non-IP X-Envoy-External-Address and fall back to the next header" in { - clientIp( - HttpRequest() - .withHeaders( - RawHeader("X-Envoy-External-Address", "evil.example.com"), - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - ) - ) should be(Some("1.1.1.1")) - } - - "use a configurable trusted proxy header" in { - clientIp( - HttpRequest() - .withHeaders( - RawHeader("X-Trusted-Client-Ip", "4.4.4.4"), - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - ), - trustedClientIpHeader = "x-trusted-client-ip", - ) should be(Some("4.4.4.4")) - } - - "match the trusted proxy header case-insensitively" in { - clientIp( - HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "4.4.4.4")), - trustedClientIpHeader = "X-Envoy-External-Address", - ) should be(Some("4.4.4.4")) - } - - "not trust any proxy header when the trusted header is disabled" in { - clientIp( - HttpRequest().withHeaders( - RawHeader("X-Envoy-External-Address", "4.4.4.4"), - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - ), - trustedClientIpHeader = "", - ) should be(Some("1.1.1.1")) - } - - "prefer X-Forwarded-For" in { - clientIp( - HttpRequest() - .withHeaders( - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), - ) - .withAttributes( - Map( - AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3")) - ) - ) - ) should be(Some("1.1.1.1")) - } - - "fall back to X-Real-Ip" in { - clientIp( - HttpRequest().withHeaders(`X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2")))) - ) should be(Some("2.2.2.2")) - } - - "not use the remote address of the transport connection" in { - // the remote address is not exposed by the server, so it must not be relied upon - clientIp( - HttpRequest().withAttributes( - Map(AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3"))) - ) - ) should be(None) - } - - "return None if no IP can be determined" in { - clientIp(HttpRequest()) should be(None) - clientIp( - HttpRequest().withHeaders(RawHeader("X-Forwarded-For", "not-an-ip")) - ) should be(None) - } - } - - "the client IP used for rate limiting" should { - - "use the full address for IPv4 clients" in { - clientIpOf("1.2.3.4") should be(Some("1.2.3.4")) - } - - "group IPv6 clients by their /64 prefix" in { - // the lower 64 bits (the interface identifier) are freely chosen by the client - clientIpOf("2001:db8:0:1:1:2:3:4") should be(Some("2001:db8:0:1:0:0:0:0/64")) - clientIpOf("2001:db8:0:1:ffff:ffff:ffff:ffff") should be( - clientIpOf("2001:db8:0:1:1:2:3:4") - ) - clientIpOf("2001:db8:0:1::") should be(clientIpOf("2001:db8:0:1:1:2:3:4")) - } - - "not group IPv6 clients of different /64 networks" in { - clientIpOf("2001:db8:0:2:1:2:3:4") should not be clientIpOf("2001:db8:0:1:1:2:3:4") - clientIpOf("2001:db9:0:1:1:2:3:4") should not be clientIpOf("2001:db8:0:1:1:2:3:4") - } - - "ignore the zone id of IPv6 addresses" in { - val scoped = Inet6Address.getByAddress( - null, - InetAddress.getByName("fe80::1:2:3:4").getAddress, - 7, - ) - // sanity check that the zone id is part of the address representation - scoped.getHostAddress should be("fe80:0:0:0:1:2:3:4%7") - clientIpOf(scoped) should be(Some("fe80:0:0:0:0:0:0:0/64")) - } - - "use the IPv4 address for IPv4-mapped IPv6 clients" in { - // dual stack sockets can report IPv4 clients as ::ffff:a.b.c.d, those must not end up in a - // single /64 bucket shared by all IPv4 clients - val ipv4Mapped = Inet6Address.getByAddress( - null, - Array[Byte](0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff.toByte, 0xff.toByte, 1, 2, 3, 4), - 0, - ) - ipv4Mapped shouldBe a[Inet6Address] - clientIpOf(ipv4Mapped) should be(Some("1.2.3.4")) - clientIpOf(ipv4Mapped) should be(clientIpOf("1.2.3.4")) - clientIpOf( - Inet6Address.getByAddress( - null, - Array[Byte](0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff.toByte, 0xff.toByte, 4, 3, 2, 1), - 0, - ) - ) should not be clientIpOf(ipv4Mapped) - } - - "apply the same grouping to all client IP sources" in { - val expected = Some("2001:db8:0:1:0:0:0:0/64") - val address = RemoteAddress(InetAddress.getByName("2001:db8:0:1:1:2:3:4")) - clientIp( - HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "2001:db8:0:1:1:2:3:4")) - ) should be(expected) - clientIp( - HttpRequest().withHeaders(`X-Forwarded-For`(address)) - ) should be(expected) - clientIp( - HttpRequest().withHeaders(`X-Real-Ip`(address)) - ) should be(expected) - } - } - - "the http rate limiter" should { - - "reject requests of a client IP over the global per client IP limit" in { - // the global per client IP limiter is enabled by default - withRoutes( - globalPerClientIp = perClientIp(1) - )("testOperation") { routes => - val route = routes("testOperation") - val results = (1 to 20).map(_ => call(route, ip = Some("1.1.1.1"))) - // 1 request per second per client IP => the burst gets rejected - results.count(_ == StatusCodes.OK) should be(1) - results.count(_ == StatusCodes.TooManyRequests) should be(19) - } - } - - "not reject requests of other client IPs" in { - withRoutes( - globalPerClientIp = perClientIp(1) - )("testOperation") { routes => - val route = routes("testOperation") - (1 to 20) - .map(_ => call(route, ip = Some("1.1.1.1"))) - .count(_ == StatusCodes.TooManyRequests) should be > 0 - call(route, ip = Some("2.2.2.2")) should be(StatusCodes.OK) - } - } - - "limit IPv6 clients of the same /64 network together" in { - withRoutes( - globalPerClientIp = perClientIp(1) - )("testOperation") { routes => - val route = routes("testOperation") - call(route, ip = Some("2001:db8:0:1:1:2:3:4")) should be(StatusCodes.OK) - // a different address of the same /64 shares the limiter, so it is rejected - call(route, ip = Some("2001:db8:0:1:ffff:ffff:ffff:ffff")) should be( - StatusCodes.TooManyRequests - ) - // a different /64 is a different client - call(route, ip = Some("2001:db8:0:2:1:2:3:4")) should be(StatusCodes.OK) - } - } - - "fall back to the default limiter if no client IP is known" in { - withRoutes( - globalPerClientIp = perClientIp(1) - )("testOperation") { routes => - val route = routes("testOperation") - call(route, ip = None) should be(StatusCodes.OK) - (1 to 20) - .map(_ => call(route, ip = None)) - .count(_ == StatusCodes.TooManyRequests) should be > 0 - // a request with a client IP uses a different limiter - call(route, ip = Some("1.1.1.1")) should be(StatusCodes.OK) - } - } - - "apply the global per client IP limiter across operations" in { - // the same client IP is limited regardless of the operation - withRoutes( - globalPerClientIp = perClientIp(1) - )("operationA", "operationB") { routes => - call(routes("operationA"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) - call(routes("operationB"), ip = Some("1.1.1.1")) should be(StatusCodes.TooManyRequests) - } - } - - "apply the global overall limiter across operations" in { - withRoutes( - global = SpliceRateLimitConfig(ratePerSecond = 1), - globalPerClientIp = PerAttributeRateLimitConfig.Disabled, - )("operationA", "operationB") { routes => - // exhaust the global budget via operationA - (1 to 20).map(_ => call(routes("operationA"), ip = Some("1.1.1.1"))) - // the global limiter ignores the operation and the client IP, so operationB is rejected too - call(routes("operationB"), ip = Some("2.2.2.2")) should be(StatusCodes.TooManyRequests) - } - } - - "not apply the per operation client IP limiter by default" in { - // no per client IP limiting configured for operations => requests from a single IP are only - // bounded by the (high) overall limiters - withRoutes()("testOperation") { routes => - val route = routes("testOperation") - (1 to 20).map(_ => call(route, ip = Some("1.1.1.1"))) should contain only StatusCodes.OK - } - } - - "apply the per operation client IP limiter when enabled for an operation" in { - withRoutes( - perClientIpOverrides = Map("limitedOperation" -> perClientIp(1)) - )("limitedOperation", "otherOperation") { routes => - val results = - (1 to 20).map(_ => call(routes("limitedOperation"), ip = Some("1.1.1.1"))) - results.count(_ == StatusCodes.OK) should be(1) - // a different operation is not affected by the per operation client IP limiter - call(routes("otherOperation"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) - } - } - - "apply the per operation overall limiter" in { - withRoutes( - rateLimiters = Map("limitedOperation" -> SpliceRateLimitConfig(ratePerSecond = 1)) - )("limitedOperation", "otherOperation") { routes => - val results = (1 to 20).map(_ => call(routes("limitedOperation"), ip = Some("1.1.1.1"))) - results.count(_ == StatusCodes.TooManyRequests) should be > 0 - // a different operation uses a separate overall limiter and is not affected - call(routes("otherOperation"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) - } - } - - "use separate per operation limiters for equally named operations of different services" in { - val rateLimiter = new HttpRateLimiter( - RateLimitersConfig( - default = withPerClientIp( - SpliceRateLimitConfig(ratePerSecond = 1), - PerAttributeRateLimitConfig.Disabled, - ), - rateLimiters = Map.empty, - global = withPerClientIp( - SpliceRateLimitConfig(ratePerSecond = 1000), - PerAttributeRateLimitConfig.Disabled, - ), - ), - new InMemoryMetricsFactory(), - loggerFactory.getTracedLogger(classOf[HttpRateLimiterTest]), - ) - try { - val routeV1 = - rateLimiter.withRateLimit("serviceV1")("sharedOperation")(complete(StatusCodes.OK)) - val routeV2 = - rateLimiter.withRateLimit("serviceV2")("sharedOperation")(complete(StatusCodes.OK)) - // the rate limiter only starts enforcing 1 second after it got created - Threading.sleep(1100) - (1 to 20) - .map(_ => call(routeV1, ip = Some("1.1.1.1"))) - .count(_ == StatusCodes.TooManyRequests) should be > 0 - call(routeV2, ip = Some("1.1.1.1")) should be(StatusCodes.OK) - } finally { - rateLimiter.close() - } - } - } - - "the order in which the rate limiters are applied" should { - - // A limiter only records (and thereby only consumes budget for) the requests that actually - // reach it, as the limiters are combined with a short-circuiting `&&`. The tests below send a - // burst of requests that is rejected by one limiter and assert that the limiters which must be - // applied later only saw the requests that were accepted by the rejecting one. - val Burst = 20 - val Rejecting = SpliceRateLimitConfig(ratePerSecond = 1) - // high enough to never reject, so that the recorded requests are exactly the ones that got here - val Downstream = SpliceRateLimitConfig(ratePerSecond = 1000) - val PerAttribute = SpliceRateLimiter.PerAttributeLimiterType - val Overall = SpliceRateLimiter.GlobalLimiterType - - // Sends a burst of requests from a single client IP and returns how many were accepted. - def burst(fixture: HttpRateLimiterTest.Fixture, operation: String): Long = { - val results = (1 to Burst).map(_ => call(fixture(operation), ip = Some("1.1.1.1"))) - results.count(_ == StatusCodes.TooManyRequests) should be > 0 - results.count(_ == StatusCodes.OK).toLong - } - - def onlySawAcceptedRequests( - fixture: HttpRateLimiterTest.Fixture, - accepted: Long, - )(limiters: (String, String)*) = - forEvery(limiters) { case (limiter, limiterType) => - withClue(s"requests seen by the $limiterType limiter '$limiter': ") { - fixture.requestsSeenBy(limiter, limiterType) should be(accepted) - fixture.requestsRejectedBy(limiter, limiterType) should be(0L) - } - } - - "apply the per operation client IP limiter before the overall limiters" in { - withRoutes( - rateLimiters = Map("limitedOperation" -> Downstream), - global = Downstream, - perClientIpOverrides = Map("limitedOperation" -> perClientIp(1)), - )("limitedOperation") { fixture => - val accepted = burst(fixture, "limitedOperation") - onlySawAcceptedRequests(fixture, accepted)( - "limitedOperation" -> Overall, - HttpRateLimiter.GlobalLimiter -> Overall, - ) - } - } - - "apply the global per client IP limiter before the overall limiters" in { - withRoutes( - rateLimiters = Map("limitedOperation" -> Downstream), - global = Downstream, - globalPerClientIp = perClientIp(1), - )("limitedOperation") { fixture => - val accepted = burst(fixture, "limitedOperation") - onlySawAcceptedRequests(fixture, accepted)( - "limitedOperation" -> Overall, - HttpRateLimiter.GlobalLimiter -> Overall, - ) - } - } - - "apply the per operation client IP limiter before the global per client IP limiter" in { - withRoutes( - globalPerClientIp = perClientIp(1000), - perClientIpOverrides = Map("limitedOperation" -> perClientIp(1)), - )("limitedOperation") { fixture => - val accepted = burst(fixture, "limitedOperation") - onlySawAcceptedRequests(fixture, accepted)( - HttpRateLimiter.GlobalLimiter -> PerAttribute - ) - } - } - - "apply the per operation overall limiter before the global overall limiter" in { - withRoutes( - rateLimiters = Map("limitedOperation" -> Rejecting), - global = Downstream, - )("limitedOperation") { fixture => - val accepted = burst(fixture, "limitedOperation") - onlySawAcceptedRequests(fixture, accepted)( - HttpRateLimiter.GlobalLimiter -> Overall - ) - } - } - - "still reject requests that pass the per client IP limiters but exceed an overall limit" in { - withRoutes( - global = SpliceRateLimitConfig(ratePerSecond = 2), - globalPerClientIp = perClientIp(1000), - perClientIpOverrides = Map("testOperation" -> perClientIp(1000)), - )("testOperation") { fixture => - // every request is below both per client IP limits, but the overall global limit applies - val results = (1 to Burst).map(i => call(fixture("testOperation"), ip = Some(s"1.1.1.$i"))) - results.count(_ == StatusCodes.OK) should be < Burst - results.count(_ == StatusCodes.TooManyRequests) should be > 0 - } - } - } - - private def perClientIp(ratePerSecond: Double): PerAttributeRateLimitConfig = - PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = ratePerSecond)) - - private def clientIp( - request: HttpRequest, - trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, - ): Option[String] = { - val route = HttpRateLimiter.extractClientIpKey(trustedClientIpHeader) { extracted => - complete(extracted.getOrElse[String](HttpRateLimiterTest.NoClientIp)) - } - request ~> route ~> check { - status should be(StatusCodes.OK) - Some(responseAs[String]).filterNot(_ == HttpRateLimiterTest.NoClientIp) - } - } - - private def clientIpOf(ip: String): Option[String] = - clientIpOf(InetAddress.getByName(ip)) - - private def clientIpOf(ip: InetAddress): Option[String] = - clientIp( - HttpRequest().withHeaders(`X-Forwarded-For`(RemoteAddress(ip))) - ) - - private def call(route: Route, ip: Option[String]): StatusCode = { - val request = ip match { - case Some(value) => - Get("/") ~> addHeader(`X-Forwarded-For`(RemoteAddress(InetAddress.getByName(value)))) - case None => Get("/") - } - request ~> route ~> check(status) - } - - private def withRoutes[A]( - // high enough by default so that only the explicitly configured limiter kicks in - default: SpliceRateLimitConfig = SpliceRateLimitConfig(ratePerSecond = 1000), - rateLimiters: Map[String, SpliceRateLimitConfig] = Map.empty, - global: SpliceRateLimitConfig = SpliceRateLimitConfig(ratePerSecond = 1000), - globalPerClientIp: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig.Disabled, - perClientIpOverrides: Map[String, PerAttributeRateLimitConfig] = Map.empty, - )(operations: String*)(f: HttpRateLimiterTest.Fixture => A): A = { - // Any operation with a per client IP override needs its own overall limiter entry so that the - // embedded per client IP limiter is used instead of the `default` one. - val perOperationConfigs: Map[String, SpliceRateLimitConfig.WithPerClientIp] = - (rateLimiters.keySet ++ perClientIpOverrides.keySet).map { operation => - operation -> withPerClientIp( - rateLimiters.getOrElse(operation, default), - perClientIpOverrides.getOrElse(operation, PerAttributeRateLimitConfig.Disabled), - ) - }.toMap - val metricsFactory = new InMemoryMetricsFactory() - val rateLimiter = new HttpRateLimiter( - RateLimitersConfig( - default = withPerClientIp(default, PerAttributeRateLimitConfig.Disabled), - rateLimiters = perOperationConfigs, - global = withPerClientIp(global, globalPerClientIp), - ), - metricsFactory, - loggerFactory.getTracedLogger(classOf[HttpRateLimiterTest]), - ) - try { - val routes = operations.map { operation => - operation -> rateLimiter.withRateLimit("testService")(operation) { - complete(StatusCodes.OK) - } - }.toMap - // the rate limiter only starts enforcing 1 second after it got created - Threading.sleep(1100) - f(HttpRateLimiterTest.Fixture(routes, metricsFactory)) - } finally { - rateLimiter.close() - } - } - - private def withPerClientIp( - overall: SpliceRateLimitConfig, - perClientIp: PerAttributeRateLimitConfig, - ): SpliceRateLimitConfig.WithPerClientIp = - SpliceRateLimitConfig.WithPerClientIp( - enabled = overall.enabled, - ratePerSecond = overall.ratePerSecond, - sustainedRatePerSecond = overall.sustainedRatePerSecond, - sustainedWindowSeconds = overall.sustainedWindowSeconds, - perClientIp = perClientIp, - ) -} - -object HttpRateLimiterTest { - private val NoClientIp = "" - - /** The routes of the rate limited operations together with the metrics recorded by their rate - * limiters. Requests are only recorded by the limiters they actually reach, which is what allows - * asserting on the order in which the limiters are applied. - */ - private final case class Fixture( - routes: Map[String, Route], - metricsFactory: InMemoryMetricsFactory, - ) { - - def apply(operation: String): Route = routes(operation) - - /** Number of requests recorded by the given limiter, i.e. that were actually evaluated by it. */ - def requestsSeenBy(limiter: String, limiterType: String): Long = - marks(limiter, limiterType, result = None) - - /** Number of requests the given limiter rejected. */ - def requestsRejectedBy(limiter: String, limiterType: String): Long = - marks(limiter, limiterType, result = Some("rejected")) - - private def marks(limiter: String, limiterType: String, result: Option[String]): Long = - metricsFactory.metrics.meters.values - .flatMap(_.values) - .flatMap(_.markers.toSeq) - .collect { - case (context, value) - if context.labels.get("limiter").contains(limiter) && - context.labels.get("limiter_type").contains(limiterType) && - result.forall(expected => context.labels.get("result").contains(expected)) => - value.get() - } - .sum - } -} diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala index 47f36979e7..f89db4a51c 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala @@ -1,6 +1,3 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - package org.lfdecentralizedtrust.splice.util import com.daml.metrics.api.MetricsContext @@ -15,7 +12,6 @@ import org.lfdecentralizedtrust.splice.admin.api.client.commands.HttpCommandExce import org.lfdecentralizedtrust.splice.util.SpliceRateLimiterTest.runRateLimited import org.scalatest.wordspec.AnyWordSpecLike -import java.time.Instant import scala.concurrent.Future import scala.concurrent.duration.DurationInt @@ -30,7 +26,7 @@ class SpliceRateLimiterTest "accept requests under limit" in { val elementsToRun = 100 - withRateLimiter() { case (rateLimitMetrics, rateLimiter) => + withRateLimiter { case (rateLimitMetrics, rateLimiter) => runThroughRateLimiter(rateLimiter, 9, elementsToRun).reduce(_ && _) shouldBe true rateLimitMetrics.meter.valueFilteredOnLabels( @@ -47,7 +43,7 @@ class SpliceRateLimiterTest } "reject requests that are over the limit" in { - withRateLimiter() { case (rateLimitMetrics, rateLimiter) => + withRateLimiter { case (rateLimitMetrics, rateLimiter) => val results = runThroughRateLimiter(rateLimiter, 100, 1000) val (accepted, rejected) = results.partition(identity) @@ -80,152 +76,6 @@ class SpliceRateLimiterTest } - "not create any limiter if disabled" in { - // a disabled limiter must not fail even for a rate that guava would reject - withRateLimiter(SpliceRateLimitConfig(enabled = false, ratePerSecond = 0)) { - case (_, rateLimiter) => - Seq.fill(100)(rateLimiter.markRun()) should contain only true - } - } - - } - - "the per attribute rate limiter" should { - - "gate the per attribute limit on the overall limiter being enabled" in { - PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 42)) - .rateLimitFor(SpliceRateLimitConfig(ratePerSecond = 100)) - .ratePerSecond should be(42d) - - PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 42)) - .rateLimitFor(SpliceRateLimitConfig(enabled = false, ratePerSecond = 100)) - .enabled should be(false) - - PerAttributeRateLimitConfig( - enabled = false, - limit = SpliceRateLimitConfig(ratePerSecond = 42), - ) - .rateLimitFor(SpliceRateLimitConfig(ratePerSecond = 100)) - .enabled should be(false) - - PerAttributeRateLimitConfig.Disabled - .rateLimitFor(SpliceRateLimitConfig(ratePerSecond = 100)) - .enabled should be(false) - } - - "limit each attribute value separately" in { - withPerAttributeRateLimiter( - SpliceRateLimitConfig(ratePerSecond = 10), - PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), - ) { case (_, perAttributeRateLimiter) => - // 1 per second per attribute value, so a burst is rejected after the first request - val ip1 = Seq.fill(20)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) - ip1.count(identity) should be(1) - ip1.count(!_) should be(19) - - // a different attribute value is not affected by the limiter of the first one - perAttributeRateLimiter.markRun(Some("2.2.2.2")) should be(true) - perAttributeRateLimiter.markRun(Some("2.2.2.2")) should be(false) - } - } - - "use a single default limiter if the attribute value is unknown" in { - withPerAttributeRateLimiter( - SpliceRateLimitConfig(ratePerSecond = 10), - PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), - ) { case (metrics, perAttributeRateLimiter) => - val results = Seq.fill(20)(perAttributeRateLimiter.markRun(None)) - results.count(identity) should be(1) - - metrics.meter.valueFilteredOnLabels( - LabelFilter("limiter", "test"), - LabelFilter("limiter_attribute", "test_attribute"), - LabelFilter("limiter_type", SpliceRateLimiter.UnknownAttributeLimiterType), - LabelFilter("result", "rejected"), - ) should be(results.count(!_)) - - // requests with a known attribute value are not affected by the default limiter - perAttributeRateLimiter.markRun(Some("1.1.1.1")) should be(true) - } - } - - "distinguish the metrics of the per attribute limiters" in { - withPerAttributeRateLimiter( - SpliceRateLimitConfig(ratePerSecond = 10), - PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), - ) { case (metrics, perAttributeRateLimiter) => - val results = Seq.fill(20)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) - - metrics.meter.valueFilteredOnLabels( - LabelFilter("limiter", "test"), - LabelFilter("limiter_attribute", "test_attribute"), - LabelFilter("limiter_type", SpliceRateLimiter.PerAttributeLimiterType), - LabelFilter("result", "accepted"), - ) should be(results.count(identity)) - metrics.meter.valueFilteredOnLabels( - LabelFilter("limiter", "test"), - LabelFilter("limiter_attribute", "test_attribute"), - LabelFilter("limiter_type", SpliceRateLimiter.PerAttributeLimiterType), - LabelFilter("result", "rejected"), - ) should be(results.count(!_)) - // no metrics are reported for the default limiter of unknown attribute values - metrics.meter.valuesWithContext.keys - .flatMap(_.labels.get("limiter_type")) - .toSet should be(Set(SpliceRateLimiter.PerAttributeLimiterType)) - } - } - - "not limit anything if disabled" in { - withPerAttributeRateLimiter( - SpliceRateLimitConfig(ratePerSecond = 10), - PerAttributeRateLimitConfig.Disabled, - ) { case (_, perAttributeRateLimiter) => - Seq.fill(100)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) should contain only true - Seq.fill(100)(perAttributeRateLimiter.markRun(None)) should contain only true - } - } - - "respect the configured rate over time" in { - withPerAttributeRateLimiter( - SpliceRateLimitConfig(ratePerSecond = 100), - PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 10)), - ) { case (_, perAttributeRateLimiter) => - // 10 per second per attribute value - val results = runRateLimited(50, 100) { - if (perAttributeRateLimiter.markRun(Some("1.1.1.1"))) Future.successful(true) - else - Future.failed( - io.grpc.Status.RESOURCE_EXHAUSTED - .withDescription("Rate limit exceeded") - .asRuntimeException() - ) - }.futureValue - // roughly 2 seconds of runtime at 10 permits per second, with some slack - results.count(identity) should (be >= 5 and be <= 40) - results.count(!_) should be > 0 - } - } - - } - - "the rate limiter with a sustained limit" should { - - "throttle to the sustained rate once the burst budget is drained" in { - withRateLimiter( - SpliceRateLimitConfig(ratePerSecond = 1000, sustainedRatePerSecond = Some(10)) - ) { case (_, rateLimiter) => - // The per-second limit is high enough to never reject the throttled input, so the sustained - // limiter (10/s) is the binding constraint over the run. The sustained limiter starts with - // an empty burst budget (Guava SmoothBursty semantics), so throughput tracks the sustained - // rate plus a small initial allowance. - val results = runRateLimited(40, 120) { - rateLimiter.runWithLimit(Future.successful(true)) - }.futureValue - // ~3 seconds of runtime at 10 permits/s, with generous slack - results.count(identity) should (be >= 10 and be <= 60) - results.count(!_) should be > 0 - } - } } private def runThroughRateLimiter( @@ -242,38 +92,13 @@ class SpliceRateLimiterTest } futureValue } - private def withRateLimiter[A]( - config: SpliceRateLimitConfig = SpliceRateLimitConfig(enabled = true, ratePerSecond = 10) - )(f: (SpliceRateLimitMetrics, SpliceRateLimiter) => A): A = { + private def withRateLimiter[A](f: (SpliceRateLimitMetrics, SpliceRateLimiter) => A): A = { val metricsFactory = new InMemoryMetricsFactory() val rateLimitMetrics = SpliceRateLimitMetrics(metricsFactory, logger)(MetricsContext.Empty) val rateLimiter = new SpliceRateLimiter( "test", - config, - rateLimitMetrics, - ) - try { - f(rateLimitMetrics, rateLimiter) - } finally { - rateLimitMetrics.close() - } - } - - private def withPerAttributeRateLimiter[A]( - config: SpliceRateLimitConfig, - attributeConfig: PerAttributeRateLimitConfig, - )(f: (SpliceRateLimitMetrics, PerAttributeRateLimiter) => A): A = { - val metricsFactory = new InMemoryMetricsFactory() - val rateLimitMetrics = SpliceRateLimitMetrics(metricsFactory, logger)(MetricsContext.Empty) - val rateLimiter = new PerAttributeRateLimiter( - "test", - "test_attribute", - config, - attributeConfig, + SpliceRateLimitConfig(enabled = true, 10), rateLimitMetrics, - // no cold start delay in tests - Instant.now().minusSeconds(1), - logger, ) try { f(rateLimitMetrics, rateLimiter) diff --git a/cluster/images/scan-app/app.conf b/cluster/images/scan-app/app.conf index c78b48e7f6..802305d344 100644 --- a/cluster/images/scan-app/app.conf +++ b/cluster/images/scan-app/app.conf @@ -60,19 +60,7 @@ canton { parameters { rate-limiting { default { - rate-per-second = 100 - sustained-rate-per-second = 50 - } - global { - rate-per-second = 400 - sustained-rate-per-second = 200 - per-client-ip { - enabled = true - limit { - rate-per-second = 100 - sustained-rate-per-second = 50 - } - } + rate-per-second = 200 } rate-limiters { getAcsSnapshot.rate-per-second = 20 diff --git a/cluster/images/sv-app/app.conf b/cluster/images/sv-app/app.conf index 8b407493d4..9cd27eda2c 100644 --- a/cluster/images/sv-app/app.conf +++ b/cluster/images/sv-app/app.conf @@ -107,19 +107,7 @@ canton { } rate-limiting { default { - rate-per-second = 20 - sustained-rate-per-second = 10 - } - global { - rate-per-second = 100 - sustained-rate-per-second = 50 - per-client-ip { - enabled = true - limit { - rate-per-second = 20 - sustained-rate-per-second = 10 - } - } + rate-per-second = 200 } rate-limiters { prepareValidatorOnboarding.rate-per-second = 1 diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index c470d903c2..f623c41987 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -20,37 +20,3 @@ release-notes:: Upcoming vote requests by their state relative to the SV (``action_needed``, ``in_progress``, ``ready_to_close``), allowing SV operators to alert on vote proposals that require their vote. - - - Scan & SV App - - - HTTP rate limiting has been extended with a global rate limiter applied across all - operations, optional per-client-IP rate limiting (enabled by default at the global level), - and an additional sustained rate limit enforced over a longer window on top of the existing - per-second burst limit. The client IP is taken from the trusted, non-spoofable - ``X-Envoy-External-Address`` header set by the Envoy/Istio ingress, falling back to the - client-controlled ``X-Forwarded-For``/ ``X-Real-Ip`` headers and finally the remote - address only for requests that did not pass through the ingress. These can be tuned via - the ``rate-limiting`` config keys. - - .. warning:: - - When per-client-IP rate limiting is enabled, SV operators must ensure that the client IP - used for rate limiting cannot be spoofed. Either configure - ``rate-limiting.trusted-client-ip-header`` to a trusted, non-spoofable header set by - your ingress/proxy (e.g. ``x-envoy-external-address`` for Istio deployments), or ensure - that the ``X-Forwarded-For`` header contains the actual client IP as its first value - and cannot be spoofed by clients. Otherwise, clients may bypass per-client-IP limits or - cause other clients to be throttled by forging these headers. - - - Default rate limits have been adjusted: - - - Scan app: the per-operation burst limit has been lowered from 200 to 100 requests per - second, with a new sustained limit of 50 requests per second. A new global limiter has - also been added, allowing 400 requests per second burst / 200 sustained across all - operations combined, with an embedded per-client-IP limiter allowing 100 requests per - second burst / 50 sustained. - - SV app: the per-operation burst limit has been lowered from 200 to 20 requests per - second, with a new sustained limit of 10 requests per second. A new global limiter has - also been added, allowing 100 requests per second burst / 50 sustained across all - operations combined, with an embedded per-client-IP limiter allowing 20 requests per - second burst / 10 sustained. diff --git a/test-full-class-names-non-integration.log b/test-full-class-names-non-integration.log index b15e928caa..3c16adfde2 100644 --- a/test-full-class-names-non-integration.log +++ b/test-full-class-names-non-integration.log @@ -11,7 +11,6 @@ org.lfdecentralizedtrust.splice.environment.ActiveContractsRestartTest org.lfdecentralizedtrust.splice.environment.CommandCircuitBreakerTest org.lfdecentralizedtrust.splice.environment.CommandIdDedupTest org.lfdecentralizedtrust.splice.environment.TopologyAwarePackageVersionSupportTest -org.lfdecentralizedtrust.splice.http.HttpRateLimiterTest org.lfdecentralizedtrust.splice.http.InvalidResponseContentTest org.lfdecentralizedtrust.splice.http.NonProxyHostsTest org.lfdecentralizedtrust.splice.http.UrlValidatorTest From 245379c7efe978caabfd152bf85ea128d7bd940e Mon Sep 17 00:00:00 2001 From: Itai Segall Date: Mon, 17 Aug 2026 18:12:36 -0400 Subject: [PATCH 249/329] add BFT enforcement for bulk storage objects (#6438) Signed-off-by: Itai Segall --- .../splice/console/ScanAppReference.scala | 12 + .../tests/ScanTimeBasedIntegrationTest.scala | 2 + apps/scan/src/main/openapi/scan.yaml | 51 ++++ .../splice/scan/ScanApp.scala | 4 + .../admin/api/client/BftScanConnection.scala | 10 + .../admin/api/client/ScanConnection.scala | 7 +- .../api/client/SingleScanConnection.scala | 11 +- .../client/commands/HttpScanAppClient.scala | 27 ++ .../scan/admin/http/HttpScanHandler.scala | 26 +- .../ScanHistoryBackfillingTrigger.scala | 66 ++--- .../splice/scan/config/ScanAppConfig.scala | 1 + ...SnapshotBulkStorageCommitFromStaging.scala | 10 +- .../splice/scan/store/bulk/BulkStorage.scala | 53 +++- .../bulk/BulkStorageCommitFromStaging.scala | 65 +++- .../scan/store/bulk/BulkStorageReader.scala | 13 + ...eHistoryBulkStorageCommitFromStaging.scala | 11 +- .../scan/util/PeerBftScanConnection.scala | 67 +++++ ...shotBulkStorageCommitFromStagingTest.scala | 4 +- .../BulkStorageCommitFromStagingTest.scala | 280 ++++++++++++++++-- .../bulk/UpdateHistoryBulkStorageTest.scala | 3 +- .../configs/shared/rate-limits/unlimited.yaml | 4 + .../scratchneta/config.resolved.yaml | 3 + .../scratchnetb/config.resolved.yaml | 3 + .../scratchnetc/config.resolved.yaml | 3 + .../scratchnetd/config.resolved.yaml | 3 + .../scratchnete/config.resolved.yaml | 3 + cluster/expected/sv-runbook/expected.json | 4 + cluster/expected/sv/expected.json | 8 + 28 files changed, 655 insertions(+), 99 deletions(-) create mode 100644 apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/util/PeerBftScanConnection.scala diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/ScanAppReference.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/ScanAppReference.scala index 8de8c4fe8e..fd9c5c4e3b 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/ScanAppReference.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/console/ScanAppReference.scala @@ -894,6 +894,18 @@ abstract class ScanAppReference( ) } + @Help.Summary( + "Get checksums for a list of bulk storage objects (using both staging and committed objects)" + ) + def getBulkObjectChecksums( + objectKeys: Seq[String] + ): definitions.GetBulkObjectChecksumsResponse = + consoleEnvironment.run { + httpCommand( + HttpScanAppClient.GetBulkObjectChecksums(objectKeys) + ) + } + @Help.Summary("Download a bulk storage object") def bulkStorageDownload(objectKey: String, output: OutputStream)(implicit ec: ExecutionContext, diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala index 9dfb43d4b9..eea9c57dd9 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanTimeBasedIntegrationTest.scala @@ -71,6 +71,8 @@ class ScanTimeBasedIntegrationTest updatesPollingInterval = NonNegativeFiniteDuration.ofSeconds(5), staging = Some(s3ConfigMock("staging")), committed = Some(s3ConfigMock("committed")), + bftCheckEnabled = + false, // bft checks don't work with a single scan. The bft functionality is tested in the unit test. ), publicUrl = Some(Uri("http://foo.bar.com")), ) diff --git a/apps/scan/src/main/openapi/scan.yaml b/apps/scan/src/main/openapi/scan.yaml index 2087189444..f5513b5d87 100644 --- a/apps/scan/src/main/openapi/scan.yaml +++ b/apps/scan/src/main/openapi/scan.yaml @@ -1780,6 +1780,30 @@ paths: "501": $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/501" + /v0/history/bulk/checksums: + post: + tags: [ internal, pre-alpha, scan ] + x-jvm-package: scan + operationId: "getBulkObjectChecksums" + description: | + **Under Development, do not use in production yet** Get checksums for bulk history objects. Searches for object_keys in both staging and committed objects. + Meant for internal use only, as part of the processing pipeline of bulk history objects. + requestBody: + required: true + content: + application/json: + schema: + "$ref": "#/components/schemas/GetBulkObjectChecksumsRequest" + responses: + "200": + description: ok + content: + application/json: + schema: + "$ref": "#/components/schemas/GetBulkObjectChecksumsResponse" + "501": + $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/501" + components: schemas: @@ -4093,6 +4117,33 @@ components: to the next `ListBulkUpdateHistoryObjectsRequest` invocation. Will be absent when there are no more pages. + GetBulkObjectChecksumsRequest: + type: object + required: + - object_keys + properties: + object_keys: + description: | + The list of keys of the bulk storage objects for which checksums are requested. + type: array + items: + type: string + + GetBulkObjectChecksumsResponse: + type: object + required: + - checksums + properties: + checksums: + description: | + The list of checksums for the requested bulk storage objects (in the same order as the object_keys). + type: array + items: + type: object + properties: + value: + type: string + BulkStorageObjectRef: type: object required: diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala index da68f46e44..0999b6cae0 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/ScanApp.scala @@ -265,6 +265,10 @@ class ScanApp( retryProvider.metricsFactory, config.automation, backoffClock = new WallClock(retryProvider.timeouts, loggerFactory), + store, + svName, + ledgerClient, + amuletAppParameters.upgradesConfig, retryProvider, loggerFactory, ) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala index f7d9fa3326..ccd90902ec 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/BftScanConnection.scala @@ -37,6 +37,7 @@ import org.lfdecentralizedtrust.splice.environment.{ import org.lfdecentralizedtrust.splice.http.HttpClient import org.lfdecentralizedtrust.splice.http.v0.definitions.{ AnsEntry, + GetBulkObjectChecksumsResponse, GetDsoInfoResponse, GetRewardAccountingActivityTotalsResponse, GetRewardAccountingBatchResponse, @@ -1120,6 +1121,15 @@ class BftScanConnection( ) .transform(tryBatch => Success(tryBatch.toOption)) } + + override def getBulkObjectChecksums( + objectKeys: Seq[String] + )(implicit ec: ExecutionContext, tc: TraceContext): Future[GetBulkObjectChecksumsResponse] = + bftCall( + _.getBulkObjectChecksums(objectKeys), + "getBulkObjectChecksums", + consensusFailureLogLevel = Level.DEBUG, + ) } trait HasUrl { def url: Uri diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/ScanConnection.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/ScanConnection.scala index caed44de87..bbf758cb1c 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/ScanConnection.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/ScanConnection.scala @@ -24,10 +24,12 @@ import org.lfdecentralizedtrust.splice.config.UpgradesConfig import org.lfdecentralizedtrust.splice.environment.* import org.lfdecentralizedtrust.splice.http.HttpClient import org.lfdecentralizedtrust.splice.http.v0.definitions.{ + GetBulkObjectChecksumsResponse, GetDsoInfoResponse, GetRewardAccountingActivityTotalsResponse, GetRewardAccountingBatchResponse, GetRewardAccountingRootHashResponse, + HoldingsSummaryRequestV1, HoldingsSummaryResponse, HoldingsSummaryResponseV1, LookupTransferCommandStatusResponse, @@ -55,7 +57,6 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.{ } import org.lfdecentralizedtrust.splice.http.v0.definitions.HoldingsSummaryRequest.RecordTimeMatch import org.lfdecentralizedtrust.splice.metrics.ScanConnectionMetrics -import org.lfdecentralizedtrust.splice.http.v0.definitions.HoldingsSummaryRequestV1 import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future} import scala.jdk.OptionConverters.* @@ -360,6 +361,10 @@ trait ScanConnection tc: TraceContext, ): Future[Option[GetRewardAccountingBatchResponse]] + def getBulkObjectChecksums(objectKeys: Seq[String])(implicit + ec: ExecutionContext, + tc: TraceContext, + ): Future[GetBulkObjectChecksumsResponse] } object ScanConnection { diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/SingleScanConnection.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/SingleScanConnection.scala index f9d818c9d7..e0852b971a 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/SingleScanConnection.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/SingleScanConnection.scala @@ -32,6 +32,7 @@ import org.lfdecentralizedtrust.splice.environment.{ } import org.lfdecentralizedtrust.splice.http.HttpClient import org.lfdecentralizedtrust.splice.http.v0.definitions.{ + GetBulkObjectChecksumsResponse, GetRewardAccountingActivityTotalsResponse, GetRewardAccountingBatchResponse, GetRewardAccountingRootHashResponse, @@ -96,7 +97,7 @@ import scala.util.{Failure, Success} * to query for the DSO party id. */ class SingleScanConnection private[client] ( - private[client] val config: ScanAppClientConfig, + val config: ScanAppClientConfig, upgradesConfig: UpgradesConfig, protected val clock: Clock, retryProvider: RetryProvider, @@ -1027,6 +1028,14 @@ class SingleScanConnection private[client] ( config.adminApi.url, HttpScanAppClient.GetRewardAccountingBatch(roundNumber, batchHash), ) + + override def getBulkObjectChecksums( + objectKeys: Seq[String] + )(implicit ec: ExecutionContext, tc: TraceContext): Future[GetBulkObjectChecksumsResponse] = + runHttpCmd( + config.adminApi.url, + HttpScanAppClient.GetBulkObjectChecksums(objectKeys), + ) } object SingleScanConnection { diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala index 0aa87796ad..11f3149683 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/api/client/commands/HttpScanAppClient.scala @@ -42,6 +42,7 @@ import org.lfdecentralizedtrust.tokenstandard.{ } import org.lfdecentralizedtrust.splice.http.v0.scan.{ ForceAcsSnapshotNowResponse, + GetBulkObjectChecksumsResponse, GetDateOfFirstSnapshotAfterResponse, GetDateOfMostRecentSnapshotBeforeResponse, GetLsuResponse, @@ -3344,6 +3345,32 @@ object HttpScanAppClient { } } + case class GetBulkObjectChecksums( + objectKeys: Seq[String] + ) extends InternalBaseCommand[ + http.GetBulkObjectChecksumsResponse, + definitions.GetBulkObjectChecksumsResponse, + ] { + override def submitRequest( + client: Client, + headers: List[HttpHeader], + ): EitherT[Future, Either[Throwable, HttpResponse], GetBulkObjectChecksumsResponse] = + client.getBulkObjectChecksums( + definitions.GetBulkObjectChecksumsRequest(objectKeys.toVector), + headers, + ) + + override protected def handleOk()(implicit + decoder: TemplateJsonDecoder + ): PartialFunction[GetBulkObjectChecksumsResponse, Either[ + String, + definitions.GetBulkObjectChecksumsResponse, + ]] = { + case http.GetBulkObjectChecksumsResponse.OK(response) => Right(response) + case http.GetBulkObjectChecksumsResponse.NotImplemented(err) => Left(err.error) + } + } + case class BulkStorageDownload( objectKey: String ) extends ScanStreamBaseCommand[ diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala index 1fe648f179..f50a470cd2 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/admin/http/HttpScanHandler.scala @@ -68,10 +68,11 @@ import org.lfdecentralizedtrust.splice.http.v0.{definitions, scan as v0} import org.lfdecentralizedtrust.splice.http.v0.definitions.{ AcsRequest, BatchListVotesByVoteRequestsRequest, - DamlValueEncoding, CountVoteResultsRequest, + DamlValueEncoding, ErrorResponse, EventHistoryRequest, + GetBulkObjectChecksumsRequest, HoldingsStateRequest, HoldingsSummaryRequest, HoldingsSummaryRequestV1, @@ -2609,6 +2610,29 @@ class HttpScanHandler( } } + override def getBulkObjectChecksums(respond: ScanResource.GetBulkObjectChecksumsResponse.type)( + body: GetBulkObjectChecksumsRequest + )(extracted: TraceContext): Future[ScanResource.GetBulkObjectChecksumsResponse] = { + implicit val tc = extracted + withSpan(s"$workflowId.getBulkObjectChecksums") { _ => _ => + bulkStorage.fold( + Future.failed[ScanResource.GetBulkObjectChecksumsResponse]( + Status.UNIMPLEMENTED + .withDescription("Bulk storage is not configured") + .asRuntimeException() + ) + ) { bulkStorage => + bulkStorage.getObjectChecksums(body.objectKeys).map { checksums => + ScanResource.GetBulkObjectChecksumsResponse.OK( + definitions.GetBulkObjectChecksumsResponse( + checksums.map(definitions.GetBulkObjectChecksumsResponse.Checksums(_)).toVector + ) + ) + } + } + } + } + def getRollForwardLsu(respond: ScanResource.GetRollForwardLsuResponse.type)()( extracted: TraceContext ): Future[ScanResource.GetRollForwardLsuResponse] = { diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanHistoryBackfillingTrigger.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanHistoryBackfillingTrigger.scala index 3876147017..a10390084f 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanHistoryBackfillingTrigger.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/automation/ScanHistoryBackfillingTrigger.scala @@ -14,11 +14,7 @@ import org.lfdecentralizedtrust.splice.automation.{ import org.lfdecentralizedtrust.splice.config.UpgradesConfig import org.lfdecentralizedtrust.splice.environment.SpliceLedgerClient import org.lfdecentralizedtrust.splice.http.HttpClient -import org.lfdecentralizedtrust.splice.scan.admin.api.client.{ - BackfillingScanConnection, - BftScanConnection, -} -import org.lfdecentralizedtrust.splice.scan.config.ScanAppClientConfig +import org.lfdecentralizedtrust.splice.scan.admin.api.client.BackfillingScanConnection import org.lfdecentralizedtrust.splice.scan.store.ScanHistoryBackfilling.{ FoundingTransactionTreeUpdate, InitialTransactionTreeUpdate, @@ -35,12 +31,13 @@ import org.lfdecentralizedtrust.splice.store.{ } import org.lfdecentralizedtrust.splice.util.TemplateJsonDecoder import com.digitalasset.canton.data.CantonTimestamp -import com.digitalasset.canton.lifecycle.{AsyncOrSyncCloseable, SyncCloseable} +import com.digitalasset.canton.lifecycle.{AsyncOrSyncCloseable, LifeCycle} import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} import com.digitalasset.canton.topology.PartyId import com.digitalasset.canton.tracing.TraceContext import io.opentelemetry.api.trace.Tracer import org.apache.pekko.stream.Materializer +import org.lfdecentralizedtrust.splice.scan.util.PeerBftScanConnection import org.lfdecentralizedtrust.splice.store.UpdateHistory.BackfillingState import scala.concurrent.{ExecutionContextExecutor, Future, blocking} @@ -65,6 +62,17 @@ class ScanHistoryBackfillingTrigger( private val currentMigrationId = updateHistory.domainMigrationId + private val scanConnection = new PeerBftScanConnection( + store, + svName, + ledgerClient, + context.config, + upgradesConfig, + context.clock, + context.retryProvider, + loggerFactory, + ) + private val historyMetrics = new HistoryMetrics(context.metricsFactory)( MetricsContext( "current_migration_id" -> currentMigrationId.toString @@ -79,10 +87,6 @@ class ScanHistoryBackfillingTrigger( @volatile private var findHistoryStartAfter: Option[(Long, CantonTimestamp)] = None - @SuppressWarnings(Array("org.wartremover.warts.Var")) - @volatile - private var connectionVar: Option[BftScanConnection] = None - @SuppressWarnings(Array("org.wartremover.warts.Var")) @volatile private var backfillingVar: Option[ScanHistoryBackfilling] = None @@ -222,34 +226,6 @@ class ScanHistoryBackfillingTrigger( } } - private def getOrCreateScanConnection()(implicit tc: TraceContext): Future[BftScanConnection] = - blocking { - mutex.exclusive { - connectionVar match { - case Some(connection) => - Future.successful(connection) - case None => - for { - connection <- BftScanConnection.peerScanConnection( - () => BftScanConnection.Bft.getPeerScansFromStore(store, svName), - ledgerClient, - // When the network is starting up, the pool of SVs is changing fast - // Using a short refresh interval to quickly pick up new SVs - scansRefreshInterval = context.config.pollingInterval, - amuletRulesCacheTimeToLive = ScanAppClientConfig.DefaultAmuletRulesCacheTimeToLive, - upgradesConfig, - context.clock, - context.retryProvider, - loggerFactory, - ) - } yield { - connectionVar = Some(connection) - connection - } - } - } - } - private def getOrCreateBackfilling( connection: BackfillingScanConnection ): ScanHistoryBackfilling = blocking { @@ -273,7 +249,7 @@ class ScanHistoryBackfillingTrigger( } private def performBackfilling()(implicit traceContext: TraceContext): Future[TaskOutcome] = for { - connection <- getOrCreateScanConnection() + connection <- scanConnection.connection backfilling = getOrCreateBackfilling(connection) outcome <- backfilling.backfill().map { case HistoryBackfilling.Outcome.MoreWorkAvailableNow(workDone) => @@ -305,7 +281,7 @@ class ScanHistoryBackfillingTrigger( private def performImportUpdatesBackfilling()(implicit traceContext: TraceContext ): Future[TaskOutcome] = for { - connection <- getOrCreateScanConnection() + connection <- scanConnection.connection backfilling = getOrCreateBackfilling(connection) outcome <- backfilling.backfillImportUpdates().map { case ImportUpdatesBackfilling.Outcome.MoreWorkAvailableNow(workDone) => @@ -327,14 +303,8 @@ class ScanHistoryBackfillingTrigger( } yield outcome override def closeAsync(): Seq[AsyncOrSyncCloseable] = { - connectionVar - .map(connection => - SyncCloseable( - "closing scan connection", - connection.close(), - ) - ) - .toList + LifeCycle.close(scanConnection)(logger) + super.closeAsync() } } diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanAppConfig.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanAppConfig.scala index 163d51e289..9273e016ed 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanAppConfig.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/config/ScanAppConfig.scala @@ -47,6 +47,7 @@ final case class BulkStorageConfig( maxParallelPartUploads: Int = 4, staging: Option[S3Config] = None, committed: Option[S3Config] = None, + bftCheckEnabled: Boolean = true, ) /** @param miningRoundsCacheTimeToLiveOverride Intended only for testing! diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStaging.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStaging.scala index fba378264d..286f0a18a4 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStaging.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStaging.scala @@ -10,18 +10,21 @@ import org.apache.pekko.NotUsed import org.apache.pekko.actor.ActorSystem import org.apache.pekko.stream.scaladsl.Flow import org.lfdecentralizedtrust.splice.scan.config.BulkStorageConfig +import org.lfdecentralizedtrust.splice.scan.util.PeerBftScanConnection import org.lfdecentralizedtrust.splice.store.{S3BucketConnection, TimestampWithMigrationId} -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContextExecutor, Future} class AcsSnapshotBulkStorageCommitFromStaging( stagingS3Connection: S3BucketConnection, committedS3Connection: S3BucketConnection, bulkStorageReader: BulkStorageReader, appConfig: BulkStorageConfig, + scanConnection: PeerBftScanConnection, val loggerFactory: NamedLoggerFactory, -)(implicit ec: ExecutionContext) - extends AcsSnapshotBulkStorageWriter +)(implicit + ec: ExecutionContextExecutor +) extends AcsSnapshotBulkStorageWriter with NamedLogging { override def getNextSnapshotTimestampAfter( @@ -55,6 +58,7 @@ class AcsSnapshotBulkStorageCommitFromStaging( Seq.empty }, appConfig, + scanConnection, loggerFactory, ) } diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorage.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorage.scala index 1ed4ff4b55..169bc8e99d 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorage.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorage.scala @@ -4,23 +4,28 @@ package org.lfdecentralizedtrust.splice.scan.store.bulk import com.daml.metrics.api.MetricHandle.LabeledMetricsFactory -import com.digitalasset.canton.lifecycle.{AsyncOrSyncCloseable, FlagCloseableAsync} +import com.digitalasset.canton.lifecycle.{AsyncOrSyncCloseable, FlagCloseableAsync, LifeCycle} import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.time.Clock import com.digitalasset.canton.tracing.TraceContext import io.grpc.Status import io.opentelemetry.api.trace.Tracer import org.apache.pekko.actor.{ActorSystem, Cancellable} -import org.lfdecentralizedtrust.splice.config.{AutomationConfig, S3Config} -import org.lfdecentralizedtrust.splice.environment.RetryProvider +import org.lfdecentralizedtrust.splice.config.{AutomationConfig, S3Config, UpgradesConfig} +import org.lfdecentralizedtrust.splice.environment.{RetryProvider, SpliceLedgerClient} import org.lfdecentralizedtrust.splice.scan.config.{BulkStorageConfig, ScanStorageConfig} -import org.lfdecentralizedtrust.splice.scan.store.{AcsSnapshotStore, ScanKeyValueProvider} +import org.lfdecentralizedtrust.splice.scan.store.{ + AcsSnapshotStore, + ScanKeyValueProvider, + ScanStore, +} import org.lfdecentralizedtrust.splice.store.{HistoryMetrics, S3BucketConnection, UpdateHistory} -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContextExecutor, Future} import cats.implicits.* import org.apache.pekko.stream.scaladsl.Source import org.lfdecentralizedtrust.splice.PekkoRetryableService +import org.lfdecentralizedtrust.splice.http.HttpClient import org.lfdecentralizedtrust.splice.scan.store.bulk.BulkStorage.{ acsCommittedKvStoreKey, acsStagingKvStoreKey, @@ -28,6 +33,8 @@ import org.lfdecentralizedtrust.splice.scan.store.bulk.BulkStorage.{ updatesCommittedKvStoreKey, updatesStagingKvStoreKey, } +import org.lfdecentralizedtrust.splice.scan.util.PeerBftScanConnection +import org.lfdecentralizedtrust.splice.util.TemplateJsonDecoder import scala.concurrent.duration.* @@ -43,13 +50,19 @@ class BulkStorage( metricsFactory: LabeledMetricsFactory, automationConfig: AutomationConfig, backoffClock: Clock, + store: ScanStore, + svName: String, + ledgerClient: SpliceLedgerClient, + upgradesConfig: UpgradesConfig, override val retryProvider: RetryProvider, override val loggerFactory: NamedLoggerFactory, )(implicit actorSystem: ActorSystem, tc: TraceContext, - ec: ExecutionContext, + ec: ExecutionContextExecutor, tracer: Tracer, + httpClient: HttpClient, + templateJsonDecoder: TemplateJsonDecoder, ) extends NamedLogging with FlagCloseableAsync with RetryProvider.Has { @@ -57,6 +70,16 @@ class BulkStorage( val stagingConnection = S3BucketConnection(stagingS3Config, loggerFactory) val committedConnection = S3BucketConnection(committedS3Config, loggerFactory) val historyMetrics = HistoryMetrics(metricsFactory, currentMigrationId) + val scanConnection = new PeerBftScanConnection( + store, + svName, + ledgerClient, + automationConfig, + upgradesConfig, + backoffClock, + retryProvider, + loggerFactory, + ) val backfillingCompleteGate: Source[Boolean, Cancellable] = Source @@ -128,6 +151,7 @@ class BulkStorage( committedConnection, reader, appConfig, + scanConnection, loggerFactory, ) val acsCommitted = new AcsSnapshotBulkStorage( @@ -160,6 +184,7 @@ class BulkStorage( committedConnection, reader, appConfig, + scanConnection, loggerFactory, ) val updatesCommitted = new UpdateHistoryBulkStorage( @@ -175,8 +200,10 @@ class BulkStorage( Seq[PekkoRetryableService[?]](acsStaging, acsCommitted, updatesStaging, updatesCommitted) .map(_.asPekkoRetryingService(automationConfig, backoffClock, retryProvider)) - final override def closeAsync(): Seq[AsyncOrSyncCloseable] = + final override def closeAsync(): Seq[AsyncOrSyncCloseable] = { + LifeCycle.close(scanConnection)(logger) services.flatMap(_.closeAsync()) + } } object BulkStorage { @@ -197,13 +224,19 @@ object BulkStorage { metricsFactory: LabeledMetricsFactory, automationConfig: AutomationConfig, backoffClock: Clock, + store: ScanStore, + svName: String, + ledgerClient: SpliceLedgerClient, + upgradesConfig: UpgradesConfig, retryProvider: RetryProvider, loggerFactory: NamedLoggerFactory, )(implicit actorSystem: ActorSystem, tc: TraceContext, - ec: ExecutionContext, + ec: ExecutionContextExecutor, tracer: Tracer, + httpClient: HttpClient, + templateJsonDecoder: TemplateJsonDecoder, ): BulkStorage = { val logger = loggerFactory.getTracedLogger(classOf[BulkStorage]) @@ -225,6 +258,10 @@ object BulkStorage { metricsFactory, automationConfig, backoffClock, + store, + svName, + ledgerClient, + upgradesConfig, retryProvider, loggerFactory, ) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageCommitFromStaging.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageCommitFromStaging.scala index 75f18af458..0faaff5139 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageCommitFromStaging.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageCommitFromStaging.scala @@ -6,13 +6,15 @@ package org.lfdecentralizedtrust.splice.scan.store.bulk import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.tracing.TraceContext import org.apache.pekko.NotUsed -import org.apache.pekko.actor.ActorSystem -import org.apache.pekko.stream.scaladsl.{Flow, Sink, Source} +import org.apache.pekko.http.scaladsl.model.StatusCodes +import org.apache.pekko.stream.scaladsl.{Flow, Source} +import org.lfdecentralizedtrust.splice.admin.http.HttpErrorWithHttpCode import org.lfdecentralizedtrust.splice.scan.config.BulkStorageConfig +import org.lfdecentralizedtrust.splice.scan.util.PeerBftScanConnection import org.lfdecentralizedtrust.splice.store.S3BucketConnection import org.lfdecentralizedtrust.splice.store.S3BucketConnection.ObjectKeyAndChecksum -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContextExecutor, Future} // TODO(#5884): review parallelism here. We use parallelism = 1 all over, but unsure whether that's actually necessary. @@ -21,19 +23,60 @@ class BulkStorageCommitFromStaging[T]( committedS3Connection: S3BucketConnection, getObjects: T => Future[Seq[ObjectKeyAndChecksum]], appConfig: BulkStorageConfig, + scanConnection: PeerBftScanConnection, override val loggerFactory: NamedLoggerFactory, )(implicit tc: TraceContext, - ec: ExecutionContext, - actorSystem: ActorSystem, + ec: ExecutionContextExecutor, ) extends NamedLogging { + private def checkBftForObjects( objects: Seq[ObjectKeyAndChecksum] ): Future[Boolean] = { logger.debug( s"Checking BFT agreement for objects: ${objects.map(_.key).mkString(", ")}" ) - Future.successful(true) + if (appConfig.bftCheckEnabled) { + for { + connection <- scanConnection.connection + bft <- connection.getBulkObjectChecksums(objects.map(_.key)).map(Some(_)).recoverWith { + case ex @ HttpErrorWithHttpCode(code, _) => + if (code == StatusCodes.BadGateway) { + logger.debug( + s"Consensus on checksums for objects ${objects.map(_.key).mkString(", ")} not reached. Assuming that this is because not all peers have processed the objects yet." + ) + Future.successful(None) + } else { + throw ex + } + } + } yield { + bft match { + case Some(bftChecksums) => + val consensusChecksums = bftChecksums.checksums.filter(_.value.isDefined) + logger.debug( + s"Consensus achieved on ${consensusChecksums.length} out of ${objects.length} objects" + ) + val consensus = + bftChecksums.checksums.filter(_.value.isDefined).map(_.value) == objects.map(oc => + Some(oc.checksum) + ) + if (consensusChecksums.length == objects.length && !consensus) { + logger.error( + s"All objects are known to the BFT peers, but the checksums do not match. This indicates an error in the actual data generated for bulk storage. Expected: ${objects + .map(_.checksum) + .mkString(", ")}, got: ${consensusChecksums.mkString(", ")}" + ) + } + consensus + case None => + false + } + } + } else { + logger.trace("BFT check is disabled, skipping BFT agreement check") + Future.successful(true) + } } // TODO(#5884): implement the BFT check @@ -42,7 +85,7 @@ class BulkStorageCommitFromStaging[T]( (T, Seq[ObjectKeyAndChecksum]), NotUsed, ] = { - Flow[(T, Seq[ObjectKeyAndChecksum])].mapAsync(parallelism = 1) { case (t, obj) => + Flow[(T, Seq[ObjectKeyAndChecksum])].flatMapConcat { case (t, obj) => Source .repeat(obj) .mapAsync(parallelism = 1)(obj => checkBftForObjects(obj).map(result => (obj, result))) @@ -60,8 +103,7 @@ class BulkStorageCommitFromStaging[T]( Source.single((obj, false)).delay(appConfig.bftRetryInterval.underlying) } .takeWhile({ case (_, bftReached) => !bftReached }, inclusive = true) - .runWith(Sink.last) - .map { case (obj, _) => (t, obj) } + .collect { case (o, true) => (t, o) } } } @@ -136,17 +178,18 @@ object BulkStorageCommitFromStaging { committedS3Connection: S3BucketConnection, getStagingObjects: T => Future[Seq[ObjectKeyAndChecksum]], appConfig: BulkStorageConfig, + scanConnection: PeerBftScanConnection, loggerFactory: NamedLoggerFactory, )(implicit tc: TraceContext, - ec: ExecutionContext, - actorSystem: ActorSystem, + ec: ExecutionContextExecutor, ): Flow[T, T, NotUsed] = { new BulkStorageCommitFromStaging[T]( stagingS3Connection, committedS3Connection, getStagingObjects, appConfig, + scanConnection, loggerFactory, ).getFlow } diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageReader.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageReader.scala index 1ce642d492..792824f059 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageReader.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageReader.scala @@ -160,6 +160,19 @@ class BulkStorageReader( } } + def getObjectChecksums( + objectKeys: Seq[String] + ): Future[Seq[Option[String]]] = { + for { + committed <- committedS3Connection.getChecksums(objectKeys) + staging <- stagingS3Connection.getChecksums(objectKeys) + } yield { + objectKeys.map { key => + committed.find(_.key == key).orElse(staging.find(_.key == key)).map(_.checksum) + } + } + } + private def getSegmentStartingAt( startTimestamp: Option[CantonTimestamp] ): Future[Option[(CantonTimestamp, CantonTimestamp)]] = diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageCommitFromStaging.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageCommitFromStaging.scala index 1911f9f893..5965a09c85 100644 --- a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageCommitFromStaging.scala +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageCommitFromStaging.scala @@ -7,21 +7,23 @@ import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.tracing.TraceContext import io.grpc.{Status, StatusRuntimeException} import org.apache.pekko.NotUsed -import org.apache.pekko.actor.ActorSystem import org.apache.pekko.stream.scaladsl.Flow import org.lfdecentralizedtrust.splice.scan.config.BulkStorageConfig +import org.lfdecentralizedtrust.splice.scan.util.PeerBftScanConnection import org.lfdecentralizedtrust.splice.store.{S3BucketConnection, TimestampWithMigrationId} -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContextExecutor, Future} class UpdateHistoryBulkStorageCommitFromStaging( stagingS3Connection: S3BucketConnection, committedS3Connection: S3BucketConnection, bulkStorageReader: BulkStorageReader, appConfig: BulkStorageConfig, + scanConnection: PeerBftScanConnection, val loggerFactory: NamedLoggerFactory, -)(implicit ec: ExecutionContext, actorSystem: ActorSystem) - extends UpdateHistoryBulkStorageWriter +)(implicit + ec: ExecutionContextExecutor +) extends UpdateHistoryBulkStorageWriter with NamedLogging { override def processSegmentsFlow(implicit tc: TraceContext @@ -39,6 +41,7 @@ class UpdateHistoryBulkStorageCommitFromStaging( Seq.empty }, appConfig, + scanConnection, loggerFactory, ) diff --git a/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/util/PeerBftScanConnection.scala b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/util/PeerBftScanConnection.scala new file mode 100644 index 0000000000..22208a9b08 --- /dev/null +++ b/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/util/PeerBftScanConnection.scala @@ -0,0 +1,67 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.scan.util + +import com.digitalasset.canton.logging.NamedLoggerFactory +import com.digitalasset.canton.time.Clock +import com.digitalasset.canton.tracing.TraceContext +import com.digitalasset.canton.util.Mutex +import org.apache.pekko.stream.Materializer +import org.lfdecentralizedtrust.splice.config.{AutomationConfig, UpgradesConfig} +import org.lfdecentralizedtrust.splice.environment.{RetryProvider, SpliceLedgerClient} +import org.lfdecentralizedtrust.splice.http.HttpClient +import org.lfdecentralizedtrust.splice.scan.admin.api.client.BftScanConnection +import org.lfdecentralizedtrust.splice.scan.config.ScanAppClientConfig +import org.lfdecentralizedtrust.splice.scan.store.ScanStore +import org.lfdecentralizedtrust.splice.util.TemplateJsonDecoder + +import scala.concurrent.{ExecutionContextExecutor, Future} + +class PeerBftScanConnection( + store: ScanStore, + svName: String, + ledgerClient: SpliceLedgerClient, + automationConfig: AutomationConfig, + upgradesConfig: UpgradesConfig, + clock: Clock, + retryProvider: RetryProvider, + loggerFactory: NamedLoggerFactory, +)(implicit + ec: ExecutionContextExecutor, + mat: Materializer, + httpClient: HttpClient, + templateDecoder: TemplateJsonDecoder, +) extends AutoCloseable { + + private val mutex = Mutex() + + @SuppressWarnings(Array("org.wartremover.warts.Var")) + @volatile private var connectionVar: Option[Future[BftScanConnection]] = None + + def connection(implicit tc: TraceContext): Future[BftScanConnection] = mutex.exclusive { + connectionVar match { + case Some(conn) => conn + case None => + val conn = BftScanConnection.peerScanConnection( + () => BftScanConnection.Bft.getPeerScansFromStore(store, svName), + ledgerClient, + // When the network is starting up, the pool of SVs is changing fast + // Using a short refresh interval to quickly pick up new SVs + scansRefreshInterval = automationConfig.pollingInterval, + amuletRulesCacheTimeToLive = ScanAppClientConfig.DefaultAmuletRulesCacheTimeToLive, + upgradesConfig, + clock, + retryProvider, + loggerFactory, + ) + connectionVar = Some(conn) + conn + } + } + + override def close(): Unit = mutex.exclusive { + connectionVar.foreach(_.foreach(_.close())) + } + +} diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStagingTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStagingTest.scala index 69d660309e..5159c40f1b 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStagingTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/AcsSnapshotBulkStorageCommitFromStagingTest.scala @@ -52,7 +52,8 @@ class AcsSnapshotBulkStorageCommitFromStagingTest zstdCompressionLevel = 3, ) val appConfig = BulkStorageConfig( - snapshotPollingInterval = NonNegativeFiniteDuration.ofSeconds(5) + snapshotPollingInterval = NonNegativeFiniteDuration.ofSeconds(5), + bftCheckEnabled = false, // bft checks are tested elsewhere ) override val initialBuckets: Seq[String] = Seq("staging", "committed") @@ -104,6 +105,7 @@ class AcsSnapshotBulkStorageCommitFromStagingTest committedConnection, reader, appConfig, + null, // not used when bft reads are disabled loggerFactory, ) val commitService = { diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageCommitFromStagingTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageCommitFromStagingTest.scala index be368bc401..7391b45019 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageCommitFromStagingTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/BulkStorageCommitFromStagingTest.scala @@ -3,40 +3,64 @@ package org.lfdecentralizedtrust.splice.scan.store.bulk +import com.digitalasset.canton.config.NonNegativeFiniteDuration import com.digitalasset.canton.lifecycle.FutureUnlessShutdown import com.digitalasset.canton.logging.SuppressionRule import com.digitalasset.canton.resource.DbStorage import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.{HasActorSystem, HasExecutionContext} +import org.apache.pekko.NotUsed +import org.apache.pekko.http.scaladsl.model.Uri +import org.lfdecentralizedtrust.splice.config.NetworkAppClientConfig +import org.lfdecentralizedtrust.splice.scan.config.ScanAppClientConfig +import org.lfdecentralizedtrust.splice.test.HasRetryProvider import org.slf4j.event.Level -import org.apache.pekko.stream.scaladsl.Keep +import org.apache.pekko.stream.scaladsl.{Flow, Keep} import org.apache.pekko.stream.testkit.scaladsl.{TestSink, TestSource} +import org.lfdecentralizedtrust.splice.environment.SpliceLedgerClient +import org.lfdecentralizedtrust.splice.http.HttpClient +import org.lfdecentralizedtrust.splice.http.v0.definitions.GetBulkObjectChecksumsResponse +import org.lfdecentralizedtrust.splice.scan.admin.api.client.{ + BftScanConnection, + SingleScanConnection, +} import org.lfdecentralizedtrust.splice.scan.config.BulkStorageConfig import org.lfdecentralizedtrust.splice.store.S3BucketConnection.ObjectKeyAndChecksum import org.lfdecentralizedtrust.splice.store.{HasS3Mock, StoreTestBase} import org.lfdecentralizedtrust.splice.store.db.SplicePostgresTest +import org.lfdecentralizedtrust.splice.util.TemplateJsonDecoder +import org.lfdecentralizedtrust.splice.scan.util.PeerBftScanConnection import java.security.MessageDigest import java.util.Base64 -import scala.concurrent.Future +import scala.concurrent.{ExecutionContext, Future} import scala.jdk.CollectionConverters.* +import scala.concurrent.duration.* class BulkStorageCommitFromStagingTest extends StoreTestBase with HasExecutionContext with HasActorSystem with HasS3Mock - with SplicePostgresTest { + with SplicePostgresTest + with HasRetryProvider { override val initialBuckets = Seq("staging", "committed") - val appConfig = BulkStorageConfig() + implicit val httpClient: HttpClient = null + implicit val templateJsonDecoder: TemplateJsonDecoder = null "BulkStorageCommitFromStaging" should { + val appConfig = BulkStorageConfig( + bftCheckEnabled = false + ) + "successfully move objects from staging to committed S3 bucket" in { val (stagingS3Connection, committedS3Connection, objsWithDigests) = setupTest - triggerCopyFlow(stagingS3Connection, committedS3Connection, objsWithDigests) + triggerCopyFlowAndAssertCompletion( + newCopyFlow(stagingS3Connection, committedS3Connection, objsWithDigests) + ) assertObjectsMoved(stagingS3Connection, committedS3Connection, objsWithDigests) } @@ -54,37 +78,253 @@ class BulkStorageCommitFromStagingTest loggerFactory.assertLogsSeq(SuppressionRule.LevelAndAbove(Level.DEBUG))( { - triggerCopyFlow(stagingS3Connection, committedS3Connection, objsWithDigests) + triggerCopyFlowAndAssertCompletion( + newCopyFlow(stagingS3Connection, committedS3Connection, objsWithDigests) + ) }, logEntries => forExactly(1, logEntries)(_.message should include("Skipping copy")), ) assertObjectsMoved(stagingS3Connection, committedS3Connection, objsWithDigests) } + + def newCopyFlow( + stagingS3Connection: S3BucketConnectionForUnitTests, + committedS3Connection: S3BucketConnectionForUnitTests, + objsWithDigests: Seq[ObjectKeyAndChecksum], + ) = { + BulkStorageCommitFromStaging[String]( + stagingS3Connection, + committedS3Connection, + _ => Future.successful(objsWithDigests), + appConfig, + null, // not used when bft reads are disabled + loggerFactory, + ) + } + } - private def triggerCopyFlow( - stagingS3Connection: S3BucketConnectionForUnitTests, - committedS3Connection: S3BucketConnectionForUnitTests, - objsWithDigests: Seq[ObjectKeyAndChecksum], - ) = { - val flow = BulkStorageCommitFromStaging[String]( - stagingS3Connection, - committedS3Connection, - _ => Future.successful(objsWithDigests), - appConfig, - loggerFactory, + "BulkStorageCommitFromStaging with BFT reads enabled" should { + val appConfig = BulkStorageConfig( + bftRetryInterval = NonNegativeFiniteDuration.ofSeconds(1) ) + "successfully move objects from staging to committed S3 bucket when there's full consensus" in { + val (stagingS3Connection, committedS3Connection, objsWithDigests) = setupTest + + val mockScanConnections = new MockScanConnections(objsWithDigests) + Seq.range(0, 7).foreach { i => + mockScanConnections.scanAgrees(i) + } + + val flow = newCopyFlow( + stagingS3Connection, + committedS3Connection, + objsWithDigests, + mockScanConnections, + ) + + triggerCopyFlowAndAssertCompletion(flow) + + assertObjectsMoved(stagingS3Connection, committedS3Connection, objsWithDigests) + } + + "wait until all objects are known to the peers, and report disagreement on consensus correctly" in { + val (stagingS3Connection, committedS3Connection, objsWithDigests) = setupTest + + val mockScanConnections = new MockScanConnections(objsWithDigests) + + val flow = newCopyFlow( + stagingS3Connection, + committedS3Connection, + objsWithDigests, + mockScanConnections, + ) + + val (pub, sub) = TestSource + .probe[String] + .via(flow) + .toMat(TestSink.probe[String])(Keep.both) + .run() + + clue("When one object is not known to the peers, the copy flow should not complete") { + Seq.range(0, 2).foreach(i => mockScanConnections.scanAgrees(i)) + Seq.range(2, 7).foreach(i => mockScanConnections.scanMissingAnObject(i, 1)) + + sub.request(1) + pub.sendNext("go") + sub.expectNoMessage(20.seconds) + + stagingS3Connection.listObjects.futureValue + .contents() + .asScala should have size objsWithDigests.size.toLong + committedS3Connection.listObjects.futureValue.contents().asScala shouldBe empty + } + + // errors on mismatching digests continue past the first clue for some time until enough scans are updated to agree on the digests, + // so we make the assertion on the logs fairly wide here to avoid the late error logs failing the log checker + loggerFactory.assertLogsSeq(SuppressionRule.LevelAndAbove(Level.ERROR))( + { + clue( + "Simulate a majority disagreeing with our digests, the copy flow should not complete and an error should be emitted" + ) { + Seq.range(2, 7).foreach(i => mockScanConnections.scanDisagreesOnDigest(i, 1)) + sub.expectNoMessage(20.seconds) + stagingS3Connection.listObjects.futureValue + .contents() + .asScala should have size objsWithDigests.size.toLong + committedS3Connection.listObjects.futureValue.contents().asScala shouldBe empty + + } + + clue("Enough scans do agree - the copy flow should complete successfully") { + Seq.range(2, 5).foreach(i => mockScanConnections.scanAgrees(i)) + sub.expectNext(20.seconds, "go") + assertObjectsMoved(stagingS3Connection, committedS3Connection, objsWithDigests) + } + + }, + logEntries => + forAtLeast(1, logEntries)( + _.message should include( + "All objects are known to the BFT peers, but the checksums do not match" + ) + ), + ) + } + + class MockScanConnections( + objsWithDigests: Seq[ObjectKeyAndChecksum] + ) { + + private val singleScanConnections: Seq[SingleScanConnection] = Seq.range(0, 7).map { i => + val mockConn = mock[SingleScanConnection] + when(mockConn.config) thenReturn ScanAppClientConfig( + NetworkAppClientConfig( + Uri(s"http://dummy-admin-$i") + ) + ) + when(mockConn.url) thenReturn Uri(s"http://scan_$i") + mockConn + } + + def scanAgrees(idx: Integer): Unit = { + when( + singleScanConnections(idx) + .getBulkObjectChecksums(any[Seq[String]])(any[ExecutionContext], any[TraceContext]) + ) + .thenReturn( + Future.successful( + new GetBulkObjectChecksumsResponse( + objsWithDigests + .map(_.checksum) + .map(digest => new GetBulkObjectChecksumsResponse.Checksums(Some(digest))) + .toVector + ) + ) + ) + () + } + + def scanDisagreesOnDigest(scanIdx: Integer, objIdx: Integer): Unit = { + when( + singleScanConnections(scanIdx) + .getBulkObjectChecksums(any[Seq[String]])(any[ExecutionContext], any[TraceContext]) + ) + .thenReturn( + Future.successful( + new GetBulkObjectChecksumsResponse( + objsWithDigests + .map(_.checksum) + .updated(objIdx, "wrong-digest") + .map(digest => new GetBulkObjectChecksumsResponse.Checksums(Some(digest))) + .toVector + ) + ) + ) + } + + def scanMissingAnObject(scanIdx: Integer, objIdx: Integer): Unit = { + when( + singleScanConnections(scanIdx) + .getBulkObjectChecksums(any[Seq[String]])(any[ExecutionContext], any[TraceContext]) + ) + .thenReturn( + Future.successful( + new GetBulkObjectChecksumsResponse( + objsWithDigests + .map(_.checksum) + .map(Some(_)) + .updated(objIdx, None) + .map(oDigest => new GetBulkObjectChecksumsResponse.Checksums(oDigest)) + .toVector + ) + ) + ) + } + + private val scanList = new BftScanConnection.AllDsoScansBft( + initialScanConnections = singleScanConnections, + initialFailedConnections = Map.empty, + connectionBuilder = _ => Future.failed(new RuntimeException("Shouldn't be refreshing!")), + scanUrlsChangedCallback = _ => Future.unit, + getScans = BftScanConnection.Bft.getScansInDsoRules, + scansRefreshInterval = NonNegativeFiniteDuration.ofDays(10), + retryProvider = testRetryProvider, + loggerFactory = loggerFactory, + ) + private val bftConnection = new BftScanConnection( + amuletLedgerClient = mock[SpliceLedgerClient], + amuletRulesCacheTimeToLive = NonNegativeFiniteDuration.ofSeconds(1), + scanList = scanList, + clock = wallClock, + retryProvider = testRetryProvider, + loggerFactory = loggerFactory, + ) + val peerBftConnection: PeerBftScanConnection = mock[PeerBftScanConnection] + when(peerBftConnection.connection(any[TraceContext])) + .thenReturn(Future.successful(bftConnection)) + } + + def newCopyFlow( + stagingS3Connection: S3BucketConnectionForUnitTests, + committedS3Connection: S3BucketConnectionForUnitTests, + objsWithDigests: Seq[ObjectKeyAndChecksum], + mockScanConnections: MockScanConnections, + ) = { + new BulkStorageCommitFromStaging[String]( + stagingS3Connection, + committedS3Connection, + _ => Future.successful(objsWithDigests), + appConfig, + mockScanConnections.peerBftConnection, + loggerFactory, + ).getFlow + } + } + + private def triggerCopyFlowAndAssertCompletion( + flow: Flow[String, String, NotUsed] + ) = { val (pub, sub) = TestSource .probe[String] .via(flow) .toMat(TestSink.probe[String])(Keep.both) .run() - sub.request(1) - pub.sendNext("go") - sub.expectNext("go") + try { + sub.request(1) + pub.sendNext("go") + sub.expectNext("go") + pub.sendComplete() + sub.expectComplete() + } catch { + case ex: Throwable => + pub.sendError(ex) + sub.cancel() + throw ex + } } private def assertObjectsMoved( diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageTest.scala index 7e400aed5d..eedad265c6 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/UpdateHistoryBulkStorageTest.scala @@ -61,7 +61,8 @@ class UpdateHistoryBulkStorageTest zstdCompressionLevel = 3, ) val appConfig = BulkStorageConfig( - updatesPollingInterval = NonNegativeFiniteDuration.ofSeconds(5) + updatesPollingInterval = NonNegativeFiniteDuration.ofSeconds(5), + bftCheckEnabled = false, // bft checks are tested elsewhere ) "UpdateHistoryBulkStorage" should { diff --git a/cluster/configs/shared/rate-limits/unlimited.yaml b/cluster/configs/shared/rate-limits/unlimited.yaml index 9ea479e8fa..d4c1d907a9 100644 --- a/cluster/configs/shared/rate-limits/unlimited.yaml +++ b/cluster/configs/shared/rate-limits/unlimited.yaml @@ -181,6 +181,10 @@ rateLimits: name: listBulkUpdateHistoryObjects type: unlimited + /api/scan/v0/history/bulk/checksums: + name: getBulkHistoryChecksums + type: unlimited + /api/scan/v0/active-synchronizer-serial: name: activeSynchronizerSerial type: unlimited diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index 3734a99ebe..c67bbbe6f6 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -287,6 +287,9 @@ sv: /api/scan/v0/history/bulk/acs: name: 'listBulkAcsSnapshotObjects' type: 'unlimited' + /api/scan/v0/history/bulk/checksums: + name: 'getBulkHistoryChecksums' + type: 'unlimited' /api/scan/v0/history/bulk/updates: name: 'listBulkUpdateHistoryObjects' type: 'unlimited' diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index 3734a99ebe..c67bbbe6f6 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -287,6 +287,9 @@ sv: /api/scan/v0/history/bulk/acs: name: 'listBulkAcsSnapshotObjects' type: 'unlimited' + /api/scan/v0/history/bulk/checksums: + name: 'getBulkHistoryChecksums' + type: 'unlimited' /api/scan/v0/history/bulk/updates: name: 'listBulkUpdateHistoryObjects' type: 'unlimited' diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index 3734a99ebe..c67bbbe6f6 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -287,6 +287,9 @@ sv: /api/scan/v0/history/bulk/acs: name: 'listBulkAcsSnapshotObjects' type: 'unlimited' + /api/scan/v0/history/bulk/checksums: + name: 'getBulkHistoryChecksums' + type: 'unlimited' /api/scan/v0/history/bulk/updates: name: 'listBulkUpdateHistoryObjects' type: 'unlimited' diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index 3734a99ebe..c67bbbe6f6 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -287,6 +287,9 @@ sv: /api/scan/v0/history/bulk/acs: name: 'listBulkAcsSnapshotObjects' type: 'unlimited' + /api/scan/v0/history/bulk/checksums: + name: 'getBulkHistoryChecksums' + type: 'unlimited' /api/scan/v0/history/bulk/updates: name: 'listBulkUpdateHistoryObjects' type: 'unlimited' diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index 3734a99ebe..c67bbbe6f6 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -287,6 +287,9 @@ sv: /api/scan/v0/history/bulk/acs: name: 'listBulkAcsSnapshotObjects' type: 'unlimited' + /api/scan/v0/history/bulk/checksums: + name: 'getBulkHistoryChecksums' + type: 'unlimited' /api/scan/v0/history/bulk/updates: name: 'listBulkUpdateHistoryObjects' type: 'unlimited' diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index f08c1f770e..549414864e 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -945,6 +945,10 @@ "name": "listBulkAcsSnapshotObjects", "type": "unlimited" }, + "/api/scan/v0/history/bulk/checksums": { + "name": "getBulkHistoryChecksums", + "type": "unlimited" + }, "/api/scan/v0/history/bulk/updates": { "name": "listBulkUpdateHistoryObjects", "type": "unlimited" diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index c44cc1ed10..699fe52989 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -1636,6 +1636,10 @@ "name": "listBulkAcsSnapshotObjects", "type": "unlimited" }, + "/api/scan/v0/history/bulk/checksums": { + "name": "getBulkHistoryChecksums", + "type": "unlimited" + }, "/api/scan/v0/history/bulk/updates": { "name": "listBulkUpdateHistoryObjects", "type": "unlimited" @@ -2025,6 +2029,10 @@ "name": "listBulkAcsSnapshotObjects", "type": "unlimited" }, + "/api/scan/v0/history/bulk/checksums": { + "name": "getBulkHistoryChecksums", + "type": "unlimited" + }, "/api/scan/v0/history/bulk/updates": { "name": "listBulkUpdateHistoryObjects", "type": "unlimited" From b0ae1ccc1cfbbe6eb1b027a5d17363f2f44a9b17 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Tue, 18 Aug 2026 08:50:48 +0200 Subject: [PATCH 250/329] fix dso missed confirmation alert (#6806) [static] Signed-off-by: Nicu Reut --- cluster/expected/observability/expected.json | 2 +- .../grafana-alerting/dso_missed_confirmations_alerts.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index b8c6d06eb1..854a5dec8e 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -81,7 +81,7 @@ "cometbft_deleted_alerts.yaml": "apiVersion: 1\ndeleteRules:\n - orgId: 1\n # Cometbft Consensus\n uid: fa4b8a18-e4be-4d19-a5af-90c7326fd935\n - orgId: 1\n # Cometbft Height Not Advancing\n uid: a3b47fca-0982-491b-80bb-0c70f04dcd8b\n - orgId: 1\n # Cometbft Not Voting\n uid: a3f23df0-40d5-41d1-8b4b-03aa8c62c030\n - orgId: 1\n # Reached Expected Maximum CometBFT Block Rate\n uid: adl2npxte6u4gd\n - orgId: 1\n # CometBFT is not pruning old blocks\n uid: ddlrp7f1f8l4wf\n", "deleted_alerts.yaml": "apiVersion: 1\ndeleteRules:\n - orgId: 1\n # SV Status - mediator -falling behind\n uid: f15539f2-f6ea-4de4-8f7c-c56434d5bd52\n - orgId: 1\n # wasted traffic (sustained)\n uid: adw5rd048zf9cb\n - orgId: 1\n # wasted traffic\n uid: adw5rd048zf9ca\n - orgId: 1\n # Confirmation Requests By Member (replaced by sequencer caps, #2917)\n uid: 88b8827c8d09\n - orgId: 1\n # Total Confirmation Requests (replaced by sequencer caps, #2917)\n uid: 5dcddc9a5487\n - orgId: 1\n uid: aescandisagree1\ndeleteContactPoints:\n - uid: grafana-default-email\n orgId: 1\n", "deployment_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: deployments\n folder: canton-network\n interval: 1m\n rules:\n - uid: adkhl6u18pqtce\n title: Failing Stacks\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n exemplar: true\n expr: splice_deployment_pulumi_stack_status{state=\"failed\"}\n format: time_series\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: stacks_active\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: D\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - D\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: QP_wDqDnz\n panelId: 11\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"11\"\n description: The pulumi operator failed to update the {{ $labels.stack }} stack. Check the logs for the deployment.\n runbook_url: \"\"\n severity: critical\n summary: '{{ $labels.stack }} stack failed to update'\n labels:\n \"\": \"\"\n isPaused: false\n - uid: bdndg5g3x4kxsf\n title: Deployments running\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: splice_deployment_pulumi_stack_condition{type=\"Reconciling\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n dashboardUid: QP_wDqDnz\n panelId: 27\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"27\"\n description: A deployment is currently running for the {{ $labels.stack }} stack.\n runbook_url: \"\"\n severity: info\n summary: Stack {{ $labels.stack }} is being updated\n labels:\n \"\": \"\"\n isPaused: false\n", - "dso_missed_confirmations_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: dso party missed confirmations\n folder: canton-network\n interval: 1m\n rules:\n - uid: ddso1miss0conf0a\n title: DSO Party Missed Confirmations\n condition: missed confirmations threshold exceeded\n data:\n - refId: missed confirmation rate\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (party) (increase(daml_mediator_timeout_non_responsive_participants_total{party=~\"DSO::.*\"}[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: missed confirmation rate\n - refId: missed confirmations threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: missed confirmation rate\n intervalMs: 1000\n maxDataPoints: 43200\n refId: missed confirmations threshold exceeded\n type: threshold\n dashboardUid: cnck4jp\n panelId: 1\n noDataState: OK\n execErrState: Alerting\n for: 0m\n annotations:\n __dashboardUid__: cnck4jp\n __panelId__: \"1\"\n severity: critical\n summary: The DSO party in namespace {{ $labels.namespace }} missed more than 0 of its confirmations over the last 10 minutes.\n labels: {}\n isPaused: false\n", + "dso_missed_confirmations_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: dso party missed confirmations\n folder: canton-network\n interval: 1m\n rules:\n - uid: ddso1miss0conf0a\n title: DSO Party Missed Confirmations\n condition: missed confirmations threshold exceeded\n data:\n - refId: missed_confirmations\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (party) (increase(daml_mediator_timeout_non_responsive_participants_total{party=~\"DSO::.*\"}[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: missed_confirmations\n - refId: missed confirmations threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: missed_confirmations\n intervalMs: 1000\n maxDataPoints: 43200\n refId: missed confirmations threshold exceeded\n type: threshold\n dashboardUid: cnck4jp\n panelId: 1\n noDataState: OK\n execErrState: Alerting\n for: 0m\n annotations:\n __dashboardUid__: cnck4jp\n __panelId__: \"1\"\n severity: critical\n summary: The DSO party missed {{ index $values \"missed_confirmations\" }}, more than 0 of its confirmations over the last 10 minutes.\n labels: {}\n isPaused: false\n", "extra_k8s_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: storage\n folder: k8s\n interval: 5m\n rules:\n - uid: adlrbu5kog0sga\n title: KubePersistentVolumeTooFull\n condition: free_space_below_threshold\n data:\n - refId: free_space\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: kubelet_volume_stats_available_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"} / kubelet_volume_stats_capacity_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: free_space\n - refId: free_space_below_threshold\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.15\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: free_space\n intervalMs: 1000\n maxDataPoints: 43200\n refId: free_space_below_threshold\n type: threshold\n dashboardUid: 919b92a8e8041bd567af9edab12c840c\n panelId: 2\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: 919b92a8e8041bd567af9edab12c840c\n __panelId__: \"2\"\n severity: warning\n description: The PersistentVolume claimed by {{ index $labels \"persistentvolumeclaim\" }} in Namespace {{ index $labels \"namespace\" }} is running out of disk space. Currently {{ humanizePercentage (index $values \"free_space\").Value }} is available.\n runbook_url: https://runbooks.prometheus-operator.dev/runbooks/kubernetes/kubepersistentvolumefillingup\n summary: PersistentVolume is too full.\n isPaused: false\n", "istio-rate-limiting_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: istio-rate-limit\n folder: platform\n interval: 5m\n rules:\n - uid: cfurlpifrmvi8w\n title: Enforced requests\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\"sv.*\"}[$__rate_interval])) by (namespace, pod)\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}} - {{pod}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnr56dj\n panelId: 9\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n runbook_url: \"\"\n severity: warning\n description: Envoy local rate limiting is active, requests are being rejected. Check the \"Istio Rate Limiting\" dashboard.\n summary: Envoy is rate limiting requests in {{ $labels.namespace }} on pod {{ $labels.pod }}.\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A\n isPaused: false\n", "load-tester_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: load-tester\n folder: canton-network\n interval: 1m\n rules:\n - uid: a1dbc4e8-7941-4351-9a14-f8573fd2be2b\n title: K6 Request Failure Rate Exceeded\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: B\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total{expected_response=\"false\"}) or on() vector(0)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: ($B / $A) * 100\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 50\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 1m\n annotations:\n description: ''\n runbook_url: ''\n summary: The k6 load tester experienced HTTP request failures at a rate past the acceptable threshold\n labels:\n gcloud_filter: ''\n isPaused: false\n - uid: ddogbytusmz9cb\n title: K6 Throughput Below Threshold\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: rate(k6_transfers_completed_total{scenario=\"generate_load\"}[$__rate_interval])\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.123\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: B\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n - refId: B\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n hide: false\n reducer: mean\n refId: B\n type: reduce\n dashboardUid: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n panelId: 28\n noDataState: Alerting\n execErrState: Alerting\n for: 15m # give some time for restarts\n annotations:\n __dashboardUid__: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n __panelId__: \"28\"\n severity: warning\n description: ''\n runbook_url: ''\n summary: Transaction rate from the k6 load tester is lower than expected threshold\n labels:\n '': ''\n isPaused: false\n", diff --git a/cluster/pulumi/observability/grafana-alerting/dso_missed_confirmations_alerts.yaml b/cluster/pulumi/observability/grafana-alerting/dso_missed_confirmations_alerts.yaml index b7e5f678eb..969e928332 100644 --- a/cluster/pulumi/observability/grafana-alerting/dso_missed_confirmations_alerts.yaml +++ b/cluster/pulumi/observability/grafana-alerting/dso_missed_confirmations_alerts.yaml @@ -9,7 +9,7 @@ groups: title: DSO Party Missed Confirmations condition: missed confirmations threshold exceeded data: - - refId: missed confirmation rate + - refId: missed_confirmations relativeTimeRange: from: $DSO_MISSED_CONFIRMATIONS_WINDOW_SECONDS to: 0 @@ -22,7 +22,7 @@ groups: legendFormat: __auto maxDataPoints: 43200 range: false - refId: missed confirmation rate + refId: missed_confirmations - refId: missed confirmations threshold exceeded datasourceUid: __expr__ model: @@ -43,7 +43,7 @@ groups: datasource: type: __expr__ uid: __expr__ - expression: missed confirmation rate + expression: missed_confirmations intervalMs: 1000 maxDataPoints: 43200 refId: missed confirmations threshold exceeded @@ -57,6 +57,6 @@ groups: __dashboardUid__: cnck4jp __panelId__: "1" severity: critical - summary: The DSO party in namespace {{ $labels.namespace }} missed more than $DSO_MISSED_CONFIRMATIONS_THRESHOLD of its confirmations over the last $DSO_MISSED_CONFIRMATIONS_WINDOW_MINUTES minutes. + summary: The DSO party missed {{ index $values "missed_confirmations" }}, more than $DSO_MISSED_CONFIRMATIONS_THRESHOLD of its confirmations over the last $DSO_MISSED_CONFIRMATIONS_WINDOW_MINUTES minutes. labels: {} isPaused: false From b39cb94c485aea1d9651af98676ca01222580aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Loeuillet?= Date: Tue, 18 Aug 2026 10:12:44 +0200 Subject: [PATCH 251/329] helm: quote validator wallet user ids (#6805) validatorWalletUsers is rendered into a HOCON fragment as canton.validator-apps.validator_backend.validator-wallet-users.0 = {{ $user }} unquoted, and validatorWalletUser into an env var value unquoted. Both break for user ids that are not bare alphanumeric strings: - '@' is not allowed in an unquoted HOCON string, so an id that is an email address -- which some identity providers use as the user id -- produces a config the validator app cannot parse. - An all-digit id becomes a HOCON number rather than a string, and in the env var case renders as a YAML integer, which is not a valid container env value. Quote both. The surrounding code already does this elsewhere -- scanAddress uses `| quote`, and the scan-client sv-names and seed-urls lists use `| toJson` -- so these two sites look like oversights rather than intent. Note for anyone who worked around this by embedding literal double quotes in the value: those must now be removed, or the id ends up double-quoted. Signed-off-by: Stephane Loeuillet Co-authored-by: Claude Opus 5 (1M context) --- .../splice-validator/templates/validator.yaml | 4 +- .../tests/validator_test.yaml | 1 + .../tests/wallet-user-ids_test.yaml | 69 +++++++++++++++++++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 cluster/helm/splice-validator/tests/wallet-user-ids_test.yaml diff --git a/cluster/helm/splice-validator/templates/validator.yaml b/cluster/helm/splice-validator/templates/validator.yaml index 1d6c2f3072..17f0e6382f 100644 --- a/cluster/helm/splice-validator/templates/validator.yaml +++ b/cluster/helm/splice-validator/templates/validator.yaml @@ -92,12 +92,12 @@ spec: value: {{ .Values.scanAddress | quote }} {{ if .Values.validatorWalletUser }} - name: SPLICE_APP_VALIDATOR_WALLET_USER_NAME - value: {{ .Values.validatorWalletUser }} + value: {{ .Values.validatorWalletUser | quote }} {{ end }} {{- range $ii, $user := .Values.validatorWalletUsers }} - name: ADDITIONAL_CONFIG_VALIDATOR_WALLET_USER_{{ $ii }} value: | - canton.validator-apps.validator_backend.validator-wallet-users.{{ $ii }} = {{ $user }} + canton.validator-apps.validator_backend.validator-wallet-users.{{ $ii }} = {{ $user | quote }} {{- end }} {{ if .Values.validatorPartyHint }} - name: SPLICE_APP_VALIDATOR_PARTY_HINT diff --git a/cluster/helm/splice-validator/tests/validator_test.yaml b/cluster/helm/splice-validator/tests/validator_test.yaml index 9d63f11f97..5db35709c6 100644 --- a/cluster/helm/splice-validator/tests/validator_test.yaml +++ b/cluster/helm/splice-validator/tests/validator_test.yaml @@ -558,3 +558,4 @@ tests: capabilities: drop: - ALL + diff --git a/cluster/helm/splice-validator/tests/wallet-user-ids_test.yaml b/cluster/helm/splice-validator/tests/wallet-user-ids_test.yaml new file mode 100644 index 0000000000..1e569b9c42 --- /dev/null +++ b/cluster/helm/splice-validator/tests/wallet-user-ids_test.yaml @@ -0,0 +1,69 @@ +# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +suite: "Validator wallet user ids" +templates: + - validator.yaml +release: + name: mock-validator +chart: + version: 0.1.1 + appVersion: 0.1.0 + +set: + # Things we need just to pass the schema. validatorWalletUser is deliberately + # left unset here so that validatorWalletUsers can be exercised: the schema + # accepts exactly one of the two. + nodeIdentifier: "helm-mock-1-validator" + validatorPartyHint: "helm-mock-1" + spliceInstanceNames: + networkName: MockNet + networkFaviconUrl: https://mock.net/favicon.ico + amuletName: Mocklet + amuletNameAcronym: MCK + nameServiceName: Mock Name Service + nameServiceNameAcronym: MNS + topup: + enabled: false + auth: + jwksUrl: "https://mock.com/.well-known/jwks.json" + audience: "mock_audience" + +tests: + - it: "quotes wallet user ids in the generated HOCON" + # An id containing '@' -- e.g. an email address, which some identity + # providers use as the user id -- is not a valid unquoted HOCON string. + set: + validatorWalletUsers: + - alice@example.com + - "12345678" + documentSelector: + path: kind + value: Deployment + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: ADDITIONAL_CONFIG_VALIDATOR_WALLET_USER_0 + value: | + canton.validator-apps.validator_backend.validator-wallet-users.0 = "alice@example.com" + # An all-digit id must stay a string rather than becoming a HOCON number. + - contains: + path: spec.template.spec.containers[0].env + content: + name: ADDITIONAL_CONFIG_VALIDATOR_WALLET_USER_1 + value: | + canton.validator-apps.validator_backend.validator-wallet-users.1 = "12345678" + + - it: "keeps a single all-digit wallet user id a string in the env var" + set: + validatorWalletUser: "12345678" + documentSelector: + path: kind + value: Deployment + asserts: + - contains: + path: spec.template.spec.containers[0].env + content: + name: SPLICE_APP_VALIDATOR_WALLET_USER_NAME + value: "12345678" From cf652e8f9b8a3df44a092d4350a1644d6f8ed44c Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Tue, 18 Aug 2026 10:39:57 +0200 Subject: [PATCH 252/329] New store for unavailable parties (#6725) Signed-off-by: Julien Tinguely --- .../stable/V073__dso_unavailable_parties.sql | 26 ++ .../splice/store/IgnoredPartiesStore.scala | 1 + .../store/UnavailablePartiesStore.scala | 25 ++ .../splice/store/db/AcsJdbcTypes.scala | 43 +-- .../splice/store/db/AcsQueries.scala | 40 +-- .../store/db/DbUnavailablePartiesStore.scala | 121 +++++++ .../splice/store/db/JdbcTypes.scala | 52 +++ .../splice/store/db/Queries.scala | 48 +++ .../store/DbUnavailablePartiesStoreTest.scala | 330 ++++++++++++++++++ .../splice/store/db/SpliceDbTest.scala | 3 +- test-full-class-names-non-integration.log | 1 + 11 files changed, 611 insertions(+), 79 deletions(-) create mode 100644 apps/common/src/main/resources/db/migration/canton-network/postgres/stable/V073__dso_unavailable_parties.sql create mode 100644 apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/UnavailablePartiesStore.scala create mode 100644 apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbUnavailablePartiesStore.scala create mode 100644 apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/JdbcTypes.scala create mode 100644 apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/Queries.scala create mode 100644 apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/DbUnavailablePartiesStoreTest.scala diff --git a/apps/common/src/main/resources/db/migration/canton-network/postgres/stable/V073__dso_unavailable_parties.sql b/apps/common/src/main/resources/db/migration/canton-network/postgres/stable/V073__dso_unavailable_parties.sql new file mode 100644 index 0000000000..9d3d90a345 --- /dev/null +++ b/apps/common/src/main/resources/db/migration/canton-network/postgres/stable/V073__dso_unavailable_parties.sql @@ -0,0 +1,26 @@ +-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +-- Table storing parties that the automation temporarily ignore, because of unresponsiveness or vetting errors. +-- Entries are ignored for updated_at + ignore_duration. +create table dso_unavailable_parties +( + -- the ID of the party that is unavailable + party text not null, + -- the time when the party was marked as unavailable, used for capped exponential backoff + updated_at bigint not null, + -- the duration (microseconds) to ignore the entry, used for capped exponential backoff + ignore_duration bigint not null, + -- the store ID when the party is added, used for resets + store_id bigint not null, + -- the metadata fields reserved for diagnostic/extra information + metadata jsonb, + primary key (party) +); + +-- Index for the expiry check per party +create index dso_unavailable_parties_pid_exp + on dso_unavailable_parties (party, (updated_at + ignore_duration)); + +-- Index for the efficient store cleanup +create index dso_unavailable_parties_sid on dso_unavailable_parties (store_id); diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IgnoredPartiesStore.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IgnoredPartiesStore.scala index b481314cdf..c082ce12e9 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IgnoredPartiesStore.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/IgnoredPartiesStore.scala @@ -8,6 +8,7 @@ import com.digitalasset.canton.topology.PartyId import java.util.concurrent.ConcurrentHashMap import scala.jdk.CollectionConverters.* +// TODO(#6817): Remove in-memory in favor of DbUnavailablePartiesStore class IgnoredPartiesStore(initialParties: Set[PartyId]) { private val parties: ConcurrentHashMap.KeySetView[PartyId, java.lang.Boolean] = { diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/UnavailablePartiesStore.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/UnavailablePartiesStore.scala new file mode 100644 index 0000000000..06acc54fd4 --- /dev/null +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/UnavailablePartiesStore.scala @@ -0,0 +1,25 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.store + +import com.digitalasset.canton.topology.PartyId +import com.digitalasset.canton.tracing.TraceContext + +import scala.concurrent.Future + +trait UnavailablePartiesStore { + + // Adds or updates parties + def addParties(parties: Seq[PartyId], nowMicros: Long)(implicit tc: TraceContext): Future[Unit] + + // Removes specific parties from the store + def removeParties(parties: Seq[PartyId])(implicit tc: TraceContext): Future[Int] + + // Removes parties from the table with matching store ID + def removePartiesUpToStoreId(storeId: Long)(implicit tc: TraceContext): Future[Int] + + // Lists parties that are being ignored + def listParties(nowMicros: Long)(implicit tc: TraceContext): Future[Seq[PartyId]] + +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AcsJdbcTypes.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AcsJdbcTypes.scala index 9eb7b7732c..4ec07e59c7 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AcsJdbcTypes.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AcsJdbcTypes.scala @@ -10,7 +10,7 @@ import com.daml.ledger.javaapi.data.codegen.{ContractId, DamlRecord, DefinedData import com.digitalasset.canton.config.CantonRequireTypes.{String2066, String3, String300} import com.digitalasset.canton.data.{CantonTimestamp, Offset} import com.digitalasset.canton.daml.lf.value.json.ApiCodecCompressed -import com.digitalasset.canton.topology.{Member, PartyId, SynchronizerId} +import com.digitalasset.canton.topology.{Member, SynchronizerId} import com.digitalasset.daml.lf.data.Ref.HexString import com.digitalasset.daml.lf.data.Time.Timestamp import com.google.protobuf.ByteString @@ -32,14 +32,12 @@ import com.digitalasset.canton.LfValue import com.digitalasset.canton.logging.ErrorLoggingContext import spray.json.{JsString, JsValue, JsonFormat, deserializationError} -import java.sql.{JDBCType, PreparedStatement, ResultSet} +import java.sql.{PreparedStatement, ResultSet} import java.io.StringWriter -trait AcsJdbcTypes { +trait AcsJdbcTypes extends JdbcTypes { import AcsJdbcTypes.JsonString - val profile: slick.jdbc.JdbcProfile - import profile.api.* protected implicit lazy val byteArrayGetResult: GetResult[Array[Byte]] = @@ -194,13 +192,6 @@ trait AcsJdbcTypes { (timestamps: Array[CantonTimestamp], pp: PositionedParameters) => longArraySetParameter(timestamps.map(_.toMicros), pp) - protected implicit lazy val stringArraySetParameter: SetParameter[Array[String]] = - (strings: Array[String], pp: PositionedParameters) => - pp.setObject( - pp.ps.getConnection.createArrayOf("text", strings.map(x => x)), - JDBCType.ARRAY.getVendorTypeNumber, - ) - protected implicit lazy val stringSeqSetParameter: SetParameter[Seq[String]] = (strings: Seq[String], pp: PositionedParameters) => stringArraySetParameter(strings.toArray, pp) @@ -208,10 +199,6 @@ trait AcsJdbcTypes { (strings: Array[String3], pp: PositionedParameters) => stringArraySetParameter(strings.map(_.str), pp) - protected implicit lazy val string2066ArraySetParameter: SetParameter[Array[String2066]] = - (strings: Array[String2066], pp: PositionedParameters) => - stringArraySetParameter(strings.map(_.str), pp) - protected implicit lazy val string2066SeqSetParameter: SetParameter[Seq[String2066]] = (strings: Seq[String2066], pp: PositionedParameters) => stringArraySetParameter(strings.map(_.str).toArray, pp) @@ -224,12 +211,6 @@ trait AcsJdbcTypes { (ids: Array[ContractId[?]], pp: PositionedParameters) => stringArraySetParameter(ids.map(_.contractId), pp) - protected implicit def partyIdGetResult[T]: GetResult[PartyId] = - GetResult.GetString.andThen(PartyId.tryFromProtoPrimitive) - - protected implicit def partyIdGetResultOption[T]: GetResult[Option[PartyId]] = - GetResult.GetStringOption.andThen(_.map(PartyId.tryFromProtoPrimitive)) - protected implicit lazy val offsetJdbcType: JdbcType[Offset] = MappedColumnType.base[Offset, String]( offset => LegacyOffset.fromLong(offset.unwrap).toHexString, @@ -273,19 +254,6 @@ trait AcsJdbcTypes { protected implicit lazy val synchronizerIdJdbcType: JdbcType[SynchronizerId] = MappedColumnType.base[SynchronizerId, String](_.toProtoPrimitive, SynchronizerId.tryFromString) - protected implicit lazy val partyIdJdbcType: JdbcType[PartyId] = - MappedColumnType.base[PartyId, String](_.toProtoPrimitive, PartyId.tryFromProtoPrimitive) - - protected implicit lazy val partyIdSetParameterOption: SetParameter[Option[PartyId]] = - (partyId: Option[PartyId], pp: PositionedParameters) => - implicitly[SetParameter[Option[String2066]]] - .apply(partyId.map(party => lengthLimited(party.toProtoPrimitive)), pp) - - protected implicit lazy val partyIdSetParameterArray: SetParameter[Array[PartyId]] = - (partyId: Array[PartyId], pp: PositionedParameters) => - implicitly[SetParameter[Array[String2066]]] - .apply(partyId.map(party => lengthLimited(party.toProtoPrimitive)), pp) - protected implicit lazy val memberIdSetParameter: SetParameter[Member] = (memberId: Member, pp: PositionedParameters) => implicitly[SetParameter[String300]].apply(memberId.toLengthLimitedString, pp) @@ -375,11 +343,6 @@ trait AcsJdbcTypes { data: DefinedDataType[?] ): Json = AcsJdbcTypes.payloadJsonFromDefinedDataType(data) - /** The DB may truncate strings of unbounded length, so it's advised to use a LengthLimitedString instead. - * We use String2066 because it's the max length of an [[com.digitalasset.canton.protocol.LfTemplateId]]. - */ - protected def lengthLimited(s: String): String2066 = String2066.tryCreate(s) - private def lengthLimitedByteString(bs: ByteString, maxLength: Int): ByteString = { require( bs.size() <= maxLength, diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AcsQueries.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AcsQueries.scala index b3c05fb390..16ce755ef4 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AcsQueries.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/AcsQueries.scala @@ -6,7 +6,6 @@ package org.lfdecentralizedtrust.splice.store.db import com.daml.ledger.javaapi.data.Identifier import com.daml.ledger.javaapi.data.codegen.ContractId import com.digitalasset.canton.resource.DbStorage.Implicits.BuilderChain.toSQLActionBuilderChain -import com.digitalasset.canton.resource.DbStorage.SQLActionBuilderChain import com.digitalasset.canton.topology.{PartyId, SynchronizerId} import com.digitalasset.daml.lf.data.Time.Timestamp import com.google.protobuf.ByteString @@ -25,13 +24,11 @@ import org.lfdecentralizedtrust.splice.util.PrettyInstances.* import scalaz.{@@, Tag} import slick.jdbc.canton.ActionBasedSQLInterpolation.Implicits.actionBasedSQLInterpolationCanton import slick.jdbc.canton.SQLActionBuilder -import slick.jdbc.{GetResult, PositionedResult, SetParameter} +import slick.jdbc.{GetResult, PositionedResult} import slick.dbio.Effect import slick.sql.SqlStreamingAction -import scala.reflect.ClassTag - -trait AcsQueries extends AcsJdbcTypes { +trait AcsQueries extends Queries with AcsJdbcTypes { /** @param tableName Must be SQL-safe, as it needs to be interpolated unsafely. * This is fine, as all calls to this method should use static string constants. @@ -267,39 +264,6 @@ trait AcsQueries extends AcsJdbcTypes { ) } - /** Constructions like `seq.mkString("(", ",", ")")` are dangerous because they can lead to SQL injection. - * Prefer using this instead. - */ - protected def sqlCommaSeparated( - seq: Iterable[SQLActionBuilder] - ): SQLActionBuilderChain = { - seq - .map(SQLActionBuilderChain(_)) - .reduceOption { (acc, next) => - acc ++ sql"," ++ next - } - .getOrElse(SQLActionBuilderChain(sql"")) - } - - /* - * TODO(#3900) move to use toInClause when canton fork has it: https://github.com/canton-network/splice/issues/3900 - */ - protected def inClause[V: ClassTag]( - field: String, - seq: Iterable[V], - )(implicit - arraySetParameter: SetParameter[Array[V]] - ): SQLActionBuilder = - sql" #$field = ANY(${seq.toArray[V]})" - - protected def notInClause[V: ClassTag]( - field: String, - seq: Iterable[V], - )(implicit - arraySetParameter: SetParameter[Array[V]] - ): SQLActionBuilder = - sql" NOT (#$field = ANY(${seq.toArray[V]}))" - protected def contractFromRow[C, TCId <: ContractId[?], T](companion: C)( row: AcsQueries.SelectFromAcsTableResult )(implicit diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbUnavailablePartiesStore.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbUnavailablePartiesStore.scala new file mode 100644 index 0000000000..e12f9ce6f2 --- /dev/null +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/DbUnavailablePartiesStore.scala @@ -0,0 +1,121 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.store.db + +import com.digitalasset.canton.config.NonNegativeFiniteDuration +import com.digitalasset.canton.discard.Implicits.DiscardOps +import com.digitalasset.canton.lifecycle.CloseContext +import com.digitalasset.canton.logging.{ErrorLoggingContext, NamedLoggerFactory, NamedLogging} +import com.digitalasset.canton.resource.DbStorage +import com.digitalasset.canton.topology.PartyId +import com.digitalasset.canton.tracing.TraceContext +import org.lfdecentralizedtrust.splice.store.UnavailablePartiesStore +import org.lfdecentralizedtrust.splice.util.FutureUnlessShutdownUtil.futureUnlessShutdownToFuture +import slick.jdbc.JdbcProfile +import slick.jdbc.canton.ActionBasedSQLInterpolation.Implicits.actionBasedSQLInterpolationCanton + +import scala.concurrent.{ExecutionContext, Future} + +class DbUnavailablePartiesStore( + storage: DbStorage, + val storeId: Int, + baseDuration: NonNegativeFiniteDuration, + maxIgnoreDuration: NonNegativeFiniteDuration, + val loggerFactory: NamedLoggerFactory, +)(implicit + val ec: ExecutionContext, + val loggingContext: ErrorLoggingContext, + val closeContext: CloseContext, +) extends UnavailablePartiesStore + with Queries + with NamedLogging { + + val profile: JdbcProfile = storage.profile.jdbc + + private val baseMicros = baseDuration.underlying.toMicros + private val maxMicros = maxIgnoreDuration.underlying.toMicros + + /** Adds or updates parties only outside the ignore window. + * a. For new parties, it sets updated_at to now and the ignore_duration to base_duration. + * b. For existing parties, it updates updated_at to now and doubles the ignore_duration (up to max_ignore_duration) + */ + def addParties(parties: Seq[PartyId], nowMicros: Long)(implicit + tc: TraceContext + ): Future[Unit] = + if (parties.isEmpty) Future.unit + else { + val partyArray = parties.distinct.toArray + logger.debug(s"Marking ${partyArray.length} parties as unavailable at $nowMicros") + storage + .update( + sql"""insert into dso_unavailable_parties + (party, updated_at, ignore_duration, store_id) + select u.party, $nowMicros, $baseMicros, $storeId + from unnest($partyArray) as u(party) + on conflict (party) do update + set updated_at = excluded.updated_at, + ignore_duration = least( + dso_unavailable_parties.ignore_duration * 2, + $maxMicros) + where dso_unavailable_parties.updated_at + dso_unavailable_parties.ignore_duration <= excluded.updated_at + """.asUpdate, + "addParties", + ) + .map(_.discard) + } + + // Removes specific parties from the table upon successful transaction processing. + def removeParties(parties: Seq[PartyId])(implicit tc: TraceContext): Future[Int] = + if (parties.isEmpty) Future.successful(0) + else { + val partyArray = parties.distinct.toArray + storage.update( + sqlu"""delete from dso_unavailable_parties where party = any($partyArray)""", + "removeParties", + ) + } + + // Removes parties from the table with matching store ID. + def removePartiesUpToStoreId(maxStoreId: Long)(implicit tc: TraceContext): Future[Int] = + storage.update( + sqlu"""delete from dso_unavailable_parties where store_id <= $maxStoreId""", + "removePartiesUpToStoreId", + ) + + // List all parties for which updated_at + ignore_duration > now. + def listParties(nowMicros: Long)(implicit tc: TraceContext): Future[Seq[PartyId]] = + storage.query( + sql"""select party + from dso_unavailable_parties + where updated_at + ignore_duration > $nowMicros""".as[PartyId], + "listParties", + ) + +} + +object DbUnavailablePartiesStore { + def apply( + storeDescriptor: StoreDescriptor, + storage: DbStorage, + baseDuration: NonNegativeFiniteDuration, + maxIgnoreDuration: NonNegativeFiniteDuration, + loggerFactory: NamedLoggerFactory, + )(implicit + ec: ExecutionContext, + lc: ErrorLoggingContext, + cc: CloseContext, + tc: TraceContext, + ): Future[DbUnavailablePartiesStore] = + StoreDescriptorStore + .getStoreIdForDescriptor(storeDescriptor, storage) + .map(storeId => + new DbUnavailablePartiesStore( + storage, + storeId, + baseDuration, + maxIgnoreDuration, + loggerFactory, + ) + ) +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/JdbcTypes.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/JdbcTypes.scala new file mode 100644 index 0000000000..6e4b98b32d --- /dev/null +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/JdbcTypes.scala @@ -0,0 +1,52 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.store.db + +import com.digitalasset.canton.config.CantonRequireTypes.String2066 +import com.digitalasset.canton.topology.PartyId +import slick.jdbc.* + +import java.sql.JDBCType + +trait JdbcTypes { + + val profile: slick.jdbc.JdbcProfile + import profile.api.* + + /** The DB may truncate strings of unbounded length, so it's advised to use a LengthLimitedString instead. + * We use String2066 because it's the max length of an [[com.digitalasset.canton.protocol.LfTemplateId]]. + */ + protected def lengthLimited(s: String): String2066 = String2066.tryCreate(s) + + protected implicit def partyIdGetResult[T]: GetResult[PartyId] = + GetResult.GetString.andThen(PartyId.tryFromProtoPrimitive) + + protected implicit def partyIdGetResultOption[T]: GetResult[Option[PartyId]] = + GetResult.GetStringOption.andThen(_.map(PartyId.tryFromProtoPrimitive)) + + protected implicit lazy val partyIdJdbcType: JdbcType[PartyId] = + MappedColumnType.base[PartyId, String](_.toProtoPrimitive, PartyId.tryFromProtoPrimitive) + + protected implicit lazy val partyIdSetParameterOption: SetParameter[Option[PartyId]] = + (partyId: Option[PartyId], pp: PositionedParameters) => + implicitly[SetParameter[Option[String2066]]] + .apply(partyId.map(party => lengthLimited(party.toProtoPrimitive)), pp) + + protected implicit lazy val partyIdSetParameterArray: SetParameter[Array[PartyId]] = + (partyId: Array[PartyId], pp: PositionedParameters) => + implicitly[SetParameter[Array[String2066]]] + .apply(partyId.map(party => lengthLimited(party.toProtoPrimitive)), pp) + + protected implicit lazy val stringArraySetParameter: SetParameter[Array[String]] = + (strings: Array[String], pp: PositionedParameters) => + pp.setObject( + pp.ps.getConnection.createArrayOf("text", strings.map(x => x)), + JDBCType.ARRAY.getVendorTypeNumber, + ) + + protected implicit lazy val string2066ArraySetParameter: SetParameter[Array[String2066]] = + (strings: Array[String2066], pp: PositionedParameters) => + stringArraySetParameter(strings.map(_.str), pp) + +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/Queries.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/Queries.scala new file mode 100644 index 0000000000..1a55b834e8 --- /dev/null +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/db/Queries.scala @@ -0,0 +1,48 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.store.db + +import com.digitalasset.canton.resource.DbStorage.SQLActionBuilderChain +import slick.jdbc.SetParameter +import slick.jdbc.canton.ActionBasedSQLInterpolation.Implicits.actionBasedSQLInterpolationCanton +import slick.jdbc.canton.SQLActionBuilder + +import scala.reflect.ClassTag + +trait Queries extends JdbcTypes { + + /** Constructions like `seq.mkString("(", ",", ")")` are dangerous because they can lead to SQL injection. + * Prefer using this instead. + */ + protected def sqlCommaSeparated( + seq: Iterable[SQLActionBuilder] + ): SQLActionBuilderChain = { + seq + .map(SQLActionBuilderChain(_)) + .reduceOption { (acc, next) => + acc ++ sql"," ++ next + } + .getOrElse(SQLActionBuilderChain(sql"")) + } + + /* + * TODO(#3900) move to use toInClause when canton fork has it: https://github.com/canton-network/splice/issues/3900 + */ + protected def inClause[V: ClassTag]( + field: String, + seq: Iterable[V], + )(implicit + arraySetParameter: SetParameter[Array[V]] + ): SQLActionBuilder = + sql" #$field = ANY(${seq.toArray[V]})" + + protected def notInClause[V: ClassTag]( + field: String, + seq: Iterable[V], + )(implicit + arraySetParameter: SetParameter[Array[V]] + ): SQLActionBuilder = + sql" NOT (#$field = ANY(${seq.toArray[V]}))" + +} diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/DbUnavailablePartiesStoreTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/DbUnavailablePartiesStoreTest.scala new file mode 100644 index 0000000000..3d01deb390 --- /dev/null +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/DbUnavailablePartiesStoreTest.scala @@ -0,0 +1,330 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.store + +import com.digitalasset.canton.HasExecutionContext +import com.digitalasset.canton.config.NonNegativeFiniteDuration +import com.digitalasset.canton.lifecycle.FutureUnlessShutdown +import com.digitalasset.canton.resource.DbStorage +import com.digitalasset.canton.tracing.TraceContext +import com.digitalasset.canton.util.MonadUtil +import org.lfdecentralizedtrust.splice.store.db.{ + DbUnavailablePartiesStore, + SplicePostgresTest, + StoreDescriptor, +} +import org.scalatest.matchers.should.Matchers + +import scala.concurrent.duration.* + +class DbUnavailablePartiesStoreTest + extends StoreTestBase + with Matchers + with HasExecutionContext + with SplicePostgresTest { + + private val storeDescriptor = StoreDescriptor( + version = 1, + name = "DbUnavailablePartiesStoreTest", + party = dsoParty, + participant = mkParticipantId("participant"), + key = Map(), + ) + + private val storeDescriptor2 = storeDescriptor.copy(version = 2) + + private val baseDuration = NonNegativeFiniteDuration(1.second) + private val maxIgnoreDuration = NonNegativeFiniteDuration(4.seconds) + private val oneSecond = 1_000_000L + + private def atSeconds(seconds: Long): Long = seconds * oneSecond + + private def mkStore(descriptor: StoreDescriptor = storeDescriptor) = + DbUnavailablePartiesStore( + descriptor, + storage, + baseDuration, + maxIgnoreDuration, + loggerFactory, + ) + + "DbUnavailablePartiesStore" should { + + "addParties" should { + + "be a no-op for an empty sequence" in { + for { + store <- mkStore() + _ <- store.addParties(Seq.empty, atSeconds(0)) + parties <- store.listParties(atSeconds(0)) + } yield parties shouldBe empty + } + + "ignores new parties for the base duration" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1), userParty(2)), atSeconds(0)) + justBefore <- store.listParties(atSeconds(1) - 1) + atExpiry <- store.listParties(atSeconds(1)) + } yield { + justBefore should contain theSameElementsAs Seq(userParty(1), userParty(2)) + atExpiry shouldBe empty + } + } + + "double the ignore duration when a party is re-added later" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) // expires at 1s + _ <- store.addParties( + Seq(userParty(1)), + atSeconds(1), + ) // should bump ignore duration to 2s => expires at 3s + justBefore <- store.listParties(atSeconds(3) - 1) + atExpiry <- store.listParties(atSeconds(3)) + } yield { + justBefore should contain(userParty(1)) + atExpiry shouldBe empty + } + } + + "not double the ignore duration when a party is re-added within its window" in { + val midWindow = atSeconds(1) / 2 + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) // expires at 1s + _ <- store.addParties(Seq(userParty(1)), midWindow) // already ignored => no-op + justBefore <- store.listParties(atSeconds(1) - 1) + atExpiry <- store.listParties(atSeconds(1)) + } yield { + justBefore should contain(userParty(1)) + atExpiry shouldBe empty + } + } + + "cap the ignore duration at maxIgnoreDuration" in { + for { + store <- mkStore() + // 0s => 1s, 1s => 2s, 3s => 4s, 7s => 4s (capped), 11s => 4s (capped) => expires at 15s + _ <- MonadUtil.sequentialTraverse(Seq(0L, 1L, 3L, 7L, 11L))(n => + store.addParties(Seq(userParty(1)), atSeconds(n)) + ) + justBefore <- store.listParties(atSeconds(15) - 1) + atExpiry <- store.listParties(atSeconds(15)) + } yield { + justBefore should contain(userParty(1)) + atExpiry shouldBe empty + } + } + + "is idempotent when replayed with the same timestamp" in { + for { + store <- mkStore() + // DbStorage.update may retry the statement, which must not double the duration: + // the expiry must stay at 1s, not move to 2s + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) + justBefore <- store.listParties(atSeconds(1) - 1) + atExpiry <- store.listParties(atSeconds(1)) + } yield { + justBefore should contain(userParty(1)) + atExpiry shouldBe empty + } + } + + "restart the expiry window from the latest marking" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) // 1s => expires at 1s + _ <- store.addParties(Seq(userParty(1)), atSeconds(2)) // 2s from 2s => expires at 4s + atOldExpiry <- store.listParties(atSeconds(1)) + justBefore <- store.listParties(atSeconds(4) - 1) + atExpiry <- store.listParties(atSeconds(4)) + } yield { + atOldExpiry should contain(userParty(1)) + justBefore should contain(userParty(1)) + atExpiry shouldBe empty + } + } + + "deduplicate parties within a single call" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1), userParty(1)), atSeconds(0)) + justBefore <- store.listParties(atSeconds(1) - 1) + atExpiry <- store.listParties(atSeconds(1)) + } yield { + justBefore should contain(userParty(1)) + atExpiry shouldBe empty + } + } + + "apply the insert and the doubling branch independently within one call" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) // expires at 1s + // party 1's window has just elapsed => doubles to 2s => expires at 3s + // party 2 is new => base duration => expires at 2s + _ <- store.addParties(Seq(userParty(1), userParty(2)), atSeconds(1)) + beforeParty2Expiry <- store.listParties(atSeconds(2) - 1) + atParty2Expiry <- store.listParties(atSeconds(2)) + atParty1Expiry <- store.listParties(atSeconds(3)) + } yield { + beforeParty2Expiry should contain theSameElementsAs Seq(userParty(1), userParty(2)) + atParty2Expiry should contain theSameElementsAs Seq(userParty(1)) + atParty1Expiry shouldBe empty + } + } + + "keep the later expiry when a marking arrives out of order" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1)), atSeconds(2)) // 1s => expires at 3s + // had the stale marking landed, updated_at would rewind to 0s and the duration + // would double to 2s, expiring at 2s instead + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) + atRewoundExpiry <- store.listParties(atSeconds(2)) + atExpiry <- store.listParties(atSeconds(3)) + } yield { + atRewoundExpiry should contain(userParty(1)) + atExpiry shouldBe empty + } + } + + } + + "removeParties" should { + + "remove only the given parties" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1), userParty(2), userParty(3)), atSeconds(0)) + deleted <- store.removeParties(Seq(userParty(1), userParty(3))) + parties <- store.listParties(atSeconds(0)) + } yield { + deleted shouldBe 2 + parties should contain theSameElementsAs Seq(userParty(2)) + } + } + + "be a no-op for unknown parties" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) + deleted <- store.removeParties(Seq(userParty(99))) + parties <- store.listParties(atSeconds(0)) + } yield { + deleted shouldBe 0 + parties should contain(userParty(1)) + } + } + + "be a no-op for an empty sequence" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) + deleted <- store.removeParties(Seq.empty) + parties <- store.listParties(atSeconds(0)) + } yield { + deleted shouldBe 0 + parties should contain(userParty(1)) + } + } + + "reset the backoff, so a re-added party starts from the base duration again" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) // 1s => expires at 1s + _ <- store.addParties(Seq(userParty(1)), atSeconds(1)) // 2s => expires at 3s + deleted <- store.removeParties(Seq(userParty(1))) + afterRemoval <- store.listParties(atSeconds(1)) + _ <- store.addParties(Seq(userParty(1)), atSeconds(3)) + justBefore <- store.listParties(atSeconds(4) - 1) + atExpiry <- store.listParties(atSeconds(4)) + } yield { + deleted shouldBe 1 + afterRemoval shouldBe empty + justBefore should contain(userParty(1)) + atExpiry shouldBe empty + } + } + + "remove parties regardless of which store recorded them" in { + for { + store1 <- mkStore(storeDescriptor) + store2 <- mkStore(storeDescriptor2) + _ <- store1.addParties(Seq(userParty(1)), atSeconds(0)) + deleted <- store2.removeParties(Seq(userParty(1))) + parties <- store1.listParties(atSeconds(0)) + } yield { + deleted shouldBe 1 + parties shouldBe empty + } + } + } + + "removePartiesUpToStoreId" should { + + "remove every party recorded at or below the given store id" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1), userParty(2)), atSeconds(0)) + deleted <- store.removePartiesUpToStoreId(store.storeId.toLong) + parties <- store.listParties(atSeconds(0)) + } yield { + deleted shouldBe 2 + parties shouldBe empty + } + } + + "leave parties recorded by a later store id in place" in { + for { + store1 <- mkStore(storeDescriptor) + store2 <- mkStore(storeDescriptor2) + _ = store1.storeId should be < store2.storeId + _ <- store1.addParties(Seq(userParty(1)), atSeconds(0)) + _ <- store2.addParties(Seq(userParty(2)), atSeconds(0)) + deleted <- store1.removePartiesUpToStoreId(store1.storeId.toLong) + remaining <- store2.listParties(atSeconds(0)) + } yield { + deleted shouldBe 1 + remaining should contain theSameElementsAs Seq(userParty(2)) + } + } + } + + "listParties" should { + + "only return entries whose ignore window has not elapsed" in { + for { + store <- mkStore() + _ <- store.addParties(Seq(userParty(1)), atSeconds(0)) // expires at 1s + _ <- store.addParties(Seq(userParty(2)), atSeconds(1)) // expires at 2s + parties <- store.listParties(atSeconds(1)) + } yield { + parties should contain theSameElementsAs Seq(userParty(2)) + } + } + + "return entries recorded by any store" in { + for { + store1 <- mkStore(storeDescriptor) + store2 <- mkStore(storeDescriptor2) + _ <- store1.addParties(Seq(userParty(1)), atSeconds(0)) + _ <- store2.addParties(Seq(userParty(2)), atSeconds(0)) + parties1 <- store1.listParties(atSeconds(0)) + parties2 <- store2.listParties(atSeconds(0)) + } yield { + parties1 should contain theSameElementsAs Seq(userParty(1), userParty(2)) + parties2 should contain theSameElementsAs Seq(userParty(1), userParty(2)) + } + } + } + } + + override protected def cleanDb( + storage: DbStorage + )(implicit traceContext: TraceContext): FutureUnlessShutdown[?] = + resetAllAppTables(storage) +} diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SpliceDbTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SpliceDbTest.scala index 0a1d07bac9..42c6268f59 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SpliceDbTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/store/db/SpliceDbTest.scala @@ -102,7 +102,8 @@ trait SpliceDbTest extends DbTest with BeforeAndAfterAll { this: Suite => key_value_store, acs_incremental_snapshot_data_next, acs_incremental_snapshot_data_backfill, - acs_incremental_snapshot + acs_incremental_snapshot, + dso_unavailable_parties RESTART IDENTITY CASCADE""".asUpdate _ <- debugPrintPgActivity() } yield (), diff --git a/test-full-class-names-non-integration.log b/test-full-class-names-non-integration.log index 3c16adfde2..01a9cacf41 100644 --- a/test-full-class-names-non-integration.log +++ b/test-full-class-names-non-integration.log @@ -29,6 +29,7 @@ org.lfdecentralizedtrust.splice.scan.store.DbScanAppRewardsStoreTest org.lfdecentralizedtrust.splice.scan.store.ScanEventStoreTest org.lfdecentralizedtrust.splice.scan.store.bulk.ZstdTest org.lfdecentralizedtrust.splice.scan.store.db.CheckMetaVersionsTest +org.lfdecentralizedtrust.splice.store.DbUnavailablePartiesStoreTest org.lfdecentralizedtrust.splice.store.DomainTimeStoreTest org.lfdecentralizedtrust.splice.store.InMemorySynchronizerStoreTest org.lfdecentralizedtrust.splice.store.KeyValueStoreTest From 24bf8a4a9ad9ab0853cfa1ee3208687ec695d5da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Tue, 18 Aug 2026 10:58:32 +0200 Subject: [PATCH 253/329] recreate node pools on machine type changes to work around a provider bug (#6819) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Mateusz Błażejewski --- cluster/pulumi/cluster/src/nodePools.ts | 100 +++++++++++++----------- 1 file changed, 56 insertions(+), 44 deletions(-) diff --git a/cluster/pulumi/cluster/src/nodePools.ts b/cluster/pulumi/cluster/src/nodePools.ts index bcaaab6170..5554727595 100644 --- a/cluster/pulumi/cluster/src/nodePools.ts +++ b/cluster/pulumi/cluster/src/nodePools.ts @@ -24,26 +24,32 @@ export async function installNodePools(): Promise { ...gkeClusterConfig.nodePools.additionalInfra, ]); - new gcp.container.NodePool('gke-node-pool', { - cluster, - nodeConfig: { - machineType: 'e2-standard-4', - taints: [ - { - effect: 'NO_SCHEDULE', - key: 'components.gke.io/gke-managed-components', - value: 'true', - }, - ], - loggingVariant: 'DEFAULT', - }, - nodeLocations: nodePoolComputeZone ? [nodePoolComputeZone] : undefined, - initialNodeCount: 1, - autoscaling: { - minNodeCount: 1, - maxNodeCount: 3, + new gcp.container.NodePool( + 'gke-node-pool', + { + cluster, + nodeConfig: { + machineType: 'e2-standard-4', + taints: [ + { + effect: 'NO_SCHEDULE', + key: 'components.gke.io/gke-managed-components', + value: 'true', + }, + ], + loggingVariant: 'DEFAULT', + }, + nodeLocations: nodePoolComputeZone ? [nodePoolComputeZone] : undefined, + initialNodeCount: 1, + autoscaling: { + minNodeCount: 1, + maxNodeCount: 3, + }, }, - }); + { + replaceOnChanges: ['nodeConfig.machineType'], + } + ); } function installAppsNodePools( @@ -57,34 +63,40 @@ function installAppsNodePools( index === 0 ? 'cn-apps-node-pool-hd' // for backwards compat : `cn-apps-node-pool-${index}-hd`; - return new gcp.container.NodePool(name, { - cluster, - nodeConfig: { - machineType: config.nodeType, - bootDisk: { - diskType: 'hyperdisk-balanced', - sizeGb: config.bootDiskSizeGb || 100, - }, - taints: [ - { - effect: 'NO_SCHEDULE', - key: 'cn_apps', - value: 'true', + return new gcp.container.NodePool( + name, + { + cluster, + nodeConfig: { + machineType: config.nodeType, + bootDisk: { + diskType: 'hyperdisk-balanced', + sizeGb: config.bootDiskSizeGb || 100, }, - ], - labels: { - cn_apps: 'hyperdisk', - ...config.labels, + taints: [ + { + effect: 'NO_SCHEDULE', + key: 'cn_apps', + value: 'true', + }, + ], + labels: { + cn_apps: 'hyperdisk', + ...config.labels, + }, + loggingVariant: 'DEFAULT', }, - loggingVariant: 'DEFAULT', + nodeLocations: + config.zones === '*' + ? allZones + : (config.zones ?? (defaultZone !== undefined ? [defaultZone] : undefined)), + initialNodeCount: 0, + autoscaling: autoscalingConfigOf(config), }, - nodeLocations: - config.zones === '*' - ? allZones - : (config.zones ?? (defaultZone !== undefined ? [defaultZone] : undefined)), - initialNodeCount: 0, - autoscaling: autoscalingConfigOf(config), - }); + { + replaceOnChanges: ['nodeConfig.machineType'], + } + ); }); } From 2982dc7a602c6aec620d4a0eed8e3731b81d343e Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Tue, 18 Aug 2026 11:06:07 +0200 Subject: [PATCH 254/329] Fix gcloud link in Enforced requests alert (#6820) Signed-off-by: Julien Tinguely --- cluster/expected/observability/expected.json | 2 +- .../grafana-alerting/istio-rate-limiting_alerts.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 854a5dec8e..1bafd28264 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -83,7 +83,7 @@ "deployment_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: deployments\n folder: canton-network\n interval: 1m\n rules:\n - uid: adkhl6u18pqtce\n title: Failing Stacks\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n exemplar: true\n expr: splice_deployment_pulumi_stack_status{state=\"failed\"}\n format: time_series\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: stacks_active\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: D\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - D\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: QP_wDqDnz\n panelId: 11\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"11\"\n description: The pulumi operator failed to update the {{ $labels.stack }} stack. Check the logs for the deployment.\n runbook_url: \"\"\n severity: critical\n summary: '{{ $labels.stack }} stack failed to update'\n labels:\n \"\": \"\"\n isPaused: false\n - uid: bdndg5g3x4kxsf\n title: Deployments running\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: splice_deployment_pulumi_stack_condition{type=\"Reconciling\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n dashboardUid: QP_wDqDnz\n panelId: 27\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"27\"\n description: A deployment is currently running for the {{ $labels.stack }} stack.\n runbook_url: \"\"\n severity: info\n summary: Stack {{ $labels.stack }} is being updated\n labels:\n \"\": \"\"\n isPaused: false\n", "dso_missed_confirmations_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: dso party missed confirmations\n folder: canton-network\n interval: 1m\n rules:\n - uid: ddso1miss0conf0a\n title: DSO Party Missed Confirmations\n condition: missed confirmations threshold exceeded\n data:\n - refId: missed_confirmations\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (party) (increase(daml_mediator_timeout_non_responsive_participants_total{party=~\"DSO::.*\"}[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: missed_confirmations\n - refId: missed confirmations threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: missed_confirmations\n intervalMs: 1000\n maxDataPoints: 43200\n refId: missed confirmations threshold exceeded\n type: threshold\n dashboardUid: cnck4jp\n panelId: 1\n noDataState: OK\n execErrState: Alerting\n for: 0m\n annotations:\n __dashboardUid__: cnck4jp\n __panelId__: \"1\"\n severity: critical\n summary: The DSO party missed {{ index $values \"missed_confirmations\" }}, more than 0 of its confirmations over the last 10 minutes.\n labels: {}\n isPaused: false\n", "extra_k8s_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: storage\n folder: k8s\n interval: 5m\n rules:\n - uid: adlrbu5kog0sga\n title: KubePersistentVolumeTooFull\n condition: free_space_below_threshold\n data:\n - refId: free_space\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: kubelet_volume_stats_available_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"} / kubelet_volume_stats_capacity_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: free_space\n - refId: free_space_below_threshold\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.15\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: free_space\n intervalMs: 1000\n maxDataPoints: 43200\n refId: free_space_below_threshold\n type: threshold\n dashboardUid: 919b92a8e8041bd567af9edab12c840c\n panelId: 2\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: 919b92a8e8041bd567af9edab12c840c\n __panelId__: \"2\"\n severity: warning\n description: The PersistentVolume claimed by {{ index $labels \"persistentvolumeclaim\" }} in Namespace {{ index $labels \"namespace\" }} is running out of disk space. Currently {{ humanizePercentage (index $values \"free_space\").Value }} is available.\n runbook_url: https://runbooks.prometheus-operator.dev/runbooks/kubernetes/kubepersistentvolumefillingup\n summary: PersistentVolume is too full.\n isPaused: false\n", - "istio-rate-limiting_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: istio-rate-limit\n folder: platform\n interval: 5m\n rules:\n - uid: cfurlpifrmvi8w\n title: Enforced requests\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\"sv.*\"}[$__rate_interval])) by (namespace, pod)\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}} - {{pod}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnr56dj\n panelId: 9\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n runbook_url: \"\"\n severity: warning\n description: Envoy local rate limiting is active, requests are being rejected. Check the \"Istio Rate Limiting\" dashboard.\n summary: Envoy is rate limiting requests in {{ $labels.namespace }} on pod {{ $labels.pod }}.\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22{{ index \"namespace\" }}%22%0A\n isPaused: false\n", + "istio-rate-limiting_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: istio-rate-limit\n folder: platform\n interval: 5m\n rules:\n - uid: cfurlpifrmvi8w\n title: Enforced requests\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\"sv.*\"}[$__rate_interval])) by (namespace, pod)\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}} - {{pod}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnr56dj\n panelId: 9\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n runbook_url: \"\"\n severity: warning\n description: Envoy local rate limiting is active, requests are being rejected. Check the \"Istio Rate Limiting\" dashboard.\n summary: Envoy is rate limiting requests in {{ $labels.namespace }} on pod {{ $labels.pod }}.\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429%0AjsonPayload.authority:%22scan.{{ index $labels \"namespace\" }}.%22\n isPaused: false\n", "load-tester_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: load-tester\n folder: canton-network\n interval: 1m\n rules:\n - uid: a1dbc4e8-7941-4351-9a14-f8573fd2be2b\n title: K6 Request Failure Rate Exceeded\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: B\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total{expected_response=\"false\"}) or on() vector(0)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: ($B / $A) * 100\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 50\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 1m\n annotations:\n description: ''\n runbook_url: ''\n summary: The k6 load tester experienced HTTP request failures at a rate past the acceptable threshold\n labels:\n gcloud_filter: ''\n isPaused: false\n - uid: ddogbytusmz9cb\n title: K6 Throughput Below Threshold\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: rate(k6_transfers_completed_total{scenario=\"generate_load\"}[$__rate_interval])\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.123\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: B\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n - refId: B\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n hide: false\n reducer: mean\n refId: B\n type: reduce\n dashboardUid: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n panelId: 28\n noDataState: Alerting\n execErrState: Alerting\n for: 15m # give some time for restarts\n annotations:\n __dashboardUid__: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n __panelId__: \"28\"\n severity: warning\n description: ''\n runbook_url: ''\n summary: Transaction rate from the k6 load tester is lower than expected threshold\n labels:\n '': ''\n isPaused: false\n", "mining-rounds_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: mining-rounds\n folder: canton-network\n interval: 1m\n rules:\n - uid: ae74w21qca2o0e\n title: Open Mining Rounds Not Advancing\n condition: No new round\n data:\n - refId: Round number diff\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: max(max(max_over_time(splice_sv_dso_store_latest_open_mining_round[30m])) by (namespace) - min(min_over_time(splice_sv_dso_store_latest_open_mining_round[30m])) by (namespace))\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Round number diff\n useBackend: false\n - refId: No new round\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.9\n type: gt\n datasource:\n type: __expr__\n uid: __expr__\n expression: Round number diff\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No new round\n type: threshold\n dashboardUid: ed94a332-4fa7-47f8-982b-fc997381175b\n panelId: 1\n noDataState: Alerting\n execErrState: OK\n for: 1m\n annotations:\n __dashboardUid__: ed94a332-4fa7-47f8-982b-fc997381175b\n __panelId__: \"1\"\n runbook_url: \"\"\n summary: \"The open mining rounds have not advanced in the last 30m\"\n description: 'None of our SV apps have seen the open mining round advancing in the last 30m. Either all of our SVs or a high enough number of SVs to break BFT guarantees are failing'\n severity: critical\n labels:\n priority: high\n isPaused: false\n - uid: ae74w21qca2o0d\n title: Issuing Rounds Not Advancing\n condition: No new round\n data:\n - refId: Round number diff\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: max(max(max_over_time(splice_sv_dso_store_latest_issuing_mining_round[30m])) by (namespace) - min(min_over_time(splice_sv_dso_store_latest_issuing_mining_round[30m])) by (namespace))\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Round number diff\n useBackend: false\n - refId: No new round\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.9\n type: gt\n datasource:\n type: __expr__\n uid: __expr__\n expression: Round number diff\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No new round\n type: threshold\n dashboardUid: ed94a332-4fa7-47f8-982b-fc997381175b\n panelId: 1\n noDataState: Alerting\n execErrState: OK\n for: 1m\n annotations:\n __dashboardUid__: ed94a332-4fa7-47f8-982b-fc997381175b\n __panelId__: \"2\"\n runbook_url: \"\"\n summary: \"The issuing mining rounds have not advanced in the last 30m\"\n description: 'None of our SV apps have seen the issuing mining round advancing in the last 30m. Either all of our SVs or a high enough number of SVs to break BFT guarantees are failing'\n severity: critical\n labels:\n priority: high\n isPaused: false\n", "mute_time_intervals.yaml": "apiVersion: 1\nmuteTimes:\n - orgId: 1\n name: sv1-mute\n time_intervals:\n - times:\n - start_time: '18:00'\n end_time: '24:00'\n weekdays:\n - wednesday\n location: UTC\n - orgId: 1\n name: sv-runbook-mute\n time_intervals:\n - times:\n - start_time: '18:00'\n end_time: '24:00'\n weekdays:\n - wednesday\n location: UTC\n - times:\n - start_time: '00:00'\n end_time: '06:00'\n - start_time: '12:00'\n end_time: '13:00'\n weekdays:\n - thursday\n location: UTC\n", diff --git a/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml b/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml index 3766cfc8ef..345a03e9fc 100644 --- a/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml +++ b/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml @@ -63,5 +63,5 @@ groups: summary: Envoy is rate limiting requests in {{ $labels.namespace }} on pod {{ $labels.pod }}. labels: "": "" - gcloud_filter: resource.labels.namespace_name=%22{{ index "namespace" }}%22%0A + gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429%0AjsonPayload.authority:%22scan.{{ index $labels "namespace" }}.%22 isPaused: false From c4940711470533ab813a5ea9b31dd08420cb0cc9 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Tue, 18 Aug 2026 11:34:23 +0200 Subject: [PATCH 255/329] extend splice rate limits (#6812) * Extend splice rate limits Valid for scan and sv app Add global rate limiter (previosly we rate limited only each individual operation), which is enabled by default. Add the ability to rate limit also per ip for each oepration (disabled by default). The global rate limiter has this option enabled by default. Extend the rate limiter to check a longer interval (60s). The previous behavior was checking only the last 1s, this is still in place and works as a burst limiter, allowing for shorter burts but the longer interval enforces a lower limit for the configured interval. [ci] Signed-off-by: Nicu Reut --- .../splice/config/SpliceConfig.scala | 24 +- .../test/resources/include/scans/_scan.conf | 10 +- .../src/test/resources/include/svs/_sv.conf | 10 +- .../tests/ScanIntegrationTest.scala | 7 +- ...dCliTestDataTimeBasedIntegrationTest.scala | 6 +- .../concurrent/BurstyRateLimiterFactory.java | 27 + .../splice/config/RateLimitersConfig.scala | 37 +- .../config/SpliceParametersConfig.scala | 4 +- .../splice/http/ClientIpDirectives.scala | 64 ++ .../splice/http/HttpRateLimiter.scala | 148 ++++- .../splice/util/SpliceRateLimiter.scala | 226 ++++++- .../splice/http/HttpRateLimiterTest.scala | 554 ++++++++++++++++++ .../splice/util/SpliceRateLimiterTest.scala | 183 +++++- cluster/images/scan-app/app.conf | 14 +- cluster/images/sv-app/app.conf | 14 +- docs/src/release_notes_upcoming.rst | 34 ++ test-full-class-names-non-integration.log | 1 + 17 files changed, 1304 insertions(+), 59 deletions(-) create mode 100644 apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java create mode 100644 apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala create mode 100644 apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala diff --git a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala index 57f81e1ce7..7d76d69eb7 100644 --- a/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala +++ b/apps/app/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceConfig.scala @@ -33,7 +33,11 @@ import org.lfdecentralizedtrust.splice.splitwell.config.{ import org.lfdecentralizedtrust.splice.sv.config.* import org.lfdecentralizedtrust.splice.sv.SvAppClientConfig import org.lfdecentralizedtrust.splice.sv.config.SvOnboardingConfig.FoundDso -import org.lfdecentralizedtrust.splice.util.{Codec, SpliceRateLimitConfig} +import org.lfdecentralizedtrust.splice.util.{ + Codec, + PerAttributeRateLimitConfig, + SpliceRateLimitConfig, +} import org.lfdecentralizedtrust.splice.validator.config.* import org.lfdecentralizedtrust.splice.wallet.config.{ AppRewardBeneficiaryConfig, @@ -427,10 +431,15 @@ object SpliceConfig { deriveReader[SpliceCachingConfigs] implicit val spliceParametersConfig: ConfigReader[SpliceParametersConfig] = deriveReader[SpliceParametersConfig] + implicit val spliceRateLimiterSimpleConfig: ConfigReader[SpliceRateLimitConfig.Simple] = + deriveReader[SpliceRateLimitConfig.Simple] + implicit val clientIpRateLimitConfig: ConfigReader[PerAttributeRateLimitConfig] = + deriveReader[PerAttributeRateLimitConfig] + implicit val spliceRateLimiterWithPerClientIpConfig + : ConfigReader[SpliceRateLimitConfig.WithPerClientIp] = + deriveReader[SpliceRateLimitConfig.WithPerClientIp] implicit val rateLimitersConfig: ConfigReader[RateLimitersConfig] = deriveReader[RateLimitersConfig] - implicit val spliceRateLimiterConfig: ConfigReader[SpliceRateLimitConfig] = - deriveReader[SpliceRateLimitConfig] implicit val enabledFeaturesConfigReader: ConfigReader[EnabledFeaturesConfig] = deriveReader[EnabledFeaturesConfig] implicit val splicePostgresConfigReader: ConfigReader[SplicePostgresConfig] = @@ -948,10 +957,15 @@ object SpliceConfig { implicit val spliceParametersConfig: ConfigWriter[SpliceParametersConfig] = deriveWriter[SpliceParametersConfig] + implicit val spliceRateLimiterSimpleConfig: ConfigWriter[SpliceRateLimitConfig.Simple] = + deriveWriter[SpliceRateLimitConfig.Simple] + implicit val clientIpRateLimitConfig: ConfigWriter[PerAttributeRateLimitConfig] = + deriveWriter[PerAttributeRateLimitConfig] + implicit val spliceRateLimiterWithPerClientIpConfig + : ConfigWriter[SpliceRateLimitConfig.WithPerClientIp] = + deriveWriter[SpliceRateLimitConfig.WithPerClientIp] implicit val rateLimitersConfig: ConfigWriter[RateLimitersConfig] = deriveWriter[RateLimitersConfig] - implicit val spliceRateLimiterConfig: ConfigWriter[SpliceRateLimitConfig] = - deriveWriter[SpliceRateLimitConfig] implicit val enabledFeaturesConfigWriter: ConfigWriter[EnabledFeaturesConfig] = deriveWriter[EnabledFeaturesConfig] diff --git a/apps/app/src/test/resources/include/scans/_scan.conf b/apps/app/src/test/resources/include/scans/_scan.conf index 147faf2366..b623e19de3 100644 --- a/apps/app/src/test/resources/include/scans/_scan.conf +++ b/apps/app/src/test/resources/include/scans/_scan.conf @@ -31,8 +31,14 @@ getAcsSnapshot = 1 minute } rate-limiting { - default { - rate-per-second = 200 + # the global limiter caps the total request rate across all operations; disabled in + # tests so that it doesn't interfere with the per-operation limits exercised here. + global { + rate-per-second = 1 + enabled = false + per-client-ip { + enabled = false + } } rate-limiters { getAcsSnapshot.rate-per-second = 2 diff --git a/apps/app/src/test/resources/include/svs/_sv.conf b/apps/app/src/test/resources/include/svs/_sv.conf index a08a5c59c4..f13c2b775f 100644 --- a/apps/app/src/test/resources/include/svs/_sv.conf +++ b/apps/app/src/test/resources/include/svs/_sv.conf @@ -64,8 +64,14 @@ onboardSvPartyMigrationAuthorize = 5 minutes } rate-limiting { - default { - rate-per-second = 200 + # the global limiter caps the total request rate across all operations; disabled in + # tests so that it doesn't interfere with the per-operation limits exercised here. + global { + rate-per-second = 1 + enabled = false + per-client-ip { + enabled = false + } } rate-limiters { prepareValidatorOnboarding.rate-per-second = 2 diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala index a934e1469c..0bbcb18f67 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/ScanIntegrationTest.scala @@ -76,9 +76,10 @@ class ScanIntegrationTest // used for the rate limit test rateLimiting = config.parameters.rateLimiting.copy( rateLimiters = - config.parameters.rateLimiting.rateLimiters + ("listAnsEntries" -> SpliceRateLimitConfig( - ratePerSecond = 5 - )) + config.parameters.rateLimiting.rateLimiters + ("listAnsEntries" -> SpliceRateLimitConfig + .WithPerClientIp( + ratePerSecond = 5 + )) ), ), ) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala index a086e43182..bd8adda202 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/TokenStandardCliTestDataTimeBasedIntegrationTest.scala @@ -144,7 +144,11 @@ class TokenStandardCliTestDataTimeBasedIntegrationTest updateAllScanAppConfigs_(config => config.copy(parameters = config.parameters.copy(rateLimiting = - RateLimitersConfig(SpliceRateLimitConfig(enabled = false, 1), Map.empty) + RateLimitersConfig( + default = SpliceRateLimitConfig.WithPerClientIp(enabled = false, 1), + rateLimiters = Map.empty, + global = SpliceRateLimitConfig.WithPerClientIp(enabled = false, 1), + ) ) ) )(config) diff --git a/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java b/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java new file mode 100644 index 0000000000..cd94bd5790 --- /dev/null +++ b/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java @@ -0,0 +1,27 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.google.common.util.concurrent; + +/** + * Shim that constructs a Guava {@link RateLimiter} backed by {@code SmoothBursty} with a custom + * maximum burst duration. It lives in this package because the relevant {@code + * SmoothRateLimiter.SmoothBursty} constructor is package-private. + */ +public final class BurstyRateLimiterFactory { + + private BurstyRateLimiterFactory() { + } + + /** + * Creates a bursty {@link RateLimiter} that sustains {@code permitsPerSecond} on average while + * allowing bursts of up to {@code permitsPerSecond * maxBurstSeconds} permits after idle periods. + */ + public static RateLimiter create(double permitsPerSecond, double maxBurstSeconds) { + RateLimiter rateLimiter = + new SmoothRateLimiter.SmoothBursty( + RateLimiter.SleepingStopwatch.createFromSystemTimer(), maxBurstSeconds); + rateLimiter.setRate(permitsPerSecond); + return rateLimiter; + } +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala index 971de2ae5f..da58ba409a 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala @@ -3,11 +3,40 @@ package org.lfdecentralizedtrust.splice.config -import org.lfdecentralizedtrust.splice.util.SpliceRateLimitConfig +import org.lfdecentralizedtrust.splice.util.{PerAttributeRateLimitConfig, SpliceRateLimitConfig} case class RateLimitersConfig( - default: SpliceRateLimitConfig, - rateLimiters: Map[String, SpliceRateLimitConfig], + /** Overall rate limiter applied per operation. Used when there is no operation-specific override + * in `rateLimiters`. The embedded `perClientIp` limiter is disabled by default; enable it to + * additionally limit per client IP. + */ + default: SpliceRateLimitConfig.WithPerClientIp = + SpliceRateLimitConfig.WithPerClientIp(ratePerSecond = 200), + /** Per-operation overrides of the overall `default` rate limiter. */ + rateLimiters: Map[String, SpliceRateLimitConfig.WithPerClientIp] = Map.empty, + global: SpliceRateLimitConfig.WithPerClientIp = RateLimitersConfig.DefaultGlobal, + /** Name of the HTTP header set by a trusted reverse proxy that carries the real client IP. This header must be set - and any + * client-provided value overwritten - by infrastructure the client cannot bypass, otherwise it + * can be spoofed. When present and parseable as an IP literal it takes precedence over the + * client-controlled `X-Forwarded-For`/`X-Real-Ip` headers. Set to an empty string to disable + * trusting a proxy header and only rely on `X-Forwarded-For`/`X-Real-Ip`/the remote address. + */ + trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, ) { - def forRateLimiter(name: String): SpliceRateLimitConfig = rateLimiters.getOrElse(name, default) + def forRateLimiter(name: String): SpliceRateLimitConfig.WithPerClientIp = + rateLimiters.getOrElse(name, default) +} + +object RateLimitersConfig { + + /** Header set by the Envoy sidecar/ingress (Istio) to the trusted external client address that + * Envoy computes from its trusted-hops configuration. + */ + val DefaultTrustedClientIpHeader: String = "x-envoy-external-address" + + private val DefaultGlobal: SpliceRateLimitConfig.WithPerClientIp = + SpliceRateLimitConfig.WithPerClientIp( + ratePerSecond = 200, + perClientIp = PerAttributeRateLimitConfig(), + ) } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala index 42c842df85..733aed37ec 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/SpliceParametersConfig.scala @@ -6,7 +6,6 @@ package org.lfdecentralizedtrust.splice.config import com.digitalasset.canton.config.RequireTypes.NonNegativeInt import com.digitalasset.canton.config.* import org.lfdecentralizedtrust.splice.store.{ChoiceContextContractFetcher, HardLimit, Limit} -import org.lfdecentralizedtrust.splice.util.SpliceRateLimitConfig final case class SpliceParametersConfig( batching: BatchingConfig = BatchingConfig(), @@ -17,8 +16,7 @@ final case class SpliceParametersConfig( // Do not define any defaults on the class containing the `SpliceParametersConfig` as they'll be overwritten. // Do it instead on the app.conf file in `cluster/images/${the_app}/app.conf` customTimeouts: Map[String, NonNegativeFiniteDuration] = Map.empty, - rateLimiting: RateLimitersConfig = - RateLimitersConfig(SpliceRateLimitConfig(enabled = true, ratePerSecond = 200), Map.empty), + rateLimiting: RateLimitersConfig = RateLimitersConfig(), // Configuration for the circuit breaker for ledger API command submissions. circuitBreakers: CircuitBreakersConfig = CircuitBreakersConfig(), enabledFeatures: EnabledFeaturesConfig = EnabledFeaturesConfig(), diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala new file mode 100644 index 0000000000..c4f620f484 --- /dev/null +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala @@ -0,0 +1,64 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.http + +import org.apache.pekko.http.scaladsl.model.headers.{`X-Forwarded-For`, `X-Real-Ip`} +import org.apache.pekko.http.scaladsl.model.RemoteAddress +import org.apache.pekko.http.scaladsl.server.Directive1 +import org.apache.pekko.http.scaladsl.server.Directives.* + +object ClientIpDirectives { + + /** Extracts the address of the client the request originated from, if it can be determined. + * + * The address is taken from the first of the following sources that yields an address: + * 1. the `trustedClientIpHeader` (if configured and parseable as an IP literal), which is set + * by a trusted reverse proxy and hence cannot be spoofed by the client, + * 1. the client-controlled `X-Forwarded-For` header, + * 1. the client-controlled `X-Real-Ip` header, + * + * @param trustedClientIpHeader + * name of the header set by a trusted reverse proxy, matched case-insensitively. An empty name + * disables trusting a proxy header. + */ + def extractClientIp(trustedClientIpHeader: String): Directive1[Option[RemoteAddress]] = + firstDefined( + trustedClientIp(trustedClientIpHeader), + forwardedForClientIp, + realIpClientIp, + ) + + private def trustedClientIp(headerName: String): Directive1[Option[RemoteAddress]] = { + val trimmedHeaderName = headerName.trim + if (trimmedHeaderName.isEmpty) provide(None) + else + // matched case-insensitively (and locale independently) as the configured header name is not + // required to be lowercase + optionalHeaderValueByName(trimmedHeaderName).map(_.flatMap(parseIpLiteral)) + } + + private val forwardedForClientIp: Directive1[Option[RemoteAddress]] = { + optionalHeaderValuePF { case `X-Forwarded-For`(Seq(address, _*)) => address } + } + + private val realIpClientIp: Directive1[Option[RemoteAddress]] = + optionalHeaderValuePF { case `X-Real-Ip`(address) => address } + + /** The value of the first directive that extracts a defined value, [[None]] if there is none. */ + private def firstDefined[A]( + directives: Directive1[Option[A]]* + ): Directive1[Option[A]] = + directives.foldRight(provide(Option.empty[A])) { (directive, fallback) => + directive.flatMap { + case defined @ Some(_) => provide(defined) + case None => fallback + } + } + + private def parseIpLiteral(value: String): Option[RemoteAddress] = + `X-Real-Ip` + .parseFromValueString(value.trim) + .toOption + .map(_.address) +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala index 2dc1c8ad1f..520150944f 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala @@ -6,11 +6,16 @@ package org.lfdecentralizedtrust.splice.http import com.daml.metrics.api.MetricHandle.LabeledMetricsFactory import com.daml.metrics.api.MetricsContext import com.digitalasset.canton.logging.TracedLogger -import org.apache.pekko.http.scaladsl.model.{HttpEntity, StatusCodes} -import org.apache.pekko.http.scaladsl.server.Directive0 +import org.apache.pekko.http.scaladsl.model.{HttpEntity, RemoteAddress, StatusCodes} +import org.apache.pekko.http.scaladsl.server.{Directive0, Directive1} import org.lfdecentralizedtrust.splice.config.RateLimitersConfig -import org.lfdecentralizedtrust.splice.util.{SpliceRateLimitMetrics, SpliceRateLimiter} +import org.lfdecentralizedtrust.splice.util.{ + PerAttributeRateLimiter, + SpliceRateLimiter, + SpliceRateLimitMetrics, +} +import java.net.{Inet6Address, InetAddress} import java.time.Instant class HttpRateLimiter( @@ -19,12 +24,20 @@ class HttpRateLimiter( logger: TracedLogger, ) extends AutoCloseable { - // need to cache it as the pekko reoutes get evaluated for each request - private val rateLimiters = scala.collection.concurrent.TrieMap[String, SpliceRateLimiter]() + // need to cache it as the pekko routes get evaluated for each request + // keyed by (service, operation) as the same operation name can be used by multiple services + private val rateLimiters = + scala.collection.concurrent.TrieMap[ + (String, String), + (SpliceRateLimiter, PerAttributeRateLimiter), + ]() private val metrics = scala.collection.concurrent.TrieMap[String, SpliceRateLimitMetrics]() - def withRateLimit(service: String)(operation: String): Directive0 = { - val rateLimiterMetrics = metrics.getOrElseUpdate( + private val trustedClientIpHeader: String = + config.trustedClientIpHeader.trim + + private def metricsFor(service: String): SpliceRateLimitMetrics = + metrics.getOrElseUpdate( service, SpliceRateLimitMetrics(metricsFactory, logger)( MetricsContext( @@ -32,22 +45,79 @@ class HttpRateLimiter( ) ), ) - val rateLimiter = rateLimiters.getOrElseUpdate( - operation, + + // the rate limiter has a cold start, to avoid the first request being rejected + // we enforce the rate limit only after 1 second + private def enforceAfter = Instant.now().plusSeconds(1) + + private val globalRateLimiter: (SpliceRateLimiter, PerAttributeRateLimiter) = { + val globalMetrics = metricsFor(HttpRateLimiter.GlobalService) + ( new SpliceRateLimiter( - operation, - config.forRateLimiter(operation), - rateLimiterMetrics, - // the rate limiter has a cold start, to avoid the first request being rejected - // we enforce the rate limit only after 1 second - Instant.now().plusSeconds(1), + HttpRateLimiter.GlobalLimiter, + config.global, + globalMetrics, + enforceAfter, + ), + new PerAttributeRateLimiter( + HttpRateLimiter.GlobalLimiter, + HttpRateLimiter.ClientIpAttribute, + config.global, + config.global.perClientIp, + globalMetrics, + enforceAfter, + logger, ), ) + } + + private def operationRateLimiter( + service: String, + operation: String, + ): (SpliceRateLimiter, PerAttributeRateLimiter) = + rateLimiters.getOrElseUpdate( + (service, operation), { + val rateLimiterMetrics = metricsFor(service) + val operationConfig = config.forRateLimiter(operation) + ( + new SpliceRateLimiter( + operation, + operationConfig, + rateLimiterMetrics, + enforceAfter, + ), + new PerAttributeRateLimiter( + operation, + HttpRateLimiter.ClientIpAttribute, + operationConfig, + operationConfig.perClientIp, + rateLimiterMetrics, + enforceAfter, + logger, + ), + ) + }, + ) + + def withRateLimit(service: String)(operation: String): Directive0 = { + val (globalLimiter, globalClientIpLimiter) = globalRateLimiter + val (operationLimiter, operationClientIpLimiter) = operationRateLimiter(service, operation) import org.apache.pekko.http.scaladsl.server.Directives.* - extractRequestContext.flatMap { _ => - if (rateLimiter.markRun()) { + HttpRateLimiter.extractClientIpKey(trustedClientIpHeader).flatMap { clientIp => + // The per client IP limiters are checked first (and `&&` short-circuits) so that a request + // rejected because of its own client IP does not consume budget from the shared overall + // limiters. Otherwise a single abusive client could exhaust the overall limits and thereby + // deny service to all other clients. + // Within each of those two groups the narrower per operation limiter is checked before the + // global one, so that a request rejected for its operation does not consume global budget. + val allowed = + operationClientIpLimiter.markRun(clientIp) && + globalClientIpLimiter.markRun(clientIp) && + operationLimiter.markRun() && + globalLimiter.markRun() + if (allowed) { pass } else { complete( @@ -62,3 +132,47 @@ class HttpRateLimiter( def close(): Unit = metrics.view.values.foreach(_.close()) } + +object HttpRateLimiter { + + private val ClientIpAttribute = "client_ip" + + private[splice] val GlobalLimiter = "global" + private[splice] val GlobalService = "global" + + private[splice] def extractClientIpKey( + trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader + ): Directive1[Option[String]] = + ClientIpDirectives + .extractClientIp(trustedClientIpHeader) + .map(_.collect { case RemoteAddress.IP(ip, _) => rateLimitKey(ip) }) + + /** Single clients are typically assigned a whole IPv6 /64 (or larger) network, so limiting per + * full IPv6 address would allow a single client to trivially bypass the per client IP limit. + * IPv6 addresses are therefore grouped by their /64 prefix, IPv4 addresses are used as is. + */ + private def rateLimitKey(address: InetAddress): String = address match { + case ipv6: Inet6Address => + val bytes = ipv6.getAddress + ipv4Mapped(bytes) match { + // connections accepted on a dual stack socket can report IPv4 clients as IPv4-mapped IPv6 + // addresses (e.g. ::ffff:192.0.2.1), those must use the same key as the plain IPv4 address + case Some(ipv4) => ipv4.getHostAddress + case None => + // zero out the lower 64 bits (the interface identifier), keeping the /64 network prefix + // note that this also drops any scope/zone id, which is not meaningful for rate limiting + val prefix = bytes.take(8) ++ Array.fill[Byte](8)(0) + s"${InetAddress.getByAddress(prefix).getHostAddress}/64" + } + case ip => ip.getHostAddress + } + + /** ::ffff:0:0/96, see [[https://www.rfc-editor.org/rfc/rfc4291#section-2.5.5.2]] */ + private val Ipv4MappedPrefix: Seq[Byte] = + Seq.fill[Byte](10)(0) ++ Seq[Byte](0xff.toByte, 0xff.toByte) + + private def ipv4Mapped(bytes: Array[Byte]): Option[InetAddress] = + Option.when(bytes.length == 16 && bytes.startsWith(Ipv4MappedPrefix))( + InetAddress.getByAddress(bytes.drop(12)) + ) +} diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala index 6d6f66019d..6e41c4eab5 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala @@ -3,22 +3,31 @@ package org.lfdecentralizedtrust.splice.util +import com.daml.metrics.CacheMetrics import com.daml.metrics.api.MetricHandle.LabeledMetricsFactory import com.daml.metrics.api.MetricQualification.Saturation import com.daml.metrics.api.{MetricHandle, MetricInfo, MetricsContext} +import com.digitalasset.canton.caching.{CaffeineCache, ConcurrentCache} import com.digitalasset.canton.discard.Implicits.DiscardOps import com.digitalasset.canton.lifecycle.LifeCycle import com.digitalasset.canton.logging.TracedLogger -import com.google.common.util.concurrent.RateLimiter +import com.digitalasset.canton.tracing.TraceContext +import com.github.benmanes.caffeine.cache.{Caffeine, RemovalCause, RemovalListener} +import com.google.common.util.concurrent.{BurstyRateLimiterFactory, RateLimiter} import org.lfdecentralizedtrust.splice.environment.SpliceMetrics -import java.time.Instant +import java.time.{Duration, Instant} import java.util import java.util.Collections +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicLong import scala.concurrent.Future import scala.jdk.CollectionConverters.CollectionHasAsScala -case class SpliceRateLimitMetrics(otelFactory: LabeledMetricsFactory, logger: TracedLogger)(implicit +case class SpliceRateLimitMetrics( + otelFactory: LabeledMetricsFactory, + private val logger: TracedLogger, +)(implicit mc: MetricsContext ) extends AutoCloseable { @@ -53,39 +62,122 @@ case class SpliceRateLimitMetrics(otelFactory: LabeledMetricsFactory, logger: Tr } -case class SpliceRateLimitConfig( +sealed trait SpliceRateLimitConfig { + + def enabled: Boolean + + def ratePerSecond: Double + + def sustainedRatePerSecond: Option[Double] + + def sustainedWindowSeconds: Long +} + +object SpliceRateLimitConfig { + + final case class Simple( + enabled: Boolean = true, + ratePerSecond: Double, + sustainedRatePerSecond: Option[Double] = None, + sustainedWindowSeconds: Long = SpliceRateLimiter.DefaultSustainedWindowSeconds, + ) extends SpliceRateLimitConfig + + final case class WithPerClientIp( + enabled: Boolean = true, + ratePerSecond: Double, + sustainedRatePerSecond: Option[Double] = None, + sustainedWindowSeconds: Long = SpliceRateLimiter.DefaultSustainedWindowSeconds, + perClientIp: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig.Disabled, + ) extends SpliceRateLimitConfig + + def apply( + enabled: Boolean = true, + ratePerSecond: Double, + sustainedRatePerSecond: Option[Double] = None, + sustainedWindowSeconds: Long = SpliceRateLimiter.DefaultSustainedWindowSeconds, + ): Simple = + Simple(enabled, ratePerSecond, sustainedRatePerSecond, sustainedWindowSeconds) +} + +case class PerAttributeRateLimitConfig( enabled: Boolean = true, - ratePerSecond: Double, -) + limit: SpliceRateLimitConfig.Simple = PerAttributeRateLimitConfig.DefaultLimit, + maxAttributeValues: Long = 10000, +) { + + def rateLimitFor(overall: SpliceRateLimitConfig): SpliceRateLimitConfig.Simple = + limit.copy(enabled = enabled && limit.enabled && overall.enabled) +} +object PerAttributeRateLimitConfig { + val DefaultLimit: SpliceRateLimitConfig.Simple = SpliceRateLimitConfig(ratePerSecond = 10) + val Disabled: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig(enabled = false) +} + +object SpliceRateLimiter { + + val GlobalLimiterType = "global" + val PerAttributeLimiterType = "per-attribute" + val UnknownAttributeLimiterType = "unknown-attribute" + + val DefaultSustainedWindowSeconds: Long = 60 + + private[util] def sustainedWindow(config: SpliceRateLimitConfig): Duration = + Duration.ofSeconds(Math.max(1L, config.sustainedWindowSeconds)) +} + +// noinspection UnstableApiUsage class SpliceRateLimiter( name: String, config: SpliceRateLimitConfig, metrics: SpliceRateLimitMetrics, enforceAfter: Instant = Instant.now(), + limiterType: String = SpliceRateLimiter.GlobalLimiterType, + extraLabels: Map[String, String] = Map.empty, + // must be disabled for the per-attribute limiters as they'd all report the same value + // and would explode the number of registered gauges + reportMaxLimit: Boolean = true, ) { - // noinspection UnstableApiUsage - private val limiter = RateLimiter.create(config.ratePerSecond) - // lazy to ensure metrics get registered only if the limiter is actually used - private lazy val rateLimiter = { - metrics - .recordMaxLimit(config.ratePerSecond)( - MetricsContext("limiter" -> name) + private val metricsContext = MetricsContext( + extraLabels ++ Map("limiter" -> name, "limiter_type" -> limiterType) + ) + + // The limiters are created eagerly so that they are already "warm" (i.e. have accumulated their + // burst budget) by the time the limit starts being enforced. They are only created for enabled + // limiters: a disabled limiter is never consulted and its configured rate might not even be a + // valid guava rate (e.g. 0). + // enforces the per-second burst limit (checked over a 1s window) + private val limiter: Option[RateLimiter] = + Option.when(config.enabled)(RateLimiter.create(config.ratePerSecond)) + // enforces the sustained limit over the sustained window, while still allowing bursts within its budget. + private val sustainedLimiter: Option[RateLimiter] = + Option + .when(config.enabled)(config.sustainedRatePerSecond) + .flatten + .map( + BurstyRateLimiterFactory + .create(_, SpliceRateLimiter.sustainedWindow(config).toSeconds.toDouble) ) + // lazy to ensure metrics get registered only if the limiter is actually used + private lazy val rateLimiter: Option[RateLimiter] = { + if (reportMaxLimit) { + metrics + .recordMaxLimit(config.ratePerSecond)(metricsContext) + } limiter } def markRun(): Boolean = { if (config.enabled && Instant.now().isAfter(enforceAfter)) { - val canRun = rateLimiter.tryAcquire() + val canRun = rateLimiter.forall(_.tryAcquire()) && sustainedLimiter.forall(_.tryAcquire()) if (canRun) { metrics.meter.mark()( - MetricsContext("result" -> "accepted", "limiter" -> name) + metricsContext.merge(MetricsContext("result" -> "accepted")) ) } else { metrics.meter.mark()( - MetricsContext("result" -> "rejected", "limiter" -> name) + metricsContext.merge(MetricsContext("result" -> "rejected")) ) } canRun @@ -105,3 +197,105 @@ class SpliceRateLimiter( } } + +class PerAttributeRateLimiter( + name: String, + attribute: String, + config: SpliceRateLimitConfig, + attributeConfig: PerAttributeRateLimitConfig, + metrics: SpliceRateLimitMetrics, + enforceAfter: Instant = Instant.now(), + logger: TracedLogger, +) { + + private val perAttributeConfig = attributeConfig.rateLimitFor(config) + private val isEnabled = perAttributeConfig.enabled && perAttributeConfig.ratePerSecond > 0 + private val attributeLabel = Map("limiter_attribute" -> attribute) + + // evictions by size can happen for every single request (e.g. when a large number of distinct + // attribute values is seen), so the warning is throttled to avoid flooding the logs + private val lastSizeEvictionWarning = + new AtomicLong(System.nanoTime() - PerAttributeRateLimiter.EvictionWarningIntervalNanos) + + private val evictionListener: RemovalListener[String, SpliceRateLimiter] = + (key: String, _: SpliceRateLimiter, cause: RemovalCause) => { + if (cause == RemovalCause.SIZE) { + implicit val tc: TraceContext = TraceContext.empty + val message = + s"Rate limiter cache for $name (attribute '$attribute') exceeded its maximum size of " + + s"${attributeConfig.maxAttributeValues}; evicting the rate limiter for attribute value '$key'. " + + "Its rate limiting state is lost. Consider increasing max-attribute-values." + val now = System.nanoTime() + val last = lastSizeEvictionWarning.get() + if ( + now - last >= PerAttributeRateLimiter.EvictionWarningIntervalNanos && lastSizeEvictionWarning + .compareAndSet(last, now) + ) { + logger.warn(message) + } else { + logger.debug(message) + } + } + } + + // lazy so that neither the cache nor its metrics are created if the limiter is disabled + private lazy val cache: ConcurrentCache[String, SpliceRateLimiter] = CaffeineCache[ + String, + SpliceRateLimiter, + ]( + Caffeine + .newBuilder() + .maximumSize(attributeConfig.maxAttributeValues) + // Evict limiters that have not been used for a full sustained rate limiting window (the bucket + // size of the interval rate limiter): after that time an idle limiter would have refilled its + // budget anyway, so dropping it does not change the enforced rate. + .expireAfterAccess(SpliceRateLimiter.sustainedWindow(perAttributeConfig)) + .evictionListener(evictionListener), + Some(new CacheMetrics(s"$name-$attribute-rate-limiter", metrics.otelFactory)), + ) + + private lazy val defaultRateLimiter = new SpliceRateLimiter( + name, + perAttributeConfig, + metrics, + enforceAfter, + limiterType = SpliceRateLimiter.UnknownAttributeLimiterType, + extraLabels = attributeLabel, + ) + + private lazy val reportedMaxLimit: Unit = + metrics.recordMaxLimit(perAttributeConfig.ratePerSecond)( + MetricsContext( + attributeLabel ++ Map( + "limiter" -> name, + "limiter_type" -> SpliceRateLimiter.PerAttributeLimiterType, + ) + ) + ) + + def markRun(attributeValue: Option[String]): Boolean = + if (isEnabled) attributeValue.fold(defaultRateLimiter)(limiterFor).markRun() + else true + + private def limiterFor(attributeValue: String): SpliceRateLimiter = { + reportedMaxLimit + cache.getOrAcquire( + attributeValue, + (_: String) => + new SpliceRateLimiter( + name, + perAttributeConfig, + metrics, + enforceAfter, + limiterType = SpliceRateLimiter.PerAttributeLimiterType, + extraLabels = attributeLabel, + reportMaxLimit = false, + ), + ) + } +} + +object PerAttributeRateLimiter { + + private val EvictionWarningIntervalNanos: Long = TimeUnit.MINUTES.toNanos(1) +} diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala new file mode 100644 index 0000000000..410bf7e4d1 --- /dev/null +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala @@ -0,0 +1,554 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package org.lfdecentralizedtrust.splice.http + +import com.daml.metrics.api.testing.InMemoryMetricsFactory +import com.digitalasset.canton.BaseTest +import com.digitalasset.canton.concurrent.Threading +import org.apache.pekko.http.scaladsl.model.headers.{RawHeader, `X-Forwarded-For`, `X-Real-Ip`} +import org.apache.pekko.http.scaladsl.model.{ + AttributeKeys, + HttpRequest, + RemoteAddress, + StatusCode, + StatusCodes, +} +import org.apache.pekko.http.scaladsl.server.Directives.* +import org.apache.pekko.http.scaladsl.server.Route +import org.apache.pekko.http.scaladsl.testkit.ScalatestRouteTest +import org.lfdecentralizedtrust.splice.config.RateLimitersConfig +import org.lfdecentralizedtrust.splice.util.{ + PerAttributeRateLimitConfig, + SpliceRateLimitConfig, + SpliceRateLimiter, +} +import org.scalatest.wordspec.AnyWordSpec + +import java.net.{Inet6Address, InetAddress} + +class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteTest { + + "clientIp" should { + + "prefer the trusted X-Envoy-External-Address over spoofable headers" in { + clientIp( + HttpRequest() + .withHeaders( + RawHeader("X-Envoy-External-Address", "4.4.4.4"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), + ) + .withAttributes( + Map( + AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3")) + ) + ) + ) should be(Some("4.4.4.4")) + } + + "ignore a non-IP X-Envoy-External-Address and fall back to the next header" in { + clientIp( + HttpRequest() + .withHeaders( + RawHeader("X-Envoy-External-Address", "evil.example.com"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + ) + ) should be(Some("1.1.1.1")) + } + + "use a configurable trusted proxy header" in { + clientIp( + HttpRequest() + .withHeaders( + RawHeader("X-Trusted-Client-Ip", "4.4.4.4"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + ), + trustedClientIpHeader = "x-trusted-client-ip", + ) should be(Some("4.4.4.4")) + } + + "match the trusted proxy header case-insensitively" in { + clientIp( + HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "4.4.4.4")), + trustedClientIpHeader = "X-Envoy-External-Address", + ) should be(Some("4.4.4.4")) + } + + "not trust any proxy header when the trusted header is disabled" in { + clientIp( + HttpRequest().withHeaders( + RawHeader("X-Envoy-External-Address", "4.4.4.4"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + ), + trustedClientIpHeader = "", + ) should be(Some("1.1.1.1")) + } + + "prefer X-Forwarded-For" in { + clientIp( + HttpRequest() + .withHeaders( + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), + ) + .withAttributes( + Map( + AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3")) + ) + ) + ) should be(Some("1.1.1.1")) + } + + "fall back to X-Real-Ip" in { + clientIp( + HttpRequest().withHeaders(`X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2")))) + ) should be(Some("2.2.2.2")) + } + + "not use the remote address of the transport connection" in { + // the remote address is not exposed by the server, so it must not be relied upon + clientIp( + HttpRequest().withAttributes( + Map(AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3"))) + ) + ) should be(None) + } + + "return None if no IP can be determined" in { + clientIp(HttpRequest()) should be(None) + clientIp( + HttpRequest().withHeaders(RawHeader("X-Forwarded-For", "not-an-ip")) + ) should be(None) + } + } + + "the client IP used for rate limiting" should { + + "use the full address for IPv4 clients" in { + clientIpOf("1.2.3.4") should be(Some("1.2.3.4")) + } + + "group IPv6 clients by their /64 prefix" in { + // the lower 64 bits (the interface identifier) are freely chosen by the client + clientIpOf("2001:db8:0:1:1:2:3:4") should be(Some("2001:db8:0:1:0:0:0:0/64")) + clientIpOf("2001:db8:0:1:ffff:ffff:ffff:ffff") should be( + clientIpOf("2001:db8:0:1:1:2:3:4") + ) + clientIpOf("2001:db8:0:1::") should be(clientIpOf("2001:db8:0:1:1:2:3:4")) + } + + "not group IPv6 clients of different /64 networks" in { + clientIpOf("2001:db8:0:2:1:2:3:4") should not be clientIpOf("2001:db8:0:1:1:2:3:4") + clientIpOf("2001:db9:0:1:1:2:3:4") should not be clientIpOf("2001:db8:0:1:1:2:3:4") + } + + "ignore the zone id of IPv6 addresses" in { + val scoped = Inet6Address.getByAddress( + null, + InetAddress.getByName("fe80::1:2:3:4").getAddress, + 7, + ) + // sanity check that the zone id is part of the address representation + scoped.getHostAddress should be("fe80:0:0:0:1:2:3:4%7") + clientIpOf(scoped) should be(Some("fe80:0:0:0:0:0:0:0/64")) + } + + "use the IPv4 address for IPv4-mapped IPv6 clients" in { + // dual stack sockets can report IPv4 clients as ::ffff:a.b.c.d, those must not end up in a + // single /64 bucket shared by all IPv4 clients + val ipv4Mapped = Inet6Address.getByAddress( + null, + Array[Byte](0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff.toByte, 0xff.toByte, 1, 2, 3, 4), + 0, + ) + ipv4Mapped shouldBe a[Inet6Address] + clientIpOf(ipv4Mapped) should be(Some("1.2.3.4")) + clientIpOf(ipv4Mapped) should be(clientIpOf("1.2.3.4")) + clientIpOf( + Inet6Address.getByAddress( + null, + Array[Byte](0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff.toByte, 0xff.toByte, 4, 3, 2, 1), + 0, + ) + ) should not be clientIpOf(ipv4Mapped) + } + + "apply the same grouping to all client IP sources" in { + val expected = Some("2001:db8:0:1:0:0:0:0/64") + val address = RemoteAddress(InetAddress.getByName("2001:db8:0:1:1:2:3:4")) + clientIp( + HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "2001:db8:0:1:1:2:3:4")) + ) should be(expected) + clientIp( + HttpRequest().withHeaders(`X-Forwarded-For`(address)) + ) should be(expected) + clientIp( + HttpRequest().withHeaders(`X-Real-Ip`(address)) + ) should be(expected) + } + } + + "the http rate limiter" should { + + "reject requests of a client IP over the global per client IP limit" in { + // the global per client IP limiter is enabled by default + withRoutes( + globalPerClientIp = perClientIp(1) + )("testOperation") { routes => + val route = routes("testOperation") + val results = (1 to 20).map(_ => call(route, ip = Some("1.1.1.1"))) + // 1 request per second per client IP => the burst gets rejected + results.count(_ == StatusCodes.OK) should be(1) + results.count(_ == StatusCodes.TooManyRequests) should be(19) + } + } + + "not reject requests of other client IPs" in { + withRoutes( + globalPerClientIp = perClientIp(1) + )("testOperation") { routes => + val route = routes("testOperation") + (1 to 20) + .map(_ => call(route, ip = Some("1.1.1.1"))) + .count(_ == StatusCodes.TooManyRequests) should be > 0 + call(route, ip = Some("2.2.2.2")) should be(StatusCodes.OK) + } + } + + "limit IPv6 clients of the same /64 network together" in { + withRoutes( + globalPerClientIp = perClientIp(1) + )("testOperation") { routes => + val route = routes("testOperation") + call(route, ip = Some("2001:db8:0:1:1:2:3:4")) should be(StatusCodes.OK) + // a different address of the same /64 shares the limiter, so it is rejected + call(route, ip = Some("2001:db8:0:1:ffff:ffff:ffff:ffff")) should be( + StatusCodes.TooManyRequests + ) + // a different /64 is a different client + call(route, ip = Some("2001:db8:0:2:1:2:3:4")) should be(StatusCodes.OK) + } + } + + "fall back to the default limiter if no client IP is known" in { + withRoutes( + globalPerClientIp = perClientIp(1) + )("testOperation") { routes => + val route = routes("testOperation") + call(route, ip = None) should be(StatusCodes.OK) + (1 to 20) + .map(_ => call(route, ip = None)) + .count(_ == StatusCodes.TooManyRequests) should be > 0 + // a request with a client IP uses a different limiter + call(route, ip = Some("1.1.1.1")) should be(StatusCodes.OK) + } + } + + "apply the global per client IP limiter across operations" in { + // the same client IP is limited regardless of the operation + withRoutes( + globalPerClientIp = perClientIp(1) + )("operationA", "operationB") { routes => + call(routes("operationA"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) + call(routes("operationB"), ip = Some("1.1.1.1")) should be(StatusCodes.TooManyRequests) + } + } + + "apply the global overall limiter across operations" in { + withRoutes( + global = SpliceRateLimitConfig(ratePerSecond = 1), + globalPerClientIp = PerAttributeRateLimitConfig.Disabled, + )("operationA", "operationB") { routes => + // exhaust the global budget via operationA + (1 to 20).map(_ => call(routes("operationA"), ip = Some("1.1.1.1"))) + // the global limiter ignores the operation and the client IP, so operationB is rejected too + call(routes("operationB"), ip = Some("2.2.2.2")) should be(StatusCodes.TooManyRequests) + } + } + + "not apply the per operation client IP limiter by default" in { + // no per client IP limiting configured for operations => requests from a single IP are only + // bounded by the (high) overall limiters + withRoutes()("testOperation") { routes => + val route = routes("testOperation") + (1 to 20).map(_ => call(route, ip = Some("1.1.1.1"))) should contain only StatusCodes.OK + } + } + + "apply the per operation client IP limiter when enabled for an operation" in { + withRoutes( + perClientIpOverrides = Map("limitedOperation" -> perClientIp(1)) + )("limitedOperation", "otherOperation") { routes => + val results = + (1 to 20).map(_ => call(routes("limitedOperation"), ip = Some("1.1.1.1"))) + results.count(_ == StatusCodes.OK) should be(1) + // a different operation is not affected by the per operation client IP limiter + call(routes("otherOperation"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) + } + } + + "apply the per operation overall limiter" in { + withRoutes( + rateLimiters = Map("limitedOperation" -> SpliceRateLimitConfig(ratePerSecond = 1)) + )("limitedOperation", "otherOperation") { routes => + val results = (1 to 20).map(_ => call(routes("limitedOperation"), ip = Some("1.1.1.1"))) + results.count(_ == StatusCodes.TooManyRequests) should be > 0 + // a different operation uses a separate overall limiter and is not affected + call(routes("otherOperation"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) + } + } + + "use separate per operation limiters for equally named operations of different services" in { + val rateLimiter = new HttpRateLimiter( + RateLimitersConfig( + default = withPerClientIp( + SpliceRateLimitConfig(ratePerSecond = 1), + PerAttributeRateLimitConfig.Disabled, + ), + rateLimiters = Map.empty, + global = withPerClientIp( + SpliceRateLimitConfig(ratePerSecond = 1000), + PerAttributeRateLimitConfig.Disabled, + ), + ), + new InMemoryMetricsFactory(), + loggerFactory.getTracedLogger(classOf[HttpRateLimiterTest]), + ) + try { + val routeV1 = + rateLimiter.withRateLimit("serviceV1")("sharedOperation")(complete(StatusCodes.OK)) + val routeV2 = + rateLimiter.withRateLimit("serviceV2")("sharedOperation")(complete(StatusCodes.OK)) + // the rate limiter only starts enforcing 1 second after it got created + Threading.sleep(1100) + (1 to 20) + .map(_ => call(routeV1, ip = Some("1.1.1.1"))) + .count(_ == StatusCodes.TooManyRequests) should be > 0 + call(routeV2, ip = Some("1.1.1.1")) should be(StatusCodes.OK) + } finally { + rateLimiter.close() + } + } + } + + "the order in which the rate limiters are applied" should { + + // A limiter only records (and thereby only consumes budget for) the requests that actually + // reach it, as the limiters are combined with a short-circuiting `&&`. The tests below send a + // burst of requests that is rejected by one limiter and assert that the limiters which must be + // applied later only saw the requests that were accepted by the rejecting one. + val Burst = 20 + val Rejecting = SpliceRateLimitConfig(ratePerSecond = 1) + // high enough to never reject, so that the recorded requests are exactly the ones that got here + val Downstream = SpliceRateLimitConfig(ratePerSecond = 1000) + val PerAttribute = SpliceRateLimiter.PerAttributeLimiterType + val Overall = SpliceRateLimiter.GlobalLimiterType + + // Sends a burst of requests from a single client IP and returns how many were accepted. + def burst(fixture: HttpRateLimiterTest.Fixture, operation: String): Long = { + val results = (1 to Burst).map(_ => call(fixture(operation), ip = Some("1.1.1.1"))) + results.count(_ == StatusCodes.TooManyRequests) should be > 0 + results.count(_ == StatusCodes.OK).toLong + } + + def onlySawAcceptedRequests( + fixture: HttpRateLimiterTest.Fixture, + accepted: Long, + )(limiters: (String, String)*) = + forEvery(limiters) { case (limiter, limiterType) => + withClue(s"requests seen by the $limiterType limiter '$limiter': ") { + fixture.requestsSeenBy(limiter, limiterType) should be(accepted) + fixture.requestsRejectedBy(limiter, limiterType) should be(0L) + } + } + + "apply the per operation client IP limiter before the overall limiters" in { + withRoutes( + rateLimiters = Map("limitedOperation" -> Downstream), + global = Downstream, + perClientIpOverrides = Map("limitedOperation" -> perClientIp(1)), + )("limitedOperation") { fixture => + val accepted = burst(fixture, "limitedOperation") + onlySawAcceptedRequests(fixture, accepted)( + "limitedOperation" -> Overall, + HttpRateLimiter.GlobalLimiter -> Overall, + ) + } + } + + "apply the global per client IP limiter before the overall limiters" in { + withRoutes( + rateLimiters = Map("limitedOperation" -> Downstream), + global = Downstream, + globalPerClientIp = perClientIp(1), + )("limitedOperation") { fixture => + val accepted = burst(fixture, "limitedOperation") + onlySawAcceptedRequests(fixture, accepted)( + "limitedOperation" -> Overall, + HttpRateLimiter.GlobalLimiter -> Overall, + ) + } + } + + "apply the per operation client IP limiter before the global per client IP limiter" in { + withRoutes( + globalPerClientIp = perClientIp(1000), + perClientIpOverrides = Map("limitedOperation" -> perClientIp(1)), + )("limitedOperation") { fixture => + val accepted = burst(fixture, "limitedOperation") + onlySawAcceptedRequests(fixture, accepted)( + HttpRateLimiter.GlobalLimiter -> PerAttribute + ) + } + } + + "apply the per operation overall limiter before the global overall limiter" in { + withRoutes( + rateLimiters = Map("limitedOperation" -> Rejecting), + global = Downstream, + )("limitedOperation") { fixture => + val accepted = burst(fixture, "limitedOperation") + onlySawAcceptedRequests(fixture, accepted)( + HttpRateLimiter.GlobalLimiter -> Overall + ) + } + } + + "still reject requests that pass the per client IP limiters but exceed an overall limit" in { + withRoutes( + global = SpliceRateLimitConfig(ratePerSecond = 2), + globalPerClientIp = perClientIp(1000), + perClientIpOverrides = Map("testOperation" -> perClientIp(1000)), + )("testOperation") { fixture => + // every request is below both per client IP limits, but the overall global limit applies + val results = (1 to Burst).map(i => call(fixture("testOperation"), ip = Some(s"1.1.1.$i"))) + results.count(_ == StatusCodes.OK) should be < Burst + results.count(_ == StatusCodes.TooManyRequests) should be > 0 + } + } + } + + private def perClientIp(ratePerSecond: Double): PerAttributeRateLimitConfig = + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = ratePerSecond)) + + private def clientIp( + request: HttpRequest, + trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, + ): Option[String] = { + val route = HttpRateLimiter.extractClientIpKey(trustedClientIpHeader) { extracted => + complete(extracted.getOrElse[String](HttpRateLimiterTest.NoClientIp)) + } + request ~> route ~> check { + status should be(StatusCodes.OK) + Some(responseAs[String]).filterNot(_ == HttpRateLimiterTest.NoClientIp) + } + } + + private def clientIpOf(ip: String): Option[String] = + clientIpOf(InetAddress.getByName(ip)) + + private def clientIpOf(ip: InetAddress): Option[String] = + clientIp( + HttpRequest().withHeaders(`X-Forwarded-For`(RemoteAddress(ip))) + ) + + private def call(route: Route, ip: Option[String]): StatusCode = { + val request = ip match { + case Some(value) => + Get("/") ~> addHeader(`X-Forwarded-For`(RemoteAddress(InetAddress.getByName(value)))) + case None => Get("/") + } + request ~> route ~> check(status) + } + + private def withRoutes[A]( + // high enough by default so that only the explicitly configured limiter kicks in + default: SpliceRateLimitConfig = SpliceRateLimitConfig(ratePerSecond = 1000), + rateLimiters: Map[String, SpliceRateLimitConfig] = Map.empty, + global: SpliceRateLimitConfig = SpliceRateLimitConfig(ratePerSecond = 1000), + globalPerClientIp: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig.Disabled, + perClientIpOverrides: Map[String, PerAttributeRateLimitConfig] = Map.empty, + )(operations: String*)(f: HttpRateLimiterTest.Fixture => A): A = { + // Any operation with a per client IP override needs its own overall limiter entry so that the + // embedded per client IP limiter is used instead of the `default` one. + val perOperationConfigs: Map[String, SpliceRateLimitConfig.WithPerClientIp] = + (rateLimiters.keySet ++ perClientIpOverrides.keySet).map { operation => + operation -> withPerClientIp( + rateLimiters.getOrElse(operation, default), + perClientIpOverrides.getOrElse(operation, PerAttributeRateLimitConfig.Disabled), + ) + }.toMap + val metricsFactory = new InMemoryMetricsFactory() + val rateLimiter = new HttpRateLimiter( + RateLimitersConfig( + default = withPerClientIp(default, PerAttributeRateLimitConfig.Disabled), + rateLimiters = perOperationConfigs, + global = withPerClientIp(global, globalPerClientIp), + ), + metricsFactory, + loggerFactory.getTracedLogger(classOf[HttpRateLimiterTest]), + ) + try { + val routes = operations.map { operation => + operation -> rateLimiter.withRateLimit("testService")(operation) { + complete(StatusCodes.OK) + } + }.toMap + // the rate limiter only starts enforcing 1 second after it got created + Threading.sleep(1100) + f(HttpRateLimiterTest.Fixture(routes, metricsFactory)) + } finally { + rateLimiter.close() + } + } + + private def withPerClientIp( + overall: SpliceRateLimitConfig, + perClientIp: PerAttributeRateLimitConfig, + ): SpliceRateLimitConfig.WithPerClientIp = + SpliceRateLimitConfig.WithPerClientIp( + enabled = overall.enabled, + ratePerSecond = overall.ratePerSecond, + sustainedRatePerSecond = overall.sustainedRatePerSecond, + sustainedWindowSeconds = overall.sustainedWindowSeconds, + perClientIp = perClientIp, + ) +} + +object HttpRateLimiterTest { + private val NoClientIp = "" + + /** The routes of the rate limited operations together with the metrics recorded by their rate + * limiters. Requests are only recorded by the limiters they actually reach, which is what allows + * asserting on the order in which the limiters are applied. + */ + private final case class Fixture( + routes: Map[String, Route], + metricsFactory: InMemoryMetricsFactory, + ) { + + def apply(operation: String): Route = routes(operation) + + /** Number of requests recorded by the given limiter, i.e. that were actually evaluated by it. */ + def requestsSeenBy(limiter: String, limiterType: String): Long = + marks(limiter, limiterType, result = None) + + /** Number of requests the given limiter rejected. */ + def requestsRejectedBy(limiter: String, limiterType: String): Long = + marks(limiter, limiterType, result = Some("rejected")) + + private def marks(limiter: String, limiterType: String, result: Option[String]): Long = + metricsFactory.metrics.meters.values + .flatMap(_.values) + .flatMap(_.markers.toSeq) + .collect { + case (context, value) + if context.labels.get("limiter").contains(limiter) && + context.labels.get("limiter_type").contains(limiterType) && + result.forall(expected => context.labels.get("result").contains(expected)) => + value.get() + } + .sum + } +} diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala index f89db4a51c..47f36979e7 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala @@ -1,3 +1,6 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + package org.lfdecentralizedtrust.splice.util import com.daml.metrics.api.MetricsContext @@ -12,6 +15,7 @@ import org.lfdecentralizedtrust.splice.admin.api.client.commands.HttpCommandExce import org.lfdecentralizedtrust.splice.util.SpliceRateLimiterTest.runRateLimited import org.scalatest.wordspec.AnyWordSpecLike +import java.time.Instant import scala.concurrent.Future import scala.concurrent.duration.DurationInt @@ -26,7 +30,7 @@ class SpliceRateLimiterTest "accept requests under limit" in { val elementsToRun = 100 - withRateLimiter { case (rateLimitMetrics, rateLimiter) => + withRateLimiter() { case (rateLimitMetrics, rateLimiter) => runThroughRateLimiter(rateLimiter, 9, elementsToRun).reduce(_ && _) shouldBe true rateLimitMetrics.meter.valueFilteredOnLabels( @@ -43,7 +47,7 @@ class SpliceRateLimiterTest } "reject requests that are over the limit" in { - withRateLimiter { case (rateLimitMetrics, rateLimiter) => + withRateLimiter() { case (rateLimitMetrics, rateLimiter) => val results = runThroughRateLimiter(rateLimiter, 100, 1000) val (accepted, rejected) = results.partition(identity) @@ -76,6 +80,152 @@ class SpliceRateLimiterTest } + "not create any limiter if disabled" in { + // a disabled limiter must not fail even for a rate that guava would reject + withRateLimiter(SpliceRateLimitConfig(enabled = false, ratePerSecond = 0)) { + case (_, rateLimiter) => + Seq.fill(100)(rateLimiter.markRun()) should contain only true + } + } + + } + + "the per attribute rate limiter" should { + + "gate the per attribute limit on the overall limiter being enabled" in { + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 42)) + .rateLimitFor(SpliceRateLimitConfig(ratePerSecond = 100)) + .ratePerSecond should be(42d) + + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 42)) + .rateLimitFor(SpliceRateLimitConfig(enabled = false, ratePerSecond = 100)) + .enabled should be(false) + + PerAttributeRateLimitConfig( + enabled = false, + limit = SpliceRateLimitConfig(ratePerSecond = 42), + ) + .rateLimitFor(SpliceRateLimitConfig(ratePerSecond = 100)) + .enabled should be(false) + + PerAttributeRateLimitConfig.Disabled + .rateLimitFor(SpliceRateLimitConfig(ratePerSecond = 100)) + .enabled should be(false) + } + + "limit each attribute value separately" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 10), + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), + ) { case (_, perAttributeRateLimiter) => + // 1 per second per attribute value, so a burst is rejected after the first request + val ip1 = Seq.fill(20)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) + ip1.count(identity) should be(1) + ip1.count(!_) should be(19) + + // a different attribute value is not affected by the limiter of the first one + perAttributeRateLimiter.markRun(Some("2.2.2.2")) should be(true) + perAttributeRateLimiter.markRun(Some("2.2.2.2")) should be(false) + } + } + + "use a single default limiter if the attribute value is unknown" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 10), + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), + ) { case (metrics, perAttributeRateLimiter) => + val results = Seq.fill(20)(perAttributeRateLimiter.markRun(None)) + results.count(identity) should be(1) + + metrics.meter.valueFilteredOnLabels( + LabelFilter("limiter", "test"), + LabelFilter("limiter_attribute", "test_attribute"), + LabelFilter("limiter_type", SpliceRateLimiter.UnknownAttributeLimiterType), + LabelFilter("result", "rejected"), + ) should be(results.count(!_)) + + // requests with a known attribute value are not affected by the default limiter + perAttributeRateLimiter.markRun(Some("1.1.1.1")) should be(true) + } + } + + "distinguish the metrics of the per attribute limiters" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 10), + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), + ) { case (metrics, perAttributeRateLimiter) => + val results = Seq.fill(20)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) + + metrics.meter.valueFilteredOnLabels( + LabelFilter("limiter", "test"), + LabelFilter("limiter_attribute", "test_attribute"), + LabelFilter("limiter_type", SpliceRateLimiter.PerAttributeLimiterType), + LabelFilter("result", "accepted"), + ) should be(results.count(identity)) + metrics.meter.valueFilteredOnLabels( + LabelFilter("limiter", "test"), + LabelFilter("limiter_attribute", "test_attribute"), + LabelFilter("limiter_type", SpliceRateLimiter.PerAttributeLimiterType), + LabelFilter("result", "rejected"), + ) should be(results.count(!_)) + // no metrics are reported for the default limiter of unknown attribute values + metrics.meter.valuesWithContext.keys + .flatMap(_.labels.get("limiter_type")) + .toSet should be(Set(SpliceRateLimiter.PerAttributeLimiterType)) + } + } + + "not limit anything if disabled" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 10), + PerAttributeRateLimitConfig.Disabled, + ) { case (_, perAttributeRateLimiter) => + Seq.fill(100)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) should contain only true + Seq.fill(100)(perAttributeRateLimiter.markRun(None)) should contain only true + } + } + + "respect the configured rate over time" in { + withPerAttributeRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 100), + PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 10)), + ) { case (_, perAttributeRateLimiter) => + // 10 per second per attribute value + val results = runRateLimited(50, 100) { + if (perAttributeRateLimiter.markRun(Some("1.1.1.1"))) Future.successful(true) + else + Future.failed( + io.grpc.Status.RESOURCE_EXHAUSTED + .withDescription("Rate limit exceeded") + .asRuntimeException() + ) + }.futureValue + // roughly 2 seconds of runtime at 10 permits per second, with some slack + results.count(identity) should (be >= 5 and be <= 40) + results.count(!_) should be > 0 + } + } + + } + + "the rate limiter with a sustained limit" should { + + "throttle to the sustained rate once the burst budget is drained" in { + withRateLimiter( + SpliceRateLimitConfig(ratePerSecond = 1000, sustainedRatePerSecond = Some(10)) + ) { case (_, rateLimiter) => + // The per-second limit is high enough to never reject the throttled input, so the sustained + // limiter (10/s) is the binding constraint over the run. The sustained limiter starts with + // an empty burst budget (Guava SmoothBursty semantics), so throughput tracks the sustained + // rate plus a small initial allowance. + val results = runRateLimited(40, 120) { + rateLimiter.runWithLimit(Future.successful(true)) + }.futureValue + // ~3 seconds of runtime at 10 permits/s, with generous slack + results.count(identity) should (be >= 10 and be <= 60) + results.count(!_) should be > 0 + } + } } private def runThroughRateLimiter( @@ -92,13 +242,38 @@ class SpliceRateLimiterTest } futureValue } - private def withRateLimiter[A](f: (SpliceRateLimitMetrics, SpliceRateLimiter) => A): A = { + private def withRateLimiter[A]( + config: SpliceRateLimitConfig = SpliceRateLimitConfig(enabled = true, ratePerSecond = 10) + )(f: (SpliceRateLimitMetrics, SpliceRateLimiter) => A): A = { val metricsFactory = new InMemoryMetricsFactory() val rateLimitMetrics = SpliceRateLimitMetrics(metricsFactory, logger)(MetricsContext.Empty) val rateLimiter = new SpliceRateLimiter( "test", - SpliceRateLimitConfig(enabled = true, 10), + config, + rateLimitMetrics, + ) + try { + f(rateLimitMetrics, rateLimiter) + } finally { + rateLimitMetrics.close() + } + } + + private def withPerAttributeRateLimiter[A]( + config: SpliceRateLimitConfig, + attributeConfig: PerAttributeRateLimitConfig, + )(f: (SpliceRateLimitMetrics, PerAttributeRateLimiter) => A): A = { + val metricsFactory = new InMemoryMetricsFactory() + val rateLimitMetrics = SpliceRateLimitMetrics(metricsFactory, logger)(MetricsContext.Empty) + val rateLimiter = new PerAttributeRateLimiter( + "test", + "test_attribute", + config, + attributeConfig, rateLimitMetrics, + // no cold start delay in tests + Instant.now().minusSeconds(1), + logger, ) try { f(rateLimitMetrics, rateLimiter) diff --git a/cluster/images/scan-app/app.conf b/cluster/images/scan-app/app.conf index 802305d344..c78b48e7f6 100644 --- a/cluster/images/scan-app/app.conf +++ b/cluster/images/scan-app/app.conf @@ -60,7 +60,19 @@ canton { parameters { rate-limiting { default { - rate-per-second = 200 + rate-per-second = 100 + sustained-rate-per-second = 50 + } + global { + rate-per-second = 400 + sustained-rate-per-second = 200 + per-client-ip { + enabled = true + limit { + rate-per-second = 100 + sustained-rate-per-second = 50 + } + } } rate-limiters { getAcsSnapshot.rate-per-second = 20 diff --git a/cluster/images/sv-app/app.conf b/cluster/images/sv-app/app.conf index 9cd27eda2c..8b407493d4 100644 --- a/cluster/images/sv-app/app.conf +++ b/cluster/images/sv-app/app.conf @@ -107,7 +107,19 @@ canton { } rate-limiting { default { - rate-per-second = 200 + rate-per-second = 20 + sustained-rate-per-second = 10 + } + global { + rate-per-second = 100 + sustained-rate-per-second = 50 + per-client-ip { + enabled = true + limit { + rate-per-second = 20 + sustained-rate-per-second = 10 + } + } } rate-limiters { prepareValidatorOnboarding.rate-per-second = 1 diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index f623c41987..c470d903c2 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -20,3 +20,37 @@ release-notes:: Upcoming vote requests by their state relative to the SV (``action_needed``, ``in_progress``, ``ready_to_close``), allowing SV operators to alert on vote proposals that require their vote. + + - Scan & SV App + + - HTTP rate limiting has been extended with a global rate limiter applied across all + operations, optional per-client-IP rate limiting (enabled by default at the global level), + and an additional sustained rate limit enforced over a longer window on top of the existing + per-second burst limit. The client IP is taken from the trusted, non-spoofable + ``X-Envoy-External-Address`` header set by the Envoy/Istio ingress, falling back to the + client-controlled ``X-Forwarded-For``/ ``X-Real-Ip`` headers and finally the remote + address only for requests that did not pass through the ingress. These can be tuned via + the ``rate-limiting`` config keys. + + .. warning:: + + When per-client-IP rate limiting is enabled, SV operators must ensure that the client IP + used for rate limiting cannot be spoofed. Either configure + ``rate-limiting.trusted-client-ip-header`` to a trusted, non-spoofable header set by + your ingress/proxy (e.g. ``x-envoy-external-address`` for Istio deployments), or ensure + that the ``X-Forwarded-For`` header contains the actual client IP as its first value + and cannot be spoofed by clients. Otherwise, clients may bypass per-client-IP limits or + cause other clients to be throttled by forging these headers. + + - Default rate limits have been adjusted: + + - Scan app: the per-operation burst limit has been lowered from 200 to 100 requests per + second, with a new sustained limit of 50 requests per second. A new global limiter has + also been added, allowing 400 requests per second burst / 200 sustained across all + operations combined, with an embedded per-client-IP limiter allowing 100 requests per + second burst / 50 sustained. + - SV app: the per-operation burst limit has been lowered from 200 to 20 requests per + second, with a new sustained limit of 10 requests per second. A new global limiter has + also been added, allowing 100 requests per second burst / 50 sustained across all + operations combined, with an embedded per-client-IP limiter allowing 20 requests per + second burst / 10 sustained. diff --git a/test-full-class-names-non-integration.log b/test-full-class-names-non-integration.log index 01a9cacf41..cf1d72da24 100644 --- a/test-full-class-names-non-integration.log +++ b/test-full-class-names-non-integration.log @@ -11,6 +11,7 @@ org.lfdecentralizedtrust.splice.environment.ActiveContractsRestartTest org.lfdecentralizedtrust.splice.environment.CommandCircuitBreakerTest org.lfdecentralizedtrust.splice.environment.CommandIdDedupTest org.lfdecentralizedtrust.splice.environment.TopologyAwarePackageVersionSupportTest +org.lfdecentralizedtrust.splice.http.HttpRateLimiterTest org.lfdecentralizedtrust.splice.http.InvalidResponseContentTest org.lfdecentralizedtrust.splice.http.NonProxyHostsTest org.lfdecentralizedtrust.splice.http.UrlValidatorTest From 44199952e85fc826464b53d3cd1caabdbcd0e5c3 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:36:00 +0200 Subject: [PATCH 256/329] Restart CometBFT when it starts replaying messages (#6823) * Restart CometBFT when it starts replaying messages fixes #6823 [static] Signed-off-by: moritz.kiefer@digitalasset.com * Apply suggestions from code review Co-authored-by: Martin Florian Signed-off-by: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> * randomize Signed-off-by: moritz.kiefer@digitalasset.com * snippet Signed-off-by: moritz.kiefer@digitalasset.com * catch all exceptions Signed-off-by: moritz.kiefer@digitalasset.com * Handle decreases in counter metrics [static] Signed-off-by: moritz.kiefer@digitalasset.com * fix config [static] Signed-off-by: moritz.kiefer@digitalasset.com --------- Signed-off-by: moritz.kiefer@digitalasset.com Signed-off-by: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Co-authored-by: Moritz Kiefer Co-authored-by: Martin Florian --- .../examples/sv-helm/cometbft-values.yaml | 5 + cluster/deployment/mock/config.yaml | 4 + cluster/expected/sv-canton/expected.json | 54 ++- .../splice-cometbft/templates/deployment.yaml | 65 +++- .../tests/cometbft_deployment_test.yaml | 2 + .../tests/cometbft_pvc_test.yaml | 2 + .../tests/cometbft_watchdog_test.yaml | 98 ++++++ .../helm/splice-cometbft/values-template.yaml | 25 ++ .../helm/splice-cometbft/values.schema.json | 82 ++++- cluster/images/cometbft-watchdog/Dockerfile | 16 + cluster/images/cometbft-watchdog/local.mk | 9 + .../cometbft-watchdog/restart-watchdog.py | 314 ++++++++++++++++++ cluster/images/local.mk | 1 + cluster/pulumi/common-sv/src/config.ts | 12 + .../common-sv/src/synchronizer/cometbft.ts | 23 ++ docs/src/release_notes_upcoming.rst | 8 + 16 files changed, 711 insertions(+), 9 deletions(-) create mode 100644 cluster/helm/splice-cometbft/tests/cometbft_watchdog_test.yaml create mode 100644 cluster/images/cometbft-watchdog/Dockerfile create mode 100644 cluster/images/cometbft-watchdog/local.mk create mode 100644 cluster/images/cometbft-watchdog/restart-watchdog.py diff --git a/apps/app/src/pack/examples/sv-helm/cometbft-values.yaml b/apps/app/src/pack/examples/sv-helm/cometbft-values.yaml index 785f80b028..57ffcd06d8 100644 --- a/apps/app/src/pack/examples/sv-helm/cometbft-values.yaml +++ b/apps/app/src/pack/examples/sv-helm/cometbft-values.yaml @@ -39,6 +39,11 @@ stateSync: # Note that the port number is significant in the list of rpcServers rpcServers: "https://sv.sv-2.TARGET_HOSTNAME:443/api/sv/v0/admin/domain/cometbft/json-rpc,https://sv.sv-2.TARGET_HOSTNAME:443/api/sv/v0/admin/domain/cometbft/json-rpc" +watchdog: + enabled: true + sequencerMetricsUrl: http://global-domain-SERIAL_ID-sequencer:10013/metrics + mediatorMetricsUrl: http://global-domain-SERIAL_ID-mediator:10013/metrics + # k8s affinity for all deployed pods (optional) # affinity: # nodeAffinity: diff --git a/cluster/deployment/mock/config.yaml b/cluster/deployment/mock/config.yaml index 3381999101..7b9e4a07d9 100644 --- a/cluster/deployment/mock/config.yaml +++ b/cluster/deployment/mock/config.yaml @@ -32,6 +32,10 @@ multiValidator: - name: MULTI_PARTICIPANT_ADDITIONAL_CONFIG_MAX_CONNECTIONS value: canton.participants.participant_INDEX.storage.parameters.max-connections = 33 sv: + cometbft: + watchdog: + threshold: 2 + evaluationIntervalSeconds: 900 synchronizer: skipInitialization: true scan: diff --git a/cluster/expected/sv-canton/expected.json b/cluster/expected/sv-canton/expected.json index 439ac79415..f91aecc5ef 100644 --- a/cluster/expected/sv-canton/expected.json +++ b/cluster/expected/sv-canton/expected.json @@ -1631,7 +1631,14 @@ "key": "cn_apps", "operator": "Exists" } - ] + ], + "watchdog": { + "enabled": true, + "evaluationIntervalSeconds": 900, + "mediatorMetricsUrl": "http://global-domain-7-mediator:10013/metrics", + "sequencerMetricsUrl": "http://global-domain-7-sequencer:10013/metrics", + "threshold": 2 + } }, "version": "0.3.20" }, @@ -1743,7 +1750,14 @@ "key": "cn_apps", "operator": "Exists" } - ] + ], + "watchdog": { + "enabled": true, + "evaluationIntervalSeconds": 900, + "mediatorMetricsUrl": "http://global-domain-8-mediator:10013/metrics", + "sequencerMetricsUrl": "http://global-domain-8-sequencer:10013/metrics", + "threshold": 2 + } }, "version": "0.3.20" }, @@ -4990,7 +5004,14 @@ "key": "cn_apps", "operator": "Exists" } - ] + ], + "watchdog": { + "enabled": true, + "evaluationIntervalSeconds": 900, + "mediatorMetricsUrl": "http://global-domain-7-mediator:10013/metrics", + "sequencerMetricsUrl": "http://global-domain-7-sequencer:10013/metrics", + "threshold": 2 + } }, "version": "0.3.20" }, @@ -5099,7 +5120,14 @@ "key": "cn_apps", "operator": "Exists" } - ] + ], + "watchdog": { + "enabled": true, + "evaluationIntervalSeconds": 900, + "mediatorMetricsUrl": "http://global-domain-8-mediator:10013/metrics", + "sequencerMetricsUrl": "http://global-domain-8-sequencer:10013/metrics", + "threshold": 2 + } }, "version": "0.3.20" }, @@ -5212,7 +5240,14 @@ "key": "cn_apps", "operator": "Exists" } - ] + ], + "watchdog": { + "enabled": true, + "evaluationIntervalSeconds": 900, + "mediatorMetricsUrl": "http://global-domain-7-mediator:10013/metrics", + "sequencerMetricsUrl": "http://global-domain-7-sequencer:10013/metrics", + "threshold": 2 + } }, "version": "0.3.20" }, @@ -5325,7 +5360,14 @@ "key": "cn_apps", "operator": "Exists" } - ] + ], + "watchdog": { + "enabled": true, + "evaluationIntervalSeconds": 900, + "mediatorMetricsUrl": "http://global-domain-8-mediator:10013/metrics", + "sequencerMetricsUrl": "http://global-domain-8-sequencer:10013/metrics", + "threshold": 2 + } }, "version": "0.3.20" }, diff --git a/cluster/helm/splice-cometbft/templates/deployment.yaml b/cluster/helm/splice-cometbft/templates/deployment.yaml index ce1f3f9b9b..0bf5d3a513 100644 --- a/cluster/helm/splice-cometbft/templates/deployment.yaml +++ b/cluster/helm/splice-cometbft/templates/deployment.yaml @@ -60,9 +60,19 @@ spec: containerPort: {{ .rpcPort }} protocol: TCP livenessProbe: + {{- if $.Values.watchdog.enabled }} + # combines the file created by the watchdog and the cometbft health check + exec: + command: + - /bin/sh + - -c + - '! [ -f {{ $.Values.watchdog.markerFile }} ] && curl -sf -o /dev/null --max-time 5 http://127.0.0.1:{{ .rpcPort }}/health' + timeoutSeconds: 10 + {{- else }} httpGet: path: /health port: rpc + {{- end }} initialDelaySeconds: {{ $.Values.livenessProbeInitialDelaySeconds | default 600 }} readinessProbe: httpGet: @@ -79,6 +89,10 @@ spec: name: data - mountPath: /tmp name: state-sync + {{- if $.Values.watchdog.enabled }} + - mountPath: {{ dir $.Values.watchdog.markerFile }} + name: watchdog + {{- end }} env: - name: HOME value: /cometbft @@ -109,11 +123,55 @@ spec: cp -rL "${HOME}"/initial-config/. "${HOME}"/config/ "${HOME}"/configure-state-sync.sh "${HOME}"/config/config.toml cp -rnL "${HOME}"/initial-data/* "${HOME}"/data || : - cometbft-canton-network start \ + {{- if $.Values.watchdog.enabled }} + # Clear the restart request of the run that just ended, otherwise the + # liveness probe fails again as soon as it starts probing. + rm -f {{ $.Values.watchdog.markerFile }} + {{- end }} + exec cometbft-canton-network start \ {{ include "cliArgs" (list $.Values) | indent 14 | trim }} {{- with $.Values.resources }} resources: {{- toYaml . | nindent 12 }} {{- end }} + {{- if $.Values.watchdog.enabled }} + # the watchdog is in a separate container mainly for easier debugging and log separation. + - name: "watchdog" + {{- if $.Values.securityContexts }} + {{- with $.Values.securityContexts.containers.watchdog }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} + image: {{ $.Values.imageRepo }}/{{ $.Values.watchdog.imageName }}:{{ $.Chart.AppVersion }}{{ (($.Values.imageDigests).cometbft_watchdog) }} + {{- with $.Values.imagePullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + volumeMounts: + - mountPath: {{ dir $.Values.watchdog.markerFile }} + name: watchdog + env: + - name: WATCHDOG_SEQUENCER_METRICS_URL + value: {{ $.Values.watchdog.sequencerMetricsUrl | quote }} + - name: WATCHDOG_MEDIATOR_METRICS_URL + value: {{ $.Values.watchdog.mediatorMetricsUrl | quote }} + - name: WATCHDOG_POLL_INTERVAL_SECONDS + value: {{ $.Values.watchdog.pollIntervalSeconds | quote }} + - name: WATCHDOG_THRESHOLD + value: {{ $.Values.watchdog.threshold | quote }} + - name: WATCHDOG_EVALUATION_INTERVAL_SECONDS + value: {{ $.Values.watchdog.evaluationIntervalSeconds | quote }} + - name: WATCHDOG_SCRAPE_TIMEOUT_SECONDS + value: {{ $.Values.watchdog.scrapeTimeoutSeconds | quote }} + - name: WATCHDOG_STARTUP_GRACE_SECONDS + value: {{ $.Values.watchdog.startupGraceSeconds | quote }} + - name: WATCHDOG_COOLDOWN_SECONDS + value: {{ $.Values.watchdog.cooldownSeconds | quote }} + - name: WATCHDOG_MARKER_FILE + value: {{ $.Values.watchdog.markerFile | quote }} + {{- with $.Values.watchdog.resources }} + resources: {{- toYaml . | nindent 12 }} + {{- end }} + {{- end }} volumes: - name: initial-config projected: @@ -139,6 +197,11 @@ spec: # since we are not going to scale this deployment. persistentVolumeClaim: claimName: {{ $.Values.db.pvcName | default (include "prefix" (list $.Values "cometbft-data")) }} + {{- if $.Values.watchdog.enabled }} + - name: watchdog + emptyDir: + sizeLimit: 1Mi + {{- end }} {{- with $.Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} diff --git a/cluster/helm/splice-cometbft/tests/cometbft_deployment_test.yaml b/cluster/helm/splice-cometbft/tests/cometbft_deployment_test.yaml index 7150e4d3f9..2e31c85688 100644 --- a/cluster/helm/splice-cometbft/tests/cometbft_deployment_test.yaml +++ b/cluster/helm/splice-cometbft/tests/cometbft_deployment_test.yaml @@ -21,6 +21,8 @@ set: genesis: chainId: "mock-3" chainIdSuffix: "1" # must be a string value + watchdog: + enabled: false tests: - it: "deploys a deployment" template: deployment.yaml diff --git a/cluster/helm/splice-cometbft/tests/cometbft_pvc_test.yaml b/cluster/helm/splice-cometbft/tests/cometbft_pvc_test.yaml index bbc4c8bf86..7bfbb8cae2 100644 --- a/cluster/helm/splice-cometbft/tests/cometbft_pvc_test.yaml +++ b/cluster/helm/splice-cometbft/tests/cometbft_pvc_test.yaml @@ -10,6 +10,8 @@ release: set: node: identifier: "global-domain-3-cometbft" + watchdog: + enabled: false tests: - it: "deploys a PVC" template: pvc.yaml diff --git a/cluster/helm/splice-cometbft/tests/cometbft_watchdog_test.yaml b/cluster/helm/splice-cometbft/tests/cometbft_watchdog_test.yaml new file mode 100644 index 0000000000..f82d197689 --- /dev/null +++ b/cluster/helm/splice-cometbft/tests/cometbft_watchdog_test.yaml @@ -0,0 +1,98 @@ +# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +suite: "cometbft watchdog" +templates: + - deployment.yaml +release: + name: global-domain-3 +set: + sv1: + externalAddress: sv.mock.net:26316 + nodeId: "4c7c99516fb3309b89b7f8ed94690994c8ec0ab0" + publicKey: "H2bcJU2zbzbLmP78YWiwMgtB0QG1MNTSozGl1tP11hI=" + keyAddress: "9473617BBC80C12F68CC25B5A754D1ED9035886C" + node: + id: "1234dedbeef1234dedbeef1234dedbeef1234ded" + identifier: "global-domain-3-cometbft" + externalAddress: "global-domain-3-cometbft.sv.mock.net:26356" + keysSecret: "cometbft-keys" + genesis: + chainId: "mock-3" + chainIdSuffix: "1" +tests: + - it: "rejects the chart defaults, so that operators need to explicitly configure the right URLs" + template: deployment.yaml + asserts: + - failedTemplate: + errorPattern: "missing properties 'sequencerMetricsUrl', 'mediatorMetricsUrl'" + + - it: "leaves the pod untouched when disabled" + template: deployment.yaml + set: + watchdog: + enabled: false + documentSelector: + path: kind + value: Deployment + asserts: + - lengthEqual: + path: spec.template.spec.containers + count: 1 + - equal: + path: spec.template.spec.containers[0].livenessProbe.httpGet + value: + path: /health + port: rpc + - notMatchRegex: + path: spec.template.spec.containers[0].command[4] + pattern: "restart-requested" + + - it: "deploys the watchdog sidecar when enabled" + template: deployment.yaml + set: + watchdog: + enabled: true + sequencerMetricsUrl: http://global-domain-3-sequencer:10013/metrics + mediatorMetricsUrl: http://global-domain-3-mediator:10013/metrics + threshold: 2.5 + evaluationIntervalSeconds: 900 + documentSelector: + path: kind + value: Deployment + asserts: + - lengthEqual: + path: spec.template.spec.containers + count: 2 + - equal: + path: spec.template.spec.containers[1].name + value: "watchdog" + - matchRegex: + path: spec.template.spec.containers[1].image + pattern: "/cometbft-watchdog:" + - contains: + path: spec.template.spec.containers[1].env + content: + name: WATCHDOG_MEDIATOR_METRICS_URL + value: "http://global-domain-3-mediator:10013/metrics" + - contains: + path: spec.template.spec.containers[1].env + content: + name: WATCHDOG_THRESHOLD + value: "2.5" + - contains: + path: spec.template.spec.containers[1].env + content: + name: WATCHDOG_EVALUATION_INTERVAL_SECONDS + value: "900" + - contains: + path: spec.template.spec.containers[1].volumeMounts + content: + mountPath: /watchdog + name: watchdog + - contains: + path: spec.template.spec.volumes + content: + name: watchdog + emptyDir: + sizeLimit: 1Mi diff --git a/cluster/helm/splice-cometbft/values-template.yaml b/cluster/helm/splice-cometbft/values-template.yaml index c075958bb0..8c78a45b82 100644 --- a/cluster/helm/splice-cometbft/values-template.yaml +++ b/cluster/helm/splice-cometbft/values-template.yaml @@ -111,6 +111,28 @@ mempool: # should stakater reloader annotation be added (note: reloader needs to be installed separately) enableReloader: true +# Restarts CometBFT when cometbft starts replaying messages +watchdog: + # Enabled by default to make sure noone accidentally forgets to enable it. + enabled: true + # Required but we don't know the migration id to set sensible defaults. + # sequencerMetricsUrl: http://global-domain-0-sequencer:10013/metrics + # mediatorMetricsUrl: http://global-domain-0-mediator:10013/metrics + pollIntervalSeconds: 30 + threshold: 5 + evaluationIntervalSeconds: 300 + scrapeTimeoutSeconds: 10 + startupGraceSeconds: 600 + cooldownSeconds: 600 + markerFile: /watchdog/restart-requested + imageName: "cometbft-watchdog" + resources: + limits: + memory: 128Mi + requests: + cpu: 0.05 + memory: 64Mi + securityContexts: pod: security_context_profile: pod_security_context @@ -119,3 +141,6 @@ securityContexts: containers: cometbft: security_context_profile: app_security_context + watchdog: + security_context_profile: app_security_context + readOnlyRootFilesystem: true diff --git a/cluster/helm/splice-cometbft/values.schema.json b/cluster/helm/splice-cometbft/values.schema.json index e28c04aa7d..a3b710be71 100644 --- a/cluster/helm/splice-cometbft/values.schema.json +++ b/cluster/helm/splice-cometbft/values.schema.json @@ -16,12 +16,20 @@ "properties": { "cometbft": { "type": "object" + }, + "watchdog": { + "type": "object" } }, - "required": ["cometbft"] + "required": [ + "cometbft" + ] } }, - "required": ["pod", "containers"] + "required": [ + "pod", + "containers" + ] }, "imageName": { "type": "string" @@ -45,6 +53,76 @@ "type": "string" } } + }, + "watchdog": { + "type": "object", + "required": [ + "enabled" + ], + "properties": { + "enabled": { + "type": "boolean" + }, + "sequencerMetricsUrl": { + "type": "string", + "format": "uri", + "minLength": 1 + }, + "mediatorMetricsUrl": { + "type": "string", + "format": "uri", + "minLength": 1 + }, + "pollIntervalSeconds": { + "type": "number", + "minimum": 1 + }, + "threshold": { + "type": "number" + }, + "evaluationIntervalSeconds": { + "type": "number", + "minimum": 1 + }, + "scrapeTimeoutSeconds": { + "type": "number", + "minimum": 1 + }, + "startupGraceSeconds": { + "type": "number", + "minimum": 0 + }, + "cooldownSeconds": { + "type": "number", + "minimum": 0 + }, + "markerFile": { + "type": "string", + "pattern": "^/[^/]+/[^/]+" + }, + "imageName": { + "type": "string" + }, + "resources": { + "type": "object" + } + }, + "if": { + "properties": { + "enabled": { + "const": true + } + }, + "required": [ + "enabled" + ] + }, + "then": { + "required": [ + "sequencerMetricsUrl", + "mediatorMetricsUrl" + ] + } } } } diff --git a/cluster/images/cometbft-watchdog/Dockerfile b/cluster/images/cometbft-watchdog/Dockerfile new file mode 100644 index 0000000000..9d075d816a --- /dev/null +++ b/cluster/images/cometbft-watchdog/Dockerfile @@ -0,0 +1,16 @@ +# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +FROM python:3.12-slim@sha256:2c941e860699f878900b0edc2403613c234d4b32eda3cc9fa7036991a2a63c4a + +LABEL org.opencontainers.image.base.name="python:3.12-slim" + +COPY restart-watchdog.py /restart-watchdog.py + +COPY target/LICENSE . + +# Ensure logs get output unbuffered even when the output is a pipe and don't write byte code in the container. +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 + +CMD ["python3", "/restart-watchdog.py"] diff --git a/cluster/images/cometbft-watchdog/local.mk b/cluster/images/cometbft-watchdog/local.mk new file mode 100644 index 0000000000..742cc33298 --- /dev/null +++ b/cluster/images/cometbft-watchdog/local.mk @@ -0,0 +1,9 @@ +# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +dir := $(call current_dir) + +$(dir)/$(docker-build): $(dir)/restart-watchdog.py $(dir)/target/LICENSE + +$(dir)/target/LICENSE: ${SPLICE_ROOT}/LICENSE | $(dir)/target + cp $< $@ diff --git a/cluster/images/cometbft-watchdog/restart-watchdog.py b/cluster/images/cometbft-watchdog/restart-watchdog.py new file mode 100644 index 0000000000..34f1d09a7b --- /dev/null +++ b/cluster/images/cometbft-watchdog/restart-watchdog.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 + +# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import json +import math +import os +import random +import re +import signal +import sys +import time +import urllib.error +import urllib.request + +# Rate of confirmation requests accepted by the sequencer. +SEQUENCER_METRIC = "daml_sequencer_block_events_total" +SEQUENCER_LABELS = {"type": "send-confirmation-request"} +# Rate of confirmation requests processed by the mediator (approved and rejected). +MEDIATOR_METRIC = "daml_mediator_requests_total" +MEDIATOR_LABELS = {} + +LABEL_RE = re.compile(r'([a-zA-Z_][a-zA-Z0-9_]*)="((?:[^"\\]|\\.)*)"') +ESCAPES = {"\\": "\\", '"': '"', "n": "\n"} + + +class ConfigError(Exception): + pass + + +class ScrapeError(Exception): + pass + + +# minimal json log helper to avoid having to pull in dependencies. field names chosen to match canton logging. +def log(severity, message, **fields): + entry = { + "level": severity, + "@timestamp": time.strftime("%Y-%m-%dT%H:%M:%S%z"), + "message": message, + } + entry.update(fields) + print(json.dumps(entry), flush=True) + +# unescape helper to deal with escaped labels +def unescape(value): + out = [] + chars = iter(value) + for char in chars: + if char == "\\": + escaped = next(chars, "") + out.append(ESCAPES.get(escaped, escaped)) + else: + out.append(char) + return "".join(out) + + +def parse_samples(text, metric, required_labels, source): + """Parse all entries for `metric` that have `required_labels` and return them as a dict indexed by the labels. + """ + samples = {} + labelled_prefix = metric + "{" + unlabelled_prefix = metric + " " + for line in text.splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + if line.startswith(labelled_prefix): + # Label values may contain '}', the value after the labels never does. + labels_end = line.rindex("}") + labels = { + match.group(1): unescape(match.group(2)) + for match in LABEL_RE.finditer(line[len(labelled_prefix) : labels_end]) + } + rest = line[labels_end + 1 :] + elif line.startswith(unlabelled_prefix): + labels = {} + rest = line[len(metric) :] + else: + continue + if any(labels.get(name) != value for name, value in required_labels.items()): + continue + # `rest` is the value, optionally followed by a timestamp. + try: + value = float(rest.split()[0]) + except (IndexError, ValueError): + log("WARNING", "Ignoring unparseable sample", source=source, line=line) + continue + if math.isnan(value): + continue + samples[tuple(sorted(labels.items()))] = value + return samples + + +def scrape(url, metric, required_labels, timeout): + try: + request = urllib.request.Request( + url, headers={"Accept": "text/plain"} + ) + with urllib.request.urlopen(request, timeout=timeout) as response: + charset = response.headers.get_content_charset() or "utf-8" + body = response.read().decode(charset, errors="replace") + except Exception as error: + raise ScrapeError(f"failed to scrape {url}: {error}") from error + samples = parse_samples(body, metric, required_labels, url) + return samples + + +def counter_increase(previous, current): + # we already did filter to relevant labels when scraping so here we just sum up everything. + total_previous = 0.0 + total_current = 0.0 + for key, value in previous.items(): + total_previous += value + for key, value in current.items(): + total_current += value + return total_current - total_previous + + +def env_float(name): + raw = os.environ.get(name) + if not raw: + raise ConfigError(f"{name} must be set") + try: + value = float(raw) + except ValueError as error: + raise ConfigError(f"{name} must be a number, got {raw!r}") from error + return value + + +def env_url(name): + url = os.environ.get(name, "").strip() + if not url: + raise ConfigError(f"{name} must be set") + return url + + +class Config: + def __init__(self): + self.sequencer_url = env_url("WATCHDOG_SEQUENCER_METRICS_URL") + self.mediator_url = env_url("WATCHDOG_MEDIATOR_METRICS_URL") + self.poll_interval = env_float("WATCHDOG_POLL_INTERVAL_SECONDS") + self.threshold = env_float("WATCHDOG_THRESHOLD") + self.evaluation_interval = env_float("WATCHDOG_EVALUATION_INTERVAL_SECONDS") + self.scrape_timeout = env_float("WATCHDOG_SCRAPE_TIMEOUT_SECONDS") + self.startup_grace = env_float("WATCHDOG_STARTUP_GRACE_SECONDS") + self.cooldown = env_float("WATCHDOG_COOLDOWN_SECONDS") + self.marker_file = os.environ.get("WATCHDOG_MARKER_FILE") + if self.evaluation_interval < self.poll_interval: + raise ConfigError( + "WATCHDOG_EVALUATION_INTERVAL_SECONDS must be at least " + "WATCHDOG_POLL_INTERVAL_SECONDS, otherwise a single poll can trigger a " + "restart" + ) + + def as_dict(self): + return { + "sequencerUrl": self.sequencer_url, + "mediatorUrl": self.mediator_url, + "pollIntervalSeconds": self.poll_interval, + "threshold": self.threshold, + "evaluationIntervalSeconds": self.evaluation_interval, + "scrapeTimeoutSeconds": self.scrape_timeout, + "startupGraceSeconds": self.startup_grace, + "cooldownSeconds": self.cooldown, + "markerFile": self.marker_file, + } + + +class Watchdog: + def __init__(self, config): + self.config = config + self.previous = None + self.breach_since = None + + def reset(self): + self.previous = None + self.breach_since = None + + def request_restart(self, reason): + marker = self.config.marker_file + # atomic file write + temporary = marker + ".tmp" + with open(temporary, "w", encoding="utf-8") as handle: + handle.write(reason + "\n") + os.replace(temporary, marker) + + def poll(self, now): + """Scrape and return last time and rate measured by sequencer and mediator""" + config = self.config + sequencer = scrape( + config.sequencer_url, SEQUENCER_METRIC, SEQUENCER_LABELS, config.scrape_timeout + ) + mediator = scrape( + config.mediator_url, MEDIATOR_METRIC, MEDIATOR_LABELS, config.scrape_timeout + ) + if not sequencer: + raise ScrapeError( + f"no {SEQUENCER_METRIC} samples matching {SEQUENCER_LABELS} were returned" + ) + if not mediator: + raise ScrapeError(f"no {MEDIATOR_METRIC} samples were returned") + + previous = self.previous + self.previous = (now, sequencer, mediator) + if previous is None: + return None + previous_time, previous_sequencer, previous_mediator = previous + elapsed = now - previous_time + if elapsed <= 0: + return None + sequencer_rate = counter_increase(previous_sequencer, sequencer) / elapsed + mediator_rate = counter_increase(previous_mediator, mediator) / elapsed + return previous_time, sequencer_rate, mediator_rate + + def evaluate(self, now, observation): + """Given the data from poll check if we exceeded the threshold. Returns the reason to restart, or None.""" + interval_start, sequencer_rate, mediator_rate = observation + # counters reset to 0 after restart so guard against that. + if sequencer_rate < 0: + Log( + "INFO", + "sequencer rate was negative likely because sequencer restarted, resetting state", + sequencerRate=round(sequencer_rate, 4) + ) + self.reset() + return None + if mediator_rate < 0: + Log( + "INFO", + "mediator rate was negative likely because mediator restarted, resetting state", + mediatorRate=round(mediator_rate, 4) + ) + self.reset() + return None + difference = sequencer_rate - mediator_rate + if difference > self.config.threshold: + if self.breach_since is None: + self.breach_since = interval_start + breach_duration = now - self.breach_since + else: + self.breach_since = None + breach_duration = 0.0 + + log( + "INFO", + "Evaluated synchronizer progress", + sequencerRate=round(sequencer_rate, 4), + mediatorRate=round(mediator_rate, 4), + difference=round(difference, 4), + threshold=self.config.threshold, + breachDurationSeconds=round(breach_duration, 1), + evaluationIntervalSeconds=self.config.evaluation_interval, + ) + + if self.breach_since is not None and breach_duration >= self.config.evaluation_interval: + return ( + f"sequencer rate exceeded mediator rate by {difference:.4f}/s " + f"(threshold {self.config.threshold}/s) for {breach_duration:.0f}s" + ) + return None + + +def main(): + signal.signal(signal.SIGTERM, lambda *_: sys.exit(0)) + try: + config = Config() + except ConfigError as error: + log("CRITICAL", f"Invalid configuration: {error}") + return 2 + + watchdog = Watchdog(config) + log("INFO", "Starting CometBFT restart watchdog", config=config.as_dict()) + + # don't immediately kill cometbft after startup + quiet_until = time.monotonic() + config.startup_grace + while True: + cycle_start = time.monotonic() + try: + observation = watchdog.poll(cycle_start) + except ScrapeError as error: + # keep cometbft running if we fail to scrape. + log("WARNING", f"Skipping evaluation: {error}") + watchdog.reset() + observation = None + + if observation is not None: + reason = watchdog.evaluate(cycle_start, observation) + if reason is not None: + quiet_remaining = quiet_until - cycle_start + if quiet_remaining > 0: + log( + "INFO", + f"Not requesting a restart yet, CometBFT is being given time to " + f"come up: {reason}", + quietRemainingSeconds=round(quiet_remaining, 1), + ) + else: + log("WARN", f"Requesting a CometBFT restart: {reason}") + try: + watchdog.request_restart(reason) + except OSError as error: + log("ERROR", f"Failed to write {config.marker_file}: {error}") + return 1 + watchdog.reset() + # don't kill cometbft after we just killed it. + quiet_until = time.monotonic() + config.cooldown + + time.sleep(random.uniform(0.5, 1.0) * max(0.0, config.poll_interval - (time.monotonic() - cycle_start))) + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/cluster/images/local.mk b/cluster/images/local.mk index 05f2f30c39..dca762212c 100644 --- a/cluster/images/local.mk +++ b/cluster/images/local.mk @@ -9,6 +9,7 @@ images := \ canton-mediator \ canton-cometbft-sequencer \ cometbft \ + cometbft-watchdog \ \ splice-app \ splice-debug \ diff --git a/cluster/pulumi/common-sv/src/config.ts b/cluster/pulumi/common-sv/src/config.ts index 2e1b728bbc..38a083bc76 100644 --- a/cluster/pulumi/common-sv/src/config.ts +++ b/cluster/pulumi/common-sv/src/config.ts @@ -97,6 +97,18 @@ export const SvConfigSchema = z.object({ .object({ volumeSize: z.string().optional(), protected: z.boolean().optional(), + watchdog: z + .object({ + disabled: z.boolean().default(false), + threshold: z.number().optional(), + evaluationIntervalSeconds: z.number().optional(), + pollIntervalSeconds: z.number().optional(), + scrapeTimeoutSeconds: z.number().optional(), + startupGraceSeconds: z.number().optional(), + cooldownSeconds: z.number().optional(), + }) + .strict() + .optional(), }) .optional(), scan: z diff --git a/cluster/pulumi/common-sv/src/synchronizer/cometbft.ts b/cluster/pulumi/common-sv/src/synchronizer/cometbft.ts index 39646df795..38b765dfa8 100644 --- a/cluster/pulumi/common-sv/src/synchronizer/cometbft.ts +++ b/cluster/pulumi/common-sv/src/synchronizer/cometbft.ts @@ -5,6 +5,7 @@ import * as _ from 'lodash'; import { activeVersion, appsAffinityAndTolerations, + ChartValues, CLUSTER_BASENAME, CLUSTER_HOSTNAME, clusterSmallDisk, @@ -143,6 +144,7 @@ export function installCometBftNode( extraLogLevelFlags: svConfiguration.logging?.cometbftExtraLogLevelFlags, serviceAccountName: imagePullServiceAccountName, resources: svConfiguration.cometbft?.resources, + watchdog: watchdogValues(migrationId), }); if (svConfiguration.cometbft?.additionalHelmValues) { _.merge(cometbftChartValues, svConfiguration.cometbft.additionalHelmValues); @@ -167,6 +169,27 @@ export function installCometBftNode( return { rpcServiceName: `${nodeConfig.identifier}-cometbft-rpc`, release }; } +const CANTON_METRICS_PORT = 10013; + +function watchdogValues(migrationId: DomainMigrationIndex): ChartValues { + const watchdog = svsConfig?.cometbft?.watchdog; + if (watchdog?.disabled) { + return { enabled: false }; + } + const synchronizer = `global-domain-${migrationId}`; + return { + enabled: true, + sequencerMetricsUrl: `http://${synchronizer}-sequencer:${CANTON_METRICS_PORT}/metrics`, + mediatorMetricsUrl: `http://${synchronizer}-mediator:${CANTON_METRICS_PORT}/metrics`, + threshold: watchdog?.threshold, + evaluationIntervalSeconds: watchdog?.evaluationIntervalSeconds, + pollIntervalSeconds: watchdog?.pollIntervalSeconds, + scrapeTimeoutSeconds: watchdog?.scrapeTimeoutSeconds, + startupGraceSeconds: watchdog?.startupGraceSeconds, + cooldownSeconds: watchdog?.cooldownSeconds, + }; +} + function installCometBftKeysSecret( xns: ExactNamespace, keyAddress: Output | string, diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index c470d903c2..3843824551 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -54,3 +54,11 @@ release-notes:: Upcoming also been added, allowing 100 requests per second burst / 50 sustained across all operations combined, with an embedded per-client-IP limiter allowing 20 requests per second burst / 10 sustained. + + - CometBFT + + - Added a watchdog to restart cometbft when we detect that it + is replaying messages. You must set + ``watchdog.sequencerMetricsUrl: http://global-domain-SERIAL_ID-sequencer:10013/metrics`` and + ``watchdog.mediatorMetricsUrl: http://global-domain-SERIAL_ID-mediator:10013/metrics`` in the + cometbft helm values. If needed, the watchdog can be disabled through ``watchdog.enabled: false``. From 8fe16e09baca0d8bbca5793b34ee3ad867cc4fac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20B=C5=82a=C5=BCejewski?= Date: Tue, 18 Aug 2026 15:15:50 +0200 Subject: [PATCH 257/329] enable split-sv deployment for scratchnets by default (#6808) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [static] Signed-off-by: Mateusz Błażejewski --- .../configs/shared/scratch-default-synchronizer-migration.yaml | 1 + cluster/deployment/scratchneta/config.resolved.yaml | 1 + cluster/deployment/scratchnetb/config.resolved.yaml | 1 + cluster/deployment/scratchnetc/config.resolved.yaml | 1 + cluster/deployment/scratchnetd/config.resolved.yaml | 1 + cluster/deployment/scratchnete/config.resolved.yaml | 1 + 6 files changed, 6 insertions(+) diff --git a/cluster/configs/shared/scratch-default-synchronizer-migration.yaml b/cluster/configs/shared/scratch-default-synchronizer-migration.yaml index 43b07dcca9..814030e96c 100644 --- a/cluster/configs/shared/scratch-default-synchronizer-migration.yaml +++ b/cluster/configs/shared/scratch-default-synchronizer-migration.yaml @@ -1,4 +1,5 @@ synchronizerMigration: + splitSvDeploymentEnabled: true frozenMigrationId: 0 active: id: 0 diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index c67bbbe6f6..cb160ab67c 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -1865,6 +1865,7 @@ synchronizerMigration: spliceRoot: 'splice' version: 'local' frozenMigrationId: 0 + splitSvDeploymentEnabled: true validator1: deduplicationDuration: '30m' logging: diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index c67bbbe6f6..cb160ab67c 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -1865,6 +1865,7 @@ synchronizerMigration: spliceRoot: 'splice' version: 'local' frozenMigrationId: 0 + splitSvDeploymentEnabled: true validator1: deduplicationDuration: '30m' logging: diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index c67bbbe6f6..cb160ab67c 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -1865,6 +1865,7 @@ synchronizerMigration: spliceRoot: 'splice' version: 'local' frozenMigrationId: 0 + splitSvDeploymentEnabled: true validator1: deduplicationDuration: '30m' logging: diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index c67bbbe6f6..cb160ab67c 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -1865,6 +1865,7 @@ synchronizerMigration: spliceRoot: 'splice' version: 'local' frozenMigrationId: 0 + splitSvDeploymentEnabled: true validator1: deduplicationDuration: '30m' logging: diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index c67bbbe6f6..cb160ab67c 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -1865,6 +1865,7 @@ synchronizerMigration: spliceRoot: 'splice' version: 'local' frozenMigrationId: 0 + splitSvDeploymentEnabled: true validator1: deduplicationDuration: '30m' logging: From f2b5d2a7267e2ed51ca81f47fc8b0a2950de9907 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:34:31 +0200 Subject: [PATCH 258/329] Apply node type changes from scratch (#6827) * Apply node type changes from scratch [static] Copypasta from https://github.com/DACH-NY/canton-network-internal/commit/f6af5604a67439e2a8d71f6204cec8a43c328eb4 Signed-off-by: moritz.kiefer@digitalasset.com * scratchnet config only [static] Signed-off-by: moritz.kiefer@digitalasset.com --------- Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- cluster/configs/shared/scratchnet.yaml | 10 ++-------- cluster/deployment/scratchneta/config.resolved.yaml | 9 ++------- cluster/deployment/scratchnetb/config.resolved.yaml | 9 ++------- cluster/deployment/scratchnetc/config.resolved.yaml | 9 ++------- cluster/deployment/scratchnetd/config.resolved.yaml | 9 ++------- cluster/deployment/scratchnete/config.resolved.yaml | 9 ++------- 6 files changed, 12 insertions(+), 43 deletions(-) diff --git a/cluster/configs/shared/scratchnet.yaml b/cluster/configs/shared/scratchnet.yaml index 4c1dc917e8..1438dce184 100644 --- a/cluster/configs/shared/scratchnet.yaml +++ b/cluster/configs/shared/scratchnet.yaml @@ -3,14 +3,8 @@ cluster: nodePools: apps: zones: '*' - additionalApps: - # Second, Intel based, pool is supposed to address GCloud compute resource issues. - - minNodes: 0 - # A high max-nodes by default to support large deployments and hard migrations - # Should be set to a lower number (currently 8) on CI clusters that do neither of those. - maxNodes: 20 - nodeType: n4-standard-16 - zones: '*' + maxNodes: 40 + nodeType: c3d-standard-8 infra: prometheus: retentionDuration: "30d" diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index cb160ab67c..ec39dae63f 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -16,15 +16,10 @@ cloudArmor: withinIntervalSeconds: 60 cluster: nodePools: - additionalApps: - - maxNodes: 20 - minNodes: 0 - nodeType: 'n4-standard-16' - zones: '*' apps: - maxNodes: 20 + maxNodes: 40 minNodes: 0 - nodeType: 'n4d-standard-16' + nodeType: 'c3d-standard-8' zones: '*' infra: maxNodes: 3 diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index cb160ab67c..ec39dae63f 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -16,15 +16,10 @@ cloudArmor: withinIntervalSeconds: 60 cluster: nodePools: - additionalApps: - - maxNodes: 20 - minNodes: 0 - nodeType: 'n4-standard-16' - zones: '*' apps: - maxNodes: 20 + maxNodes: 40 minNodes: 0 - nodeType: 'n4d-standard-16' + nodeType: 'c3d-standard-8' zones: '*' infra: maxNodes: 3 diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index cb160ab67c..ec39dae63f 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -16,15 +16,10 @@ cloudArmor: withinIntervalSeconds: 60 cluster: nodePools: - additionalApps: - - maxNodes: 20 - minNodes: 0 - nodeType: 'n4-standard-16' - zones: '*' apps: - maxNodes: 20 + maxNodes: 40 minNodes: 0 - nodeType: 'n4d-standard-16' + nodeType: 'c3d-standard-8' zones: '*' infra: maxNodes: 3 diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index cb160ab67c..ec39dae63f 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -16,15 +16,10 @@ cloudArmor: withinIntervalSeconds: 60 cluster: nodePools: - additionalApps: - - maxNodes: 20 - minNodes: 0 - nodeType: 'n4-standard-16' - zones: '*' apps: - maxNodes: 20 + maxNodes: 40 minNodes: 0 - nodeType: 'n4d-standard-16' + nodeType: 'c3d-standard-8' zones: '*' infra: maxNodes: 3 diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index cb160ab67c..ec39dae63f 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -16,15 +16,10 @@ cloudArmor: withinIntervalSeconds: 60 cluster: nodePools: - additionalApps: - - maxNodes: 20 - minNodes: 0 - nodeType: 'n4-standard-16' - zones: '*' apps: - maxNodes: 20 + maxNodes: 40 minNodes: 0 - nodeType: 'n4d-standard-16' + nodeType: 'c3d-standard-8' zones: '*' infra: maxNodes: 3 From 5ce4a5263645d632eb8cf948e8d7e0aadb982ef3 Mon Sep 17 00:00:00 2001 From: Martin Florian Date: Tue, 18 Aug 2026 17:39:46 +0200 Subject: [PATCH 259/329] Double scan rate limits for `/registry/transfer-instruction/v1/transfer-factory` (#6830) Based on experience on MainNet, see DACH-NY/canton-network-internal#6543 This endpoint is cheap, some IP(s) are getting rate limited at 3-4 requests / second. [static] Signed-off-by: Martin Florian --- .../shared/rate-limits/token-registry.yaml | 8 ++--- .../scratchneta/config.resolved.yaml | 8 ++--- .../scratchnetb/config.resolved.yaml | 8 ++--- .../scratchnetc/config.resolved.yaml | 8 ++--- .../scratchnetd/config.resolved.yaml | 8 ++--- .../scratchnete/config.resolved.yaml | 8 ++--- cluster/expected/sv-runbook/expected.json | 16 +++++----- cluster/expected/sv/expected.json | 32 +++++++++---------- 8 files changed, 48 insertions(+), 48 deletions(-) diff --git a/cluster/configs/shared/rate-limits/token-registry.yaml b/cluster/configs/shared/rate-limits/token-registry.yaml index 7997521b60..abda7327bd 100644 --- a/cluster/configs/shared/rate-limits/token-registry.yaml +++ b/cluster/configs/shared/rate-limits/token-registry.yaml @@ -52,12 +52,12 @@ rateLimits: /registry/transfer-instruction/v1/transfer-factory: name: registry-transfer-factory type: limited - maxTokens: 730 - tokensPerFill: 730 + maxTokens: 1440 + tokensPerFill: 1440 fillInterval: 60s perIpLimits: - maxTokens: 120 - tokensPerFill: 120 + maxTokens: 240 + tokensPerFill: 240 fillInterval: 60s /registry/allocation/v2/settlement-factory: name: registry-settlement-factory-v2 diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index ec39dae63f..784ad2223c 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -458,13 +458,13 @@ sv: type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 730 + maxTokens: 1440 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 120 - tokensPerFill: 120 - tokensPerFill: 730 + maxTokens: 240 + tokensPerFill: 240 + tokensPerFill: 1440 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index ec39dae63f..784ad2223c 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -458,13 +458,13 @@ sv: type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 730 + maxTokens: 1440 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 120 - tokensPerFill: 120 - tokensPerFill: 730 + maxTokens: 240 + tokensPerFill: 240 + tokensPerFill: 1440 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index ec39dae63f..784ad2223c 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -458,13 +458,13 @@ sv: type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 730 + maxTokens: 1440 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 120 - tokensPerFill: 120 - tokensPerFill: 730 + maxTokens: 240 + tokensPerFill: 240 + tokensPerFill: 1440 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index ec39dae63f..784ad2223c 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -458,13 +458,13 @@ sv: type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 730 + maxTokens: 1440 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 120 - tokensPerFill: 120 - tokensPerFill: 730 + maxTokens: 240 + tokensPerFill: 240 + tokensPerFill: 1440 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index ec39dae63f..784ad2223c 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -458,13 +458,13 @@ sv: type: 'limited' /registry/transfer-instruction/v1/transfer-factory: fillInterval: '60s' - maxTokens: 730 + maxTokens: 1440 name: 'registry-transfer-factory' perIpLimits: fillInterval: '60s' - maxTokens: 120 - tokensPerFill: 120 - tokensPerFill: 730 + maxTokens: 240 + tokensPerFill: 240 + tokensPerFill: 1440 type: 'limited' /registry/transfer-instruction/v2: fillInterval: '60s' diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index 549414864e..abb6ca2217 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -1177,14 +1177,14 @@ }, "/registry/transfer-instruction/v1/transfer-factory": { "fillInterval": "60s", - "maxTokens": 730, + "maxTokens": 1440, "name": "registry-transfer-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 + "maxTokens": 240, + "tokensPerFill": 240 }, - "tokensPerFill": 730, + "tokensPerFill": 1440, "type": "limited" }, "/registry/transfer-instruction/v2": { @@ -2446,8 +2446,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 730, - "tokens_per_fill": 730 + "max_tokens": 1440, + "tokens_per_fill": 1440 } }, { @@ -2462,8 +2462,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 + "max_tokens": 240, + "tokens_per_fill": 240 } }, { diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index 699fe52989..c048300f28 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -1868,14 +1868,14 @@ }, "/registry/transfer-instruction/v1/transfer-factory": { "fillInterval": "60s", - "maxTokens": 730, + "maxTokens": 1440, "name": "registry-transfer-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 + "maxTokens": 240, + "tokensPerFill": 240 }, - "tokensPerFill": 730, + "tokensPerFill": 1440, "type": "limited" }, "/registry/transfer-instruction/v2": { @@ -2261,14 +2261,14 @@ }, "/registry/transfer-instruction/v1/transfer-factory": { "fillInterval": "60s", - "maxTokens": 730, + "maxTokens": 1440, "name": "registry-transfer-factory", "perIpLimits": { "fillInterval": "60s", - "maxTokens": 120, - "tokensPerFill": 120 + "maxTokens": 240, + "tokensPerFill": 240 }, - "tokensPerFill": 730, + "tokensPerFill": 1440, "type": "limited" }, "/registry/transfer-instruction/v2": { @@ -3759,8 +3759,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 730, - "tokens_per_fill": 730 + "max_tokens": 1440, + "tokens_per_fill": 1440 } }, { @@ -3775,8 +3775,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 + "max_tokens": 240, + "tokens_per_fill": 240 } }, { @@ -6093,8 +6093,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 730, - "tokens_per_fill": 730 + "max_tokens": 1440, + "tokens_per_fill": 1440 } }, { @@ -6109,8 +6109,8 @@ ], "token_bucket": { "fill_interval": "60s", - "max_tokens": 120, - "tokens_per_fill": 120 + "max_tokens": 240, + "tokens_per_fill": 240 } }, { From 53780c1ef895779e1e658ba89c2afedd6158b45f Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Tue, 18 Aug 2026 17:55:29 +0200 Subject: [PATCH 260/329] Revert "Re-enable `SplitwellUpgradeIntegrationTest` (#6748)" (#6829) [ci] fixes #9652 This reverts commit 429825f355658d46f30dda24c3a4dc8a992590dc. Co-authored-by: moritz.kiefer@digitalasset.com --- .../integration/tests/SplitwellTestUtil.scala | 10 ++---- ...itwellUpgradeFrontendIntegrationTest.scala | 3 ++ .../SplitwellUpgradeIntegrationTest.scala | 31 ++++++++++++++++--- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellTestUtil.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellTestUtil.scala index 1dbce9df33..5a1bb670ec 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellTestUtil.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellTestUtil.scala @@ -2,7 +2,6 @@ package org.lfdecentralizedtrust.splice.util import com.digitalasset.canton.SynchronizerAlias import com.digitalasset.canton.admin.api.client.data.GrpcSequencerConnection -import com.digitalasset.canton.integration.util.MultiSynchronizerFeatureFlag import com.digitalasset.canton.topology.PartyId import org.lfdecentralizedtrust.splice.codegen.java.splice.splitwell as splitwellCodegen import org.lfdecentralizedtrust.splice.codegen.java.splice.wallet.payment as walletCodegen @@ -43,13 +42,8 @@ trait SplitwellTestUtil extends TestCommon with WalletTestUtil with TimeTestUtil actAndCheck( timeUntilSuccess = 40.seconds )( - "Connect splitwell upgrade domain", { - participant.synchronizers.connect(splitwellUpgradeAlias, url) - participant.upload_dar_unless_exists(splitwellDarPath) - participant.synchronizers.list_connected().foreach { sync => - MultiSynchronizerFeatureFlag.enable(Seq(participant), sync.synchronizerId) - } - }, + "Connect splitwell upgrade domain", + participant.synchronizers.connect(splitwellUpgradeAlias, url), )( s"Wait for splitwell upgrade domain to be connected for party $ensurePartyIsOnNewDomain", _ => { diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeFrontendIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeFrontendIntegrationTest.scala index a1b3a30b9a..a46c72f5ee 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeFrontendIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeFrontendIntegrationTest.scala @@ -13,7 +13,10 @@ import org.lfdecentralizedtrust.splice.util.{ WalletTestUtil, } import SplitwellUpgradeFrontendIntegrationTest.* +import org.scalatest.Ignore +// TODO(DACH-NY/canton-network-internal#1834) Reenable once we sorted out the reassignment issues +@Ignore class SplitwellUpgradeFrontendIntegrationTest extends FrontendIntegrationTest(aliceSplitwellFE, bobSplitwellFE) with FrontendLoginUtil diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeIntegrationTest.scala index 18e7604812..c59b765738 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellUpgradeIntegrationTest.scala @@ -8,12 +8,16 @@ import org.lfdecentralizedtrust.splice.console.SplitwellAppClientReference import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.IntegrationTest import SpliceTests.BracketSynchronous.* +import com.digitalasset.canton.logging.SuppressingLogger.LogEntryOptionality import org.lfdecentralizedtrust.splice.util.{MultiDomainTestUtil, SplitwellTestUtil, WalletTestUtil} import com.digitalasset.canton.topology.{PartyId, SynchronizerId} +import org.scalatest.Ignore import scala.concurrent.duration.DurationInt import scala.util.Try +// TODO(#2703) Reenable or delete +@Ignore class SplitwellUpgradeIntegrationTest extends IntegrationTest with MultiDomainTestUtil @@ -57,7 +61,27 @@ class SplitwellUpgradeIntegrationTest def createInstalls(splitwells: SplitwellAppClientReference*) = for { splitwell <- splitwells } eventually() { - Try(splitwell.createInstallRequests()).toEither.valueOr(fail(_)) + loggerFactory + .assertLogsUnorderedOptionalFromResult[Try[Unit]]( + Try(splitwell.createInstallRequests()), + { r => + if (r.isFailure) { + Seq( + ( + LogEntryOptionality.Required, + log => + log.errorMessage should include( + "Not all informee are on the specified domainID: splitwellUpgrade" + ), + ) + ) + } else { + Seq.empty + } + }, + ) + .toEither + .valueOr(fail(_)) } def twoInstalls(alice: PartyId, install: splitwellCodegen.SplitwellInstall.Contract)(implicit @@ -128,6 +152,8 @@ class SplitwellUpgradeIntegrationTest val acceptedInvite = bobSplitwellClient.acceptInvite(invite) val splitwellSynchronizerId = aliceValidatorBackend.participantClient.synchronizers.id_of(splitwellAlias).logical + val splitwellUpgradeSynchronizerId = + aliceValidatorBackend.participantClient.synchronizers.id_of(splitwellUpgradeAlias).logical eventually() { val contractDomains = @@ -149,9 +175,6 @@ class SplitwellUpgradeIntegrationTest connectSplitwellUpgradeDomain(aliceValidatorBackend.participantClient, alice), disconnectSplitwellUpgradeDomain(aliceValidatorBackend.participantClient), ) { - val splitwellUpgradeSynchronizerId = - aliceValidatorBackend.participantClient.synchronizers.id_of(splitwellUpgradeAlias).logical - bracket( connectSplitwellUpgradeDomain(bobValidatorBackend.participantClient, bob), disconnectSplitwellUpgradeDomain(bobValidatorBackend.participantClient), From fdaafa09ffdd36d160bb13bb35932650b71454ee Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Tue, 18 Aug 2026 19:57:58 +0200 Subject: [PATCH 261/329] Upgrade Canton to 3.5.14-snapshot.20260815.19176.0.v65fa04f6 (#6797) fwd port from #6789 [ci] --------- Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index f3aee487e2..ec54bec040 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.13", - "oss_sha256": "sha256:0fqh6zxcbmlamb5c0yqsgsdipll7x7vwlyib57bxgscya5wwwa1f", - "canton_base_image_sha256": "sha256:0c4fb100f48245ff06c3f86686003c04db6575e43214ac69bf8e2cbd5fd5aa32", - "canton_participant_image_sha256": "sha256:dd4b434fab29b40ac9278ac1d9d194683f9c36d9bca551c5618075ec19a1776c", - "canton_mediator_image_sha256": "sha256:59a2f423ed0292d33514e450bdcba59b0b50410d93b04a9f62751f18765e1c16", - "canton_sequencer_image_sha256": "sha256:e8ef032c530b078093b0e45d83e5133e308eefc40728ed0b6d424538f3f95768" + "version": "3.5.14-snapshot.20260815.19176.0.v65fa04f6", + "oss_sha256": "sha256:1wik69kgc7b809960x35wihxqp5lqvdjani7xr9428glvaxwy069", + "canton_base_image_sha256": "sha256:d157ae44eba82688812e80d74f7a206bbad6f7657d5cb5d2f3aedc98608f5f0a", + "canton_participant_image_sha256": "sha256:a2d45ef08e5a255a9858e29b88d0d73f8f52362ea7e7a646d67e729b7984303f", + "canton_mediator_image_sha256": "sha256:b80fb5b639ad3c054d5bd770d30b3bfa165eaad86ee2284c5b4447d4847a467b", + "canton_sequencer_image_sha256": "sha256:286ab89653f2320552c8ecd9f0cd4740095475255742168386408fdde8a30672" } From eeaee3930359bc616701381f3f6ae8bfb01d328b Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 19 Aug 2026 11:33:54 +0200 Subject: [PATCH 262/329] Add proposal search functionality (#6491) Signed-off-by: Tim Pelzer --- .../governance/governance-page.test.tsx | 73 ++++- .../proposal-search-validation.test.ts | 23 ++ .../governance/proposal-search.test.tsx | 83 ++++++ .../frontend/src/__tests__/mocks/constants.ts | 5 + .../governance/ActionRequiredSection.tsx | 12 +- .../governance/ProposalListingSection.tsx | 32 ++- .../components/governance/ProposalSearch.tsx | 215 ++++++++++++++ apps/sv/frontend/src/hooks/index.ts | 2 +- .../src/hooks/useListVoteRequests.tsx | 142 +++++++++- apps/sv/frontend/src/hooks/useVoteRequest.tsx | 9 +- .../src/hooks/useVoteRequestResultByCid.tsx | 57 ++-- apps/sv/frontend/src/routes/governance.tsx | 263 +++++++++++------- .../src/routes/voteRequestDetails.tsx | 2 +- apps/sv/frontend/src/utils/governance.ts | 52 ++++ apps/sv/frontend/src/utils/proposalSearch.ts | 94 +++++++ 15 files changed, 918 insertions(+), 146 deletions(-) create mode 100644 apps/sv/frontend/src/__tests__/governance/proposal-search-validation.test.ts create mode 100644 apps/sv/frontend/src/__tests__/governance/proposal-search.test.tsx create mode 100644 apps/sv/frontend/src/components/governance/ProposalSearch.tsx create mode 100644 apps/sv/frontend/src/utils/proposalSearch.ts diff --git a/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx b/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx index 2721724541..fa85a645a9 100644 --- a/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx @@ -1,6 +1,6 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { render, screen, waitFor, within } from '@testing-library/react'; +import { fireEvent, render, screen, waitFor, within } from '@testing-library/react'; import { describe, expect, test } from 'vitest'; import { SvConfigProvider } from '../../utils'; import userEvent from '@testing-library/user-event'; @@ -8,7 +8,13 @@ import dayjs from 'dayjs'; import { dateTimeFormatISO } from '@canton-network/splice-common-frontend-utils'; import App from '../../App'; import { navigateToGovernancePage } from '../helpers'; -import { voteResultsAmuletRules, voteResultsDsoRules } from '../mocks/constants'; +import { + activeProposalCid, + closedVoteCid, + voteResultsAmuletRules, + voteResultsDsoRules, +} from '../mocks/constants'; +import { CONTRACT_ID_VALIDATION_MESSAGE } from '../../utils/proposalSearch'; type UserEvent = ReturnType; @@ -255,4 +261,67 @@ describe('Governance Page', () => { expect(screen.getByTestId('your-vote-accept')).toBeInTheDocument(); expect(screen.getByTestId('your-vote-reject')).toBeInTheDocument(); }); + + describe('Proposal Search', () => { + test('renders search field and filters action required by full contract ID', async () => { + const user = userEvent.setup(); + await login(user); + await navigateToGovernancePage(user); + + expect(screen.getByTestId('proposal-search')).toBeInTheDocument(); + expect(screen.getAllByTestId('action-required-card')).toHaveLength(4); + + fireEvent.change(screen.getByTestId('proposal-search-input'), { + target: { value: activeProposalCid }, + }); + + await waitFor(() => { + expect(screen.getByTestId('proposal-search-clear')).toBeInTheDocument(); + expect(screen.getAllByTestId('action-required-card')).toHaveLength(1); + }); + }); + + test('filters vote history to matching full contract ID', async () => { + const user = userEvent.setup(); + await login(user); + await navigateToGovernancePage(user); + + expect(screen.getAllByTestId('vote-history-row')).toHaveLength(5); + + fireEvent.change(screen.getByTestId('proposal-search-input'), { + target: { value: closedVoteCid }, + }); + + await waitFor(() => { + expect(screen.getAllByTestId('vote-history-row')).toHaveLength(1); + }); + }); + + test('invalid contract ID shows validation and does not filter', async () => { + const user = userEvent.setup(); + await login(user); + await navigateToGovernancePage(user); + + const input = screen.getByTestId('proposal-search-input'); + fireEvent.change(input, { target: { value: 'not-a-valid-contract-id' } }); + + expect(screen.getByText(CONTRACT_ID_VALIDATION_MESSAGE)).toBeInTheDocument(); + expect(screen.getAllByTestId('action-required-card')).toHaveLength(4); + fireEvent.keyDown(input, { key: 'Enter' }); + expect(screen.getByTestId('governance-page-header')).toBeInTheDocument(); + expect(screen.queryByTestId('proposal-details-title')).not.toBeInTheDocument(); + }); + + test('contract ID search navigates to proposal details on enter', async () => { + const user = userEvent.setup(); + await login(user); + await navigateToGovernancePage(user); + + const input = screen.getByTestId('proposal-search-input'); + fireEvent.change(input, { target: { value: activeProposalCid } }); + fireEvent.keyDown(input, { key: 'Enter' }); + + expect(await screen.findByTestId('proposal-details-title')).toBeInTheDocument(); + }); + }); }); diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-search-validation.test.ts b/apps/sv/frontend/src/__tests__/governance/proposal-search-validation.test.ts new file mode 100644 index 0000000000..ede3aa3fd0 --- /dev/null +++ b/apps/sv/frontend/src/__tests__/governance/proposal-search-validation.test.ts @@ -0,0 +1,23 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, test } from 'vitest'; +import { activeProposalCid, closedVoteCid } from '../mocks/constants'; +import { isValidContractId } from '../../utils/proposalSearch'; + +describe('isValidContractId', () => { + test.each([ + [activeProposalCid, true], + [closedVoteCid, true], + [`00${'ab'.repeat(32)}`, true], + [`01${'ab'.repeat(12)}`, true], + ['', false], + ['not-a-valid-contract-id', false], + [`00${'a'.repeat(65)}`, false], + [`00${'ab'.repeat(31)}`, false], + [`01${'a'.repeat(25)}`, false], + ['00', false], + ])('%p -> %s', (value, expected) => { + expect(isValidContractId(value)).toBe(expected); + }); +}); diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-search.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-search.test.tsx new file mode 100644 index 0000000000..3a525043b9 --- /dev/null +++ b/apps/sv/frontend/src/__tests__/governance/proposal-search.test.tsx @@ -0,0 +1,83 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import { fireEvent, render, screen } from '@testing-library/react'; +import { describe, expect, test, vi } from 'vitest'; +import { MemoryRouter, Route, Routes, useSearchParams } from 'react-router'; +import { ProposalSearch } from '../../components/governance/ProposalSearch'; +import { activeProposalCid } from '../mocks/constants'; + +const SearchHarness: React.FC<{ onSearchChange: (query: string) => void }> = ({ + onSearchChange, +}) => { + const [searchParams, setSearchParams] = useSearchParams(); + + return ( + <> + + {searchParams.get('q') ?? ''} + + + ); +}; + +describe('ProposalSearch', () => { + test('does not reset input when stale URL update arrives while typing ahead', async () => { + const onSearchChange = vi.fn(); + render( + + + } /> + + + ); + + const input = screen.getByTestId('proposal-search-input'); + fireEvent.change(input, { target: { value: 'abc' } }); + + expect(input).toHaveValue('abc'); + expect(onSearchChange).not.toHaveBeenCalled(); + + fireEvent.click(screen.getByTestId('set-stale-url')); + + expect(input).toHaveValue('abc'); + expect(onSearchChange).not.toHaveBeenCalled(); + }); + + test('syncs input from URL on initial load', () => { + render( + + + } /> + + + ); + + expect(screen.getByTestId('proposal-search-input')).toHaveValue('from-url'); + }); + + test('clear search removes q from the URL', () => { + const onSearchChange = vi.fn(); + render( + + + } /> + + + ); + + expect(screen.getByTestId('url-q').textContent).toBe(activeProposalCid); + + fireEvent.click(screen.getByTestId('proposal-search-clear')); + + expect(screen.getByTestId('proposal-search-input')).toHaveValue(''); + expect(screen.getByTestId('url-q').textContent).toBe(''); + expect(onSearchChange).toHaveBeenLastCalledWith(''); + }); +}); diff --git a/apps/sv/frontend/src/__tests__/mocks/constants.ts b/apps/sv/frontend/src/__tests__/mocks/constants.ts index 8db0757eb4..5ef3a43091 100644 --- a/apps/sv/frontend/src/__tests__/mocks/constants.ts +++ b/apps/sv/frontend/src/__tests__/mocks/constants.ts @@ -389,3 +389,8 @@ if (!result.ok) { } export const svPartyId = dsoInfo.sv_party_id; + +export const activeProposalCid = + '10f1a2cbcd5a2dc9ad2fb9d17fec183d75de19ca91f623cbd2eaaf634e8d7cb4b5ca101220b5c5c20442f608e151ca702e0c4f51341a338c5979c0547dfcc80f911061ca91'; +export const closedVoteCid = + '99f1a2cbcd5a2dc9ad2fb9d17fec183d75de19ca91f623cbd2eaaf634e8d7cb4b5ca101220b5c5c20442f608e151ca702e0c4f51341a338c5979c0547dfcc80f911061ca99'; diff --git a/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx b/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx index eeca50f4a4..455c39973b 100644 --- a/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx +++ b/apps/sv/frontend/src/components/governance/ActionRequiredSection.tsx @@ -24,13 +24,15 @@ export interface ActionRequiredData { export interface ActionRequiredProps { actionRequiredRequests: ActionRequiredData[]; + noDataMessage?: string; } -export const ActionRequiredSection: React.FC = ( - props: ActionRequiredProps -) => { - const { actionRequiredRequests } = props; +const DEFAULT_NO_DATA_MESSAGE = 'No Action Required items available'; +export const ActionRequiredSection: React.FC = ({ + actionRequiredRequests, + noDataMessage = DEFAULT_NO_DATA_MESSAGE, +}) => { // Sort by voting closes date ascending (closest deadline first) const sortedRequests = actionRequiredRequests.toSorted((a, b) => dayjs(a.votingCloses).isBefore(dayjs(b.votingCloses)) ? -1 : 1 @@ -48,7 +50,7 @@ export const ActionRequiredSection: React.FC = ( {sortedRequests.length === 0 ? ( - No Action Required items available + {noDataMessage} ) : ( sortedRequests.map((ar, index) => ( diff --git a/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx b/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx index 6f35dfe5e5..5f9722a10a 100644 --- a/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx +++ b/apps/sv/frontend/src/components/governance/ProposalListingSection.tsx @@ -33,6 +33,8 @@ interface ProposalListingSectionProps { noDataMessage: string; uniqueId: string; badgeCount?: number; + isLoading?: boolean; + loadingMessage?: string; showThresholdDeadline?: boolean; showVoteStats?: boolean; showStatus?: boolean; @@ -154,6 +156,8 @@ export const ProposalListingSection: React.FC = pro noDataMessage, uniqueId, badgeCount, + isLoading, + loadingMessage = 'Searching…', showThresholdDeadline, showVoteStats, showStatus, @@ -189,7 +193,11 @@ export const ProposalListingSection: React.FC = pro /> {sortedData.length === 0 && !hasNextPage ? ( - + isLoading ? ( + + ) : ( + + ) ) : ( <> @@ -296,6 +304,28 @@ const InfoBox: React.FC = ({ info, 'data-testid': testId }) => { ); }; +interface LoadingBoxProps { + message: string; + 'data-testid': string; +} + +const LoadingBox: React.FC = ({ message, 'data-testid': testId }) => { + return ( + + + + {message} + + + ); +}; + interface VoteRowProps { actionName: string; description?: string; diff --git a/apps/sv/frontend/src/components/governance/ProposalSearch.tsx b/apps/sv/frontend/src/components/governance/ProposalSearch.tsx new file mode 100644 index 0000000000..6a7212d096 --- /dev/null +++ b/apps/sv/frontend/src/components/governance/ProposalSearch.tsx @@ -0,0 +1,215 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +import React, { memo, useEffect, useRef, useState } from 'react'; +import SearchIcon from '@mui/icons-material/Search'; +import { Box, InputAdornment, Link, TextField, Typography } from '@mui/material'; +import type { Theme } from '@mui/material/styles'; +import { useNavigate, useSearchParams } from 'react-router'; +import { ContractId } from '@daml/types'; +import { VoteRequest } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules'; +import { CONTRACT_ID_VALIDATION_MESSAGE, isValidContractId } from '../../utils/proposalSearch'; +import { + fieldDescriptionSx, + fieldSectionSx, + fieldSectionTitleSx, + singleLineFieldSx, +} from '../../themes/fieldStyles'; +import { scrollableTextFieldSx } from '../beta/identifierStyles'; + +const searchTextFieldSx = (theme: Theme) => ({ + ...(typeof singleLineFieldSx === 'function' ? singleLineFieldSx(theme) : singleLineFieldSx), + ...scrollableTextFieldSx, +}); + +function getEffectiveSearchQuery(value: string): string { + const trimmed = value.trim(); + return isValidContractId(trimmed) ? trimmed : ''; +} + +function syncSearchQuery( + value: string, + lastSyncedSearchRef: React.MutableRefObject, + onSearchChange: (query: string) => void +): void { + const effective = getEffectiveSearchQuery(value); + if (effective === lastSyncedSearchRef.current) { + return; + } + lastSyncedSearchRef.current = effective; + onSearchChange(effective); +} + +export interface ProposalSearchProps { + onSearchChange: (query: string) => void; +} + +export const ProposalSearch: React.FC = memo(function ProposalSearch({ + onSearchChange, +}) { + const navigate = useNavigate(); + const [searchParams, setSearchParams] = useSearchParams(); + const urlQuery = searchParams.get('q') ?? ''; + const [inputValue, setInputValue] = useState(urlQuery); + /** Blocks stale URL from overwriting local input while a pending write is in flight. */ + const pendingUrlValueRef = useRef(null); + const lastSyncedSearchRef = useRef(''); + const onSearchChangeRef = useRef(onSearchChange); + onSearchChangeRef.current = onSearchChange; + + useEffect(() => { + setInputValue(prev => { + if (prev === urlQuery) { + if (pendingUrlValueRef.current === urlQuery) { + pendingUrlValueRef.current = null; + } + return prev; + } + + if (pendingUrlValueRef.current !== null) { + if (urlQuery === pendingUrlValueRef.current) { + pendingUrlValueRef.current = null; + } + return prev; + } + + syncSearchQuery(urlQuery, lastSyncedSearchRef, query => { + onSearchChangeRef.current(query); + }); + return urlQuery; + }); + }, [urlQuery]); + + const syncUrl = (value: string) => { + const trimmed = value.trim(); + if (!trimmed || !isValidContractId(trimmed)) { + setSearchParams( + prev => { + if (!prev.has('q')) { + return prev; + } + const next = new URLSearchParams(prev); + next.delete('q'); + return next; + }, + { replace: true } + ); + return; + } + + pendingUrlValueRef.current = trimmed; + setSearchParams( + prev => { + if (prev.get('q') === trimmed) { + return prev; + } + const next = new URLSearchParams(prev); + next.set('q', trimmed); + return next; + }, + { replace: true } + ); + }; + + const handleChange = (value: string) => { + pendingUrlValueRef.current = value; + setInputValue(value); + syncSearchQuery(value, lastSyncedSearchRef, query => { + onSearchChangeRef.current(query); + }); + syncUrl(value); + }; + + const handleClear = () => { + pendingUrlValueRef.current = ''; + lastSyncedSearchRef.current = ''; + setInputValue(''); + onSearchChangeRef.current(''); + syncUrl(''); + }; + + const handleKeyDown = (event: React.KeyboardEvent) => { + if (event.key !== 'Enter') { + return; + } + + const trimmed = inputValue.trim(); + if (isValidContractId(trimmed)) { + event.preventDefault(); + syncSearchQuery(trimmed, lastSyncedSearchRef, query => { + onSearchChangeRef.current(query); + }); + syncUrl(trimmed); + navigate(`/governance/proposals/${trimmed as ContractId}`); + } + }; + + const showValidationError = inputValue.trim().length > 0 && !isValidContractId(inputValue); + + return ( + + + Search Proposals + + + handleChange(event.target.value)} + error={showValidationError} + helperText={showValidationError ? CONTRACT_ID_VALIDATION_MESSAGE : undefined} + sx={searchTextFieldSx} + slotProps={{ + input: { + startAdornment: ( + + + + ), + }, + htmlInput: { + 'data-testid': 'proposal-search-input', + onKeyDown: handleKeyDown, + }, + }} + /> + + {isValidContractId(inputValue) && ( + + Clear search + + )} + + ); +}); + +export default ProposalSearch; diff --git a/apps/sv/frontend/src/hooks/index.ts b/apps/sv/frontend/src/hooks/index.ts index 2542c581fe..086047c7dc 100644 --- a/apps/sv/frontend/src/hooks/index.ts +++ b/apps/sv/frontend/src/hooks/index.ts @@ -3,7 +3,7 @@ import { useAmuletPriceVotes } from './useAmuletPriceVotes'; import { useCometBftDebug } from './useCometBftDebug'; import { useInfiniteVoteRequestResults } from './useInfiniteVoteRequestResults'; -import { useListVoteRequestResult, useListDsoRulesVoteRequests } from './useListVoteRequests'; +import { useListDsoRulesVoteRequests, useListVoteRequestResult } from './useListVoteRequests'; import { useListVotes } from './useListVotes'; import { useMediatorStatus } from './useMediatorStatus'; import { useNetworkInstanceName } from './useNetworkInstanceName'; diff --git a/apps/sv/frontend/src/hooks/useListVoteRequests.tsx b/apps/sv/frontend/src/hooks/useListVoteRequests.tsx index bf48c8d821..b3b38f3a44 100644 --- a/apps/sv/frontend/src/hooks/useListVoteRequests.tsx +++ b/apps/sv/frontend/src/hooks/useListVoteRequests.tsx @@ -6,11 +6,16 @@ import { DsoRules_CloseVoteRequestResult, VoteRequest, } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules/module'; -import { Contract } from '@canton-network/splice-common-frontend-utils'; -import { type UseQueryResult, useQuery } from '@tanstack/react-query'; +import { Contract, PollingStrategy } from '@canton-network/splice-common-frontend-utils'; +import { type UseQueryResult, useInfiniteQuery, useQuery } from '@tanstack/react-query'; +import { useEffect, useRef } from 'react'; import { useSvAdminClient } from '../contexts/SvAdminServiceContext'; import { useConfigPollInterval } from '../utils'; +import { shouldContinueVoteHistorySearch } from '../utils/proposalSearch'; + +const PAGINATED_VOTE_RESULTS_QUERY_KEY = 'paginatedVoteRequestResults'; +const PAGINATED_VOTE_RESULTS_PAGE_SIZE = 500; export type ListVoteRequestResultParams = { actionName?: string; @@ -67,3 +72,136 @@ export const useListVoteRequestResult = ( retry, }); }; + +function usePaginatedVoteRequestResultsBucket( + contractId: string, + accepted: boolean, + enabled: boolean, + shouldContinueRef: React.MutableRefObject<() => boolean> +) { + const { listVoteRequestResults } = useSvAdminClient(); + const queryKey = [ + PAGINATED_VOTE_RESULTS_QUERY_KEY, + contractId, + accepted, + PAGINATED_VOTE_RESULTS_PAGE_SIZE, + ] as const; + + const { + hasNextPage, + isFetchingNextPage, + isPending, + dataUpdatedAt, + fetchNextPage, + data, + isSuccess, + } = useInfiniteQuery({ + queryKey, + queryFn: async ({ pageParam }) => { + const response = await listVoteRequestResults( + PAGINATED_VOTE_RESULTS_PAGE_SIZE, + undefined, + undefined, + undefined, + undefined, + accepted, + pageParam ?? undefined + ); + + return { + results: List(DsoRules_CloseVoteRequestResult).decoder.runWithException( + response.dso_rules_vote_results + ), + nextPageToken: response.next_page_token, + }; + }, + initialPageParam: null as number | null, + getNextPageParam: lastPage => lastPage?.nextPageToken ?? null, + enabled, + refetchInterval: PollingStrategy.NONE, + refetchOnWindowFocus: false, + }); + + useEffect(() => { + if ( + !enabled || + !shouldContinueRef.current() || + !hasNextPage || + isFetchingNextPage || + isPending + ) { + return; + } + void fetchNextPage(); + }, [ + enabled, + hasNextPage, + isFetchingNextPage, + isPending, + dataUpdatedAt, + fetchNextPage, + shouldContinueRef, + ]); + + const results = data?.pages.flatMap(page => page.results) ?? []; + + return { + results, + isNaturallyComplete: isSuccess && !hasNextPage, + hasFirstPage: (data?.pages.length ?? 0) > 0, + }; +} + +export function usePaginatedVoteRequestResultsByContractId( + enabled: boolean, + { contractId = '' }: { contractId?: string } = {} +): { + results: DsoRules_CloseVoteRequestResult[]; + isComplete: boolean; +} { + const shouldContinueRef = useRef<() => boolean>(() => false); + const fetchDisabledRef = useRef(false); + const previousLookupRef = useRef({ enabled, contractId }); + + if ( + previousLookupRef.current.enabled !== enabled || + previousLookupRef.current.contractId !== contractId + ) { + fetchDisabledRef.current = false; + previousLookupRef.current = { enabled, contractId }; + } + + const bucketEnabled = enabled && !fetchDisabledRef.current; + + const accepted = usePaginatedVoteRequestResultsBucket( + contractId, + true, + bucketEnabled, + shouldContinueRef + ); + const rejected = usePaginatedVoteRequestResultsBucket( + contractId, + false, + bucketEnabled, + shouldContinueRef + ); + const results = enabled ? [...accepted.results, ...rejected.results] : []; + + const shouldContinue = + bucketEnabled && + shouldContinueVoteHistorySearch(contractId, results, result => result.request.trackingCid); + shouldContinueRef.current = () => shouldContinue; + + const isComplete = + (accepted.isNaturallyComplete && rejected.isNaturallyComplete) || + (!shouldContinue && accepted.hasFirstPage && rejected.hasFirstPage); + + if (isComplete) { + fetchDisabledRef.current = true; + } + + return { + results, + isComplete, + }; +} diff --git a/apps/sv/frontend/src/hooks/useVoteRequest.tsx b/apps/sv/frontend/src/hooks/useVoteRequest.tsx index b8ccc90762..e5358a00bf 100644 --- a/apps/sv/frontend/src/hooks/useVoteRequest.tsx +++ b/apps/sv/frontend/src/hooks/useVoteRequest.tsx @@ -1,18 +1,22 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { Contract } from '@canton-network/splice-common-frontend-utils'; +import { Contract, PollingStrategy } from '@canton-network/splice-common-frontend-utils'; import { useQuery, UseQueryResult } from '@tanstack/react-query'; import { VoteRequest } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules/module'; import { ContractId } from '@daml/types'; import { useSvAdminClient } from '../contexts/SvAdminServiceContext'; +import { useConfigPollInterval } from '../utils'; export const useVoteRequest = ( contractId: ContractId, - retry: boolean = true + retry: boolean = true, + poll: boolean = true ): UseQueryResult> => { const { lookupDsoRulesVoteRequest } = useSvAdminClient(); + const pollInterval = useConfigPollInterval(); + return useQuery({ queryKey: ['listDsoRulesVoteRequests', contractId], queryFn: async () => { @@ -20,5 +24,6 @@ export const useVoteRequest = ( return Contract.decodeOpenAPI(request.dso_rules_vote_request, VoteRequest); }, retry, + refetchInterval: poll ? pollInterval : PollingStrategy.NONE, }); }; diff --git a/apps/sv/frontend/src/hooks/useVoteRequestResultByCid.tsx b/apps/sv/frontend/src/hooks/useVoteRequestResultByCid.tsx index 48bffd44e5..98b9a6706c 100644 --- a/apps/sv/frontend/src/hooks/useVoteRequestResultByCid.tsx +++ b/apps/sv/frontend/src/hooks/useVoteRequestResultByCid.tsx @@ -2,15 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 import { ContractId } from '@daml/types'; -import { useVoteRequest } from './useVoteRequest'; import { DsoRules_CloseVoteRequestResult, VoteRequest, } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules'; -import { useVotesHooks } from '@canton-network/splice-common-frontend'; import { Contract } from '@canton-network/splice-common-frontend-utils'; -const QUERY_LIMIT = 500; +import { usePaginatedVoteRequestResultsByContractId } from './useListVoteRequests'; +import { useVoteRequest } from './useVoteRequest'; +import { findByContractId } from '../utils/proposalSearch'; interface UseVoteRequestResultByCidResult { voteRequest: Contract | undefined; @@ -25,54 +25,37 @@ interface UseVoteRequestResultByCidResult { export function useVoteRequestResultByCid( contractId: ContractId ): UseVoteRequestResultByCidResult { - const votesHooks = useVotesHooks(); - const voteRequestQuery = useVoteRequest(contractId, false); + const voteRequestQuery = useVoteRequest(contractId, false, false); - const voteResultsWithAcceptedQuery = (accepted: boolean) => - votesHooks.useListVoteRequestResult( - QUERY_LIMIT, - undefined, - undefined, - undefined, - undefined, - accepted, - false - ); - const acceptedResultsQuery = voteResultsWithAcceptedQuery(true); - const notAcceptedResultsQuery = voteResultsWithAcceptedQuery(false); + const hasVoteRequest = voteRequestQuery.isSuccess && voteRequestQuery.data != null; - const acceptedResult = acceptedResultsQuery.data?.find( - vr => vr.request.trackingCid === contractId - ); - const notAcceptedResult = notAcceptedResultsQuery.data?.find( - vr => vr.request.trackingCid === contractId - ); + const needsClosedVoteFetch = + (voteRequestQuery.isSuccess || voteRequestQuery.isError) && !hasVoteRequest; - const hasVoteRequest = - voteRequestQuery.isSuccess && - voteRequestQuery.data != null && - voteRequestQuery.data != undefined; + const closedVoteResults = usePaginatedVoteRequestResultsByContractId(needsClosedVoteFetch, { + contractId, + }); - const hasVoteResult = - (acceptedResultsQuery.isSuccess && acceptedResult != undefined) || - (notAcceptedResultsQuery.isSuccess && notAcceptedResult != undefined); + const voteResult = findByContractId( + closedVoteResults.results, + contractId, + result => result.request.trackingCid + ); + const hasVoteResult = voteResult !== undefined; const isPending = voteRequestQuery.isPending || - acceptedResultsQuery.isPending || - notAcceptedResultsQuery.isPending; + (needsClosedVoteFetch && !closedVoteResults.isComplete && !hasVoteResult); const isComplete = (voteRequestQuery.isSuccess || voteRequestQuery.isError) && - (acceptedResultsQuery.isSuccess || acceptedResultsQuery.isError) && - (notAcceptedResultsQuery.isSuccess || notAcceptedResultsQuery.isError); + (!needsClosedVoteFetch || closedVoteResults.isComplete || hasVoteResult); const voteRequest = voteRequestQuery.data; - const voteResult = acceptedResult || notAcceptedResult; return { - voteRequest: voteRequest, - voteResult: voteResult, + voteRequest, + voteResult, hasVoteRequest, hasVoteResult, isPending, diff --git a/apps/sv/frontend/src/routes/governance.tsx b/apps/sv/frontend/src/routes/governance.tsx index 5f9702ec8e..67eb67ba74 100644 --- a/apps/sv/frontend/src/routes/governance.tsx +++ b/apps/sv/frontend/src/routes/governance.tsx @@ -2,48 +2,43 @@ // SPDX-License-Identifier: Apache-2.0 import * as React from 'react'; import { Box, Button, Stack, Typography } from '@mui/material'; -import { useMemo } from 'react'; +import { useMemo, useState } from 'react'; import { ActionRequiredSection, ActionRequiredData, } from '../components/governance/ActionRequiredSection'; +import { ProposalListingSection } from '../components/governance/ProposalListingSection'; +import ProposalSearch from '../components/governance/ProposalSearch'; import { Loading, useVotesHooks } from '@canton-network/splice-common-frontend'; import { dateTimeFormatISO } from '@canton-network/splice-common-frontend-utils'; import dayjs from 'dayjs'; import { ContractId } from '@daml/types'; -import { - ActionRequiringConfirmation, - VoteRequest, -} from '@daml.js/splice-dso-governance/lib/Splice/DsoRules'; +import { VoteRequest } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules'; import { useSvConfig } from '../utils'; import { PageHeader } from '../components/beta'; -import { ProposalListingSection } from '../components/governance/ProposalListingSection'; import { actionTagToTitle, + buildVoteHistoryData, computeVoteStats, computeYourVote, - getVoteResultStatus, + getGovernanceActionTag, getRequesterPartyId, } from '../utils/governance'; +import { filterByContractId, isValidContractId } from '../utils/proposalSearch'; import { SupportedActionTag, ProposalListingData } from '../utils/types'; -import { Link as RouterLink } from 'react-router'; +import { Link as RouterLink, useSearchParams } from 'react-router'; import { InfoOutlined, WarningAmberOutlined } from '@mui/icons-material'; import { useInfiniteVoteRequestResults, useVoteRequestResultsCount } from '../hooks'; - -function getAction(action: ActionRequiringConfirmation): string { - switch (action.tag) { - case 'ARC_AmuletRules': - return action.value.amuletRulesAction.tag; - case 'ARC_DsoRules': - return action.value.dsoAction.tag; - default: - return 'Action tag not defined.'; - } -} +import { usePaginatedVoteRequestResultsByContractId } from '../hooks/useListVoteRequests'; export const Governance: React.FC = () => { const svConfig = useSvConfig(); const amuletName = svConfig.spliceInstanceNames.amuletName; + const [searchParams] = useSearchParams(); + const initialSearchQuery = searchParams.get('q') ?? ''; + const [searchQuery, setSearchQuery] = useState(() => + isValidContractId(initialSearchQuery) ? initialSearchQuery.trim() : '' + ); const votesHooks = useVotesHooks(); const dsoInfosQuery = votesHooks.useDsoInfos(); @@ -70,43 +65,136 @@ export const Governance: React.FC = () => { if (!pages || !svPartyId || votingThreshold === undefined) return []; const allVoteResults = pages.flatMap(page => page.results); + return buildVoteHistoryData(allVoteResults, amuletName, svPartyId, votingThreshold, svs); + }, [voteResultsInfiniteQuery.data?.pages, amuletName, svPartyId, votingThreshold, svs]); - return allVoteResults - .filter( - vr => - (vr.outcome.tag === 'VRO_Accepted' && - dayjs(vr.outcome.value.effectiveAt).isBefore(dayjs())) || - vr.outcome.tag === 'VRO_Expired' || - vr.outcome.tag === 'VRO_Rejected' - ) - .map(vr => { - const votes = vr.request.votes.entriesArray().map(e => e[1]); + const voteRequests = listVoteRequestsQuery.data; + + const actionRequiredBase = useMemo(() => { + if (!voteRequests) { + return []; + } + + return voteRequests + .filter(v => !alreadyVotedRequestIds.has(v.payload.trackingCid || v.contractId)) + .map(vr => ({ + contractId: vr.payload.trackingCid || vr.contractId, + actionName: + actionTagToTitle(amuletName)[ + getGovernanceActionTag(vr.payload.action) as SupportedActionTag + ], + description: vr.payload.reason.body, + votingCloses: dayjs(vr.payload.voteBefore).format(dateTimeFormatISO), + createdAt: dayjs(vr.createdAt).format(dateTimeFormatISO), + requester: getRequesterPartyId(vr.payload.requester, svs), + })) as ActionRequiredData[]; + }, [voteRequests, alreadyVotedRequestIds, amuletName, svs]); + + const inflightBase = useMemo(() => { + if (!voteRequests || votingThreshold === undefined) { + return []; + } + + return voteRequests + .filter(v => alreadyVotedRequestIds.has(v.payload.trackingCid || v.contractId)) + .map(v => { + const effectiveAt = v.payload.targetEffectiveAt + ? dayjs(v.payload.targetEffectiveAt).format(dateTimeFormatISO) + : 'Threshold'; + const votes = v.payload.votes.entriesArray().map(e => e[1]); return { - contractId: (vr.request.trackingCid ?? '') as ContractId, + contractId: v.payload.trackingCid || v.contractId, actionName: - actionTagToTitle(amuletName)[getAction(vr.request.action) as SupportedActionTag], - description: vr.request.reason.body, - votingThresholdDeadline: dayjs(vr.request.voteBefore).format(dateTimeFormatISO), - voteTakesEffect: - (vr.outcome.tag === 'VRO_Accepted' && - dayjs(vr.outcome.value.effectiveAt).format(dateTimeFormatISO)) || - dayjs(vr.completedAt).format(dateTimeFormatISO), + actionTagToTitle(amuletName)[ + getGovernanceActionTag(v.payload.action) as SupportedActionTag + ], + description: v.payload.reason.body, + votingThresholdDeadline: dayjs(v.payload.voteBefore).format(dateTimeFormatISO), + voteTakesEffect: effectiveAt, yourVote: computeYourVote(votes, svPartyId), - status: getVoteResultStatus(vr.outcome), + status: 'In Progress', voteStats: computeVoteStats(votes), acceptanceThreshold: votingThreshold, - requester: getRequesterPartyId(vr.request.requester, svs), + requester: getRequesterPartyId(v.payload.requester, svs), } as ProposalListingData; }); - }, [voteResultsInfiniteQuery.data?.pages, amuletName, svPartyId, votingThreshold, svs]); + }, [voteRequests, votingThreshold, alreadyVotedRequestIds, amuletName, svPartyId, svs]); - if ( + const isLoading = dsoInfosQuery.isPending || listVoteRequestsQuery.isPending || votesQuery.isPending || - voteResultsInfiniteQuery.isPending - ) { + voteResultsInfiniteQuery.isPending; + + const hasSearch = isValidContractId(searchQuery); + + const actionRequiredRequests = useMemo( + () => (hasSearch ? filterByContractId(actionRequiredBase, searchQuery) : actionRequiredBase), + [hasSearch, actionRequiredBase, searchQuery] + ); + + const inflightRequests = useMemo( + () => (hasSearch ? filterByContractId(inflightBase, searchQuery) : inflightBase), + [hasSearch, inflightBase, searchQuery] + ); + + const loadedVoteHistoryMatches = useMemo( + () => (hasSearch ? filterByContractId(voteHistory, searchQuery) : []), + [hasSearch, voteHistory, searchQuery] + ); + + const needsClosedVoteFetch = + !isLoading && + hasSearch && + actionRequiredRequests.length === 0 && + inflightRequests.length === 0 && + loadedVoteHistoryMatches.length === 0; + + const searchVoteResults = usePaginatedVoteRequestResultsByContractId(needsClosedVoteFetch, { + contractId: searchQuery, + }); + + const searchVoteHistoryBase = useMemo(() => { + if (!svPartyId || votingThreshold === undefined) { + return []; + } + + return buildVoteHistoryData( + searchVoteResults.results, + amuletName, + svPartyId, + votingThreshold, + svs + ); + }, [searchVoteResults.results, amuletName, svPartyId, votingThreshold, svs]); + + const filteredVoteHistory = useMemo(() => { + if (!hasSearch) { + return voteHistory; + } + + if (loadedVoteHistoryMatches.length > 0) { + return loadedVoteHistoryMatches; + } + + return filterByContractId(searchVoteHistoryBase, searchQuery); + }, [hasSearch, voteHistory, searchQuery, loadedVoteHistoryMatches, searchVoteHistoryBase]); + + const showVoteHistorySectionLoading = + needsClosedVoteFetch && !searchVoteResults.isComplete && filteredVoteHistory.length === 0; + + const hasLoadedAllVoteHistoryPages = !voteResultsInfiniteQuery.hasNextPage; + + const showEmptyState = + !isLoading && + !hasSearch && + actionRequiredRequests.length === 0 && + inflightRequests.length === 0 && + filteredVoteHistory.length === 0 && + hasLoadedAllVoteHistoryPages; + + if (isLoading) { return ; } @@ -119,45 +207,6 @@ export const Governance: React.FC = () => { return ; } - const voteRequests = listVoteRequestsQuery.data; - - const actionRequiredRequests = voteRequests - .filter(v => !alreadyVotedRequestIds.has(v.payload.trackingCid || v.contractId)) - .map(vr => { - return { - contractId: vr.payload.trackingCid || vr.contractId, - actionName: - actionTagToTitle(amuletName)[getAction(vr.payload.action) as SupportedActionTag], - description: vr.payload.reason.body, - votingCloses: dayjs(vr.payload.voteBefore).format(dateTimeFormatISO), - createdAt: dayjs(vr.createdAt).format(dateTimeFormatISO), - requester: getRequesterPartyId(vr.payload.requester, svs), - } as ActionRequiredData; - }); - - const inflightRequests = voteRequests - .filter(v => alreadyVotedRequestIds.has(v.payload.trackingCid || v.contractId)) - .map(v => { - const effectiveAt = v.payload.targetEffectiveAt - ? dayjs(v.payload.targetEffectiveAt).format(dateTimeFormatISO) - : 'Threshold'; - - const votes = v.payload.votes.entriesArray().map(e => e[1]); - - return { - contractId: v.payload.trackingCid || v.contractId, - actionName: actionTagToTitle(amuletName)[getAction(v.payload.action) as SupportedActionTag], - description: v.payload.reason.body, - votingThresholdDeadline: dayjs(v.payload.voteBefore).format(dateTimeFormatISO), - voteTakesEffect: effectiveAt, - yourVote: computeYourVote(votes, svPartyId), - status: 'In Progress', - voteStats: computeVoteStats(votes), - acceptanceThreshold: dsoInfosQuery.data.votingThreshold, - requester: getRequesterPartyId(v.payload.requester, svs), - } as ProposalListingData; - }); - return ( { data-testid="governance-page-header" /> - {actionRequiredRequests.length === 0 && - inflightRequests.length === 0 && - voteHistory.length === 0 && - !voteResultsInfiniteQuery.hasNextPage ? ( + + + {showEmptyState ? ( ) : ( <> - + { p.results.length > 0).length + hasSearch + ? undefined + : voteResultsInfiniteQuery.data?.pages.filter(p => p.results.length > 0).length } /> diff --git a/apps/sv/frontend/src/routes/voteRequestDetails.tsx b/apps/sv/frontend/src/routes/voteRequestDetails.tsx index 1f920d54b0..ac156a2dc4 100644 --- a/apps/sv/frontend/src/routes/voteRequestDetails.tsx +++ b/apps/sv/frontend/src/routes/voteRequestDetails.tsx @@ -60,7 +60,7 @@ export const VoteRequestDetails: React.FC = () => { currentEffectiveAt ); - if (dsoInfosQuery.isPending && isPending) { + if (dsoInfosQuery.isPending || isPending) { return ; } diff --git a/apps/sv/frontend/src/utils/governance.ts b/apps/sv/frontend/src/utils/governance.ts index 18d298dc67..92d423a5c0 100644 --- a/apps/sv/frontend/src/utils/governance.ts +++ b/apps/sv/frontend/src/utils/governance.ts @@ -5,6 +5,7 @@ import type { ActionRequiringConfirmation, AmuletRules_ActionRequiringConfirmation, DsoRules_ActionRequiringConfirmation, + DsoRules_CloseVoteRequestResult, DsoRules_SetConfig, DsoRulesConfig, SvInfo, @@ -25,6 +26,7 @@ import type { PendingConfigFieldInfo, Proposal, ProposalListingStatus, + ProposalListingData, SupportedActionTag, UnclaimedActivityRecordProposal, UnfeatureAppProposal, @@ -127,6 +129,56 @@ export function computeYourVote(votes: Vote[], svPartyId: string | undefined): Y return vote ? (vote.accept ? 'accepted' : 'rejected') : 'no-vote'; } +export function getGovernanceActionTag(action: ActionRequiringConfirmation): string { + switch (action.tag) { + case 'ARC_AmuletRules': + return action.value.amuletRulesAction.tag; + case 'ARC_DsoRules': + return action.value.dsoAction.tag; + default: + return 'Action tag not defined.'; + } +} + +export function buildVoteHistoryData( + voteResults: DsoRules_CloseVoteRequestResult[], + amuletName: string, + svPartyId: string | undefined, + votingThreshold: bigint, + svs: { entriesArray(): [string, SvInfo][] } | undefined +): ProposalListingData[] { + return voteResults + .filter( + vr => + (vr.outcome.tag === 'VRO_Accepted' && + dayjs(vr.outcome.value.effectiveAt).isBefore(dayjs())) || + vr.outcome.tag === 'VRO_Expired' || + vr.outcome.tag === 'VRO_Rejected' + ) + .map(vr => { + const votes = vr.request.votes.entriesArray().map(e => e[1]); + + return { + contractId: vr.request.trackingCid, + actionName: + actionTagToTitle(amuletName)[ + getGovernanceActionTag(vr.request.action) as SupportedActionTag + ], + description: vr.request.reason.body, + votingThresholdDeadline: dayjs(vr.request.voteBefore).format(dateTimeFormatISO), + voteTakesEffect: + (vr.outcome.tag === 'VRO_Accepted' && + dayjs(vr.outcome.value.effectiveAt).format(dateTimeFormatISO)) || + dayjs(vr.completedAt).format(dateTimeFormatISO), + yourVote: computeYourVote(votes, svPartyId), + status: getVoteResultStatus(vr.outcome), + voteStats: computeVoteStats(votes), + acceptanceThreshold: votingThreshold, + requester: getRequesterPartyId(vr.request.requester, svs), + } as ProposalListingData; + }); +} + export function buildProposal(action: ActionRequiringConfirmation, dsoInfo?: DsoInfo): Proposal { if (action.tag === 'ARC_DsoRules') { const dsoAction = action.value.dsoAction; diff --git a/apps/sv/frontend/src/utils/proposalSearch.ts b/apps/sv/frontend/src/utils/proposalSearch.ts new file mode 100644 index 0000000000..65dc86c70b --- /dev/null +++ b/apps/sv/frontend/src/utils/proposalSearch.ts @@ -0,0 +1,94 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +/** MSW mock CIDs use this length with non-ledger prefixes (`10…`, `99…`). */ +const MOCK_TEST_CONTRACT_ID_LENGTH = 138; + +export const CONTRACT_ID_VALIDATION_MESSAGE = 'Enter a valid contract ID.'; + +/** Bounds mirror LfValue.ContractId.fromString (Value.scala) + even hex for Bytes.fromString. */ +const V1_MIN_LENGTH = 66; +const V1_MAX_LENGTH = 254; +const V2_MIN_LENGTH = 26; +const V2_MAX_LENGTH = 92; + +function isHexChar(code: number): boolean { + return (code >= 48 && code <= 57) || (code >= 97 && code <= 102) || (code >= 65 && code <= 70); +} + +function isPlausibleContractId(value: string): boolean { + const len = value.length; + if (len === 0 || (len & 1) !== 0 || len < V2_MIN_LENGTH || len > V1_MAX_LENGTH) { + return false; + } + + for (let i = 0; i < len; i++) { + if (!isHexChar(value.charCodeAt(i))) { + return false; + } + } + + if (value.charCodeAt(0) === 48 && value.charCodeAt(1) === 48) { + return len >= V1_MIN_LENGTH && len <= V1_MAX_LENGTH; + } + if (value.charCodeAt(0) === 48 && value.charCodeAt(1) === 49) { + return len >= V2_MIN_LENGTH && len <= V2_MAX_LENGTH; + } + return len === MOCK_TEST_CONTRACT_ID_LENGTH; +} + +export function isValidContractId(value: string): boolean { + return isPlausibleContractId(value.trim()); +} + +function normalizeContractIdQuery(query: string): string | null { + const trimmed = query.trim(); + if (!trimmed || !isPlausibleContractId(trimmed)) { + return null; + } + return trimmed.toLowerCase(); +} + +function matchesNormalizedContractId( + normalizedQuery: string, + contractId: string | null | undefined +): boolean { + return typeof contractId === 'string' && contractId.toLowerCase() === normalizedQuery; +} + +export function filterByContractId( + items: T[], + query: string | null | undefined +): T[] { + const normalizedQuery = query == null ? null : normalizeContractIdQuery(query); + if (normalizedQuery === null) { + return []; + } + return items.filter(item => + matchesNormalizedContractId(normalizedQuery, item.contractId as string) + ); +} + +export function findByContractId( + items: T[], + query: string, + getContractId: (item: T) => string | null | undefined +): T | undefined { + const normalizedQuery = normalizeContractIdQuery(query); + if (normalizedQuery === null) { + return undefined; + } + return items.find(item => matchesNormalizedContractId(normalizedQuery, getContractId(item))); +} + +export function shouldContinueVoteHistorySearch( + query: string, + results: T[], + getContractId: (item: T) => string | null | undefined +): boolean { + const normalizedQuery = normalizeContractIdQuery(query); + if (normalizedQuery === null) { + return false; + } + return !results.some(item => matchesNormalizedContractId(normalizedQuery, getContractId(item))); +} From 088d8d40dc07cb93d5ef1f4074d65add994e8e91 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:39:49 +0200 Subject: [PATCH 263/329] Bump deduplication cache size to 1000000 (#6852) [static] We already asked everyone to apply that on mainnet so may as well make it the default. Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- cluster/helm/splice-cometbft/values-template.yaml | 2 +- docs/src/release_notes_upcoming.rst | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cluster/helm/splice-cometbft/values-template.yaml b/cluster/helm/splice-cometbft/values-template.yaml index 8c78a45b82..ce844eb38c 100644 --- a/cluster/helm/splice-cometbft/values-template.yaml +++ b/cluster/helm/splice-cometbft/values-template.yaml @@ -104,7 +104,7 @@ mempool: # max number of transactions kept in the mempool size: 4000 # number of transactions to keep to deduplicate new transactions - deduplicationCacheSize: 200000 + deduplicationCacheSize: 1000000 # max number of seconds that a transaction will be kept in the mempool without being included in a block before being discarded ttlSeconds: 60 diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 3843824551..73ea5b0058 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -62,3 +62,6 @@ release-notes:: Upcoming ``watchdog.sequencerMetricsUrl: http://global-domain-SERIAL_ID-sequencer:10013/metrics`` and ``watchdog.mediatorMetricsUrl: http://global-domain-SERIAL_ID-mediator:10013/metrics`` in the cometbft helm values. If needed, the watchdog can be disabled through ``watchdog.enabled: false``. + + + - Bump the default ``deduplicationCacheSize`` to ``1000000``. From 7f80d754caa1ec19ade6504606c9e6819dc868d7 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Wed, 19 Aug 2026 12:33:34 +0200 Subject: [PATCH 264/329] Extend splice rate limiting monitoring (#6851) Signed-off-by: Julien Tinguely --- cluster/configs/shared/base.yaml | 4 + .../scratchneta/config.resolved.yaml | 4 + .../scratchnetb/config.resolved.yaml | 4 + .../scratchnetc/config.resolved.yaml | 4 + .../scratchnetd/config.resolved.yaml | 4 + .../scratchnete/config.resolved.yaml | 4 + cluster/expected/observability/expected.json | 5 +- .../istio-rate-limiting_alerts.yaml | 2 +- .../splice-rate-limiting_alerts.yaml | 123 +++++++++ .../platform/rate_limiters.json | 256 +++++++++++++++--- cluster/pulumi/observability/src/config.ts | 7 + .../pulumi/observability/src/observability.ts | 17 ++ 12 files changed, 399 insertions(+), 35 deletions(-) create mode 100644 cluster/pulumi/observability/grafana-alerting/splice-rate-limiting_alerts.yaml diff --git a/cluster/configs/shared/base.yaml b/cluster/configs/shared/base.yaml index 4e605de8e2..cd292b6b00 100644 --- a/cluster/configs/shared/base.yaml +++ b/cluster/configs/shared/base.yaml @@ -123,6 +123,10 @@ monitoring: dsoMissedConfirmations: threshold: 0 # alert as soon as there's any confirmation missing windowMinutes: 10 + spliceRateLimits: + usageThreshold: 0.8 + rejectionCountThreshold: 10 + excludedLimiters: [] cloudSql: maintenance: false cometbft: diff --git a/cluster/deployment/scratchneta/config.resolved.yaml b/cluster/deployment/scratchneta/config.resolved.yaml index 784ad2223c..c328b59eaa 100644 --- a/cluster/deployment/scratchneta/config.resolved.yaml +++ b/cluster/deployment/scratchneta/config.resolved.yaml @@ -106,6 +106,10 @@ monitoring: sequencerRateLimits: circuitBreakerStateThreshold: 0.5 rejectionRateThreshold: 0 + spliceRateLimits: + excludedLimiters: [] + rejectionCountThreshold: 10 + usageThreshold: 0.8 trafficBasedRewards: featuredAppRightsLimit: 10000 verdictIngestionBatchSizePendingPeriodMinutes: 60 diff --git a/cluster/deployment/scratchnetb/config.resolved.yaml b/cluster/deployment/scratchnetb/config.resolved.yaml index 784ad2223c..c328b59eaa 100644 --- a/cluster/deployment/scratchnetb/config.resolved.yaml +++ b/cluster/deployment/scratchnetb/config.resolved.yaml @@ -106,6 +106,10 @@ monitoring: sequencerRateLimits: circuitBreakerStateThreshold: 0.5 rejectionRateThreshold: 0 + spliceRateLimits: + excludedLimiters: [] + rejectionCountThreshold: 10 + usageThreshold: 0.8 trafficBasedRewards: featuredAppRightsLimit: 10000 verdictIngestionBatchSizePendingPeriodMinutes: 60 diff --git a/cluster/deployment/scratchnetc/config.resolved.yaml b/cluster/deployment/scratchnetc/config.resolved.yaml index 784ad2223c..c328b59eaa 100644 --- a/cluster/deployment/scratchnetc/config.resolved.yaml +++ b/cluster/deployment/scratchnetc/config.resolved.yaml @@ -106,6 +106,10 @@ monitoring: sequencerRateLimits: circuitBreakerStateThreshold: 0.5 rejectionRateThreshold: 0 + spliceRateLimits: + excludedLimiters: [] + rejectionCountThreshold: 10 + usageThreshold: 0.8 trafficBasedRewards: featuredAppRightsLimit: 10000 verdictIngestionBatchSizePendingPeriodMinutes: 60 diff --git a/cluster/deployment/scratchnetd/config.resolved.yaml b/cluster/deployment/scratchnetd/config.resolved.yaml index 784ad2223c..c328b59eaa 100644 --- a/cluster/deployment/scratchnetd/config.resolved.yaml +++ b/cluster/deployment/scratchnetd/config.resolved.yaml @@ -106,6 +106,10 @@ monitoring: sequencerRateLimits: circuitBreakerStateThreshold: 0.5 rejectionRateThreshold: 0 + spliceRateLimits: + excludedLimiters: [] + rejectionCountThreshold: 10 + usageThreshold: 0.8 trafficBasedRewards: featuredAppRightsLimit: 10000 verdictIngestionBatchSizePendingPeriodMinutes: 60 diff --git a/cluster/deployment/scratchnete/config.resolved.yaml b/cluster/deployment/scratchnete/config.resolved.yaml index 784ad2223c..c328b59eaa 100644 --- a/cluster/deployment/scratchnete/config.resolved.yaml +++ b/cluster/deployment/scratchnete/config.resolved.yaml @@ -106,6 +106,10 @@ monitoring: sequencerRateLimits: circuitBreakerStateThreshold: 0.5 rejectionRateThreshold: 0 + spliceRateLimits: + excludedLimiters: [] + rejectionCountThreshold: 10 + usageThreshold: 0.8 trafficBasedRewards: featuredAppRightsLimit: 10000 verdictIngestionBatchSizePendingPeriodMinutes: 60 diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index 1bafd28264..ed6653c777 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -83,7 +83,7 @@ "deployment_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: deployments\n folder: canton-network\n interval: 1m\n rules:\n - uid: adkhl6u18pqtce\n title: Failing Stacks\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n exemplar: true\n expr: splice_deployment_pulumi_stack_status{state=\"failed\"}\n format: time_series\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: stacks_active\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: D\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - D\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: QP_wDqDnz\n panelId: 11\n noDataState: OK\n execErrState: OK\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"11\"\n description: The pulumi operator failed to update the {{ $labels.stack }} stack. Check the logs for the deployment.\n runbook_url: \"\"\n severity: critical\n summary: '{{ $labels.stack }} stack failed to update'\n labels:\n \"\": \"\"\n isPaused: false\n - uid: bdndg5g3x4kxsf\n title: Deployments running\n condition: A\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: splice_deployment_pulumi_stack_condition{type=\"Reconciling\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n dashboardUid: QP_wDqDnz\n panelId: 27\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: QP_wDqDnz\n __panelId__: \"27\"\n description: A deployment is currently running for the {{ $labels.stack }} stack.\n runbook_url: \"\"\n severity: info\n summary: Stack {{ $labels.stack }} is being updated\n labels:\n \"\": \"\"\n isPaused: false\n", "dso_missed_confirmations_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: dso party missed confirmations\n folder: canton-network\n interval: 1m\n rules:\n - uid: ddso1miss0conf0a\n title: DSO Party Missed Confirmations\n condition: missed confirmations threshold exceeded\n data:\n - refId: missed_confirmations\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (party) (increase(daml_mediator_timeout_non_responsive_participants_total{party=~\"DSO::.*\"}[10m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: missed_confirmations\n - refId: missed confirmations threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: missed_confirmations\n intervalMs: 1000\n maxDataPoints: 43200\n refId: missed confirmations threshold exceeded\n type: threshold\n dashboardUid: cnck4jp\n panelId: 1\n noDataState: OK\n execErrState: Alerting\n for: 0m\n annotations:\n __dashboardUid__: cnck4jp\n __panelId__: \"1\"\n severity: critical\n summary: The DSO party missed {{ index $values \"missed_confirmations\" }}, more than 0 of its confirmations over the last 10 minutes.\n labels: {}\n isPaused: false\n", "extra_k8s_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: storage\n folder: k8s\n interval: 5m\n rules:\n - uid: adlrbu5kog0sga\n title: KubePersistentVolumeTooFull\n condition: free_space_below_threshold\n data:\n - refId: free_space\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: kubelet_volume_stats_available_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"} / kubelet_volume_stats_capacity_bytes{job=\"kubelet\",metrics_path=\"/metrics\",namespace=~\".*\"}\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: free_space\n - refId: free_space_below_threshold\n relativeTimeRange:\n from: 360\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.15\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: free_space\n intervalMs: 1000\n maxDataPoints: 43200\n refId: free_space_below_threshold\n type: threshold\n dashboardUid: 919b92a8e8041bd567af9edab12c840c\n panelId: 2\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: 919b92a8e8041bd567af9edab12c840c\n __panelId__: \"2\"\n severity: warning\n description: The PersistentVolume claimed by {{ index $labels \"persistentvolumeclaim\" }} in Namespace {{ index $labels \"namespace\" }} is running out of disk space. Currently {{ humanizePercentage (index $values \"free_space\").Value }} is available.\n runbook_url: https://runbooks.prometheus-operator.dev/runbooks/kubernetes/kubepersistentvolumefillingup\n summary: PersistentVolume is too full.\n isPaused: false\n", - "istio-rate-limiting_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: istio-rate-limit\n folder: platform\n interval: 5m\n rules:\n - uid: cfurlpifrmvi8w\n title: Enforced requests\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\"sv.*\"}[$__rate_interval])) by (namespace, pod)\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}} - {{pod}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnr56dj\n panelId: 9\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n runbook_url: \"\"\n severity: warning\n description: Envoy local rate limiting is active, requests are being rejected. Check the \"Istio Rate Limiting\" dashboard.\n summary: Envoy is rate limiting requests in {{ $labels.namespace }} on pod {{ $labels.pod }}.\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429%0AjsonPayload.authority:%22scan.{{ index $labels \"namespace\" }}.%22\n isPaused: false\n", + "istio-rate-limiting_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: istio-rate-limit\n folder: platform\n interval: 5m\n rules:\n - uid: cfurlpifrmvi8w\n title: Enforced requests\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\"sv.*\"}[$__rate_interval])) by (namespace, pod)\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}} - {{pod}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnr56dj\n panelId: 9\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n runbook_url: \"\"\n severity: warning\n description: Envoy local rate limiting is active, requests are being rejected. Check the \"Istio Rate Limiting\" dashboard.\n summary: Envoy is rate limiting requests in {{ $labels.namespace }} on pod {{ $labels.pod }}.\n labels:\n \"\": \"\"\n gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429\n isPaused: false\n", "load-tester_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: load-tester\n folder: canton-network\n interval: 1m\n rules:\n - uid: a1dbc4e8-7941-4351-9a14-f8573fd2be2b\n title: K6 Request Failure Rate Exceeded\n condition: D\n data:\n - refId: A\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: B\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n disableTextWrap: false\n editorMode: builder\n expr: sum(k6_http_reqs_total{expected_response=\"false\"}) or on() vector(0)\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: ($B / $A) * 100\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 50\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 1m\n annotations:\n description: ''\n runbook_url: ''\n summary: The k6 load tester experienced HTTP request failures at a rate past the acceptable threshold\n labels:\n gcloud_filter: ''\n isPaused: false\n - uid: ddogbytusmz9cb\n title: K6 Throughput Below Threshold\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: rate(k6_transfers_completed_total{scenario=\"generate_load\"}[$__rate_interval])\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n relativeTimeRange:\n from: 3600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.123\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: B\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n - refId: B\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n hide: false\n reducer: mean\n refId: B\n type: reduce\n dashboardUid: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n panelId: 28\n noDataState: Alerting\n execErrState: Alerting\n for: 15m # give some time for restarts\n annotations:\n __dashboardUid__: ccbb2351-2ae2-462f-ae0e-f2c893ad1028\n __panelId__: \"28\"\n severity: warning\n description: ''\n runbook_url: ''\n summary: Transaction rate from the k6 load tester is lower than expected threshold\n labels:\n '': ''\n isPaused: false\n", "mining-rounds_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: mining-rounds\n folder: canton-network\n interval: 1m\n rules:\n - uid: ae74w21qca2o0e\n title: Open Mining Rounds Not Advancing\n condition: No new round\n data:\n - refId: Round number diff\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: max(max(max_over_time(splice_sv_dso_store_latest_open_mining_round[30m])) by (namespace) - min(min_over_time(splice_sv_dso_store_latest_open_mining_round[30m])) by (namespace))\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Round number diff\n useBackend: false\n - refId: No new round\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.9\n type: gt\n datasource:\n type: __expr__\n uid: __expr__\n expression: Round number diff\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No new round\n type: threshold\n dashboardUid: ed94a332-4fa7-47f8-982b-fc997381175b\n panelId: 1\n noDataState: Alerting\n execErrState: OK\n for: 1m\n annotations:\n __dashboardUid__: ed94a332-4fa7-47f8-982b-fc997381175b\n __panelId__: \"1\"\n runbook_url: \"\"\n summary: \"The open mining rounds have not advanced in the last 30m\"\n description: 'None of our SV apps have seen the open mining round advancing in the last 30m. Either all of our SVs or a high enough number of SVs to break BFT guarantees are failing'\n severity: critical\n labels:\n priority: high\n isPaused: false\n - uid: ae74w21qca2o0d\n title: Issuing Rounds Not Advancing\n condition: No new round\n data:\n - refId: Round number diff\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: max(max(max_over_time(splice_sv_dso_store_latest_issuing_mining_round[30m])) by (namespace) - min(min_over_time(splice_sv_dso_store_latest_issuing_mining_round[30m])) by (namespace))\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: Round number diff\n useBackend: false\n - refId: No new round\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.9\n type: gt\n datasource:\n type: __expr__\n uid: __expr__\n expression: Round number diff\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No new round\n type: threshold\n dashboardUid: ed94a332-4fa7-47f8-982b-fc997381175b\n panelId: 1\n noDataState: Alerting\n execErrState: OK\n for: 1m\n annotations:\n __dashboardUid__: ed94a332-4fa7-47f8-982b-fc997381175b\n __panelId__: \"2\"\n runbook_url: \"\"\n summary: \"The issuing mining rounds have not advanced in the last 30m\"\n description: 'None of our SV apps have seen the issuing mining round advancing in the last 30m. Either all of our SVs or a high enough number of SVs to break BFT guarantees are failing'\n severity: critical\n labels:\n priority: high\n isPaused: false\n", "mute_time_intervals.yaml": "apiVersion: 1\nmuteTimes:\n - orgId: 1\n name: sv1-mute\n time_intervals:\n - times:\n - start_time: '18:00'\n end_time: '24:00'\n weekdays:\n - wednesday\n location: UTC\n - orgId: 1\n name: sv-runbook-mute\n time_intervals:\n - times:\n - start_time: '18:00'\n end_time: '24:00'\n weekdays:\n - wednesday\n location: UTC\n - times:\n - start_time: '00:00'\n end_time: '06:00'\n - start_time: '12:00'\n end_time: '13:00'\n weekdays:\n - thursday\n location: UTC\n", @@ -94,6 +94,7 @@ "sequencer_client_delay_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer client delay\n folder: canton\n interval: 1m\n rules:\n - uid: efe3v7bed6134e\n title: Sequencer Client Delay\n condition: client delay threshold exceeded\n data:\n - refId: client delay\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by (namespace, job) (timestamp(daml_sequencer_client_handler_last_sequencing_time_micros) - (daml_sequencer_client_handler_last_sequencing_time_micros>0)/1e6)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: client delay\n - refId: client delay threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 90\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: client delay\n intervalMs: 1000\n maxDataPoints: 43200\n refId: client delay threshold exceeded\n type: threshold\n dashboardUid: ca9df344-c699-4efe-83c2-5fb2639d96d9\n panelId: 3\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: ca9df344-c699-4efe-83c2-5fb2639d96d9\n __panelId__: \"3\"\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting an increased sequencer client delay.\n severity: critical\n labels: {}\n isPaused: false\n", "sequencer_connection_pool_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer connection pool\n folder: canton\n interval: 1m\n rules:\n - uid: dfeb9wma1dk3kc\n title: Health of a connection\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n adhocFilters: []\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (connection, namespace, job) (daml_sequencer_client_sequencer_connection_pool_connection_health{namespace=~\".*\", job=~\".*\"})\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}}:{{job}}:{{connection}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 2\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n panelId: 6\n noDataState: NoData\n execErrState: Error\n for: 1m\n annotations:\n __dashboardUid__: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n __panelId__: \"6\"\n description: A failed connection is periodically retried for availability. A fatal subscription is considered invalid and will never be retried.\n summary: The sequencer connection {{ $labels.connection }} of job {{ $labels.job }} in namespace {{ $labels.namespace }} failed validation.\n isPaused: false\n - uid: dfebau7w2r7cwa\n title: Number of active subscriptions in the subscription pool\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n adhocFilters: []\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (namespace, job) (daml_sequencer_client_sequencer_connection_pool_active_subscriptions{namespace=~\".*\",job=~\".*\"})\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}}:{{job}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: B\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (namespace, job) (daml_sequencer_client_sequencer_connection_pool_subscription_threshold{namespace=~\".*\",job=~\".*\"})\n hide: false\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: $A-$B\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n panelId: 5\n noDataState: NoData\n execErrState: Error\n for: 5m\n annotations:\n __dashboardUid__: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n __panelId__: \"5\"\n description: The liveness margin determines how many subscriptions on different sequencers are continuously maintained, beyond the minimum number defined by the trust threshold. In other words, the subscription pool will strive to maintain at all times (trust threshold + liveness margin)-many subscriptions active. This provides tolerance to subscriptions falling, enabling the node to continue operating while some sequencers are down.\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} has {{ index $values \"C\" }} less active subscriptions than the sum of trust threshold in the subscription pool\n severity: critical\n labels: {}\n isPaused: false\n", "sequencer_rate_limit_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer-rate-limits\n folder: canton\n interval: 1m\n rules:\n - uid: a3f7c8e1d492\n title: Sequencer Throughput Cap Rejections\n condition: rejection_rate_threshold\n data:\n - refId: rejection_rate\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by (namespace, job)(rate(daml_sequencer_throughput_cap_confirmation_request_rejections_total[5m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: rejection_rate\n - refId: rejection_rate_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: rejection_rate\n hide: false\n refId: rejection_rate_threshold\n type: threshold\n dashboardUid: eb26bc60-6c26-408a-b0de-befc19f6915c\n panelId: 3\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eb26bc60-6c26-408a-b0de-befc19f6915c\n __panelId__: \"3\"\n description: Sequencer throughput cap is rejecting confirmation requests on namespace {{ $labels.namespace }} job {{ $labels.job }}. This means members are exceeding their rate limits.\n severity: warning\n summary: Sequencer throughput cap rejections detected on {{ $labels.namespace }} {{ $labels.job }}\n labels: {}\n isPaused: false\n - uid: b4e8d9f2e5a3\n title: Sequencer Circuit Breaker Open\n condition: circuit_breaker_threshold\n data:\n - refId: circuit_breaker_state\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (namespace, job)(daml_sequencer_circuit_breaker_confirmation_request_state)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: circuit_breaker_state\n - refId: circuit_breaker_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.5\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: circuit_breaker_state\n hide: false\n refId: circuit_breaker_threshold\n type: threshold\n dashboardUid: eb26bc60-6c26-408a-b0de-befc19f6915c\n panelId: 10\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eb26bc60-6c26-408a-b0de-befc19f6915c\n __panelId__: \"10\"\n description: Sequencer circuit breaker for confirmation requests is open on namespace {{ $labels.namespace }} job {{ $labels.job }}. The sequencer is rejecting all incoming confirmation requests.\n severity: critical\n summary: Sequencer circuit breaker OPEN on {{ $labels.namespace }} {{ $labels.job }}\n labels: {}\n isPaused: false\n", + "splice-rate-limiting_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: splice-rate-limiting\n folder: platform\n interval: 5m\n rules:\n - uid: cfvmu2hs596v4d\n title: Splice Rate Limiting Usage\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: |-\n (max by (namespace, node_name, http_service, limiter, limiter_type) (\n splice_rate_limiting_max_limit_per_second\n ) * 0.8)\n -\n sum by (namespace, node_name, http_service, limiter, limiter_type) (\n rate(splice_rate_limiting_total[$__rate_interval])\n )\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: splice-rate-limit-db\n panelId: 6\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: Incoming request rate for this limiter has exceeded 0.8 of its configured splice_rate_limiting_max_limit_per_second for 5 minutes.\n summary: Rate limiter \"{{ $labels.limiter }}\" on {{ $labels.node_name }} ({{ $labels.http_service }}) in {{ $labels.namespace }} is receiving more requests than 0.8 of its configured limit allows.\n labels:\n gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429\n isPaused: false\n - uid: chvmb2hs494v1y\n title: Splice Rate Limiting Rejections\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: |-\n sum by (namespace, node_name, http_service, limiter, limiter_type) (\n increase(splice_rate_limiting_total{result!=\"accepted\"}[$__rate_interval])\n )\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 10\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: splice-rate-limit-db\n panelId: 6\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n description: This limiter has been rejecting more than 10 requests with HTTP 429 for 5 minutes.\n summary: Rate limiter \"{{ $labels.limiter }}\" ({{ $labels.limiter_type }}) on {{ $labels.node_name }} ({{ $labels.http_service }}) in {{ $labels.namespace }} is rejecting requests.\n labels:\n gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429\n isPaused: false\n", "sv-status-report_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sv status reports\n folder: canton-network\n interval: 1m\n rules:\n - uid: adlmhpz5iv4sgc\n title: Report Creation Time Lag (internal SVs 5m)\n condition: No recent report\n data:\n - refId: Report time lag\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: >-\n time() - max by (report_publisher)\n (splice_sv_status_report_creation_time_us{namespace=~\".*\",\n report_publisher=~\"Digital-Asset-1|Digital-Asset-2|DA-Helm-Test-Node\",\n canton_version=~\".*\"}) / 1000000\n instant: false\n interval: ''\n intervalMs: 30000\n legendFormat: '{{report_publisher}}'\n maxDataPoints: 43200\n range: true\n refId: Report time lag\n - refId: Latest report time lag\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params: []\n type: gt\n operator:\n type: and\n query:\n params:\n - B\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Report time lag\n intervalMs: 1000\n maxDataPoints: 43200\n reducer: last\n refId: Latest report time lag\n settings:\n mode: dropNN\n type: reduce\n - refId: No recent report\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 300\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Latest report time lag\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No recent report\n type: threshold\n dashboardUid: caffa6f7-c421-4579-a839-b026d3b76826\n panelId: 18\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: caffa6f7-c421-4579-a839-b026d3b76826\n __panelId__: '18'\n severity: critical\n description: >-\n The SV {{ $labels.report_publisher }} has not submitted a status\n report recently\n runbook_url: ''\n summary: Status report creation time lag too high\n labels:\n team: canton-network\n isPaused: false\n - uid: bdlmhpz5iv4sgc\n title: Report Creation Time Lag (internal SVs 15m)\n condition: No recent report\n data:\n - refId: Report time lag\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: >-\n time() - max by (report_publisher)\n (splice_sv_status_report_creation_time_us{namespace=~\".*\",\n report_publisher=~\"Digital-Asset-1|Digital-Asset-2|DA-Helm-Test-Node\",\n canton_version=~\".*\"}) / 1000000\n instant: false\n interval: ''\n intervalMs: 30000\n legendFormat: '{{report_publisher}}'\n maxDataPoints: 43200\n range: true\n refId: Report time lag\n - refId: Latest report time lag\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params: []\n type: gt\n operator:\n type: and\n query:\n params:\n - B\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Report time lag\n intervalMs: 1000\n maxDataPoints: 43200\n reducer: last\n refId: Latest report time lag\n settings:\n mode: dropNN\n type: reduce\n - refId: No recent report\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 300\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Latest report time lag\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No recent report\n type: threshold\n dashboardUid: caffa6f7-c421-4579-a839-b026d3b76826\n panelId: 18\n noDataState: Alerting\n execErrState: Alerting\n for: 15m\n annotations:\n __dashboardUid__: caffa6f7-c421-4579-a839-b026d3b76826\n __panelId__: '18'\n severity: critical\n description: >-\n The SV {{ $labels.report_publisher }} has not submitted a status\n report recently\n runbook_url: ''\n summary: Status report creation time lag too high\n labels:\n team: support\n isPaused: false\n - uid: cdlmhpz5iv4sgc\n title: Report Creation Time Lag (external SVs 15m)\n condition: No recent report\n data:\n - refId: Report time lag\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: >-\n time() - max by (report_publisher)\n (splice_sv_status_report_creation_time_us{namespace=~\".*\",\n report_publisher!~\"Digital-Asset-1|Digital-Asset-2|DA-Helm-Test-Node\",\n canton_version=~\".*\"}) / 1000000\n instant: false\n interval: ''\n intervalMs: 30000\n legendFormat: '{{report_publisher}}'\n maxDataPoints: 43200\n range: true\n refId: Report time lag\n - refId: Latest report time lag\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params: []\n type: gt\n operator:\n type: and\n query:\n params:\n - B\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Report time lag\n intervalMs: 1000\n maxDataPoints: 43200\n reducer: last\n refId: Latest report time lag\n settings:\n mode: dropNN\n type: reduce\n - refId: No recent report\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 300\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: Latest report time lag\n intervalMs: 1000\n maxDataPoints: 43200\n refId: No recent report\n type: threshold\n dashboardUid: caffa6f7-c421-4579-a839-b026d3b76826\n panelId: 18\n noDataState: Alerting\n execErrState: Alerting\n for: 15m\n annotations:\n __dashboardUid__: caffa6f7-c421-4579-a839-b026d3b76826\n __panelId__: '18'\n severity: critical\n description: >-\n The SV {{ $labels.report_publisher }} has not submitted a status\n report recently\n runbook_url: ''\n summary: Status report creation time lag too high\n labels:\n team: da\n isPaused: false\n", "templates.yaml": "# config file version\napiVersion: 1\n\n# List of templates to import or update\n# source https://community.grafana.com/t/working-configuration-example-for-alerts-templating-telegram-and-slack/80988\ntemplates:\n - name: slack\n template: |\n {{ define \"slack_title\" }}\n {{ $hasCritical := false }}{{ $hasWarning := false }}{{ $hasInfo := false }}{{ $hasOthers := false }}\n {{- range .Alerts.Firing -}}\n {{- if eq .Annotations.severity \"critical\" -}}\n {{- $hasCritical = true -}}\n {{- else if eq .Annotations.severity \"warning\" -}}\n {{- $hasWarning = true -}}\n {{- else if eq .Annotations.severity \"info\" -}}\n {{- $hasInfo = true -}}\n {{- else -}}\n {{- $hasOthers = true -}}\n {{- end -}}\n {{- end -}}\n\n mock\n {{ if gt (len .Alerts.Firing) 0 }}\n {{- if $hasCritical }}\n 🔥 {{ len .Alerts.Firing }} Alert{{ if gt (len .Alerts.Firing) 1 }}s{{ end }} firing\n {{- else if $hasWarning }}\n ⚠️ {{ len .Alerts.Firing }} Alert{{ if gt (len .Alerts.Firing) 1 }}s{{ end }} firing\n {{- else }}\n :information_source: {{ len .Alerts.Firing }} Alert{{ if gt (len .Alerts.Firing) 1 }}s{{ end }} firing\n {{- end }}\n {{ end }}\n {{ if gt (len .Alerts.Resolved) 0 }} ✅ {{ len .Alerts.Resolved }} alert(s) resolved {{ end }}\n {{ end }}\n\n {{ define \"slack_message\" }}\n {{ $hasCritical := false }}{{ $hasWarning := false }}{{ $hasInfo := false }}{{ $hasOthers := false }}\n {{- range .Alerts.Firing -}}\n {{- if eq .Annotations.severity \"critical\" -}}\n {{- $hasCritical = true -}}\n {{- else if eq .Annotations.severity \"warning\" -}}\n {{- $hasWarning = true -}}\n {{- else if eq .Annotations.severity \"info\" -}}\n {{- $hasInfo = true -}}\n {{- else -}}\n {{- $hasOthers = true -}}\n {{- end -}}\n {{- end -}}\n {{ if $hasCritical }} 🔥Critical alerts {{ range .Alerts.Firing }} {{- if eq .Annotations.severity \"critical\" -}} {{ template \"slack_alert_firing\" .}} {{ end }} {{ end }} {{ end }}\n {{ if $hasWarning }} ⚠️Warning alerts {{ range .Alerts.Firing }} {{- if eq .Annotations.severity \"warning\" -}} {{ template \"slack_alert_firing\" .}} {{ end }} {{ end }} {{ end }}\n {{ if $hasInfo }} :information_source:Info alerts {{ range .Alerts.Firing }} {{- if eq .Annotations.severity \"info\" -}} {{ template \"slack_alert_firing\" .}} {{ end }} {{ end }} {{ end }}\n {{ if $hasOthers }} Other alerts {{ range .Alerts.Firing }} {{- if and (and (ne .Annotations.severity \"info\") (ne .Annotations.severity \"warning\")) (ne .Annotations.severity \"critical\") -}} {{ template \"slack_alert_firing\" . }} {{ end }} {{ end }} {{ end }}\n {{ if gt (len .Alerts.Resolved) 0 }} ✅Resolved Alerts {{ range .Alerts.Resolved }} {{ template \"slack_alert_resolved\" .}} {{ end }} {{ end }}\n {{ end }}\n\n {{ define \"slack_alert_firing\" }}\n *{{ .Labels.alertname }}*\n {{ .Annotations.summary }}\n {{ if .Annotations.description }}{{ .Annotations.description }}{{ end }}\n {{- if .Labels.service }}\n Service: {{ .Labels.service }}\n {{- end }}\n {{ template \"slack_gcloud_log_link\" . }}\n {{ end }}\n\n {{ define \"slack_alert_resolved\" }}\n *{{ .Labels.alertname }}*\n {{ if .Annotations.severity }}{{ .Annotations.severity }}{{ end }}\n {{ .Annotations.summary }}\n {{ if .Annotations.description }}{{ .Annotations.description }}{{ end }}\n {{ end }}\n\n {{ define \"slack_gcloud_log_link\" }}{{ end }}\n\n {{ define \"slack_color\" -}}\n {{ $hasCritical := false }}{{ $hasWarning := false }}{{ $hasInfo := false }}{{ $hasOthers := false }}\n {{- range .Alerts.Firing -}}\n {{- if eq .Annotations.severity \"critical\" -}}\n {{- $hasCritical = true -}}\n {{- else if eq .Annotations.severity \"warning\" -}}\n {{- $hasWarning = true -}}\n {{- else if eq .Annotations.severity \"info\" -}}\n {{- $hasInfo = true -}}\n {{- else -}}\n {{- $hasOthers = true -}}\n {{- end -}}\n {{- end -}}\n {{ if eq .Status \"firing\" -}}\n {{ if $hasCritical -}}\n danger\n {{- else if $hasWarning -}}\n warning\n {{- else -}}\n #439FE0\n {{- end -}}\n {{ else -}}\n good\n {{- end }}\n {{- end }}\n\n {{ define \"support_email_message\" }}\n [ MAINNET-DA2-SVN-CRITICAL-ALERT 9f2b7e1a-4c3d-58b9-9f1e-df9c4a5b6e7d ]\n {{ if gt (len .Alerts.Firing) 0 }}**Firing**\n {{ template \"__text_alert_list\" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }}\n {{ end }}{{ end }}{{ if gt (len .Alerts.Resolved) 0 }}**Resolved**\n {{ template \"__text_alert_list\" .Alerts.Resolved }}{{ end }}{{ end }}\n", "traffic_based_rewards_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: traffic-based-rewards\n folder: canton-network\n interval: 1m\n rules:\n - uid: afht9v3djz2tcf\n title: Number of featured app rights\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (namespace)(pg_stat_user_tables_n_live_tup{relname=\"scan_rewards_reference_store_active\"})\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 10000\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: \"00000039\"\n panelId: 74\n noDataState: OK\n execErrState: Alerting\n for: 60m\n annotations:\n __dashboardUid__: \"00000039\"\n __panelId__: \"74\"\n description: |\n Scan rewards reference store on namespace {{ $labels.namespace }} has too many live rows,\n which is likely because there are too many featured app right contracts.\n This may become a problem as the app activity record computation loads\n all featured app right contracts at round start into memory at once.\n\n Please investigate the reason for there being that many active featured\n app right contracts and determine the best course of action.\n (Note: bumping the alert limit may be OK, if the Scan app has enough memory available.)\n severity: warning\n summary: Scan rewards reference store on namespace {{ $labels.namespace }} has too many live rows.\n labels: {}\n isPaused: false\n - uid: hn7pcll44xugj7\n title: Number of active CalculateRewardsV2 contracts\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (dryRun)(max_over_time(splice_calculate_rewards_v2_active_contracts[5m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 1\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: \"cnx7gsn\"\n panelId: 33\n noDataState: OK\n execErrState: Alerting\n for: 1m\n annotations:\n __dashboardUid__: cnx7gsn\n __panelId__: 33\n description: |\n At least one of our sv app instances observed more than one active CalculateRewardsV2 contract\n for dryRun={{ $labels.dryRun }}.\n\n CalculateRewardsV2 contracts are created when a round is closed, and represent a request\n to vote on the root hash of app rewards for a round.\n The contract is archived as soon as 2/3 of all SVs have voted for the same root hash,\n which should typically happen within a minute after the contract is created.\n\n Suggested actions:\n - Check if enough SVs are healthy\n - Investigate the status of traffic-based app rewards automation.\n Look at the \"Traffic-based app rewards (CIP-104)\" dashboard in Grafana, in particular the\n \"Voting on root hash\" section.\n - Check what votes are being cast by other SVs.\n Filter logs for \"not yet executing\" AND \"CRARC_StartProcessingRewardsV2\".\n Look for missing SVs or SVs that are voting for a different root hash.\n severity: critical\n summary: Too many active CalculateRewardsV2 contracts for dryRun={{ $labels.dryRun }}.\n labels: {}\n isPaused: false\n - uid: o4v0a20pvxhd2\n title: Number of active ProcessRewardsV2 contracts\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by (dryRun)(min_over_time((splice_process_rewards_v2_active_contracts != -1)[15m:]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: \"cnx7gsn\"\n panelId: 34\n noDataState: OK\n execErrState: Alerting\n for: 1m\n annotations:\n __dashboardUid__: cnx7gsn\n __panelId__: 34\n description: |\n None our sv app instances observed the number of active ProcessRewardsV2 contracts recently\n reaching zero for dryRun={{ $labels.dryRun }}.\n\n ProcessRewardsV2 contracts are nodes of the reward data merkle tree.\n They are converted first into more ProcessRewardsV2 contracts and then finally into RewardCouponV2 contracts\n by the delegate based ProcessRewardsTrigger.\n\n If these contracts don't all disappear within 10min (when the next round starts), it means the creation of\n minting allowances probably can't keep up and is falling behind.\n\n Suggested actions:\n - Investigate the status of traffic-based app rewards automation.\n Look at the \"Traffic-based app rewards (CIP-104)\" dashboard in Grafana, in particular the\n \"Creating minting allowances\" section.\n - Filter logs for \"ProcessRewards.*Trigger\".\n Check why the trigger is not processing the ProcessRewardsV2 contracts.\n severity: critical\n summary: ProcessRewardsV2 contracts are not being archived in time for dryRun={{ $labels.dryRun }}.\n labels: {}\n isPaused: false\n - uid: 3pcvfr7j92x7o\n title: Average verdict ingestion batch size over the last 10min\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: (histogram_sum(sum by(namespace)(rate(splice_scan_verdict_ingestion_batch_size[10m])))/histogram_count(sum by(namespace)(rate(splice_scan_verdict_ingestion_batch_size[10m]))))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 40\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: \"cnx7gsn\"\n panelId: 19\n noDataState: OK\n execErrState: Alerting\n for: 60m\n annotations:\n __dashboardUid__: cnx7gsn\n __panelId__: 19\n description: |\n The average verdict ingestion batch size has been above 40\n for more than for 60 minutes for namespace {{ $labels.namespace }}.\n\n The default maximum batch size is 50, a high average batch size may indicate that ingestion can't keep up.\n\n Suggested actions:\n - If this node is known to be catching up, ignore or silence the alert.\n - Investigate the status of traffic-based app rewards automation.\n Look at the \"Traffic-based app rewards (CIP-104)\" dashboard in Grafana, in particular the\n \"Ingest verdicts and activity records\" section.\n - Filter logs for \"ScanVerdictIngestionService\".\n - If you find that this alert produces too many false positives, adjust the \"verdictIngestionBatchSizeThreshold\"\n in the cluster config.\n severity: warning\n summary: Average verdict ingestion batch size is above 40 for namespace {{ $labels.namespace }}.\n labels: {}\n isPaused: false\n", @@ -325,7 +326,7 @@ "http.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 1182,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 15,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"label_replace(histogram_count(rate(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\", operation=~\\\"$operation\\\", status_code=~\\\"2.*\\\"}[$__rate_interval])), \\\"source_display\\\", \\\"Source UI: $1\\\", \\\"source_ui\\\", \\\"(.+)\\\")\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_service}} {{operation}} {{status_code}} {{source_display}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Handled Requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 9,\n \"x\": 15,\n \"y\": 0\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"histogram_count(rate(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\", operation=~\\\"$operation\\\", status_code!~\\\"2.*\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_service}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Errors\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 12\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, rate(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\", operation=~\\\"$operation\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_service}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Request Timing quantile $percentile\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"requests\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 22\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_http_requests_inflight{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\", operation=~\\\"$operation\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_service}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"In Flight Requests\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_requests_duration_seconds,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_requests_duration_seconds,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\"},job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},http_service)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"service\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},http_service)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\"},operation)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"operation\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_service=~\\\"$service\\\"},operation)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"0.9\",\n \"value\": \"0.9\"\n },\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": true,\n \"text\": \"0.9\",\n \"value\": \"0.9\"\n },\n {\n \"selected\": false,\n \"text\": \"0.95\",\n \"value\": \"0.95\"\n },\n {\n \"selected\": false,\n \"text\": \"0.99\",\n \"value\": \"0.99\"\n }\n ],\n \"query\": \"0.9,0.95,0.99\",\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-5m\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Http Server\",\n \"uid\": \"aerbb811lwveod\",\n \"version\": 4\n}\n", "http_client.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 1,\n \"id\": 15005,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 15,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"histogram_count(rate(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", operation=~\\\"$operation\\\", status_code=~\\\"2.*\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": false,\n \"legendFormat\": \"{{namespace}} {{job}} {{http_client}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Client requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 9,\n \"x\": 15,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"histogram_count(rate(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", operation=~\\\"$operation\\\", status_code!~\\\"2.*\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} {{job}} {{http_client}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Errors\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 11\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile($percentile, rate(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_client=~\\\"$http_client\\\", operation=~\\\"$operation\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}} {{job}} {{http_client}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Client Requests Timing quantile 0.95 \",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 19\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"daml_http_client_requests_inflight{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_client=~\\\"$http_client\\\", operation=~\\\"$operation\\\"}\\n\",\n \"legendFormat\": \"{{namespace}} {{job}} {{http_client}} {{operation}} {{status_code}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"In Flight Client Requests\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 41,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": [\n \"validator1\"\n ],\n \"value\": [\n \"validator1\"\n ]\n },\n \"definition\": \"label_values(daml_http_client_requests_duration_seconds,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_client_requests_duration_seconds,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"definition\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\"},job)\",\n \"description\": \"\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"job\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"definition\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_client=~\\\"$http_client\\\"},operation)\",\n \"description\": \"\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"operation\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\", http_client=~\\\"$http_client\\\"},operation)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"definition\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},http_client)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"http_client\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_http_client_requests_duration_seconds{namespace=~\\\"$namespace\\\", job=~\\\"$job\\\"},http_client)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"0.9\",\n \"value\": \"0.9\"\n },\n \"name\": \"percentile\",\n \"options\": [\n {\n \"selected\": true,\n \"text\": \"0.9\",\n \"value\": \"0.9\"\n },\n {\n \"selected\": false,\n \"text\": \"0.95\",\n \"value\": \"0.95\"\n },\n {\n \"selected\": false,\n \"text\": \"0.99\",\n \"value\": \"0.99\"\n }\n ],\n \"query\": \"0.9,0.95,0.99\",\n \"type\": \"custom\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-12h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Http Client\",\n \"uid\": \"a8a3113a-bccf-4728-b752-dd7a5d6f9bda\",\n \"version\": 3\n}\n", "istio.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of HTTP 429 (Too Many Requests) responses per second, broken down by the namespace reporting the request and the destination service receiving it.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, destination_service) (rate(istio_requests_total{response_code=\\\"429\\\", namespace=~\\\"$namespace\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Istio total rejected requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Fraction of requests evaluated by the Envoy local rate limit filter that were rejected with HTTP 429, per namespace and pod.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"percent\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\\n/\\nsum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enabled{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"interval\": \"\",\n \"legendFormat\": \"{{namespace}} - {{pod}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rejection ratio\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of number of requests for which rate limiting was applied (e.g.: 429 returned)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 8\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enforced{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\",\n \"legendFormat\": \"{{namespace}} - {{pod}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Enforced requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate for responses without an available token (but not necessarily enforced)\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 8\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_rate_limited{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace,job)\",\n \"legendFormat\": \"{{namespace}} - {{job}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rejected requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate for number of requests for which the rate limiter was consulted\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 16\n },\n \"id\": 8,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_enabled{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\",\n \"legendFormat\": \"{{namespace}} - {{pod}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Enabled requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate for number of requests under limit responses from the token bucket\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 16\n },\n \"id\": 7,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(envoy_http_local_rate_limiter_http_local_rate_limit_ok{namespace=~\\\"$namespace\\\"}[$__rate_interval])) by (namespace, pod)\",\n \"legendFormat\": \"{{namespace}} - {{pod}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Allowed requests\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Istio Rate Limiting\",\n \"uid\": \"cnr56dj\",\n \"version\": 3,\n \"weekStart\": \"\"\n}\n", - "rate_limiters.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"target\": {\n \"limit\": 100,\n \"matchAny\": false,\n \"tags\": [],\n \"type\": \"dashboard\"\n },\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 510,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\", result!=\\\"accepted\\\"}[$__rate_interval])) by (result, limiter)\",\n \"instant\": false,\n \"legendFormat\": \"{{result}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rejections\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n }\n },\n \"mappings\": [],\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 4,\n \"options\": {\n \"displayLabels\": [\n \"name\",\n \"percent\"\n ],\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"values\": [\n \"percent\",\n \"value\"\n ]\n },\n \"pieType\": \"pie\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(increase(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\"}[$__range])) by (result)\",\n \"instant\": true,\n \"legendFormat\": \"{{result}}\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Requests by Result\",\n \"type\": \"piechart\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 24,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.1.1\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\"}[$__rate_interval])) by (limiter)\",\n \"instant\": false,\n \"legendFormat\": \"{{limiter}}\",\n \"range\": true,\n \"refId\": \"A\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"splice_rate_limiting_max_limit_per_second\",\n \"hide\": false,\n \"instant\": false,\n \"legendFormat\": \"{{limiter}}\",\n \"range\": true,\n \"refId\": \"B\"\n }\n ],\n \"title\": \"Total Requests\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"1m\",\n \"schemaVersion\": 41,\n \"tags\": [\n \"prometheus\",\n \"rate-limiting\"\n ],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": [\n \"sv-1\"\n ],\n \"value\": [\n \"sv-1\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total, namespace)\",\n \"includeAll\": false,\n \"label\": \"Namespace\",\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(splice_rate_limiting_total, namespace)\",\n \"refId\": \"StandardVariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": [\n \"scan-app\"\n ],\n \"value\": [\n \"scan-app\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\"},node_name)\",\n \"includeAll\": true,\n \"label\": \"Node Name\",\n \"multi\": true,\n \"name\": \"node_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\"},node_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"sort\": 3,\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\"},http_service)\",\n \"includeAll\": true,\n \"label\": \"HTTP Service\",\n \"multi\": true,\n \"name\": \"http_service\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\"},http_service)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"sort\": 1,\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", node_name=~\\\"$node_name\\\"},limiter)\",\n \"includeAll\": true,\n \"label\": \"Limiter\",\n \"multi\": true,\n \"name\": \"limiter\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", node_name=~\\\"$node_name\\\"},limiter)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"sort\": 2,\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-6h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Splice Rate Limiting\",\n \"uid\": \"splice-rate-limit-db\",\n \"version\": 3\n}\n" + "rate_limiters.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"target\": {\n \"limit\": 100,\n \"matchAny\": false,\n \"tags\": [],\n \"type\": \"dashboard\"\n },\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\", result!=\\\"accepted\\\"}[$__rate_interval])) by (result)\",\n \"instant\": false,\n \"legendFormat\": \"{{ result }}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Rejections\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n }\n },\n \"mappings\": [],\n \"unit\": \"short\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 0\n },\n \"id\": 4,\n \"options\": {\n \"displayLabels\": [\n \"name\",\n \"percent\"\n ],\n \"legend\": {\n \"displayMode\": \"list\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"values\": [\n \"percent\",\n \"value\"\n ]\n },\n \"pieType\": \"pie\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"sort\": \"desc\",\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(increase(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\"}[$__range])) by (result)\",\n \"instant\": true,\n \"legendFormat\": \"{{result}}\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Requests by Result\",\n \"type\": \"piechart\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 8\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\"}[$__rate_interval])) by (limiter)\",\n \"instant\": false,\n \"legendFormat\": \"{{ limiter }}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Requests\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 17\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\", node_name=~\\\"$node_name\\\", result!=\\\"accepted\\\"}[$__rate_interval])) by (result, limiter)\",\n \"instant\": false,\n \"legendFormat\": \"{{ limiter }}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rejections\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"reqps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 25\n },\n \"id\": 7,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"builder\",\n \"expr\": \"splice_rate_limiting_max_limit_per_second{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\", http_service=~\\\"$http_service\\\", limiter=~\\\"$limiter\\\"}\",\n \"instant\": false,\n \"legendFormat\": \"{{ limiter }}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Limits\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"1m\",\n \"schemaVersion\": 42,\n \"tags\": [\n \"prometheus\",\n \"rate-limiting\"\n ],\n \"templating\": {\n \"list\": [\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"sv-1\",\n \"value\": \"sv-1\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total, namespace)\",\n \"includeAll\": false,\n \"label\": \"Namespace\",\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(splice_rate_limiting_total, namespace)\",\n \"refId\": \"StandardVariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\"},node_name)\",\n \"includeAll\": true,\n \"label\": \"Node Name\",\n \"multi\": true,\n \"name\": \"node_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\"},node_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"sort\": 3,\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\"},http_service)\",\n \"includeAll\": true,\n \"label\": \"HTTP Service\",\n \"multi\": true,\n \"name\": \"http_service\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", node_name=~\\\"$node_name\\\"},http_service)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"sort\": 1,\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": false,\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", node_name=~\\\"$node_name\\\"},limiter)\",\n \"includeAll\": true,\n \"label\": \"Limiter\",\n \"multi\": true,\n \"name\": \"limiter\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_rate_limiting_total{namespace=~\\\"$namespace\\\", http_service=~\\\"$http_service\\\", node_name=~\\\"$node_name\\\"},limiter)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"sort\": 2,\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-12h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Splice Rate Limiting\",\n \"uid\": \"splice-rate-limit-db\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n" }, "kind": "ConfigMap", "metadata": { diff --git a/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml b/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml index 345a03e9fc..774ba9e502 100644 --- a/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml +++ b/cluster/pulumi/observability/grafana-alerting/istio-rate-limiting_alerts.yaml @@ -63,5 +63,5 @@ groups: summary: Envoy is rate limiting requests in {{ $labels.namespace }} on pod {{ $labels.pod }}. labels: "": "" - gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429%0AjsonPayload.authority:%22scan.{{ index $labels "namespace" }}.%22 + gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429 isPaused: false diff --git a/cluster/pulumi/observability/grafana-alerting/splice-rate-limiting_alerts.yaml b/cluster/pulumi/observability/grafana-alerting/splice-rate-limiting_alerts.yaml new file mode 100644 index 0000000000..861a70dd65 --- /dev/null +++ b/cluster/pulumi/observability/grafana-alerting/splice-rate-limiting_alerts.yaml @@ -0,0 +1,123 @@ +apiVersion: 1 +groups: + - orgId: 1 + name: splice-rate-limiting + folder: platform + interval: 5m + rules: + - uid: cfvmu2hs596v4d + title: Splice Rate Limiting Usage + condition: C + data: + - refId: A + relativeTimeRange: + from: 600 + to: 0 + datasourceUid: prometheus + model: + editorMode: code + expr: |- + (max by (namespace, node_name, http_service, limiter, limiter_type) ( + splice_rate_limiting_max_limit_per_second + ) * $SPLICE_RATE_LIMITS_USAGE_THRESHOLD) + - + sum by (namespace, node_name, http_service, limiter, limiter_type) ( + rate(splice_rate_limiting_total[$__rate_interval]) + ) + instant: true + intervalMs: 1000 + legendFormat: __auto + maxDataPoints: 43200 + range: false + refId: A + - refId: C + datasourceUid: __expr__ + model: + conditions: + - evaluator: + params: + - 0 + type: lt + operator: + type: and + query: + params: [] + reducer: + params: [] + type: last + type: query + datasource: + type: __expr__ + uid: __expr__ + expression: A + intervalMs: 1000 + maxDataPoints: 43200 + refId: C + type: threshold + dashboardUid: splice-rate-limit-db + panelId: 6 + noDataState: OK + execErrState: Alerting + for: 5m + annotations: + description: Incoming request rate for this limiter has exceeded $SPLICE_RATE_LIMITS_USAGE_THRESHOLD of its configured splice_rate_limiting_max_limit_per_second for 5 minutes. + summary: Rate limiter "{{ $labels.limiter }}" on {{ $labels.node_name }} ({{ $labels.http_service }}) in {{ $labels.namespace }} is receiving more requests than $SPLICE_RATE_LIMITS_USAGE_THRESHOLD of its configured limit allows. + labels: + gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429 + isPaused: false + - uid: chvmb2hs494v1y + title: Splice Rate Limiting Rejections + condition: C + data: + - refId: A + relativeTimeRange: + from: 600 + to: 0 + datasourceUid: prometheus + model: + editorMode: code + expr: |- + sum by (namespace, node_name, http_service, limiter, limiter_type) ( + increase(splice_rate_limiting_total{result!="accepted"$SPLICE_RATE_LIMITS_FILTER}[$__rate_interval]) + ) + instant: true + intervalMs: 1000 + legendFormat: __auto + maxDataPoints: 43200 + range: false + refId: A + - refId: C + datasourceUid: __expr__ + model: + conditions: + - evaluator: + params: + - $SPLICE_RATE_LIMITS_REJECTION_COUNT_THRESHOLD + type: gt + operator: + type: and + query: + params: [] + reducer: + params: [] + type: last + type: query + datasource: + type: __expr__ + uid: __expr__ + expression: A + intervalMs: 1000 + maxDataPoints: 43200 + refId: C + type: threshold + dashboardUid: splice-rate-limit-db + panelId: 6 + noDataState: OK + execErrState: Alerting + for: 5m + annotations: + description: This limiter has been rejecting more than $SPLICE_RATE_LIMITS_REJECTION_COUNT_THRESHOLD requests with HTTP 429 for 5 minutes. + summary: Rate limiter "{{ $labels.limiter }}" ({{ $labels.limiter_type }}) on {{ $labels.node_name }} ({{ $labels.http_service }}) in {{ $labels.namespace }} is rejecting requests. + labels: + gcloud_filter: resource.labels.namespace_name=%22cluster-ingress%22%0AjsonPayload.response_code=429 + isPaused: false diff --git a/cluster/pulumi/observability/grafana-dashboards/platform/rate_limiters.json b/cluster/pulumi/observability/grafana-dashboards/platform/rate_limiters.json index 521957dc9f..a737f977f3 100644 --- a/cluster/pulumi/observability/grafana-dashboards/platform/rate_limiters.json +++ b/cluster/pulumi/observability/grafana-dashboards/platform/rate_limiters.json @@ -24,7 +24,6 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 0, - "id": 510, "links": [], "panels": [ { @@ -61,6 +60,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -89,7 +89,7 @@ "overrides": [] }, "gridPos": { - "h": 9, + "h": 8, "w": 12, "x": 0, "y": 0 @@ -108,7 +108,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -116,14 +116,14 @@ "uid": "prometheus" }, "editorMode": "code", - "expr": "sum(rate(splice_rate_limiting_total{namespace=~\"$namespace\", http_service=~\"$http_service\", limiter=~\"$limiter\", node_name=~\"$node_name\", result!=\"accepted\"}[$__rate_interval])) by (result, limiter)", + "expr": "sum(rate(splice_rate_limiting_total{namespace=~\"$namespace\", http_service=~\"$http_service\", limiter=~\"$limiter\", node_name=~\"$node_name\", result!=\"accepted\"}[$__rate_interval])) by (result)", "instant": false, - "legendFormat": "{{result}}", + "legendFormat": "{{ result }}", "range": true, "refId": "A" } ], - "title": "Rejections", + "title": "Total Rejections", "type": "timeseries" }, { @@ -149,7 +149,7 @@ "overrides": [] }, "gridPos": { - "h": 9, + "h": 8, "w": 12, "x": 12, "y": 0 @@ -177,13 +177,14 @@ "fields": "", "values": false }, + "sort": "desc", "tooltip": { "hideZeros": false, "mode": "multi", "sort": "none" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -235,6 +236,7 @@ "type": "linear" }, "showPoints": "auto", + "showValues": false, "spanNulls": false, "stacking": { "group": "A", @@ -263,10 +265,10 @@ "overrides": [] }, "gridPos": { - "h": 12, + "h": 9, "w": 24, "x": 0, - "y": 9 + "y": 8 }, "id": 5, "options": { @@ -282,7 +284,7 @@ "sort": "desc" } }, - "pluginVersion": "12.1.1", + "pluginVersion": "12.4.0", "targets": [ { "datasource": { @@ -292,31 +294,218 @@ "editorMode": "code", "expr": "sum(rate(splice_rate_limiting_total{namespace=~\"$namespace\", http_service=~\"$http_service\", limiter=~\"$limiter\", node_name=~\"$node_name\"}[$__rate_interval])) by (limiter)", "instant": false, - "legendFormat": "{{limiter}}", + "legendFormat": "{{ limiter }}", "range": true, "refId": "A" + } + ], + "title": "Total Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 17 + }, + "id": 6, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.4.0", + "targets": [ { "datasource": { "type": "prometheus", "uid": "prometheus" }, "editorMode": "code", - "expr": "splice_rate_limiting_max_limit_per_second", - "hide": false, + "expr": "sum(rate(splice_rate_limiting_total{namespace=~\"$namespace\", http_service=~\"$http_service\", limiter=~\"$limiter\", node_name=~\"$node_name\", result!=\"accepted\"}[$__rate_interval])) by (result, limiter)", "instant": false, - "legendFormat": "{{limiter}}", + "legendFormat": "{{ limiter }}", "range": true, - "refId": "B" + "refId": "A" } ], - "title": "Total Requests", + "title": "Rejections", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "barWidthFactor": 0.6, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "showValues": false, + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": 0 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "reqps" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 7, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "hideZeros": false, + "mode": "multi", + "sort": "desc" + } + }, + "pluginVersion": "12.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "prometheus" + }, + "editorMode": "builder", + "expr": "splice_rate_limiting_max_limit_per_second{namespace=~\"$namespace\", node_name=~\"$node_name\", http_service=~\"$http_service\", limiter=~\"$limiter\"}", + "instant": false, + "legendFormat": "{{ limiter }}", + "range": true, + "refId": "A" + } + ], + "title": "Limits", "type": "timeseries" } ], "preload": false, "refresh": "1m", - "schemaVersion": 41, + "schemaVersion": 42, "tags": [ "prometheus", "rate-limiting" @@ -326,12 +515,8 @@ { "allowCustomValue": false, "current": { - "text": [ - "sv-1" - ], - "value": [ - "sv-1" - ] + "text": "sv-1", + "value": "sv-1" }, "datasource": { "type": "prometheus", @@ -348,16 +533,15 @@ }, "refresh": 1, "regex": "", + "regexApplyTo": "value", "type": "query" }, { "allowCustomValue": false, "current": { - "text": [ - "scan-app" - ], + "text": "All", "value": [ - "scan-app" + "$__all" ] }, "datasource": { @@ -377,6 +561,7 @@ }, "refresh": 1, "regex": "", + "regexApplyTo": "value", "sort": 3, "type": "query" }, @@ -384,7 +569,9 @@ "allowCustomValue": false, "current": { "text": "All", - "value": "$__all" + "value": [ + "$__all" + ] }, "datasource": { "type": "prometheus", @@ -403,6 +590,7 @@ }, "refresh": 1, "regex": "", + "regexApplyTo": "value", "sort": 1, "type": "query" }, @@ -410,7 +598,9 @@ "allowCustomValue": false, "current": { "text": "All", - "value": "$__all" + "value": [ + "$__all" + ] }, "datasource": { "type": "prometheus", @@ -429,18 +619,20 @@ }, "refresh": 1, "regex": "", + "regexApplyTo": "value", "sort": 2, "type": "query" } ] }, "time": { - "from": "now-6h", + "from": "now-12h", "to": "now" }, "timepicker": {}, "timezone": "", "title": "Splice Rate Limiting", "uid": "splice-rate-limit-db", - "version": 3 + "version": 1, + "weekStart": "" } diff --git a/cluster/pulumi/observability/src/config.ts b/cluster/pulumi/observability/src/config.ts index df8baf1602..7572bf2554 100644 --- a/cluster/pulumi/observability/src/config.ts +++ b/cluster/pulumi/observability/src/config.ts @@ -102,6 +102,13 @@ const MonitoringConfigSchema = z // rate is computed. windowMinutes: z.number(), }), + spliceRateLimits: z.object({ + // Fraction (0-1) of a rate limiter's configured maximum rate above which the alert fires + usageThreshold: z.number(), + // Rejected requests per second, above which the rejection alert fires + rejectionCountThreshold: z.number(), + excludedLimiters: z.array(z.string()).default([]), + }), cloudSql: z.object({ maintenance: z.boolean(), }), diff --git a/cluster/pulumi/observability/src/observability.ts b/cluster/pulumi/observability/src/observability.ts index fb2c708433..ec4bdf911c 100644 --- a/cluster/pulumi/observability/src/observability.ts +++ b/cluster/pulumi/observability/src/observability.ts @@ -769,6 +769,20 @@ function substituteDsoMissedConfirmationsAlerts(alert: string): string { .replaceAll('$DSO_MISSED_CONFIRMATIONS_WINDOW_MINUTES', config.windowMinutes.toString()); } +function substituteSpliceRateLimitsAlerts(alert: string): string { + const config = monitoringConfig.alerting.alerts.spliceRateLimits; + const bareFilter = + config.excludedLimiters.length > 0 ? `limiter!~"${config.excludedLimiters.join('|')}"` : ''; + const filter = bareFilter ? `, ${bareFilter}` : ''; + return alert + .replaceAll('$SPLICE_RATE_LIMITS_USAGE_THRESHOLD', config.usageThreshold.toString()) + .replaceAll( + '$SPLICE_RATE_LIMITS_REJECTION_COUNT_THRESHOLD', + config.rejectionCountThreshold.toString() + ) + .replaceAll('$SPLICE_RATE_LIMITS_FILTER', filter); +} + // AmuletMetrics was previously using owner.toString instead of owner.toProtoPrimitive // This function makes it compatible for both. function partyIdTransform(partyId: string) { @@ -1036,6 +1050,9 @@ function createGrafanaAlerting(namespace: Input) { 'istio-rate-limiting_alerts.yaml': readGrafanaAlertingFile( 'istio-rate-limiting_alerts.yaml' ), + 'splice-rate-limiting_alerts.yaml': substituteSpliceRateLimitsAlerts( + readGrafanaAlertingFile('splice-rate-limiting_alerts.yaml') + ), }, }).map(([k, v]) => [k, defaultAlertSubstitutions(v)]) ), From 3c66ff49f2ad79a10d687ae6967b3bc59c357e05 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Wed, 19 Aug 2026 13:16:06 +0200 Subject: [PATCH 265/329] Start splice rate limiters with the configured no of permits available (#6849) If not the per IP rate limiters come in to force too aggressive. [ci] Signed-off-by: Nicu Reut --- .../concurrent/BurstyRateLimiterFactory.java | 28 +++++++++++++++++-- .../splice/http/HttpRateLimiter.scala | 9 ------ .../splice/util/SpliceRateLimiter.scala | 16 +++-------- .../splice/http/HttpRateLimiterTest.scala | 23 +++++++-------- .../splice/util/SpliceRateLimiterTest.scala | 25 +++++++++-------- 5 files changed, 56 insertions(+), 45 deletions(-) diff --git a/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java b/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java index cd94bd5790..593e053bc6 100644 --- a/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java +++ b/apps/common/src/main/java/com/google/common/util/concurrent/BurstyRateLimiterFactory.java @@ -6,22 +6,46 @@ /** * Shim that constructs a Guava {@link RateLimiter} backed by {@code SmoothBursty} with a custom * maximum burst duration. It lives in this package because the relevant {@code - * SmoothRateLimiter.SmoothBursty} constructor is package-private. + * SmoothRateLimiter.SmoothBursty} constructor and its permit bookkeeping fields are package-private. + * + *

In contrast to {@link RateLimiter#create(double)}, the limiters created here already hold + * {@code permitsPerSecond} permits (capped by the maximum burst budget) at creation time, instead of + * starting with an empty bucket that only fills up over time. That matters for limiters that are + * created lazily (e.g. one per client IP), which would otherwise reject an initial burst that an + * already running limiter would have accepted. */ public final class BurstyRateLimiterFactory { + /** + * Guava's default burst window for {@code RateLimiter.create(double)}. + */ + private static final double DEFAULT_MAX_BURST_SECONDS = 1.0; + private BurstyRateLimiterFactory() { } + /** + * Creates a {@link RateLimiter} allowing {@code permitsPerSecond} permits per second, starting + * with {@code permitsPerSecond} permits already available. + */ + public static RateLimiter create(double permitsPerSecond) { + return create(permitsPerSecond, DEFAULT_MAX_BURST_SECONDS); + } + /** * Creates a bursty {@link RateLimiter} that sustains {@code permitsPerSecond} on average while * allowing bursts of up to {@code permitsPerSecond * maxBurstSeconds} permits after idle periods. + * The limiter starts with one second worth of permits, i.e. {@code permitsPerSecond} permits + * (capped by the maximum burst budget), already available. */ public static RateLimiter create(double permitsPerSecond, double maxBurstSeconds) { - RateLimiter rateLimiter = + SmoothRateLimiter.SmoothBursty rateLimiter = new SmoothRateLimiter.SmoothBursty( RateLimiter.SleepingStopwatch.createFromSystemTimer(), maxBurstSeconds); rateLimiter.setRate(permitsPerSecond); + synchronized (rateLimiter) { + rateLimiter.storedPermits = Math.min(permitsPerSecond, rateLimiter.maxPermits); + } return rateLimiter; } } diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala index 520150944f..1e2afe378e 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala @@ -16,7 +16,6 @@ import org.lfdecentralizedtrust.splice.util.{ } import java.net.{Inet6Address, InetAddress} -import java.time.Instant class HttpRateLimiter( config: RateLimitersConfig, @@ -46,10 +45,6 @@ class HttpRateLimiter( ), ) - // the rate limiter has a cold start, to avoid the first request being rejected - // we enforce the rate limit only after 1 second - private def enforceAfter = Instant.now().plusSeconds(1) - private val globalRateLimiter: (SpliceRateLimiter, PerAttributeRateLimiter) = { val globalMetrics = metricsFor(HttpRateLimiter.GlobalService) ( @@ -57,7 +52,6 @@ class HttpRateLimiter( HttpRateLimiter.GlobalLimiter, config.global, globalMetrics, - enforceAfter, ), new PerAttributeRateLimiter( HttpRateLimiter.GlobalLimiter, @@ -65,7 +59,6 @@ class HttpRateLimiter( config.global, config.global.perClientIp, globalMetrics, - enforceAfter, logger, ), ) @@ -84,7 +77,6 @@ class HttpRateLimiter( operation, operationConfig, rateLimiterMetrics, - enforceAfter, ), new PerAttributeRateLimiter( operation, @@ -92,7 +84,6 @@ class HttpRateLimiter( operationConfig, operationConfig.perClientIp, rateLimiterMetrics, - enforceAfter, logger, ), ) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala index 6e41c4eab5..9fe0f73eb2 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala @@ -16,7 +16,7 @@ import com.github.benmanes.caffeine.cache.{Caffeine, RemovalCause, RemovalListen import com.google.common.util.concurrent.{BurstyRateLimiterFactory, RateLimiter} import org.lfdecentralizedtrust.splice.environment.SpliceMetrics -import java.time.{Duration, Instant} +import java.time.Duration import java.util import java.util.Collections import java.util.concurrent.TimeUnit @@ -131,7 +131,6 @@ class SpliceRateLimiter( name: String, config: SpliceRateLimitConfig, metrics: SpliceRateLimitMetrics, - enforceAfter: Instant = Instant.now(), limiterType: String = SpliceRateLimiter.GlobalLimiterType, extraLabels: Map[String, String] = Map.empty, // must be disabled for the per-attribute limiters as they'd all report the same value @@ -143,13 +142,9 @@ class SpliceRateLimiter( extraLabels ++ Map("limiter" -> name, "limiter_type" -> limiterType) ) - // The limiters are created eagerly so that they are already "warm" (i.e. have accumulated their - // burst budget) by the time the limit starts being enforced. They are only created for enabled - // limiters: a disabled limiter is never consulted and its configured rate might not even be a - // valid guava rate (e.g. 0). - // enforces the per-second burst limit (checked over a 1s window) + // The limiters are created with one second worth of permits already available private val limiter: Option[RateLimiter] = - Option.when(config.enabled)(RateLimiter.create(config.ratePerSecond)) + Option.when(config.enabled)(BurstyRateLimiterFactory.create(config.ratePerSecond)) // enforces the sustained limit over the sustained window, while still allowing bursts within its budget. private val sustainedLimiter: Option[RateLimiter] = Option @@ -169,7 +164,7 @@ class SpliceRateLimiter( } def markRun(): Boolean = { - if (config.enabled && Instant.now().isAfter(enforceAfter)) { + if (config.enabled) { val canRun = rateLimiter.forall(_.tryAcquire()) && sustainedLimiter.forall(_.tryAcquire()) if (canRun) { metrics.meter.mark()( @@ -204,7 +199,6 @@ class PerAttributeRateLimiter( config: SpliceRateLimitConfig, attributeConfig: PerAttributeRateLimitConfig, metrics: SpliceRateLimitMetrics, - enforceAfter: Instant = Instant.now(), logger: TracedLogger, ) { @@ -258,7 +252,6 @@ class PerAttributeRateLimiter( name, perAttributeConfig, metrics, - enforceAfter, limiterType = SpliceRateLimiter.UnknownAttributeLimiterType, extraLabels = attributeLabel, ) @@ -286,7 +279,6 @@ class PerAttributeRateLimiter( name, perAttributeConfig, metrics, - enforceAfter, limiterType = SpliceRateLimiter.PerAttributeLimiterType, extraLabels = attributeLabel, reportMaxLimit = false, diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala index 410bf7e4d1..b78f57d88f 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala @@ -5,7 +5,6 @@ package org.lfdecentralizedtrust.splice.http import com.daml.metrics.api.testing.InMemoryMetricsFactory import com.digitalasset.canton.BaseTest -import com.digitalasset.canton.concurrent.Threading import org.apache.pekko.http.scaladsl.model.headers.{RawHeader, `X-Forwarded-For`, `X-Real-Ip`} import org.apache.pekko.http.scaladsl.model.{ AttributeKeys, @@ -198,9 +197,10 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT )("testOperation") { routes => val route = routes("testOperation") val results = (1 to 20).map(_ => call(route, ip = Some("1.1.1.1"))) - // 1 request per second per client IP => the burst gets rejected - results.count(_ == StatusCodes.OK) should be(1) - results.count(_ == StatusCodes.TooManyRequests) should be(19) + // 1 request per second per client IP, with 1 permit available from the creation of the + // limiter plus guava's deferred payment for the next one => the rest of the burst is rejected + results.count(_ == StatusCodes.OK) should be(2) + results.count(_ == StatusCodes.TooManyRequests) should be(18) } } @@ -221,7 +221,10 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT globalPerClientIp = perClientIp(1) )("testOperation") { routes => val route = routes("testOperation") - call(route, ip = Some("2001:db8:0:1:1:2:3:4")) should be(StatusCodes.OK) + // drain the budget of the /64 network + (1 to 20) + .map(_ => call(route, ip = Some("2001:db8:0:1:1:2:3:4"))) + .count(_ == StatusCodes.OK) should be > 0 // a different address of the same /64 shares the limiter, so it is rejected call(route, ip = Some("2001:db8:0:1:ffff:ffff:ffff:ffff")) should be( StatusCodes.TooManyRequests @@ -250,7 +253,9 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT withRoutes( globalPerClientIp = perClientIp(1) )("operationA", "operationB") { routes => - call(routes("operationA"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) + (1 to 20) + .map(_ => call(routes("operationA"), ip = Some("1.1.1.1"))) + .count(_ == StatusCodes.OK) should be > 0 call(routes("operationB"), ip = Some("1.1.1.1")) should be(StatusCodes.TooManyRequests) } } @@ -282,7 +287,7 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT )("limitedOperation", "otherOperation") { routes => val results = (1 to 20).map(_ => call(routes("limitedOperation"), ip = Some("1.1.1.1"))) - results.count(_ == StatusCodes.OK) should be(1) + results.count(_ == StatusCodes.OK) should be(2) // a different operation is not affected by the per operation client IP limiter call(routes("otherOperation"), ip = Some("1.1.1.1")) should be(StatusCodes.OK) } @@ -320,8 +325,6 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT rateLimiter.withRateLimit("serviceV1")("sharedOperation")(complete(StatusCodes.OK)) val routeV2 = rateLimiter.withRateLimit("serviceV2")("sharedOperation")(complete(StatusCodes.OK)) - // the rate limiter only starts enforcing 1 second after it got created - Threading.sleep(1100) (1 to 20) .map(_ => call(routeV1, ip = Some("1.1.1.1"))) .count(_ == StatusCodes.TooManyRequests) should be > 0 @@ -495,8 +498,6 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT complete(StatusCodes.OK) } }.toMap - // the rate limiter only starts enforcing 1 second after it got created - Threading.sleep(1100) f(HttpRateLimiterTest.Fixture(routes, metricsFactory)) } finally { rateLimiter.close() diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala index 47f36979e7..241c6cbd09 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala @@ -15,7 +15,6 @@ import org.lfdecentralizedtrust.splice.admin.api.client.commands.HttpCommandExce import org.lfdecentralizedtrust.splice.util.SpliceRateLimiterTest.runRateLimited import org.scalatest.wordspec.AnyWordSpecLike -import java.time.Instant import scala.concurrent.Future import scala.concurrent.duration.DurationInt @@ -80,6 +79,16 @@ class SpliceRateLimiterTest } + "start with the configured rate per second worth of permits" in { + withRateLimiter(SpliceRateLimitConfig(ratePerSecond = 10)) { case (_, rateLimiter) => + // the limiter must not have to warm up first: it holds its configured rate worth of permits + // (10) right from its creation, plus guava's deferred payment for the next one + val results = Seq.fill(50)(rateLimiter.markRun()) + results.take(11) should contain only true + results.count(!_) should be > 35 + } + } + "not create any limiter if disabled" in { // a disabled limiter must not fail even for a rate that guava would reject withRateLimiter(SpliceRateLimitConfig(enabled = false, ratePerSecond = 0)) { @@ -118,13 +127,13 @@ class SpliceRateLimiterTest SpliceRateLimitConfig(ratePerSecond = 10), PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), ) { case (_, perAttributeRateLimiter) => - // 1 per second per attribute value, so a burst is rejected after the first request val ip1 = Seq.fill(20)(perAttributeRateLimiter.markRun(Some("1.1.1.1"))) - ip1.count(identity) should be(1) - ip1.count(!_) should be(19) + ip1.count(identity) should be(2) + ip1.count(!_) should be(18) // a different attribute value is not affected by the limiter of the first one perAttributeRateLimiter.markRun(Some("2.2.2.2")) should be(true) + perAttributeRateLimiter.markRun(Some("2.2.2.2")) should be(true) perAttributeRateLimiter.markRun(Some("2.2.2.2")) should be(false) } } @@ -135,7 +144,7 @@ class SpliceRateLimiterTest PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), ) { case (metrics, perAttributeRateLimiter) => val results = Seq.fill(20)(perAttributeRateLimiter.markRun(None)) - results.count(identity) should be(1) + results.count(identity) should be(2) metrics.meter.valueFilteredOnLabels( LabelFilter("limiter", "test"), @@ -214,10 +223,6 @@ class SpliceRateLimiterTest withRateLimiter( SpliceRateLimitConfig(ratePerSecond = 1000, sustainedRatePerSecond = Some(10)) ) { case (_, rateLimiter) => - // The per-second limit is high enough to never reject the throttled input, so the sustained - // limiter (10/s) is the binding constraint over the run. The sustained limiter starts with - // an empty burst budget (Guava SmoothBursty semantics), so throughput tracks the sustained - // rate plus a small initial allowance. val results = runRateLimited(40, 120) { rateLimiter.runWithLimit(Future.successful(true)) }.futureValue @@ -271,8 +276,6 @@ class SpliceRateLimiterTest config, attributeConfig, rateLimitMetrics, - // no cold start delay in tests - Instant.now().minusSeconds(1), logger, ) try { From cfb7d11eaf36f3c0243db5a3e95d0480dab814bc Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Wed, 19 Aug 2026 14:27:59 +0200 Subject: [PATCH 266/329] Improve istio rate limits (#6824) * Improve istio rate limits change per ip attribute used increase max dynamic attributes enable rate limiting headers, log them, then drop them add validation to the limits [static] Signed-off-by: Nicu Reut --- cluster/expected/infra/expected.json | 49 ++- cluster/expected/sv-runbook/expected.json | 144 +++++---- cluster/expected/sv/expected.json | 288 +++++++++++------- .../templates/rateLimit.yaml | 12 +- .../src/ratelimit/envoyRateLimiter.test.ts | 99 +++++- .../common/src/ratelimit/envoyRateLimiter.ts | 117 ++++++- cluster/pulumi/common/src/ratelimit/index.ts | 1 + .../pulumi/common/src/ratelimit/rateLimit.ts | 34 +++ .../common/src/ratelimit/rateLimitHeaders.ts | 14 + cluster/pulumi/infra/src/istio.ts | 54 ++++ 10 files changed, 625 insertions(+), 187 deletions(-) create mode 100644 cluster/pulumi/common/src/ratelimit/rateLimitHeaders.ts diff --git a/cluster/expected/infra/expected.json b/cluster/expected/infra/expected.json index 67ee7f62fa..9a5fc2903e 100644 --- a/cluster/expected/infra/expected.json +++ b/cluster/expected/infra/expected.json @@ -1941,7 +1941,7 @@ "meshConfig": { "accessLogEncoding": "JSON", "accessLogFile": "", - "accessLogFormat": "{\"trace_id\":\"%REQ(traceparent)%\",\"authority\":\"%REQ(:AUTHORITY)%\",\"bytes_received\":\"%BYTES_RECEIVED%\",\"bytes_sent\":\"%BYTES_SENT%\",\"downstream_local_address\":\"%DOWNSTREAM_LOCAL_ADDRESS%\",\"downstream_remote_address\":\"%DOWNSTREAM_REMOTE_ADDRESS%\",\"duration\":\"%DURATION%\",\"method\":\"%REQ(:METHOD)%\",\"path\":\"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%\",\"protocol\":\"%PROTOCOL%\",\"request_id\":\"%REQ(X-REQUEST-ID)%\",\"requested_server_name\":\"%REQUESTED_SERVER_NAME%\",\"response_code\":\"%RESPONSE_CODE%\",\"response_code_details\":\"%RESPONSE_CODE_DETAILS%\",\"response_flags\":\"%RESPONSE_FLAGS%\",\"start_time\":\"%START_TIME%\",\"upstream_cluster\":\"%UPSTREAM_CLUSTER%\",\"upstream_host\":\"%UPSTREAM_HOST%\",\"upstream_local_address\":\"%UPSTREAM_LOCAL_ADDRESS%\",\"upstream_service_time\":\"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%\",\"user_agent\":\"%REQ(USER-AGENT)%\",\"x_forwarded_for\":\"%REQ(X-FORWARDED-FOR)%\"}", + "accessLogFormat": "{\"trace_id\":\"%REQ(traceparent)%\",\"authority\":\"%REQ(:AUTHORITY)%\",\"bytes_received\":\"%BYTES_RECEIVED%\",\"bytes_sent\":\"%BYTES_SENT%\",\"downstream_local_address\":\"%DOWNSTREAM_LOCAL_ADDRESS%\",\"downstream_remote_address\":\"%DOWNSTREAM_REMOTE_ADDRESS%\",\"duration\":\"%DURATION%\",\"method\":\"%REQ(:METHOD)%\",\"path\":\"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%\",\"protocol\":\"%PROTOCOL%\",\"request_id\":\"%REQ(X-REQUEST-ID)%\",\"requested_server_name\":\"%REQUESTED_SERVER_NAME%\",\"response_code\":\"%RESPONSE_CODE%\",\"response_code_details\":\"%RESPONSE_CODE_DETAILS%\",\"response_flags\":\"%RESPONSE_FLAGS%\",\"start_time\":\"%START_TIME%\",\"upstream_cluster\":\"%UPSTREAM_CLUSTER%\",\"upstream_host\":\"%UPSTREAM_HOST%\",\"upstream_local_address\":\"%UPSTREAM_LOCAL_ADDRESS%\",\"upstream_service_time\":\"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%\",\"user_agent\":\"%REQ(USER-AGENT)%\",\"x_forwarded_for\":\"%REQ(X-FORWARDED-FOR)%\",\"local_rate_limited\":\"%RESP(x-local-rate-limit)%\",\"rate_limit_limit\":\"%RESP(x-ratelimit-limit)%\",\"rate_limit_remaining\":\"%RESP(x-ratelimit-remaining)%\",\"rate_limit_reset\":\"%RESP(x-ratelimit-reset)%\"}", "defaultConfig": { "gatewayTopology": { "numTrustedProxies": 0 @@ -1956,7 +1956,10 @@ "defaultHttpRetryPolicy": { "attempts": 0 }, - "enablePrometheusMerge": false + "enablePrometheusMerge": false, + "pathNormalization": { + "normalization": "MERGE_SLASHES" + } }, "telemetry": { "enabled": true, @@ -2351,6 +2354,48 @@ "provider": "", "type": "kubernetes:networking.istio.io/v1alpha3:EnvoyFilter" }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "networking.istio.io/v1alpha3", + "kind": "EnvoyFilter", + "metadata": { + "name": "strip-rate-limit-headers", + "namespace": "cluster-ingress" + }, + "spec": { + "configPatches": [ + { + "applyTo": "ROUTE_CONFIGURATION", + "match": { + "context": "GATEWAY" + }, + "patch": { + "operation": "MERGE", + "value": { + "response_headers_to_remove": [ + "x-local-rate-limit", + "x-ratelimit-limit", + "x-ratelimit-remaining", + "x-ratelimit-reset", + "x-envoy-ratelimited" + ] + } + } + } + ], + "workloadSelector": { + "labels": { + "istio": "ingress" + } + } + } + }, + "name": "strip-rate-limit-headers", + "provider": "", + "type": "kubernetes:networking.istio.io/v1alpha3:EnvoyFilter" + }, { "custom": true, "id": "", diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index abb6ca2217..ace2c82d22 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -1612,6 +1612,40 @@ "provider": "", "type": "kubernetes:core/v1:Secret" }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "telemetry.istio.io/v1", + "kind": "Telemetry", + "metadata": { + "name": "scan-app-rate-limit-access-log", + "namespace": "sv" + }, + "spec": { + "accessLogging": [ + { + "filter": { + "expression": "response.code == 429" + }, + "providers": [ + { + "name": "envoy" + } + ] + } + ], + "selector": { + "matchLabels": { + "app": "scan-app" + } + } + } + }, + "name": "sv-scan-app-rate-limit-access-log", + "provider": "", + "type": "kubernetes:telemetry.istio.io/v1:Telemetry" + }, { "custom": true, "id": "", @@ -1705,9 +1739,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -1749,9 +1783,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -1793,9 +1827,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -1837,9 +1871,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -1881,9 +1915,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -1925,9 +1959,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -1969,9 +2003,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -2013,9 +2047,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -2057,9 +2091,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -2101,9 +2135,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -2145,9 +2179,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -2189,9 +2223,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -2233,9 +2267,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -2266,7 +2300,7 @@ "value": "acs" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2295,7 +2329,7 @@ "value": "registry-allocations" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2324,8 +2358,8 @@ "value": "registry-metadata-info" }, { - "key": "client_ip", - "value": "192.68.78.50" + "key": "masked_remote_address", + "value": "192.68.78.50/32" } ], "token_bucket": { @@ -2341,7 +2375,7 @@ "value": "registry-metadata-info" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2370,7 +2404,7 @@ "value": "registry-metadata-instruments" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2399,7 +2433,7 @@ "value": "registry-allocation-factory" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2428,7 +2462,7 @@ "value": "registry-transfer-instruction" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2457,7 +2491,7 @@ "value": "registry-transfer-factory" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2486,7 +2520,7 @@ "value": "registry-settlement-factory-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2515,7 +2549,7 @@ "value": "registry-allocations-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2544,7 +2578,7 @@ "value": "registry-allocation-factory-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2573,7 +2607,7 @@ "value": "registry-allocation-instruction-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2602,7 +2636,7 @@ "value": "registry-transfer-factory-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2631,7 +2665,7 @@ "value": "registry-transfer-instruction-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -2641,6 +2675,7 @@ } } ], + "enable_x_ratelimit_headers": "DRAFT_VERSION_03", "filter_enabled": { "default_value": { "denominator": "HUNDRED", @@ -2655,6 +2690,7 @@ }, "runtime_key": "local_rate_limit_enforced" }, + "max_dynamic_descriptors": 10000, "response_headers_to_add": [ { "append_action": "OVERWRITE_IF_EXISTS_OR_ADD", diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index c048300f28..46d286b230 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -2925,6 +2925,40 @@ "provider": "", "type": "kubernetes:helm.sh/v3:Release" }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "telemetry.istio.io/v1", + "kind": "Telemetry", + "metadata": { + "name": "scan-app-rate-limit-access-log", + "namespace": "sv-1" + }, + "spec": { + "accessLogging": [ + { + "filter": { + "expression": "response.code == 429" + }, + "providers": [ + { + "name": "envoy" + } + ] + } + ], + "selector": { + "matchLabels": { + "app": "scan-app" + } + } + } + }, + "name": "sv-1-scan-app-rate-limit-access-log", + "provider": "", + "type": "kubernetes:telemetry.istio.io/v1:Telemetry" + }, { "custom": true, "id": "", @@ -3018,9 +3052,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3062,9 +3096,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3106,9 +3140,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3150,9 +3184,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3194,9 +3228,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3238,9 +3272,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3282,9 +3316,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3326,9 +3360,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3370,9 +3404,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3414,9 +3448,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3458,9 +3492,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3502,9 +3536,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3546,9 +3580,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -3579,7 +3613,7 @@ "value": "acs" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3608,7 +3642,7 @@ "value": "registry-allocations" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3637,8 +3671,8 @@ "value": "registry-metadata-info" }, { - "key": "client_ip", - "value": "192.68.78.50" + "key": "masked_remote_address", + "value": "192.68.78.50/32" } ], "token_bucket": { @@ -3654,7 +3688,7 @@ "value": "registry-metadata-info" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3683,7 +3717,7 @@ "value": "registry-metadata-instruments" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3712,7 +3746,7 @@ "value": "registry-allocation-factory" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3741,7 +3775,7 @@ "value": "registry-transfer-instruction" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3770,7 +3804,7 @@ "value": "registry-transfer-factory" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3799,7 +3833,7 @@ "value": "registry-settlement-factory-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3828,7 +3862,7 @@ "value": "registry-allocations-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3857,7 +3891,7 @@ "value": "registry-allocation-factory-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3886,7 +3920,7 @@ "value": "registry-allocation-instruction-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3915,7 +3949,7 @@ "value": "registry-transfer-factory-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3944,7 +3978,7 @@ "value": "registry-transfer-instruction-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -3954,6 +3988,7 @@ } } ], + "enable_x_ratelimit_headers": "DRAFT_VERSION_03", "filter_enabled": { "default_value": { "denominator": "HUNDRED", @@ -3968,6 +4003,7 @@ }, "runtime_key": "local_rate_limit_enforced" }, + "max_dynamic_descriptors": 10000, "response_headers_to_add": [ { "append_action": "OVERWRITE_IF_EXISTS_OR_ADD", @@ -5259,6 +5295,40 @@ "provider": "", "type": "kubernetes:helm.sh/v3:Release" }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "telemetry.istio.io/v1", + "kind": "Telemetry", + "metadata": { + "name": "scan-app-rate-limit-access-log", + "namespace": "sv-da-1" + }, + "spec": { + "accessLogging": [ + { + "filter": { + "expression": "response.code == 429" + }, + "providers": [ + { + "name": "envoy" + } + ] + } + ], + "selector": { + "matchLabels": { + "app": "scan-app" + } + } + } + }, + "name": "sv-da-1-scan-app-rate-limit-access-log", + "provider": "", + "type": "kubernetes:telemetry.istio.io/v1:Telemetry" + }, { "custom": true, "id": "", @@ -5352,9 +5422,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5396,9 +5466,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5440,9 +5510,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5484,9 +5554,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5528,9 +5598,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5572,9 +5642,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5616,9 +5686,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5660,9 +5730,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5704,9 +5774,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5748,9 +5818,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5792,9 +5862,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5836,9 +5906,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5880,9 +5950,9 @@ } }, { - "request_headers": { - "descriptor_key": "client_ip", - "header_name": "x-forwarded-for" + "masked_remote_address": { + "v4_prefix_mask_len": 32, + "v6_prefix_mask_len": 128 } } ] @@ -5913,7 +5983,7 @@ "value": "acs" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -5942,7 +6012,7 @@ "value": "registry-allocations" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -5971,8 +6041,8 @@ "value": "registry-metadata-info" }, { - "key": "client_ip", - "value": "192.68.78.50" + "key": "masked_remote_address", + "value": "192.68.78.50/32" } ], "token_bucket": { @@ -5988,7 +6058,7 @@ "value": "registry-metadata-info" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6017,7 +6087,7 @@ "value": "registry-metadata-instruments" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6046,7 +6116,7 @@ "value": "registry-allocation-factory" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6075,7 +6145,7 @@ "value": "registry-transfer-instruction" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6104,7 +6174,7 @@ "value": "registry-transfer-factory" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6133,7 +6203,7 @@ "value": "registry-settlement-factory-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6162,7 +6232,7 @@ "value": "registry-allocations-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6191,7 +6261,7 @@ "value": "registry-allocation-factory-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6220,7 +6290,7 @@ "value": "registry-allocation-instruction-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6249,7 +6319,7 @@ "value": "registry-transfer-factory-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6278,7 +6348,7 @@ "value": "registry-transfer-instruction-v2" }, { - "key": "client_ip" + "key": "masked_remote_address" } ], "token_bucket": { @@ -6288,6 +6358,7 @@ } } ], + "enable_x_ratelimit_headers": "DRAFT_VERSION_03", "filter_enabled": { "default_value": { "denominator": "HUNDRED", @@ -6302,6 +6373,7 @@ }, "runtime_key": "local_rate_limit_enforced" }, + "max_dynamic_descriptors": 10000, "response_headers_to_add": [ { "append_action": "OVERWRITE_IF_EXISTS_OR_ADD", diff --git a/cluster/helm/splice-cluster-ingress-runbook/templates/rateLimit.yaml b/cluster/helm/splice-cluster-ingress-runbook/templates/rateLimit.yaml index e858d50853..9e0a4634de 100644 --- a/cluster/helm/splice-cluster-ingress-runbook/templates/rateLimit.yaml +++ b/cluster/helm/splice-cluster-ingress-runbook/templates/rateLimit.yaml @@ -48,9 +48,12 @@ spec: route: rate_limits: - actions: - - request_headers: - descriptor_key: client_ip - header_name: x-forwarded-for + # Deliberately not keyed on the raw x-forwarded-for header: clients can + # prepend arbitrary entries to it, so per-IP limits keyed on it can be + # evaded. masked_remote_address uses the address envoy trusts instead. + - masked_remote_address: + v4_prefix_mask_len: 32 + v6_prefix_mask_len: 128 - header_value_match: descriptor_value: acs expect_match: true @@ -83,9 +86,10 @@ spec: header: key: x-local-rate-limit value: 'true' + max_dynamic_descriptors: 10000 descriptors: - entries: - - key: "client_ip" + - key: "masked_remote_address" - key: header_match value: acs token_bucket: diff --git a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.test.ts b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.test.ts index 17118ebd7a..c4d69f9f56 100644 --- a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.test.ts +++ b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.test.ts @@ -5,7 +5,9 @@ import { expect, jest, test } from '@jest/globals'; import { buildRateLimitActions, buildRateLimitDescriptors, + parseFillIntervalMs, validateIpLimits, + validateTokenBuckets, } from './envoyRateLimiter'; jest.mock('@canton-network/splice-pulumi-common/src/config/envConfig', () => ({ @@ -49,7 +51,10 @@ test('buildRateLimitDescriptors generates per-endpoint and generic per-IP descri }, }); expect(descriptors[1]).toEqual({ - entries: [{ key: 'header_match', value: 'registry-metadata-info' }, { key: 'client_ip' }], + entries: [ + { key: 'header_match', value: 'registry-metadata-info' }, + { key: 'masked_remote_address' }, + ], token_bucket: { max_tokens: 120, tokens_per_fill: 120, @@ -87,7 +92,7 @@ test('buildRateLimitDescriptors emits named IP overrides before generic per-IP d expect(descriptors[1]).toEqual({ entries: [ { key: 'header_match', value: 'registry-metadata-info' }, - { key: 'client_ip', value: '192.68.78.50' }, + { key: 'masked_remote_address', value: '192.68.78.50/32' }, ], token_bucket: { max_tokens: 220, @@ -97,7 +102,10 @@ test('buildRateLimitDescriptors emits named IP overrides before generic per-IP d }); expect(descriptors[2]).toEqual( expect.objectContaining({ - entries: [{ key: 'header_match', value: 'registry-metadata-info' }, { key: 'client_ip' }], + entries: [ + { key: 'header_match', value: 'registry-metadata-info' }, + { key: 'masked_remote_address' }, + ], }) ); }); @@ -126,7 +134,7 @@ test('buildRateLimitDescriptors emits descriptors for named overrides with multi expect(descriptors[1]).toEqual({ entries: [ { key: 'header_match', value: 'registry-metadata-info' }, - { key: 'client_ip', value: '192.68.78.51' }, + { key: 'masked_remote_address', value: '192.68.78.51/32' }, ], token_bucket: { max_tokens: 250, @@ -137,7 +145,7 @@ test('buildRateLimitDescriptors emits descriptors for named overrides with multi expect(descriptors[2]).toEqual({ entries: [ { key: 'header_match', value: 'registry-metadata-info' }, - { key: 'client_ip', value: '192.68.78.52' }, + { key: 'masked_remote_address', value: '192.68.78.52/32' }, ], token_bucket: { max_tokens: 250, @@ -195,9 +203,10 @@ test('buildRateLimitActions emits per-endpoint and per-IP actions', () => { }, }, { - request_headers: { - descriptor_key: 'client_ip', - header_name: 'x-forwarded-for', + // the raw x-forwarded-for header must not be used, it is attacker controlled + masked_remote_address: { + v4_prefix_mask_len: 32, + v6_prefix_mask_len: 128, }, }, ], @@ -257,3 +266,77 @@ test('validateIpLimits accepts unique IPs across named overrides', () => { }) ).not.toThrow(); }); + +test('parseFillIntervalMs parses protobuf durations and rejects other formats', () => { + expect(parseFillIntervalMs('60s', 'ctx')).toEqual(60000); + expect(parseFillIntervalMs('0.5s', 'ctx')).toEqual(500); + expect(() => parseFillIntervalMs('500ms', 'ctx')).toThrow('invalid fillInterval'); + expect(() => parseFillIntervalMs('1m', 'ctx')).toThrow('invalid fillInterval'); +}); + +test('validateTokenBuckets accepts intervals that are multiples of the global interval', () => { + expect(() => + validateTokenBuckets(baseLimits, { + '/api/scan/v0/acs': { + name: 'acs', + type: 'limited', + maxTokens: 500, + tokensPerFill: 500, + fillInterval: '120s', + perIpLimits, + }, + }) + ).not.toThrow(); +}); + +test('validateTokenBuckets rejects intervals that envoy would NACK', () => { + expect(() => + validateTokenBuckets(baseLimits, { + '/api/scan/v0/acs': { + name: 'acs', + type: 'limited', + maxTokens: 500, + tokensPerFill: 500, + fillInterval: '90s', + }, + }) + ).toThrow('must be a multiple of the globalLimits fillInterval'); + + // below envoy's 50ms minimum + expect(() => + validateTokenBuckets( + { maxTokens: 1, tokensPerFill: 1, fillInterval: '0.01s' }, + { + '/api/scan/v0/acs': { + name: 'acs', + type: 'limited', + maxTokens: 500, + tokensPerFill: 500, + fillInterval: '60s', + }, + } + ) + ).toThrow('below the 50ms minimum'); + + // per-IP overrides are validated as well + expect(() => + validateTokenBuckets(baseLimits, { + '/api/scan/v0/acs': { + name: 'acs', + type: 'limited', + ...baseLimits, + perIpLimits: { + ...perIpLimits, + overrides: { + 'single-validator': { + ips: ['192.68.78.50'], + maxTokens: 220, + tokensPerFill: 220, + fillInterval: '90s', + }, + }, + }, + }, + }) + ).toThrow("perIpLimits override 'single-validator'"); +}); diff --git a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts index e5944338b2..2655d568c1 100644 --- a/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts +++ b/cluster/pulumi/common/src/ratelimit/envoyRateLimiter.ts @@ -4,6 +4,7 @@ import * as k8s from '@pulumi/kubernetes'; import * as pulumi from '@pulumi/pulumi'; import { parseScanYamlEndpoints, parseTokenRegistrySpecEndpoints } from '../config/scanEndpoints'; +import { localRateLimitedHeader } from './rateLimitHeaders'; interface Limits { maxTokens: number; @@ -43,10 +44,23 @@ type LocalLimit = { name: string; } & L; -// This is arbitrary, but must not match any limit `name` used for an EnvoyFilter -// above. All existing manual YAML entries use 'client_ip' so this is the nicest -// migration away from always specifying that. -const clientIpEntryKey = 'client_ip'; +// The descriptor entry key emitted by envoy's `masked_remote_address` rate limit action. +// This is hardcoded in envoy (see MaskedRemoteAddressAction::populateDescriptor) and +// cannot be configured, so the descriptors we generate must use exactly this key. +const clientIpEntryKey = 'masked_remote_address'; +const reservedEntryKeys = [clientIpEntryKey, 'client_ip']; + +// The per-IP descriptors are wildcard descriptors: envoy allocates a token bucket per +// observed client IP and keeps them in an LRU cache whose size defaults to a mere 20 +// entries. +export const maxDynamicDescriptorsPerLimit = 10000; + +// Envoy rejects token buckets refilling faster than this, and requires every descriptor +// fill interval to be a multiple of the global (default) bucket's fill interval. +const minFillIntervalMs = 50; + +// uint32 in envoy's TokenBucket proto +const maxTokenValue = 4294967295; interface RateLimitEnvoyFilterArgs extends PerEndpointLimits { namespace: string; @@ -123,15 +137,76 @@ export function validateIpLimits(pathPrefix: string, rateLimit: LocalLimit !Number.isInteger(value) || value < 0 || value > maxTokenValue) + .forEach(([field, value]) => { + throw new Error( + `${context}: ${field} must be an integer in [0, ${maxTokenValue}], got ${value}` + ); + }); +} + +export function validateTokenBuckets( + globalLimits: Limits, + effectiveRateLimits: LocalLimits +): void { + validateLimits('globalLimits', globalLimits); + const globalFillIntervalMs = parseFillIntervalMs(globalLimits.fillInterval, 'globalLimits'); + Object.entries(effectiveRateLimits).forEach(([pathPrefix, rateLimit]) => { + validateLimits(pathPrefix, rateLimit, globalFillIntervalMs); + if (rateLimit.perIpLimits) { + validateLimits(`${pathPrefix} perIpLimits`, rateLimit.perIpLimits, globalFillIntervalMs); + Object.entries(rateLimit.perIpLimits.overrides || {}).forEach(([name, override]) => + validateLimits( + `${pathPrefix} perIpLimits override '${name}'`, + override, + globalFillIntervalMs + ) + ); + } + }); +} + function validateEffectiveRateLimits( args: RateLimitEnvoyFilterArgs ): LocalLimits | undefined { const collidingPathNames = Object.entries(args.rateLimits || {}) - .filter(([, rl]) => rl.name === clientIpEntryKey) + .filter(([, rl]) => reservedEntryKeys.includes(rl.name)) .map(([path]) => path); if (collidingPathNames.length > 0) { throw new Error( - `${collidingPathNames.join(', ')} use reserved name ${clientIpEntryKey}; choose a different name` + `${collidingPathNames.join(', ')} use reserved name ${reservedEntryKeys.join('/')}; choose a different name` ); } @@ -188,9 +263,15 @@ function validateEffectiveRateLimits( validateIpLimits(pathPrefix, rateLimit); }); + validateTokenBuckets(args.globalLimits, effectiveRateLimits); + return effectiveRateLimits; } +export function clientIpDescriptorValue(ip: string): string { + return `${ip}/32`; +} + export function buildRateLimitActions(effectiveRateLimits: LocalLimits): unknown[] { return Object.entries(effectiveRateLimits).flatMap(([pathPrefix, rateLimit]) => { const actions = []; @@ -220,9 +301,18 @@ export function buildRateLimitActions(effectiveRateLimits: LocalLimits, @@ -977,6 +1029,7 @@ export function configureIstio( ingressNs.ns ); const sequencerFlowControl = configureSequencerFlowControl(ingressNs.ns); + const rateLimitHeaderStripping = stripRateLimitHeaders(ingressNs.ns, gwSvc); return { allResources: [ ...gateways, @@ -985,6 +1038,7 @@ export function configureIstio( ...publicTokenRegistry, ...sequencerHighPerformanceGrpcRules, ...[sequencerFlowControl], + ...[rateLimitHeaderStripping], ], httpServiceName: 'istio-ingress', istioResource: gwSvc, From 306014bc81fa2045c60c7fee50b599f8218bf0f4 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Wed, 19 Aug 2026 15:20:15 +0200 Subject: [PATCH 267/329] Improve client ip rate limits (#6853) Add ability to disable fallback for headers Remove default rate limit when no ip is known and instead just count it through a metric (we count on the global rate limiter to avoid SVs that don't extract the ip correctly to just rate limit everyone more aggressivly) [ci] Signed-off-by: Nicu Reut --- .../splice/config/RateLimitersConfig.scala | 7 +++ .../splice/http/ClientIpDirectives.scala | 24 ++++++--- .../splice/http/HttpRateLimiter.scala | 51 ++++++++++--------- .../splice/util/SpliceRateLimiter.scala | 34 +++++++++---- .../splice/http/HttpRateLimiterTest.scala | 42 +++++++++++---- .../splice/util/SpliceRateLimiterTest.scala | 21 +++++--- docs/src/release_notes_upcoming.rst | 4 ++ 7 files changed, 123 insertions(+), 60 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala index da58ba409a..e655c163d4 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala @@ -22,6 +22,13 @@ case class RateLimitersConfig( * trusting a proxy header and only rely on `X-Forwarded-For`/`X-Real-Ip`/the remote address. */ trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, + /** Whether to fall back to the client-controlled `X-Forwarded-For`/`X-Real-Ip` headers when the + * trusted proxy header (see `trustedClientIpHeader`) does not yield a client IP. Enabled by + * default. Set to `false` in deployments where a trusted proxy always sets + * `trustedClientIpHeader`, so that clients cannot influence the extracted IP - and thereby the + * per-client-IP rate limiting - by forging these spoofable headers. + */ + enableClientProvidedIpHeaders: Boolean = true, ) { def forRateLimiter(name: String): SpliceRateLimitConfig.WithPerClientIp = rateLimiters.getOrElse(name, default) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala index c4f620f484..0d9ef4235c 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala @@ -15,19 +15,27 @@ object ClientIpDirectives { * The address is taken from the first of the following sources that yields an address: * 1. the `trustedClientIpHeader` (if configured and parseable as an IP literal), which is set * by a trusted reverse proxy and hence cannot be spoofed by the client, - * 1. the client-controlled `X-Forwarded-For` header, - * 1. the client-controlled `X-Real-Ip` header, + * 1. the client-controlled `X-Forwarded-For` header (unless disabled), + * 1. the client-controlled `X-Real-Ip` header (unless disabled), * * @param trustedClientIpHeader * name of the header set by a trusted reverse proxy, matched case-insensitively. An empty name * disables trusting a proxy header. + * @param enableClientProvidedIpHeaders + * whether to fall back to the client-controlled `X-Forwarded-For`/`X-Real-Ip` headers when the + * trusted proxy header does not yield an address. Set to `false` to only rely on the trusted + * proxy header, so that clients cannot influence the extracted address by forging these headers. */ - def extractClientIp(trustedClientIpHeader: String): Directive1[Option[RemoteAddress]] = - firstDefined( - trustedClientIp(trustedClientIpHeader), - forwardedForClientIp, - realIpClientIp, - ) + def extractClientIp( + trustedClientIpHeader: String, + enableClientProvidedIpHeaders: Boolean, + ): Directive1[Option[RemoteAddress]] = { + val clientProvidedSources: Seq[Directive1[Option[RemoteAddress]]] = + if (enableClientProvidedIpHeaders) Seq(forwardedForClientIp, realIpClientIp) + else Seq.empty + val sources = trustedClientIp(trustedClientIpHeader) +: clientProvidedSources + firstDefined(sources*) + } private def trustedClientIp(headerName: String): Directive1[Option[RemoteAddress]] = { val trimmedHeaderName = headerName.trim diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala index 1e2afe378e..893364a61d 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala @@ -96,29 +96,31 @@ class HttpRateLimiter( import org.apache.pekko.http.scaladsl.server.Directives.* - HttpRateLimiter.extractClientIpKey(trustedClientIpHeader).flatMap { clientIp => - // The per client IP limiters are checked first (and `&&` short-circuits) so that a request - // rejected because of its own client IP does not consume budget from the shared overall - // limiters. Otherwise a single abusive client could exhaust the overall limits and thereby - // deny service to all other clients. - // Within each of those two groups the narrower per operation limiter is checked before the - // global one, so that a request rejected for its operation does not consume global budget. - val allowed = - operationClientIpLimiter.markRun(clientIp) && - globalClientIpLimiter.markRun(clientIp) && - operationLimiter.markRun() && - globalLimiter.markRun() - if (allowed) { - pass - } else { - complete( - StatusCodes.TooManyRequests, - HttpEntity( - "Too Many Requests: Server is busy, please try again later." - ), - ) + HttpRateLimiter + .extractClientIpKey(trustedClientIpHeader, config.enableClientProvidedIpHeaders) + .flatMap { clientIp => + // The per client IP limiters are checked first (and `&&` short-circuits) so that a request + // rejected because of its own client IP does not consume budget from the shared overall + // limiters. Otherwise a single abusive client could exhaust the overall limits and thereby + // deny service to all other clients. + // Within each of those two groups the narrower per operation limiter is checked before the + // global one, so that a request rejected for its operation does not consume global budget. + val allowed = + operationClientIpLimiter.markRun(clientIp) && + globalClientIpLimiter.markRun(clientIp) && + operationLimiter.markRun() && + globalLimiter.markRun() + if (allowed) { + pass + } else { + complete( + StatusCodes.TooManyRequests, + HttpEntity( + "Too Many Requests: Server is busy, please try again later." + ), + ) + } } - } } def close(): Unit = metrics.view.values.foreach(_.close()) @@ -132,10 +134,11 @@ object HttpRateLimiter { private[splice] val GlobalService = "global" private[splice] def extractClientIpKey( - trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader + trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, + enableClientProvidedIpHeaders: Boolean, ): Directive1[Option[String]] = ClientIpDirectives - .extractClientIp(trustedClientIpHeader) + .extractClientIp(trustedClientIpHeader, enableClientProvidedIpHeaders) .map(_.collect { case RemoteAddress.IP(ip, _) => rateLimitKey(ip) }) /** Single clients are typically assigned a whole IPv6 /64 (or larger) network, so limiting per diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala index 9fe0f73eb2..c1885094dd 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiter.scala @@ -41,6 +41,17 @@ case class SpliceRateLimitMetrics( ) ) + val unknownAttributeNotLimited: MetricHandle.Meter = otelFactory.meter( + MetricInfo( + SpliceMetrics.MetricsPrefix :+ "rate_limiting_unknown_attribute_not_limited", + "Number of requests not rate limited by a per-attribute limiter because the attribute value is unknown", + Saturation, + ) + ) + + def recordUnknownAttributeNotLimited()(implicit extraMc: MetricsContext): Unit = + unknownAttributeNotLimited.mark()(mc.merge(extraMc)) + /*we need to pass the full context when we create it to avoid duplicate values warnings*/ def recordMaxLimit(limit: Double)(implicit extraMc: MetricsContext): Unit = { val createdGauge = otelFactory.gauge[Double]( @@ -118,7 +129,6 @@ object SpliceRateLimiter { val GlobalLimiterType = "global" val PerAttributeLimiterType = "per-attribute" - val UnknownAttributeLimiterType = "unknown-attribute" val DefaultSustainedWindowSeconds: Long = 60 @@ -248,14 +258,6 @@ class PerAttributeRateLimiter( Some(new CacheMetrics(s"$name-$attribute-rate-limiter", metrics.otelFactory)), ) - private lazy val defaultRateLimiter = new SpliceRateLimiter( - name, - perAttributeConfig, - metrics, - limiterType = SpliceRateLimiter.UnknownAttributeLimiterType, - extraLabels = attributeLabel, - ) - private lazy val reportedMaxLimit: Unit = metrics.recordMaxLimit(perAttributeConfig.ratePerSecond)( MetricsContext( @@ -267,7 +269,19 @@ class PerAttributeRateLimiter( ) def markRun(attributeValue: Option[String]): Boolean = - if (isEnabled) attributeValue.fold(defaultRateLimiter)(limiterFor).markRun() + if (isEnabled) attributeValue match { + case Some(value) => limiterFor(value).markRun() + case None => + metrics.recordUnknownAttributeNotLimited()( + MetricsContext( + attributeLabel ++ Map( + "limiter" -> name, + "limiter_type" -> SpliceRateLimiter.PerAttributeLimiterType, + ) + ) + ) + true + } else true private def limiterFor(attributeValue: String): SpliceRateLimiter = { diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala index b78f57d88f..37c30d8bd0 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala @@ -84,6 +84,27 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT ) should be(Some("1.1.1.1")) } + "not fall back to client-provided headers when they are disabled" in { + clientIp( + HttpRequest().withHeaders( + RawHeader("X-Envoy-External-Address", "4.4.4.4"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), + ), + enableClientProvidedIpHeaders = false, + ) should be(Some("4.4.4.4")) + } + + "return None when client-provided headers are disabled and no trusted header is present" in { + clientIp( + HttpRequest().withHeaders( + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), + ), + enableClientProvidedIpHeaders = false, + ) should be(None) + } + "prefer X-Forwarded-For" in { clientIp( HttpRequest() @@ -234,17 +255,15 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT } } - "fall back to the default limiter if no client IP is known" in { + "not apply the per client IP limiter if no client IP is known" in { withRoutes( globalPerClientIp = perClientIp(1) )("testOperation") { routes => val route = routes("testOperation") - call(route, ip = None) should be(StatusCodes.OK) - (1 to 20) - .map(_ => call(route, ip = None)) - .count(_ == StatusCodes.TooManyRequests) should be > 0 - // a request with a client IP uses a different limiter - call(route, ip = Some("1.1.1.1")) should be(StatusCodes.OK) + (1 to 20).map(_ => call(route, ip = None)) should contain only StatusCodes.OK + val results = (1 to 20).map(_ => call(route, ip = Some("1.1.1.1"))) + results.count(_ == StatusCodes.OK) should be(2) + results.count(_ == StatusCodes.TooManyRequests) should be(18) } } @@ -438,10 +457,13 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT private def clientIp( request: HttpRequest, trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, + enableClientProvidedIpHeaders: Boolean = true, ): Option[String] = { - val route = HttpRateLimiter.extractClientIpKey(trustedClientIpHeader) { extracted => - complete(extracted.getOrElse[String](HttpRateLimiterTest.NoClientIp)) - } + val route = + HttpRateLimiter.extractClientIpKey(trustedClientIpHeader, enableClientProvidedIpHeaders) { + extracted => + complete(extracted.getOrElse[String](HttpRateLimiterTest.NoClientIp)) + } request ~> route ~> check { status should be(StatusCodes.OK) Some(responseAs[String]).filterNot(_ == HttpRateLimiterTest.NoClientIp) diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala index 241c6cbd09..d673e1ff0e 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/util/SpliceRateLimiterTest.scala @@ -138,22 +138,27 @@ class SpliceRateLimiterTest } } - "use a single default limiter if the attribute value is unknown" in { + "not limit requests with an unknown attribute value" in { withPerAttributeRateLimiter( SpliceRateLimitConfig(ratePerSecond = 10), PerAttributeRateLimitConfig(limit = SpliceRateLimitConfig(ratePerSecond = 1)), ) { case (metrics, perAttributeRateLimiter) => + // requests without an attribute value are not rate limited here; the overall/global + // rate limiter is relied upon to bound them instead val results = Seq.fill(20)(perAttributeRateLimiter.markRun(None)) - results.count(identity) should be(2) + results.count(identity) should be(20) - metrics.meter.valueFilteredOnLabels( + metrics.unknownAttributeNotLimited.valueFilteredOnLabels( LabelFilter("limiter", "test"), LabelFilter("limiter_attribute", "test_attribute"), - LabelFilter("limiter_type", SpliceRateLimiter.UnknownAttributeLimiterType), - LabelFilter("result", "rejected"), - ) should be(results.count(!_)) + LabelFilter("limiter_type", SpliceRateLimiter.PerAttributeLimiterType), + ) should be(20) + + metrics.meter.valuesWithContext.keys + .flatMap(_.labels.get("limiter_attribute")) + .toSeq should be(empty) - // requests with a known attribute value are not affected by the default limiter + // requests with a known attribute value are still limited perAttributeRateLimiter.markRun(Some("1.1.1.1")) should be(true) } } @@ -177,7 +182,7 @@ class SpliceRateLimiterTest LabelFilter("limiter_type", SpliceRateLimiter.PerAttributeLimiterType), LabelFilter("result", "rejected"), ) should be(results.count(!_)) - // no metrics are reported for the default limiter of unknown attribute values + // only per attribute limiters report metrics metrics.meter.valuesWithContext.keys .flatMap(_.labels.get("limiter_type")) .toSet should be(Set(SpliceRateLimiter.PerAttributeLimiterType)) diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index 73ea5b0058..ad63b6151d 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -42,6 +42,10 @@ release-notes:: Upcoming and cannot be spoofed by clients. Otherwise, clients may bypass per-client-IP limits or cause other clients to be throttled by forging these headers. + The fallback to the client-controlled ``X-Forwarded-For``/ ``X-Real-Ip`` headers can be + disabled by setting ``rate-limiting.enable-client-provided-ip-headers`` to ``false``. + If no IP can be extracted no per IP rate limit is enforced. + - Default rate limits have been adjusted: - Scan app: the per-operation burst limit has been lowered from 200 to 100 requests per From 3f7c217c3ce575b162427eb42347cc255c73efac Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Wed, 19 Aug 2026 15:43:30 +0200 Subject: [PATCH 268/329] Add the ability to restrict whitelisting only to the required hosts (#6621) * Add the ability to restrict whitelisting only to the required hosts Instead of allowing access through the general whitelist we add specific scan/sv app/sequencer/cantonbft allow lists that target only the ip that are required and keep the general whitelisting only for internal ips. cantonbft is exposed only to the svs [static] Signed-off-by: Nicu Reut --- cluster/deployment/mock/config.yaml | 1 + cluster/expected/infra/expected.json | 250 +++++++++++++++++- cluster/pulumi/infra/src/config.ts | 52 +--- cluster/pulumi/infra/src/istio.ts | 176 +----------- .../pulumi/infra/src/whitelisting/gateway.ts | 37 +++ .../pulumi/infra/src/whitelisting/index.ts | 26 ++ .../pulumi/infra/src/whitelisting/ipRanges.ts | 56 ++++ .../pulumi/infra/src/whitelisting/policies.ts | 83 ++++++ .../infra/src/whitelisting/publicInfo.ts | 54 ++++ .../src/whitelisting/publicTokenRegistry.ts | 39 +++ .../infra/src/whitelisting/scanAndSvApp.ts | 25 ++ .../infra/src/whitelisting/sequencer.ts | 60 +++++ 12 files changed, 637 insertions(+), 222 deletions(-) create mode 100644 cluster/pulumi/infra/src/whitelisting/gateway.ts create mode 100644 cluster/pulumi/infra/src/whitelisting/index.ts create mode 100644 cluster/pulumi/infra/src/whitelisting/ipRanges.ts create mode 100644 cluster/pulumi/infra/src/whitelisting/policies.ts create mode 100644 cluster/pulumi/infra/src/whitelisting/publicInfo.ts create mode 100644 cluster/pulumi/infra/src/whitelisting/publicTokenRegistry.ts create mode 100644 cluster/pulumi/infra/src/whitelisting/scanAndSvApp.ts create mode 100644 cluster/pulumi/infra/src/whitelisting/sequencer.ts diff --git a/cluster/deployment/mock/config.yaml b/cluster/deployment/mock/config.yaml index 7b9e4a07d9..71b301eb91 100644 --- a/cluster/deployment/mock/config.yaml +++ b/cluster/deployment/mock/config.yaml @@ -310,6 +310,7 @@ infra: key: value anotherKey: anotherValue istio: + enableGeneralIpWhitelist: false istiodValues: # example istiod overrides global: proxy: diff --git a/cluster/expected/infra/expected.json b/cluster/expected/infra/expected.json index 9a5fc2903e..960d72d94a 100644 --- a/cluster/expected/infra/expected.json +++ b/cluster/expected/infra/expected.json @@ -1288,16 +1288,6 @@ "source": { "remoteIpBlocks": [ "", - "1.2.3.4/32", - "5.6.7.8/32", - "11.12.13.14/32", - "4.3.2.1/32", - "8.7.6.5/32", - "8.7.6.4/32", - "9.8.7.6/32", - "11.22.33.45/32", - "12.34.56.78/32", - "2.3.4.5/32", "10.160.0.0/16" ] } @@ -2248,6 +2238,72 @@ "provider": "", "type": "gcp:compute/router:Router" }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "security.istio.io/v1beta1", + "kind": "AuthorizationPolicy", + "metadata": { + "name": "scan-sv-app-ip-whitelist-0", + "namespace": "cluster-ingress" + }, + "spec": { + "action": "ALLOW", + "rules": [ + { + "from": [ + { + "source": { + "remoteIpBlocks": [ + "", + "1.2.3.4/32", + "5.6.7.8/32", + "11.12.13.14/32", + "4.3.2.1/32", + "8.7.6.5/32", + "8.7.6.4/32", + "9.8.7.6/32", + "11.22.33.45/32", + "12.34.56.78/32", + "2.3.4.5/32" + ] + } + } + ], + "to": [ + { + "operation": { + "hosts": [ + "scan.sv-2.mock.network.canton.global", + "sv.sv-2.mock.network.canton.global", + "scan.sv-2.mock.global.canton.network.digitalasset.com", + "sv.sv-2.mock.global.canton.network.digitalasset.com", + "scan.sv-1.mock.network.canton.global", + "sv.sv-1.mock.network.canton.global", + "scan.sv-1.mock.global.canton.network.digitalasset.com", + "sv.sv-1.mock.global.canton.network.digitalasset.com", + "scan.sv.mock.network.canton.global", + "sv.sv.mock.network.canton.global", + "scan.sv.mock.global.canton.network.digitalasset.com", + "sv.sv.mock.global.canton.network.digitalasset.com" + ] + } + } + ] + } + ], + "selector": { + "matchLabels": { + "app": "istio-ingress" + } + } + } + }, + "name": "scan-sv-app-ip-whitelist-0", + "provider": "", + "type": "kubernetes:security.istio.io/v1beta1:AuthorizationPolicy" + }, { "custom": true, "id": "", @@ -2354,6 +2410,180 @@ "provider": "", "type": "kubernetes:networking.istio.io/v1alpha3:EnvoyFilter" }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "security.istio.io/v1beta1", + "kind": "AuthorizationPolicy", + "metadata": { + "name": "sequencer-p2p-ip-whitelist-0", + "namespace": "cluster-ingress" + }, + "spec": { + "action": "ALLOW", + "rules": [ + { + "from": [ + { + "source": { + "remoteIpBlocks": [ + "", + "1.2.3.4/32", + "5.6.7.8/32", + "11.12.13.14/32", + "2.3.4.5/32" + ] + } + } + ], + "to": [ + { + "operation": { + "hosts": [ + "sequencer-p2p-9.sv-2.mock.network.canton.global", + "sequencer-p2p-9.sv-2.mock.network.canton.global:*", + "sequencer-p2p-9.sv-2.mock.global.canton.network.digitalasset.com", + "sequencer-p2p-9.sv-2.mock.global.canton.network.digitalasset.com:*", + "sequencer-p2p-10.sv-2.mock.network.canton.global", + "sequencer-p2p-10.sv-2.mock.network.canton.global:*", + "sequencer-p2p-10.sv-2.mock.global.canton.network.digitalasset.com", + "sequencer-p2p-10.sv-2.mock.global.canton.network.digitalasset.com:*", + "sequencer-p2p-9.sv-1.mock.network.canton.global", + "sequencer-p2p-9.sv-1.mock.network.canton.global:*", + "sequencer-p2p-9.sv-1.mock.global.canton.network.digitalasset.com", + "sequencer-p2p-9.sv-1.mock.global.canton.network.digitalasset.com:*", + "sequencer-p2p-10.sv-1.mock.network.canton.global", + "sequencer-p2p-10.sv-1.mock.network.canton.global:*", + "sequencer-p2p-10.sv-1.mock.global.canton.network.digitalasset.com", + "sequencer-p2p-10.sv-1.mock.global.canton.network.digitalasset.com:*", + "sequencer-p2p-9.sv.mock.network.canton.global", + "sequencer-p2p-9.sv.mock.network.canton.global:*", + "sequencer-p2p-9.sv.mock.global.canton.network.digitalasset.com", + "sequencer-p2p-9.sv.mock.global.canton.network.digitalasset.com:*", + "sequencer-p2p-10.sv.mock.network.canton.global", + "sequencer-p2p-10.sv.mock.network.canton.global:*", + "sequencer-p2p-10.sv.mock.global.canton.network.digitalasset.com", + "sequencer-p2p-10.sv.mock.global.canton.network.digitalasset.com:*" + ] + } + } + ] + } + ], + "selector": { + "matchLabels": { + "app": "istio-ingress" + } + } + } + }, + "name": "sequencer-p2p-ip-whitelist-0", + "provider": "", + "type": "kubernetes:security.istio.io/v1beta1:AuthorizationPolicy" + }, + { + "custom": true, + "id": "", + "inputs": { + "apiVersion": "security.istio.io/v1beta1", + "kind": "AuthorizationPolicy", + "metadata": { + "name": "sequencer-pub-ip-whitelist-0", + "namespace": "cluster-ingress" + }, + "spec": { + "action": "ALLOW", + "rules": [ + { + "from": [ + { + "source": { + "remoteIpBlocks": [ + "", + "1.2.3.4/32", + "5.6.7.8/32", + "11.12.13.14/32", + "4.3.2.1/32", + "8.7.6.5/32", + "8.7.6.4/32", + "9.8.7.6/32", + "11.22.33.45/32", + "12.34.56.78/32", + "2.3.4.5/32" + ] + } + } + ], + "to": [ + { + "operation": { + "hosts": [ + "sequencer-9.sv-2.mock.network.canton.global", + "sequencer-9.sv-2.mock.network.canton.global:*", + "sequencer-9.sv-2.mock.global.canton.network.digitalasset.com", + "sequencer-9.sv-2.mock.global.canton.network.digitalasset.com:*", + "sequencer-8.sv-2.mock.network.canton.global", + "sequencer-8.sv-2.mock.network.canton.global:*", + "sequencer-8.sv-2.mock.global.canton.network.digitalasset.com", + "sequencer-8.sv-2.mock.global.canton.network.digitalasset.com:*", + "sequencer-7.sv-2.mock.network.canton.global", + "sequencer-7.sv-2.mock.network.canton.global:*", + "sequencer-7.sv-2.mock.global.canton.network.digitalasset.com", + "sequencer-7.sv-2.mock.global.canton.network.digitalasset.com:*", + "sequencer-10.sv-2.mock.network.canton.global", + "sequencer-10.sv-2.mock.network.canton.global:*", + "sequencer-10.sv-2.mock.global.canton.network.digitalasset.com", + "sequencer-10.sv-2.mock.global.canton.network.digitalasset.com:*", + "sequencer-9.sv-1.mock.network.canton.global", + "sequencer-9.sv-1.mock.network.canton.global:*", + "sequencer-9.sv-1.mock.global.canton.network.digitalasset.com", + "sequencer-9.sv-1.mock.global.canton.network.digitalasset.com:*", + "sequencer-8.sv-1.mock.network.canton.global", + "sequencer-8.sv-1.mock.network.canton.global:*", + "sequencer-8.sv-1.mock.global.canton.network.digitalasset.com", + "sequencer-8.sv-1.mock.global.canton.network.digitalasset.com:*", + "sequencer-7.sv-1.mock.network.canton.global", + "sequencer-7.sv-1.mock.network.canton.global:*", + "sequencer-7.sv-1.mock.global.canton.network.digitalasset.com", + "sequencer-7.sv-1.mock.global.canton.network.digitalasset.com:*", + "sequencer-10.sv-1.mock.network.canton.global", + "sequencer-10.sv-1.mock.network.canton.global:*", + "sequencer-10.sv-1.mock.global.canton.network.digitalasset.com", + "sequencer-10.sv-1.mock.global.canton.network.digitalasset.com:*", + "sequencer-9.sv.mock.network.canton.global", + "sequencer-9.sv.mock.network.canton.global:*", + "sequencer-9.sv.mock.global.canton.network.digitalasset.com", + "sequencer-9.sv.mock.global.canton.network.digitalasset.com:*", + "sequencer-8.sv.mock.network.canton.global", + "sequencer-8.sv.mock.network.canton.global:*", + "sequencer-8.sv.mock.global.canton.network.digitalasset.com", + "sequencer-8.sv.mock.global.canton.network.digitalasset.com:*", + "sequencer-7.sv.mock.network.canton.global", + "sequencer-7.sv.mock.network.canton.global:*", + "sequencer-7.sv.mock.global.canton.network.digitalasset.com", + "sequencer-7.sv.mock.global.canton.network.digitalasset.com:*", + "sequencer-10.sv.mock.network.canton.global", + "sequencer-10.sv.mock.network.canton.global:*", + "sequencer-10.sv.mock.global.canton.network.digitalasset.com", + "sequencer-10.sv.mock.global.canton.network.digitalasset.com:*" + ] + } + } + ] + } + ], + "selector": { + "matchLabels": { + "app": "istio-ingress" + } + } + } + }, + "name": "sequencer-pub-ip-whitelist-0", + "provider": "", + "type": "kubernetes:security.istio.io/v1beta1:AuthorizationPolicy" + }, { "custom": true, "id": "", diff --git a/cluster/pulumi/infra/src/config.ts b/cluster/pulumi/infra/src/config.ts index c677c47c5d..636937c790 100644 --- a/cluster/pulumi/infra/src/config.ts +++ b/cluster/pulumi/infra/src/config.ts @@ -1,13 +1,8 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import * as pulumi from '@pulumi/pulumi'; -import { - config, - loadJsonFromFile, - externalIpRangesFile, -} from '@canton-network/splice-pulumi-common'; +import { config } from '@canton-network/splice-pulumi-common'; import { clusterYamlConfig } from '@canton-network/splice-pulumi-common/src/config/config'; -import { getSecretVersionOutput } from '@pulumi/gcp/secretmanager'; import util from 'node:util'; import { z } from 'zod'; @@ -58,6 +53,7 @@ export const InfraConfigSchema = z.object({ enableIngressAccessLogging: z.boolean(), enableClusterAccessLogging: z.boolean().default(false), enablePublicTokenRegistry: z.boolean().default(false), + enableGeneralIpWhitelist: z.boolean().default(true), istiodValues: z.object({}).catchall(z.any()).default({}), sequencerFlowControl: z.object({ initialStreamWindowSize: z.int(), @@ -86,47 +82,3 @@ console.error( export const infraConfig = fullConfig.infra; export const cloudArmorConfig: CloudArmorConfig = fullConfig.cloudArmor; - -type IpRangesDict = { [key: string]: IpRangesDict } | string[]; - -function extractIpRanges(x: IpRangesDict, svsOnly: boolean = false): string[] { - if (svsOnly) { - if (Array.isArray(x)) { - throw new Error('Cannot distinguish SV IP ranges from non-SV IP ranges in an array'); - } - return extractIpRanges(x['svs'], false); - } else { - return Array.isArray(x) - ? x - : Object.keys(x).reduce((acc: string[], k: string) => acc.concat(extractIpRanges(x[k])), []); - } -} - -export function loadIPRanges(svsOnly: boolean = false): pulumi.Output { - const file = externalIpRangesFile(); - const externalIpRanges = file ? extractIpRanges(loadJsonFromFile(file), svsOnly) : []; - - const internalWhitelistedIps = getSecretVersionOutput({ - secret: 'pulumi-internal-whitelists', - }).apply(whitelists => { - const secretData = whitelists.secretData; - const json = JSON.parse(secretData); - const ret: string[] = []; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - json.forEach((ip: any) => { - ret.push(ip); - }); - return ret; - }); - - const configWhitelistedIps = infraConfig.ipWhitelisting?.extraWhitelistedIngress || []; - const excludedIps = infraConfig.ipWhitelisting?.excludedIps || []; - - return internalWhitelistedIps.apply(whitelists => { - const ips = whitelists - .concat(externalIpRanges) - .concat(configWhitelistedIps) - .filter(ip => excludedIps.indexOf(ip) < 0); - return [...new Set(ips)]; - }); -} diff --git a/cluster/pulumi/infra/src/istio.ts b/cluster/pulumi/infra/src/istio.ts index fee6575c2c..2d27567689 100644 --- a/cluster/pulumi/infra/src/istio.ts +++ b/cluster/pulumi/infra/src/istio.ts @@ -3,13 +3,11 @@ import * as gcp from '@pulumi/gcp'; import * as k8s from '@pulumi/kubernetes'; import * as pulumi from '@pulumi/pulumi'; -import * as assert from 'assert/strict'; import { allSvsToDeployBasic, coreSvsToDeployBasic, } from '@canton-network/splice-pulumi-common-sv/src/svConfigsBasic'; import { cometBFTExternalPort } from '@canton-network/splice-pulumi-common-sv/src/synchronizer/cometbftConfig'; -import { spliceConfig } from '@canton-network/splice-pulumi-common/src/config/config'; import { rateLimitResponseHeaders } from '@canton-network/splice-pulumi-common/src/ratelimit/rateLimitHeaders'; import { mergeWith } from 'lodash'; @@ -26,7 +24,11 @@ import { isDevNet, isMainNet, } from '../../common'; -import { clusterBasename, infraConfig, loadIPRanges } from './config'; +import { clusterBasename, infraConfig } from './config'; +import { configureIstioGatewayPolicies, installAppWhitelisting } from './whitelisting'; +import { loadInternalWhitelistedIps, loadIPRanges } from './whitelisting/ipRanges'; +import { configurePublicInfo } from './whitelisting/publicInfo'; +import { configurePublicTokenRegistry } from './whitelisting/publicTokenRegistry'; interface ConfiguredIstio { allResources: pulumi.Resource[]; @@ -247,13 +249,17 @@ function configureInternalGatewayService( // The loopback traffic would be prevented by our policy. To still allow it, we // add the node pool ip ranges to the list. // eslint-disable-next-line promise/prefer-await-to-then - const internalIPRanges = cluster.then(c => + const gcpInternalIPRanges = cluster.then(c => c.nodePools.map(p => p.networkConfigs.map(c => c.podIpv4CidrBlock)).flat() ); - const externalIPRanges = loadIPRanges(); + const gatewayIPRanges = infraConfig.istio.enableGeneralIpWhitelist + ? pulumi.all([loadIPRanges(), gcpInternalIPRanges]).apply(([a, b]) => a.concat(b)) + : pulumi + .all([loadInternalWhitelistedIps(), gcpInternalIPRanges]) + .apply(([a, b]) => a.concat(b)); return configureGatewayService( ingressNs, - pulumi.all([externalIPRanges, internalIPRanges]).apply(([a, b]) => a.concat(b)), + gatewayIPRanges, ingress.viaGKEL7 ? { type: 'ClusterIP' } : { @@ -318,82 +324,6 @@ function configureCometBFTGatewayService( ); } -/** - * There doesn't seem to be an istio-level limit on number of IP lists but at - * some point we probably hit some k8s limits on the size of a definition so we - * split it into 100-500 IP ranges per policy. - * - * For 100k IPs, the difference between a chunk size of 100 vs 500 from scratch - * is 20min in pulumi vs 130min in pulumi. But we're still concerned about k8s - * limits on definition size. So if we break 10000 we'll gradually increase - * the chunk size, 20 IPs at a time, until reaching 500 chunk size for 50k IPs, - * which at least is tested for up to 100k IPs. - * - * Why 20? Too small jumps makes much noisier Pulumi previews. Too large, and we - * might jump right into a limit only revealed after extensive testing without - * really knowing where that limit is. 20 is a compromise: only jumps every 200 - * IPs so realignment updates are rare. - */ -function istioAccessPolicyChunkSize(ipRangesLength: number) { - assert.ok(ipRangesLength >= 0, 'nonsense'); - assert.ok( - ipRangesLength < 250000, - `${ipRangesLength} IPs untested, consider testing & increasing maximum chunk size` - ); - const stepSize = 20; - return Math.max(100, Math.min(500, Math.ceil(ipRangesLength / (stepSize * 100)) * stepSize)); -} - -const istioApiVersion = 'security.istio.io/v1beta1'; - -function istioAccessPolicies( - ingressNs: k8s.core.v1.Namespace, - externalIPRanges: pulumi.Output, - suffix: string -) { - const selector = { - matchLabels: { - app: `istio-ingress${suffix}`, - }, - }; - const defaultDenyAll = new k8s.apiextensions.CustomResource( - `istio-access-policy-deny-all${suffix}`, - { - apiVersion: istioApiVersion, - kind: 'AuthorizationPolicy', - metadata: { - name: `istio-access-policy-deny-all${suffix}`, - namespace: ingressNs.metadata.name, - }, - // empty spec is deny all - spec: { selector }, - } - ); - return externalIPRanges.apply(ipRanges => { - const chunkSize = istioAccessPolicyChunkSize(ipRanges.length); - const chunks = Array.from({ length: Math.ceil(ipRanges.length / chunkSize) }, (_, i) => - ipRanges.slice(i * chunkSize, i * chunkSize + chunkSize) - ); - const policies = chunks.map( - (chunk, i) => - new k8s.apiextensions.CustomResource(`istio-access-policy-allow${suffix}-${i}`, { - apiVersion: istioApiVersion, - kind: 'AuthorizationPolicy', - metadata: { - name: `istio-access-policy-allow${suffix}-${i}`, - namespace: ingressNs.metadata.name, - }, - spec: { - selector, - action: 'ALLOW', - rules: [{ from: [{ source: { remoteIpBlocks: chunk } }] }], - }, - }) - ); - return [defaultDenyAll].concat(policies); - }); -} - // how gateway is configured: https://github.com/istio/istio/blob/master/manifests/charts/gateway/templates/service.yaml type IstioGatewayVariant = | { @@ -421,8 +351,7 @@ function configureGatewayService( // - For cometbft traffic, which is tcp traffic, we failed to use istio policies, so we route it through a dedicated // LoadBalancer service that uses loadBalancerSourceRanges. The size limit is not an issue as we need only SV IPs. // These IPs should be provided in externalIPRangesInLB. - - const istioPolicies = istioAccessPolicies(ingressNs, externalIPRangesInIstio, suffix); + const istioPolicies = configureIstioGatewayPolicies(ingressNs, externalIPRangesInIstio, suffix); const { serviceValues, deploymentValues } = gatewayVariant.type === 'LoadBalancer' @@ -732,84 +661,6 @@ function configureDocsAndReleases( ]; } -function configurePublicInfo(ingressNs: k8s.core.v1.Namespace): k8s.apiextensions.CustomResource[] { - return spliceConfig.pulumiProjectConfig.hasPublicInfo - ? [ - new k8s.apiextensions.CustomResource('allow-sv-info', { - apiVersion: 'security.istio.io/v1beta1', - kind: 'AuthorizationPolicy', - metadata: { - name: 'allow-sv-info', - namespace: ingressNs.metadata.name, - }, - spec: { - selector: { - matchLabels: { - istio: 'ingress', - }, - }, - action: 'ALLOW', - rules: [ - { - to: [ - { - operation: { - hosts: [ - // We could also have done `info.sv*.whatever` here but enumerating what we expect seems slightly more secure - ...new Set( - allSvsToDeployBasic - .map(sv => [ - `info.${sv.ingressName}.${getDnsNames().cantonDnsName}`, - `info.${sv.ingressName}.${getDnsNames().daDnsName}`, - ]) - .flat() - ), - ], - }, - }, - ], - }, - ], - }, - }), - ] - : []; -} - -function configurePublicTokenRegistry( - ingressNs: k8s.core.v1.Namespace -): k8s.apiextensions.CustomResource[] { - return [ - new k8s.apiextensions.CustomResource('allow-public-token-registry', { - apiVersion: istioApiVersion, - kind: 'AuthorizationPolicy', - metadata: { - name: 'allow-public-token-registry', - namespace: ingressNs.metadata.name, - }, - spec: { - selector: { - matchLabels: { - app: 'istio-ingress', - }, - }, - action: 'ALLOW', - rules: [ - { - to: [ - { - operation: { - paths: ['/registry/*'], - }, - }, - ], - }, - ], - }, - }), - ]; -} - function configureSequencerHighPerformanceGrpcDestinationRules( ingressNs: k8s.core.v1.Namespace ): Array { @@ -1029,6 +880,7 @@ export function configureIstio( ingressNs.ns ); const sequencerFlowControl = configureSequencerFlowControl(ingressNs.ns); + installAppWhitelisting(ingressNs.ns); const rateLimitHeaderStripping = stripRateLimitHeaders(ingressNs.ns, gwSvc); return { allResources: [ diff --git a/cluster/pulumi/infra/src/whitelisting/gateway.ts b/cluster/pulumi/infra/src/whitelisting/gateway.ts new file mode 100644 index 0000000000..868c7bc1ad --- /dev/null +++ b/cluster/pulumi/infra/src/whitelisting/gateway.ts @@ -0,0 +1,37 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as k8s from '@pulumi/kubernetes'; +import * as pulumi from '@pulumi/pulumi'; + +import { createIstioIpAllowPolicies, istioApiVersion } from './policies'; + +export function configureGatewayAccessPolicies( + ingressNs: k8s.core.v1.Namespace, + ipRanges: pulumi.Output, + suffix: string +): pulumi.Output { + const selector = { + matchLabels: { + app: `istio-ingress${suffix}`, + }, + }; + const defaultDenyAll = new k8s.apiextensions.CustomResource( + `istio-access-policy-deny-all${suffix}`, + { + apiVersion: istioApiVersion, + kind: 'AuthorizationPolicy', + metadata: { + name: `istio-access-policy-deny-all${suffix}`, + namespace: ingressNs.metadata.name, + }, + // empty spec is deny all + spec: { selector }, + } + ); + return createIstioIpAllowPolicies({ + namePrefix: `istio-access-policy-allow${suffix}`, + namespace: ingressNs.metadata.name, + selector, + ipRanges, + }).apply(policies => [defaultDenyAll, ...policies]); +} diff --git a/cluster/pulumi/infra/src/whitelisting/index.ts b/cluster/pulumi/infra/src/whitelisting/index.ts new file mode 100644 index 0000000000..f912f3b2bb --- /dev/null +++ b/cluster/pulumi/infra/src/whitelisting/index.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as k8s from '@pulumi/kubernetes'; +import * as pulumi from '@pulumi/pulumi'; + +import { infraConfig } from '../config'; +import { configureGatewayAccessPolicies } from './gateway'; +import { configureScanAndSvAppWhitelist } from './scanAndSvApp'; +import { configureSequencerWhitelist } from './sequencer'; + +export function installAppWhitelisting( + namespace: k8s.core.v1.Namespace +): pulumi.Output[] { + if (infraConfig.istio.enableGeneralIpWhitelist) { + return []; + } + return [configureScanAndSvAppWhitelist(namespace), ...configureSequencerWhitelist(namespace)]; +} + +export function configureIstioGatewayPolicies( + ingressNs: k8s.core.v1.Namespace, + externalIPRangesInIstio: pulumi.Output, + suffix: string +): pulumi.Output { + return configureGatewayAccessPolicies(ingressNs, externalIPRangesInIstio, suffix); +} diff --git a/cluster/pulumi/infra/src/whitelisting/ipRanges.ts b/cluster/pulumi/infra/src/whitelisting/ipRanges.ts new file mode 100644 index 0000000000..4265119cf9 --- /dev/null +++ b/cluster/pulumi/infra/src/whitelisting/ipRanges.ts @@ -0,0 +1,56 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as pulumi from '@pulumi/pulumi'; +import { externalIpRangesFile, loadJsonFromFile } from '@canton-network/splice-pulumi-common'; +import { getSecretVersionOutput } from '@pulumi/gcp/secretmanager'; + +import { infraConfig } from '../config'; + +type IpRangesDict = { [key: string]: IpRangesDict } | string[]; + +function extractIpRanges(x: IpRangesDict, svsOnly: boolean = false): string[] { + if (svsOnly) { + if (Array.isArray(x)) { + throw new Error('Cannot distinguish SV IP ranges from non-SV IP ranges in an array'); + } + return extractIpRanges(x['svs'], false); + } else { + return Array.isArray(x) + ? x + : Object.keys(x).reduce((acc: string[], k: string) => acc.concat(extractIpRanges(x[k])), []); + } +} + +export function loadInternalWhitelistedIps(): pulumi.Output { + const excludedIps = infraConfig.ipWhitelisting?.excludedIps || []; + + return getSecretVersionOutput({ + secret: 'pulumi-internal-whitelists', + }).apply(whitelists => { + const secretData = whitelists.secretData; + const json = JSON.parse(secretData); + const ret: string[] = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + json.forEach((ip: any) => { + ret.push(ip); + }); + const ips = ret.filter(ip => excludedIps.indexOf(ip) < 0); + return [...new Set(ips)]; + }); +} + +export function loadIPRanges(svsOnly: boolean = false): pulumi.Output { + const file = externalIpRangesFile(); + const externalIpRanges = file ? extractIpRanges(loadJsonFromFile(file), svsOnly) : []; + + const configWhitelistedIps = infraConfig.ipWhitelisting?.extraWhitelistedIngress || []; + const excludedIps = infraConfig.ipWhitelisting?.excludedIps || []; + + return loadInternalWhitelistedIps().apply(whitelists => { + const ips = whitelists + .concat(externalIpRanges) + .concat(configWhitelistedIps) + .filter(ip => excludedIps.indexOf(ip) < 0); + return [...new Set(ips)]; + }); +} diff --git a/cluster/pulumi/infra/src/whitelisting/policies.ts b/cluster/pulumi/infra/src/whitelisting/policies.ts new file mode 100644 index 0000000000..34ddae3ed8 --- /dev/null +++ b/cluster/pulumi/infra/src/whitelisting/policies.ts @@ -0,0 +1,83 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as k8s from '@pulumi/kubernetes'; +import * as pulumi from '@pulumi/pulumi'; +import * as assert from 'assert/strict'; + +export const istioApiVersion = 'security.istio.io/v1beta1'; +export const istioIngressSelector = { matchLabels: { app: 'istio-ingress' } }; + +/** + * There doesn't seem to be an istio-level limit on number of IP lists but at + * some point we probably hit some k8s limits on the size of a definition so we + * split it into 100-500 IP ranges per policy. + * + * For 100k IPs, the difference between a chunk size of 100 vs 500 from scratch + * is 20min in pulumi vs 130min in pulumi. But we're still concerned about k8s + * limits on definition size. So if we break 10000 we'll gradually increase + * the chunk size, 20 IPs at a time, until reaching 500 chunk size for 50k IPs, + * which at least is tested for up to 100k IPs. + * + * Why 20? Too small jumps makes much noisier Pulumi previews. Too large, and we + * might jump right into a limit only revealed after extensive testing without + * really knowing where that limit is. 20 is a compromise: only jumps every 200 + * IPs so realignment updates are rare. + */ +export function istioAccessPolicyChunkSize(ipRangesLength: number): number { + assert.ok(ipRangesLength >= 0, 'nonsense'); + assert.ok( + ipRangesLength < 250000, + `${ipRangesLength} IPs untested, consider testing & increasing maximum chunk size` + ); + const stepSize = 20; + return Math.max(100, Math.min(500, Math.ceil(ipRangesLength / (stepSize * 100)) * stepSize)); +} + +function chunkIpRanges(ipRanges: string[]): string[][] { + const chunkSize = istioAccessPolicyChunkSize(ipRanges.length); + return Array.from({ length: Math.ceil(ipRanges.length / chunkSize) }, (_, i) => + ipRanges.slice(i * chunkSize, i * chunkSize + chunkSize) + ); +} + +export interface IstioIpAllowPolicyArgs { + namePrefix: string; + namespace: pulumi.Input; + selector: object; + ipRanges: pulumi.Output; + to?: object[]; + opts?: pulumi.CustomResourceOptions; +} + +export function createIstioIpAllowPolicies( + args: IstioIpAllowPolicyArgs +): pulumi.Output { + const { namePrefix, namespace, selector, ipRanges, to, opts } = args; + return ipRanges.apply(ranges => + chunkIpRanges(ranges).map( + (chunk, i) => + new k8s.apiextensions.CustomResource( + `${namePrefix}-${i}`, + { + apiVersion: istioApiVersion, + kind: 'AuthorizationPolicy', + metadata: { + name: `${namePrefix}-${i}`, + namespace, + }, + spec: { + selector, + action: 'ALLOW', + rules: [ + { + from: [{ source: { remoteIpBlocks: chunk } }], + ...(to ? { to } : {}), + }, + ], + }, + }, + opts + ) + ) + ); +} diff --git a/cluster/pulumi/infra/src/whitelisting/publicInfo.ts b/cluster/pulumi/infra/src/whitelisting/publicInfo.ts new file mode 100644 index 0000000000..83e669d089 --- /dev/null +++ b/cluster/pulumi/infra/src/whitelisting/publicInfo.ts @@ -0,0 +1,54 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as k8s from '@pulumi/kubernetes'; +import { getDnsNames } from '@canton-network/splice-pulumi-common'; +import { allSvsToDeployBasic } from '@canton-network/splice-pulumi-common-sv/src/svConfigsBasic'; +import { spliceConfig } from '@canton-network/splice-pulumi-common/src/config/config'; + +import { istioApiVersion } from './policies'; + +export function configurePublicInfo( + ingressNs: k8s.core.v1.Namespace +): k8s.apiextensions.CustomResource[] { + return spliceConfig.pulumiProjectConfig.hasPublicInfo + ? [ + new k8s.apiextensions.CustomResource('allow-sv-info', { + apiVersion: istioApiVersion, + kind: 'AuthorizationPolicy', + metadata: { + name: 'allow-sv-info', + namespace: ingressNs.metadata.name, + }, + spec: { + selector: { + matchLabels: { + istio: 'ingress', + }, + }, + action: 'ALLOW', + rules: [ + { + to: [ + { + operation: { + hosts: [ + // We could also have done `info.sv*.whatever` here but enumerating what we expect seems slightly more secure + ...new Set( + allSvsToDeployBasic + .map(sv => [ + `info.${sv.ingressName}.${getDnsNames().cantonDnsName}`, + `info.${sv.ingressName}.${getDnsNames().daDnsName}`, + ]) + .flat() + ), + ], + }, + }, + ], + }, + ], + }, + }), + ] + : []; +} diff --git a/cluster/pulumi/infra/src/whitelisting/publicTokenRegistry.ts b/cluster/pulumi/infra/src/whitelisting/publicTokenRegistry.ts new file mode 100644 index 0000000000..bee80586a5 --- /dev/null +++ b/cluster/pulumi/infra/src/whitelisting/publicTokenRegistry.ts @@ -0,0 +1,39 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as k8s from '@pulumi/kubernetes'; + +import { istioApiVersion } from './policies'; + +export function configurePublicTokenRegistry( + ingressNs: k8s.core.v1.Namespace +): k8s.apiextensions.CustomResource[] { + return [ + new k8s.apiextensions.CustomResource('allow-public-token-registry', { + apiVersion: istioApiVersion, + kind: 'AuthorizationPolicy', + metadata: { + name: 'allow-public-token-registry', + namespace: ingressNs.metadata.name, + }, + spec: { + selector: { + matchLabels: { + app: 'istio-ingress', + }, + }, + action: 'ALLOW', + rules: [ + { + to: [ + { + operation: { + paths: ['/registry/*'], + }, + }, + ], + }, + ], + }, + }), + ]; +} diff --git a/cluster/pulumi/infra/src/whitelisting/scanAndSvApp.ts b/cluster/pulumi/infra/src/whitelisting/scanAndSvApp.ts new file mode 100644 index 0000000000..6f84446e3c --- /dev/null +++ b/cluster/pulumi/infra/src/whitelisting/scanAndSvApp.ts @@ -0,0 +1,25 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as k8s from '@pulumi/kubernetes'; +import * as pulumi from '@pulumi/pulumi'; +import { getDnsNames } from '@canton-network/splice-pulumi-common'; +import { allSvsToDeployBasic } from '@canton-network/splice-pulumi-common-sv/src/svConfigsBasic'; + +import { loadIPRanges } from './ipRanges'; +import { createIstioIpAllowPolicies, istioIngressSelector } from './policies'; + +export function configureScanAndSvAppWhitelist( + namespace: k8s.core.v1.Namespace +): pulumi.Output { + const dnsNames = [getDnsNames().cantonDnsName, getDnsNames().daDnsName]; + const hosts = allSvsToDeployBasic.flatMap(sv => + dnsNames.flatMap(dns => [`scan.${sv.ingressName}.${dns}`, `sv.${sv.ingressName}.${dns}`]) + ); + return createIstioIpAllowPolicies({ + namePrefix: 'scan-sv-app-ip-whitelist', + namespace: namespace.metadata.name, + selector: istioIngressSelector, + ipRanges: loadIPRanges(), + to: [{ operation: { hosts } }], + }); +} diff --git a/cluster/pulumi/infra/src/whitelisting/sequencer.ts b/cluster/pulumi/infra/src/whitelisting/sequencer.ts new file mode 100644 index 0000000000..5aa706c2d9 --- /dev/null +++ b/cluster/pulumi/infra/src/whitelisting/sequencer.ts @@ -0,0 +1,60 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import * as k8s from '@pulumi/kubernetes'; +import * as pulumi from '@pulumi/pulumi'; +import { + DecentralizedSynchronizerUpgradeConfig, + getDnsNames, +} from '@canton-network/splice-pulumi-common'; +import { allSvsToDeployBasic } from '@canton-network/splice-pulumi-common-sv/src/svConfigsBasic'; + +import { loadIPRanges } from './ipRanges'; +import { createIstioIpAllowPolicies, istioIngressSelector } from './policies'; + +export function configureSequencerWhitelist( + namespace: k8s.core.v1.Namespace +): pulumi.Output[] { + const dnsNames = [getDnsNames().cantonDnsName, getDnsNames().daDnsName]; + const migrations = DecentralizedSynchronizerUpgradeConfig.runningMigrations(); + + const publicApiHosts = allSvsToDeployBasic.flatMap(sv => + migrations.flatMap(migration => + dnsNames.flatMap(dns => [ + `sequencer-${migration.id}.${sv.ingressName}.${dns}`, + `sequencer-${migration.id}.${sv.ingressName}.${dns}:*`, + ]) + ) + ); + const p2pHosts = allSvsToDeployBasic.flatMap(sv => + migrations + .filter(migration => migration.sequencer.enableBftSequencer) + .flatMap(migration => + dnsNames.flatMap(dns => [ + `sequencer-p2p-${migration.id}.${sv.ingressName}.${dns}`, + `sequencer-p2p-${migration.id}.${sv.ingressName}.${dns}:*`, + ]) + ) + ); + + const policies = [ + createIstioIpAllowPolicies({ + namePrefix: 'sequencer-pub-ip-whitelist', + namespace: namespace.metadata.name, + selector: istioIngressSelector, + ipRanges: loadIPRanges(), + to: [{ operation: { hosts: publicApiHosts } }], + }), + ]; + if (p2pHosts.length > 0) { + policies.push( + createIstioIpAllowPolicies({ + namePrefix: 'sequencer-p2p-ip-whitelist', + namespace: namespace.metadata.name, + selector: istioIngressSelector, + ipRanges: loadIPRanges(true), + to: [{ operation: { hosts: p2pHosts } }], + }) + ); + } + return policies; +} From 0c47bcc67a603bfab2b6a9682d04434ee6a8c4e8 Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Wed, 19 Aug 2026 09:51:55 -0400 Subject: [PATCH 269/329] improve failure message in packagesAreVetted test (#6838) Signed-off-by: Stephen Compall --- .../tests/BootstrapPackageConfigIntegrationTest.scala | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala index 1dec85f5c7..636cee7873 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/BootstrapPackageConfigIntegrationTest.scala @@ -477,12 +477,12 @@ class BootstrapPackageConfigIntegrationTest ) ) .filter(_.metadata.version <= bootstrapPackage.metadata.version) - expectedToBeVettedVersions.foreach { expectedVettedVersion => + forEvery(expectedToBeVettedVersions) { expectedVettedVersion => val newVettedPackage = vettingState.packages .find(_.packageId == expectedVettedVersion.packageId) - .value - newVettedPackage.validFromInclusive should ( - equal(scheduledTimeO) or equal(scheduledTime1) or equal(scheduledTime2) + .value withClue "newVettedPackage" + Seq(scheduledTimeO, scheduledTime1, scheduledTime2) should contain( + newVettedPackage.validFromInclusive ) } } From 1973921ef727fded6d6d3734b00e7eeca4010b51 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Wed, 19 Aug 2026 16:44:40 +0200 Subject: [PATCH 270/329] Allow external CI on forks for staging bases (#6860) (#6864) * Allow external CI on forks for staging bases (#6860) [static] Signed-off-by: Nicu Reut * Actually enable ci for staging bases [static] Signed-off-by: Nicu Reut --------- Signed-off-by: Nicu Reut --- .github/workflows/pr_non_contributors.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_non_contributors.yml b/.github/workflows/pr_non_contributors.yml index ff44bef249..4453efa321 100644 --- a/.github/workflows/pr_non_contributors.yml +++ b/.github/workflows/pr_non_contributors.yml @@ -2,7 +2,7 @@ name: CI on PRs from forks on: pull_request_target: types: [ opened, synchronize, reopened ] - branches: [ main ] # Only run on PRs with `main` as their base + branches: [ main, 'staging-*' ] # Only run on PRs with `main` or `staging` branches as their base concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.head.ref }} From b85c1d51ee352036613badbc937f325614e91233 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Wed, 19 Aug 2026 17:43:18 +0200 Subject: [PATCH 271/329] Upgrade Canton to 3.5.14-snapshot.20260819.19183.0.va7a6d3ae (#6865) [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index ec54bec040..bfbef73901 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.14-snapshot.20260815.19176.0.v65fa04f6", - "oss_sha256": "sha256:1wik69kgc7b809960x35wihxqp5lqvdjani7xr9428glvaxwy069", - "canton_base_image_sha256": "sha256:d157ae44eba82688812e80d74f7a206bbad6f7657d5cb5d2f3aedc98608f5f0a", - "canton_participant_image_sha256": "sha256:a2d45ef08e5a255a9858e29b88d0d73f8f52362ea7e7a646d67e729b7984303f", - "canton_mediator_image_sha256": "sha256:b80fb5b639ad3c054d5bd770d30b3bfa165eaad86ee2284c5b4447d4847a467b", - "canton_sequencer_image_sha256": "sha256:286ab89653f2320552c8ecd9f0cd4740095475255742168386408fdde8a30672" + "version": "3.5.14-snapshot.20260819.19183.0.va7a6d3ae", + "oss_sha256": "sha256:12xz6wgv7piylwnw11qjiwy1xmp66ak54b28h0mjrb5n12q9yh58", + "canton_base_image_sha256": "sha256:169a896a20f10375395b7a360361eecd94055aa6f574eece83bbaec2393a0fdf", + "canton_participant_image_sha256": "sha256:9229c01e8cc93be26fc46c3d572ec191e7f06161e9d0ca8fba455e841de17363", + "canton_mediator_image_sha256": "sha256:b963d94b31cf2140eb0819af6a849b23e8c7799c920bf736d99672211125a8e9", + "canton_sequencer_image_sha256": "sha256:e321c8fe1b3ea4a4211b6791e14b6e63c4fd2606a6534338eca1ed27d8f8f3b1" } From 05b5559c4858fa6815abe204712867f748c8d527 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Wed, 19 Aug 2026 18:26:27 +0200 Subject: [PATCH 272/329] Allow closing contributions that did not get maintainer approval beforehand (#6867) [static] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- CONTRIBUTING.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31e337f501..7bd392189e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,9 +46,9 @@ Splice maintainers may use the following GitHub issue labels to highlight issues Note that not all `good first issue`s are also `help wanted`; some may require access to infrastructure (CI, test deployments) that is not openly available. If you are planning to work on an issue please assign yourself to it (if you are able to) or leave a comment, to -avoid duplicate work across contributors. If the issue is not new, it is also a good idea to reach out to the -core contributors before working on it, to check how relevant it still is, and whether it is something worth -working on. +avoid duplicate work across contributors. + +For any contribution, first check with a maintainer on an issue (either an existing one or a new one) that this makes sense to work on. Contributions that have not gotten explicit agreement from a maintainer on an issue beforehand may be closed without further comment. ## Opening PRs @@ -322,4 +322,3 @@ grant write permissions to the main Splice repo. - CI assumes the branch for the latest release line, so after every Splice release (assuming you rebase/merge the fork's main to Splice main), you will need to pull the corresponding release-line from splice and push it to the fork. In the future we might change CI to pull the release line from main Splice rather than look for it in the fork, but as of now, it assumes it exists in the same repo on which it is running. - From a86095320d3f3a69b5bd31e2f965e138c33766b9 Mon Sep 17 00:00:00 2001 From: kajalshah-da Date: Wed, 19 Aug 2026 14:12:36 -0400 Subject: [PATCH 273/329] manually clear BigQuery with datatransfer instead of relying on table expiry (#6813) * added purge datatransfer job for manually deleting records after 7 days from staging tables Signed-off-by: Kajal * Addressed PR comments [ci] Signed-off-by: Kajal * Addressed PR comments - sharing IAMMember [ci] Signed-off-by: Kajal * Addressed PR comments - sharing IAMMember with defaul removed for 7 days [ci] Signed-off-by: Kajal * Addressed PR comments - sharing IAMMember with defaul removed for 7 days [ci] new Signed-off-by: Kajal * fixed zod error messages [ci] Signed-off-by: Kajal * npm run fix changes [[ci] Signed-off-by: Kajal * formatting cahnges to cloudArmor.ts [ci] Signed-off-by: Kajal * [ci] Signed-off-by: Kajal --------- Signed-off-by: Kajal --- cluster/pulumi/common-sv/src/bigQuery.ts | 117 +++++++++++++++--- .../pulumi/common-sv/src/singleSvConfig.ts | 10 ++ cluster/pulumi/infra/src/cloudArmor.ts | 5 +- 3 files changed, 114 insertions(+), 18 deletions(-) diff --git a/cluster/pulumi/common-sv/src/bigQuery.ts b/cluster/pulumi/common-sv/src/bigQuery.ts index b45c294d12..92f73ff906 100644 --- a/cluster/pulumi/common-sv/src/bigQuery.ts +++ b/cluster/pulumi/common-sv/src/bigQuery.ts @@ -355,7 +355,9 @@ function installBigqueryStagingDataset(scanBigQuery: ScanBigQueryConfig): gcp.bi friendlyName: `${scanBigQuery.dataset} Staging Dataset`, location: cloudsdkComputeRegion(), deleteContentsOnDestroy: true, - defaultTableExpirationMs: THREE_DAYS_MS, + // ISSUE#6814: Do not rely on ingestion timestamps for retention in staging. + // GCP calculates expiration from the table creation date, which will delete + // staging tables at the 3-day mark even if production sync is incomplete. labels: { cluster: CLUSTER_BASENAME, datastream_id: 'stag_prod', @@ -368,27 +370,28 @@ function installBigqueryProdDataset(scanBigQuery: ScanBigQueryConfig): gcp.bigqu datasetId: `${scanBigQuery.dataset}_prod`, friendlyName: `${scanBigQuery.dataset} Production Dataset`, location: cloudsdkComputeRegion(), - deleteContentsOnDestroy: true, + deleteContentsOnDestroy: false, labels: { cluster: CLUSTER_BASENAME, }, }); } - // ============================================================================ -// HOURLY DEDUPLICATION & SCHEDULED QUERIES +// IAM PERMISSIONS for SCHEDULED QUERIES // ============================================================================ +interface ScheduledQueryContext { + projectId: pulumi.Output; + transferServiceAgentPermission: gcp.projects.IAMMember; +} -const rawSqlTemplate = fs.readFileSync(path.join(__dirname, 'hourly_append.sql'), 'utf8'); - -function installHourlyScheduledQueries( - namespace: ExactNamespace, - stagingDataset: gcp.bigquery.Dataset, - prodDataset: gcp.bigquery.Dataset -) { +function installBqScheduledQueryContext(): ScheduledQueryContext { const currentProject = gcp.organizations.getProjectOutput({}); - const projectId = currentProject.apply(p => p.projectId!); - const schemaName = scanAppDatabaseName(namespace); + const projectId = currentProject.apply(p => { + if (!p.projectId) { + throw new Error('Current GCP project output is missing a projectId.'); + } + return p.projectId; + }); const transferServiceAgentPermission = new gcp.projects.IAMMember('bq-transfer-token-creator', { project: projectId, @@ -398,6 +401,23 @@ function installHourlyScheduledQueries( ), }); + return { projectId, transferServiceAgentPermission }; +} + +// ============================================================================ +// HOURLY DEDUPLICATION & SCHEDULED QUERIES +// ============================================================================ + +const rawSqlTemplate = fs.readFileSync(path.join(__dirname, 'hourly_append.sql'), 'utf8'); + +function installHourlyScheduledQueries( + namespace: ExactNamespace, + stagingDataset: gcp.bigquery.Dataset, + prodDataset: gcp.bigquery.Dataset, + context: ScheduledQueryContext +) { + const { projectId, transferServiceAgentPermission } = context; + const schemaName = scanAppDatabaseName(namespace); Object.entries(replicatedTables).forEach(([tableName, tableConfig]) => { const primaryKeyExpr = tableConfig.primaryKey; const colName = tableConfig.datePartitionColumn; @@ -463,7 +483,67 @@ function installHourlyScheduledQueries( ); }); } +// ============================================================================ +// Purging data older than 7 days from the staging table +// ============================================================================ +function installDailyPurgeScheduledQueries( + namespace: ExactNamespace, + stagingDataset: gcp.bigquery.Dataset, + context: ScheduledQueryContext, + retentionPeriodSeconds: number +) { + const { projectId, transferServiceAgentPermission } = context; + const schemaName = scanAppDatabaseName(namespace); + const retentionDays = retentionPeriodSeconds / 86400; + Object.entries(replicatedTables).forEach(([tableName, tableConfig]) => { + const timeExpression = + tableConfig.timeType === 'datastream_metadata' + ? `TIMESTAMP_MILLIS(datastream_metadata.${tableConfig.datePartitionColumn})` + : `TIMESTAMP_MICROS(${tableConfig.datePartitionColumn})`; + + const procedureBody = pulumi + .all([projectId, stagingDataset.datasetId]) + .apply(([proj, stagingDs]) => { + const stagingTable = `\`${proj}.${stagingDs}.${schemaName}_${tableName}\``; + + return ` + DELETE FROM ${stagingTable} + WHERE ${timeExpression} < TIMESTAMP_SUB(CURRENT_TIMESTAMP(), INTERVAL ${retentionPeriodSeconds} SECOND); + `; + }); + + const routineId = `sp_purge_old_records_${tableName}`; + + // Create the cleanup Stored Procedure + const purgeRoutine = new gcp.bigquery.Routine(`${tableName}-purge-routine`, { + datasetId: stagingDataset.datasetId, + routineId: routineId, + routineType: 'PROCEDURE', + language: 'SQL', + definitionBody: procedureBody, + }); + + // Schedule the Stored Procedure to run daily + new gcp.bigquery.DataTransferConfig( + `${CLUSTER_BASENAME}_${tableName}-daily-purge`, + { + displayName: `${CLUSTER_BASENAME}_${tableName} Daily Retention Purge`, + location: cloudsdkComputeRegion(), + serviceAccountName: pulumi.interpolate`bigquery@${projectId}.iam.gserviceaccount.com`, + dataSourceId: 'scheduled_query', + schedule: 'every day 05:21', // Runs daily at 05:21 AM + + params: { + query: pulumi.interpolate`CALL \`${projectId}.${stagingDataset.datasetId}.${routineId}\`();`, + }, + }, + { + dependsOn: [transferServiceAgentPermission, purgeRoutine], + } + ); + }); +} // ============================================================================ // CONNECTION PROFILES & NETWORKING // ============================================================================ @@ -775,6 +855,7 @@ export async function configureScanBigQuery({ enableStagProdDatastream, legacyDesiredState, stagProdDesiredState, + retentionPeriodSeconds, } = bigQueryConfig; if (!enableLegacyDatastream && !enableStagProdDatastream) { @@ -856,8 +937,14 @@ export async function configureScanBigQuery({ slots.slot2, stagProdDesiredState ); - - installHourlyScheduledQueries(namespace, stagingDataset, prodDataset); + const scheduledQueryContext = installBqScheduledQueryContext(); + installHourlyScheduledQueries(namespace, stagingDataset, prodDataset, scheduledQueryContext); + installDailyPurgeScheduledQueries( + namespace, + stagingDataset, + scheduledQueryContext, + retentionPeriodSeconds + ); } // TODO (DACH-NY/canton-network-internal#6451) not sure if this function needs to return anything, // but we need to return something to satisfy the ScanBigQuery type. diff --git a/cluster/pulumi/common-sv/src/singleSvConfig.ts b/cluster/pulumi/common-sv/src/singleSvConfig.ts index fc4d720b57..bbc8f07cd6 100644 --- a/cluster/pulumi/common-sv/src/singleSvConfig.ts +++ b/cluster/pulumi/common-sv/src/singleSvConfig.ts @@ -98,6 +98,7 @@ export type BulkStorageConfig = z.infer; // 1. Extract ScanBigQueryConfigSchema to validate all Datastream settings. // All new fields are optional to ensure existing deployments do not fail parsing. +const SECONDS_PER_DAY = 24 * 3600; export const ScanBigQueryConfigSchema = z .object({ dataset: z.string(), @@ -107,6 +108,15 @@ export const ScanBigQueryConfigSchema = z enableStagProdDatastream: z.boolean().default(false), legacyDesiredState: z.enum(['RUNNING', 'PAUSED']).default('RUNNING'), stagProdDesiredState: z.enum(['RUNNING', 'PAUSED']).default('RUNNING'), + retentionPeriodSeconds: z + .number() + .min(3 * SECONDS_PER_DAY, { + message: 'Value must be at least 3 days (259,200 seconds)', + }) + .refine(v => v % SECONDS_PER_DAY === 0, { + message: 'Value must be an exact number of days, expressed in seconds', + }) + .default(7 * SECONDS_PER_DAY), }) .strict(); // Keeps strict mode safe now that all known fields are explicitly defined diff --git a/cluster/pulumi/infra/src/cloudArmor.ts b/cluster/pulumi/infra/src/cloudArmor.ts index 82fda8ef65..de26856a32 100644 --- a/cluster/pulumi/infra/src/cloudArmor.ts +++ b/cluster/pulumi/infra/src/cloudArmor.ts @@ -76,9 +76,8 @@ export function configureCloudArmorPolicy( // Step 2: Add predefined WAF rules if (cac.predefinedWafRules && cac.predefinedWafRules.length > 0) { - addPredefinedWafRules( - /*securityPolicy, args.predefinedWafRules, cac.allRulesPreviewOnly, ruleOpts*/ - ); + addPredefinedWafRules(); + /*securityPolicy, args.predefinedWafRules, cac.allRulesPreviewOnly, ruleOpts*/ } // Step 3: Add IP whitelisting rules From 6f68f6b307bd72eb37f5cfd510009e382b533f61 Mon Sep 17 00:00:00 2001 From: Stephen Compall Date: Wed, 19 Aug 2026 15:00:49 -0400 Subject: [PATCH 274/329] in splitwell test, wait longer for checkWallets via argument rather than wrapped eventually (#6869) Signed-off-by: Stephen Compall --- .../SplitwellFrontendIntegrationTest.scala | 20 ++++++++++++++----- .../splice/util/WalletTestUtil.scala | 3 ++- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellFrontendIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellFrontendIntegrationTest.scala index e74b45aa45..c257e5de95 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellFrontendIntegrationTest.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SplitwellFrontendIntegrationTest.scala @@ -174,11 +174,21 @@ class SplitwellFrontendIntegrationTest } } - eventually() { - // Check final amounts in the wallets - checkWallet(aliceUserParty, aliceWalletClient, Seq((400.0, 400))) - checkWallet(bobUserParty, bobWalletClient, Seq((39.0, 39.0))) - checkWallet(charlieUserParty, charlieWalletClient, Seq((111.0, 111.0))) + withClue("check final amounts in the wallets") { + val waitTime = 20.seconds + checkWallet( + aliceUserParty, + aliceWalletClient, + Seq((400.0, 400)), + timeUntilSuccess = waitTime, + ) + checkWallet(bobUserParty, bobWalletClient, Seq((39.0, 39.0)), timeUntilSuccess = waitTime) + checkWallet( + charlieUserParty, + charlieWalletClient, + Seq((111.0, 111.0)), + timeUntilSuccess = waitTime, + ) } } diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/WalletTestUtil.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/WalletTestUtil.scala index 9bed4ecebe..1422e68567 100644 --- a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/WalletTestUtil.scala +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/util/WalletTestUtil.scala @@ -66,11 +66,12 @@ trait WalletTestUtil extends TestCommon with AnsTestUtil { wallet: WalletAppClientReference, expectedAmountRanges: Seq[(BigDecimal, BigDecimal)], holdingFee: BigDecimal = defaultHoldingFeeAmulet.bigDecimal, + timeUntilSuccess: FiniteDuration = 10.seconds, ): Unit = clue(s"checking wallet with $expectedAmountRanges") { val expectedRatePerRound = new feesCodegen.RatePerRound( holdingFee.bigDecimal setScale 10 ) - eventually(10.seconds, 500.millis) { + eventually(timeUntilSuccess, 500.millis) { val amulets = wallet.list().amulets.sortBy(amulet => amulet.contract.payload.amount.initialAmount) amulets should have size (expectedAmountRanges.size.toLong) From 0b8bbe10cb6c9943dab15f34a84d6e1699f4b80c Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Thu, 20 Aug 2026 09:21:08 +0200 Subject: [PATCH 275/329] Enable gcp enhanced insights for enterprise plus (#6873) [static] Signed-off-by: Nicu Reut --- cluster/expected/splitwell/expected.json | 3 ++ cluster/expected/sv-canton/expected.json | 42 +++++++++++++++++++++++ cluster/expected/sv-runbook/expected.json | 1 + cluster/expected/sv/expected.json | 5 +++ cluster/expected/validator1/expected.json | 2 ++ cluster/pulumi/common/src/postgres.ts | 2 +- 6 files changed, 54 insertions(+), 1 deletion(-) diff --git a/cluster/expected/splitwell/expected.json b/cluster/expected/splitwell/expected.json index afabb893bc..58853a42c7 100644 --- a/cluster/expected/splitwell/expected.json +++ b/cluster/expected/splitwell/expected.json @@ -631,6 +631,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -925,6 +926,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -1054,6 +1056,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { diff --git a/cluster/expected/sv-canton/expected.json b/cluster/expected/sv-canton/expected.json index f91aecc5ef..116be57591 100644 --- a/cluster/expected/sv-canton/expected.json +++ b/cluster/expected/sv-canton/expected.json @@ -2551,6 +2551,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -2682,6 +2683,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -2813,6 +2815,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -2944,6 +2947,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -3075,6 +3079,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -3206,6 +3211,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -3337,6 +3343,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -3468,6 +3475,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -3599,6 +3607,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -3730,6 +3739,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -3861,6 +3871,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -3992,6 +4003,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -4123,6 +4135,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -4254,6 +4267,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -6161,6 +6175,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -6292,6 +6307,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -6423,6 +6439,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -6554,6 +6571,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -6685,6 +6703,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -6816,6 +6835,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -6947,6 +6967,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -7078,6 +7099,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -7209,6 +7231,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -7340,6 +7363,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -7471,6 +7495,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -7602,6 +7627,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -7733,6 +7759,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -7864,6 +7891,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -9030,6 +9058,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -9162,6 +9191,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -9294,6 +9324,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -9426,6 +9457,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -9558,6 +9590,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -9690,6 +9723,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -9822,6 +9856,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -9954,6 +9989,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -10086,6 +10122,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -10218,6 +10255,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -10350,6 +10388,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -10482,6 +10521,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -10614,6 +10654,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -10746,6 +10787,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index ace2c82d22..31362ce6f2 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -1323,6 +1323,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index 46d286b230..d40a5fe142 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -2433,6 +2433,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -2800,6 +2801,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -4774,6 +4776,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -5141,6 +5144,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -7119,6 +7123,7 @@ "deletionProtectionEnabled": false, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { diff --git a/cluster/expected/validator1/expected.json b/cluster/expected/validator1/expected.json index 900e4880f3..601ff76d13 100644 --- a/cluster/expected/validator1/expected.json +++ b/cluster/expected/validator1/expected.json @@ -667,6 +667,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { @@ -986,6 +987,7 @@ "deletionProtectionEnabled": true, "edition": "ENTERPRISE", "insightsConfig": { + "enhancedQueryInsightsEnabled": false, "queryInsightsEnabled": true }, "ipConfiguration": { diff --git a/cluster/pulumi/common/src/postgres.ts b/cluster/pulumi/common/src/postgres.ts index 798f6a3ec8..a4dd8667e9 100644 --- a/cluster/pulumi/common/src/postgres.ts +++ b/cluster/pulumi/common/src/postgres.ts @@ -20,7 +20,6 @@ import { spliceConfig } from './config/config'; import { GcpProject } from './config/gcpConfig'; import { appsAffinityAndTolerations, - CnInput, infraAffinityAndTolerations, installSpliceHelmChart, SpliceCustomResourceOptions, @@ -149,6 +148,7 @@ export class CloudPostgres }, insightsConfig: { queryInsightsEnabled: true, + enhancedQueryInsightsEnabled: cloudSqlConfig.enterprisePlus, }, tier: cloudSqlConfig.tier, edition: cloudSqlConfig.enterprisePlus ? 'ENTERPRISE_PLUS' : 'ENTERPRISE', From 49c7708199c6e11f7828e3748f4595befe7a0ce1 Mon Sep 17 00:00:00 2001 From: jarekr-da Date: Thu, 20 Aug 2026 10:02:37 +0200 Subject: [PATCH 276/329] fix: added missing feature flag for multisync (#6809) --------- Signed-off-by: jarekr-da --- .../resources/localnet-reassign-topology.conf | 38 ++++ .../LocalNetReassignIntegrationTest.scala | 196 ++++++++++++++++++ .../localnet/conf/console/app-synchronizer.sc | 53 ++++- test-full-class-names-docker-no-canton.log | 1 + 4 files changed, 287 insertions(+), 1 deletion(-) create mode 100644 apps/app/src/test/resources/localnet-reassign-topology.conf create mode 100644 apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LocalNetReassignIntegrationTest.scala diff --git a/apps/app/src/test/resources/localnet-reassign-topology.conf b/apps/app/src/test/resources/localnet-reassign-topology.conf new file mode 100644 index 0000000000..426525f88e --- /dev/null +++ b/apps/app/src/test/resources/localnet-reassign-topology.conf @@ -0,0 +1,38 @@ +include required("include/canton-basic.conf") +canton { + validator-app-clients { + userValidatorClient { + admin-api { + url = "http://wallet.localhost:2000" + } + } + providerValidatorClient { + admin-api { + url = "http://wallet.localhost:3000" + } + } + } + remote-participants { + app-provider { + ledger-api { + address = "grpc-ledger-api.localhost" + port = 3000 + } + admin-api { + address = "localhost" + port = 3902 + } + } + app-user { + ledger-api { + address = "grpc-ledger-api.localhost" + port = 2000 + } + admin-api { + address = "localhost" + port = 2902 + } + } + } +} + diff --git a/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LocalNetReassignIntegrationTest.scala b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LocalNetReassignIntegrationTest.scala new file mode 100644 index 0000000000..39c9e1560c --- /dev/null +++ b/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/LocalNetReassignIntegrationTest.scala @@ -0,0 +1,196 @@ +package org.lfdecentralizedtrust.splice.integration.tests + +import com.daml.ledger.api.v2.transaction_filter.CumulativeFilter.IdentifierFilter +import com.daml.ledger.api.v2.transaction_filter.{ + CumulativeFilter, + EventFormat, + Filters, + WildcardFilter, +} +import com.digitalasset.canton.protocol.LfContractId +import com.digitalasset.canton.topology.SynchronizerId +import monocle.Monocle.toAppliedFocusOps +import org.lfdecentralizedtrust.splice.auth.AuthUtil +import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.test.dummyholding.DummyHolding +import org.lfdecentralizedtrust.splice.console.ParticipantClientReference +import org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition +import org.lfdecentralizedtrust.splice.integration.tests.SpliceTests.IntegrationTestWithIsolatedEnvironment +import org.lfdecentralizedtrust.splice.util.JavaDecodeUtil + +import java.nio.file.Paths +import scala.jdk.CollectionConverters.* +import scala.sys.process.* + +/** Verifies the actual capability unlocked by the multi-synchronizer topology feature flag set in + * cluster/compose/localnet/conf/console/app-synchronizer.sc: reassigning a contract between the + * global synchronizer and the app-synchronizer. Without the flag, the unassignment is rejected + * with MultiSynchronizerIsNotEnabled. + * + * This spins up the docker-compose localnet with the `multi-sync` profile enabled (-M) + */ +class LocalNetReassignIntegrationTest extends IntegrationTestWithIsolatedEnvironment { + + override def environmentDefinition: SpliceEnvironmentDefinition = + EnvironmentDefinition + .fromResources(Seq("localnet-reassign-topology.conf"), this.getClass.getSimpleName) + .updateTestingConfig( + _.focus(_.participantsWithoutLapiVerification).replace( + Set( + "app-provider", + "app-user", + ) + ) + ) + .withManualStart + + // These do nothing as the clients will not actually be connected to the compose setup. + override protected def runTokenStandardCliSanityCheck: Boolean = false + override lazy val resetRequiredTopologyState = false + + // The user all localnet nodes use for their ledger API access, see + // cluster/compose/localnet/env/*-auth-on.env + private val ledgerApiUserId = "ledger-api-user" + + private val token = AuthUtil.testToken(AuthUtil.testAudience, ledgerApiUserId, "unsafe") + + private val dummyHoldingDarPath = Paths + .get( + "token-standard/examples/splice-token-test-dummy-holding/.daml/dist/splice-token-test-dummy-holding-current.dar" + ) + .toAbsolutePath + .toString + + private def withLocalNet( + )(f: FixtureParam => Any)(implicit env: FixtureParam): Unit = + try { + val ret = (Seq("build-tools/splice-localnet-compose.sh", "start") ++ Seq("-M")).! + if (ret != 0) { + fail("Failed to start docker-compose SV and validator") + } + f(env) + } finally { + (Seq("build-tools/splice-localnet-compose.sh", "stop", "-D") ++ Seq("-M")).! + } + + private def participantClient(name: String)(implicit env: FixtureParam) = { + val remoteParticipant = + env.participants.remote + .find(_.name == name) + .getOrElse(fail(s"$name participant not found")) + new ParticipantClientReference( + env, + remoteParticipant.name, + remoteParticipant.config.copy(token = Some(token)), + ) + } + + private def synchronizerId( + participant: ParticipantClientReference, + alias: String, + ): SynchronizerId = + participant.synchronizers + .list_connected() + .find(_.synchronizerAlias.unwrap == alias) + .getOrElse(fail(s"${participant.name} is not connected to $alias")) + .synchronizerId + + private def testReassignment(participantName: String, validatorClientName: String)(implicit + env: FixtureParam + ): Unit = + clue(s"Reassign a contract between global and app-synchronizer on $participantName") { + val participant = participantClient(participantName) + val party = vc(validatorClientName).copy(token = Some(token)).getValidatorPartyId() + val globalSynchronizerId = synchronizerId(participant, "global") + val appSynchronizerId = synchronizerId(participant, "app-synchronizer") + + participant.upload_dar_unless_exists(dummyHoldingDarPath) + + val createdContract = clue("Create a DummyHolding on the global synchronizer") { + val tx = participant.ledger_api_extensions.commands.submitJava( + actAs = Seq(party), + commands = new DummyHolding( + party.toProtoPrimitive, + party.toProtoPrimitive, + BigDecimal(42).bigDecimal, + ).create().commands().asScala.toSeq, + synchronizerId = Some(globalSynchronizerId), + userId = ledgerApiUserId, + ) + JavaDecodeUtil.decodeAllCreated(DummyHolding.COMPANION)(tx).loneElement + } + val contractId = createdContract.id.contractId + val lfContractId = LfContractId.assertFromString(contractId) + + def contractSynchronizerId(): Option[String] = + participant.ledger_api.state.acs + .active_contracts_of_party(party = party) + .find(_.createdEvent.value.contractId == contractId) + .map(_.synchronizerId) + + contractSynchronizerId() shouldBe Some(globalSynchronizerId.toProtoPrimitive) + + // Scope the reassignment event format to `party` + val eventFormat = + EventFormat( + filtersByParty = Map( + party.toProtoPrimitive -> Filters( + Seq( + CumulativeFilter( + IdentifierFilter.WildcardFilter( + WildcardFilter(includeCreatedEventBlob = false) + ) + ) + ) + ) + ), + filtersForAnyParty = None, + verbose = true, + ) + + def reassign(source: SynchronizerId, target: SynchronizerId): Unit = { + val unassigned = participant.ledger_api.commands + .submit_unassign_with_format( + submitter = party, + contractIds = Seq(lfContractId), + source = source, + target = target, + userId = ledgerApiUserId, + eventFormat = Some(eventFormat), + timeout = None, + ) + .unassignedWrapper + val _ = participant.ledger_api.commands.submit_assign_with_format( + submitter = party, + reassignmentId = unassigned.reassignmentId, + source = source, + target = target, + userId = ledgerApiUserId, + eventFormat = Some(eventFormat), + timeout = None, + ) + } + + actAndCheck( + "Reassign the contract to the app-synchronizer", + reassign(globalSynchronizerId, appSynchronizerId), + )( + "The contract is now assigned to the app-synchronizer", + _ => contractSynchronizerId() shouldBe Some(appSynchronizerId.toProtoPrimitive), + ) + + actAndCheck( + "Reassign the contract back to the global synchronizer", + reassign(appSynchronizerId, globalSynchronizerId), + )( + "The contract is assigned to the global synchronizer again", + _ => contractSynchronizerId() shouldBe Some(globalSynchronizerId.toProtoPrimitive), + ) + } + + "docker-compose based localnet supports reassignment between synchronizers" in { implicit env => + withLocalNet() { implicit env => + testReassignment("app-provider", "providerValidatorClient") + testReassignment("app-user", "userValidatorClient") + } + } +} diff --git a/cluster/compose/localnet/conf/console/app-synchronizer.sc b/cluster/compose/localnet/conf/console/app-synchronizer.sc index 1b782b0d15..bc09c5e5cd 100644 --- a/cluster/compose/localnet/conf/console/app-synchronizer.sc +++ b/cluster/compose/localnet/conf/console/app-synchronizer.sc @@ -1,4 +1,4 @@ -bootstrap.synchronizer( +val appSynchronizerId = bootstrap.synchronizer( synchronizerName = "app-synchronizer", sequencers = Seq(`app-sequencer`), mediators = Seq(`app-mediator`), @@ -14,3 +14,54 @@ utils.retry_until_true { `app-provider`.synchronizers.active("app-synchronizer") && `app-user`.synchronizers.active("app-synchronizer") } + +// Enable the multi-synchronizer topology feature flag on every synchronizer each +// participant is connected to +val multiSyncParticipants = Seq(`app-provider`, `app-user`) + +// Wait until the participants are also connected to the global synchronizer, otherwise +// we would only enable the flag on the app-synchronizer. +utils.retry_until_true { + multiSyncParticipants.forall( + _.synchronizers.list_connected().exists(_.synchronizerId != appSynchronizerId.logical) + ) +} + +val multiSyncFeatureFlag = + SynchronizerTrustCertificate.ParticipantTopologyFeatureFlag.EnableMultiSynchronizer +multiSyncParticipants.foreach { participant => + participant.synchronizers.list_connected().map(_.synchronizerId).distinct.foreach { + synchronizerId => + val existingFlags = participant.topology.synchronizer_trust_certificates + .list( + store = Some(TopologyStoreId.Synchronizer(synchronizerId)), + filterUid = participant.id.filterString, + ) + .map(_.item.featureFlags) + .flatten + .distinct + if (!existingFlags.contains(multiSyncFeatureFlag)) { + participant.topology.synchronizer_trust_certificates + .propose( + participant.id, + synchronizerId, + featureFlags = existingFlags :+ multiSyncFeatureFlag, + ) + } + } +} + +// Ensure the flag became effective on all synchronizers before the console exits. +utils.retry_until_true { + multiSyncParticipants.forall { participant => + participant.synchronizers.list_connected().map(_.synchronizerId).distinct.forall { + synchronizerId => + participant.topology.synchronizer_trust_certificates + .list( + store = Some(TopologyStoreId.Synchronizer(synchronizerId)), + filterUid = participant.id.filterString, + ) + .exists(_.item.featureFlags.contains(multiSyncFeatureFlag)) + } + } +} diff --git a/test-full-class-names-docker-no-canton.log b/test-full-class-names-docker-no-canton.log index 4e8668c1bd..68b85d6602 100644 --- a/test-full-class-names-docker-no-canton.log +++ b/test-full-class-names-docker-no-canton.log @@ -1,4 +1,5 @@ org.lfdecentralizedtrust.splice.integration.tests.LocalNetFrontendIntegrationTest +org.lfdecentralizedtrust.splice.integration.tests.LocalNetReassignIntegrationTest org.lfdecentralizedtrust.splice.scan.store.bulk.AcsSnapshotBulkStorageCommitFromStagingTest org.lfdecentralizedtrust.splice.scan.store.bulk.AcsSnapshotBulkStorageWriterFromDbTest org.lfdecentralizedtrust.splice.scan.store.bulk.BulkStorageCommitFromStagingTest From 126f7cfdceb7f8f9e193e2708ed168dc985290d9 Mon Sep 17 00:00:00 2001 From: Martin Florian Date: Thu, 20 Aug 2026 10:37:43 +0200 Subject: [PATCH 277/329] Remove in-app rate limit override for `getDateOfMostRecentSnapshotBefore` (#6875) [static] A client gets rate limited on this on MainNet DA-2. Upon closer inspection, this endpoint triggers a very cheap query answered by an index scan: https://github.com/canton-network/splice/blob/1973921ef727fded6d6d3734b00e7eeca4010b51/apps/scan/src/main/scala/org/lfdecentralizedtrust/splice/scan/store/AcsSnapshotStore.scala#L72 This doesn't sound as if special rate-limiting is needed at all. Signed-off-by: Martin Florian --- cluster/images/scan-app/app.conf | 1 - 1 file changed, 1 deletion(-) diff --git a/cluster/images/scan-app/app.conf b/cluster/images/scan-app/app.conf index c78b48e7f6..7b5ec38a3a 100644 --- a/cluster/images/scan-app/app.conf +++ b/cluster/images/scan-app/app.conf @@ -77,7 +77,6 @@ canton { rate-limiters { getAcsSnapshot.rate-per-second = 20 getAcsSnapshotAt.rate-per-second = 10 - getDateOfMostRecentSnapshotBefore.rate-per-second = 10 } } # TODO(DACH-NY/canton-network-internal#2125) Revisit timeouts on 3.4 From 7b1084b65a773992c59a8799a06cbcb104db5544 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:02:03 +0200 Subject: [PATCH 278/329] Bump Canton to 3.5.14 (#6887) [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- nix/canton-sources.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nix/canton-sources.json b/nix/canton-sources.json index bfbef73901..13dfbe82f2 100644 --- a/nix/canton-sources.json +++ b/nix/canton-sources.json @@ -1,8 +1,8 @@ { - "version": "3.5.14-snapshot.20260819.19183.0.va7a6d3ae", - "oss_sha256": "sha256:12xz6wgv7piylwnw11qjiwy1xmp66ak54b28h0mjrb5n12q9yh58", - "canton_base_image_sha256": "sha256:169a896a20f10375395b7a360361eecd94055aa6f574eece83bbaec2393a0fdf", - "canton_participant_image_sha256": "sha256:9229c01e8cc93be26fc46c3d572ec191e7f06161e9d0ca8fba455e841de17363", - "canton_mediator_image_sha256": "sha256:b963d94b31cf2140eb0819af6a849b23e8c7799c920bf736d99672211125a8e9", - "canton_sequencer_image_sha256": "sha256:e321c8fe1b3ea4a4211b6791e14b6e63c4fd2606a6534338eca1ed27d8f8f3b1" + "version": "3.5.14", + "oss_sha256": "sha256:1xg07rn2cqxgkx9gzvjqazcf1nbz4b0grwf7bmrwq127kskspciv", + "canton_base_image_sha256": "sha256:b99d7fd82aa01b9983e0b200ea645d5d0030fd8c45518f2f58b609d5eee74f00", + "canton_participant_image_sha256": "sha256:ba3b8d071716f9a963c894234cf28a2f3b123b6e440c1493cb1032fdfa9598bf", + "canton_mediator_image_sha256": "sha256:a64991f2d6ca820a844868b9c46bb6bc239f1031dc30445a171ad8b1691d6565", + "canton_sequencer_image_sha256": "sha256:36b19cfde59f4ce5c9630ec49d5f93bd51083f34ca06751112d7886c10800eb7" } From 386eaff2d7c147fff8f7543713c32240f25e3faf Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Thu, 20 Aug 2026 16:26:38 +0200 Subject: [PATCH 279/329] Clear release notes for 0.7.4 (#6888) [static] Signed-off-by: Nicu Reut --- docs/src/release_notes_upcoming.rst | 63 ----------------------------- 1 file changed, 63 deletions(-) diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index ad63b6151d..dc39b70538 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -6,66 +6,3 @@ .. NOTE: add your upcoming release notes below this line. They are included in the `release_notes.rst`. release-notes:: Upcoming - - - Scan app - - - The ``enable-app-activity-record-and-traffic-ingestion`` and - ``serve-app-activity-records-and-traffic`` configuration options have been removed. App - activity records and sequencer traffic are now always ingested, and app activity is - always computed and served on the corresponding HTTP endpoints. - - - SV App - - - The SV app now exposes a ``splice.sv_vote_requests.active`` metric counting the active - vote requests by their state relative to the SV (``action_needed``, ``in_progress``, - ``ready_to_close``), allowing SV operators to alert on vote proposals that require - their vote. - - - Scan & SV App - - - HTTP rate limiting has been extended with a global rate limiter applied across all - operations, optional per-client-IP rate limiting (enabled by default at the global level), - and an additional sustained rate limit enforced over a longer window on top of the existing - per-second burst limit. The client IP is taken from the trusted, non-spoofable - ``X-Envoy-External-Address`` header set by the Envoy/Istio ingress, falling back to the - client-controlled ``X-Forwarded-For``/ ``X-Real-Ip`` headers and finally the remote - address only for requests that did not pass through the ingress. These can be tuned via - the ``rate-limiting`` config keys. - - .. warning:: - - When per-client-IP rate limiting is enabled, SV operators must ensure that the client IP - used for rate limiting cannot be spoofed. Either configure - ``rate-limiting.trusted-client-ip-header`` to a trusted, non-spoofable header set by - your ingress/proxy (e.g. ``x-envoy-external-address`` for Istio deployments), or ensure - that the ``X-Forwarded-For`` header contains the actual client IP as its first value - and cannot be spoofed by clients. Otherwise, clients may bypass per-client-IP limits or - cause other clients to be throttled by forging these headers. - - The fallback to the client-controlled ``X-Forwarded-For``/ ``X-Real-Ip`` headers can be - disabled by setting ``rate-limiting.enable-client-provided-ip-headers`` to ``false``. - If no IP can be extracted no per IP rate limit is enforced. - - - Default rate limits have been adjusted: - - - Scan app: the per-operation burst limit has been lowered from 200 to 100 requests per - second, with a new sustained limit of 50 requests per second. A new global limiter has - also been added, allowing 400 requests per second burst / 200 sustained across all - operations combined, with an embedded per-client-IP limiter allowing 100 requests per - second burst / 50 sustained. - - SV app: the per-operation burst limit has been lowered from 200 to 20 requests per - second, with a new sustained limit of 10 requests per second. A new global limiter has - also been added, allowing 100 requests per second burst / 50 sustained across all - operations combined, with an embedded per-client-IP limiter allowing 20 requests per - second burst / 10 sustained. - - - CometBFT - - - Added a watchdog to restart cometbft when we detect that it - is replaying messages. You must set - ``watchdog.sequencerMetricsUrl: http://global-domain-SERIAL_ID-sequencer:10013/metrics`` and - ``watchdog.mediatorMetricsUrl: http://global-domain-SERIAL_ID-mediator:10013/metrics`` in the - cometbft helm values. If needed, the watchdog can be disabled through ``watchdog.enabled: false``. - - - - Bump the default ``deduplicationCacheSize`` to ``1000000``. From 7ac21fee4a7ca730d4ae4e5326cc7bcc26728640 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Thu, 20 Aug 2026 18:56:21 +0200 Subject: [PATCH 280/329] Update versions after 0.7.4 (#6890) [ci] Signed-off-by: Nicu Reut --- LATEST_RELEASE | 2 +- VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LATEST_RELEASE b/LATEST_RELEASE index f38fc5393f..0a1ffad4b4 100644 --- a/LATEST_RELEASE +++ b/LATEST_RELEASE @@ -1 +1 @@ -0.7.3 +0.7.4 diff --git a/VERSION b/VERSION index 0a1ffad4b4..8bd6ba8c5c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.7.4 +0.7.5 From 6ff08be0378dcc5087cfacb6701b1855e0a07456 Mon Sep 17 00:00:00 2001 From: Itai Segall Date: Thu, 20 Aug 2026 13:08:32 -0400 Subject: [PATCH 281/329] handle missing objects correctly when getting s3 checksums (#6844) * [ci] handle missing objects correctly when getting s3 checksums Signed-off-by: Itai Segall * [ci] better logging Signed-off-by: Itai Segall * [ci] . Signed-off-by: Itai Segall * [ci] fmt Signed-off-by: Itai Segall --------- Signed-off-by: Itai Segall --- .../splice/store/S3BucketConnection.scala | 28 +++++++++++++------ .../splice/scan/store/bulk/S3UploadTest.scala | 13 +++++++-- 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/S3BucketConnection.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/S3BucketConnection.scala index 44a8c89caa..fe992c363b 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/S3BucketConnection.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/store/S3BucketConnection.scala @@ -117,27 +117,39 @@ class S3BucketConnection( def getChecksums( objectKeys: Seq[String] - )(implicit ec: ExecutionContext, as: ActorSystem): Future[Seq[ObjectKeyAndChecksum]] = { + )(implicit + ec: ExecutionContext, + as: ActorSystem, + tc: TraceContext, + ): Future[Seq[ObjectKeyAndChecksum]] = { Source(objectKeys.toList) .mapAsync(4) { key => // TODO(#3429): make this parallelism configurable readChecksum(key) - .map(checksum => ObjectKeyAndChecksum(key, checksum)) + .map(checksum => checksum.map(ObjectKeyAndChecksum(key, _))) } + .collect { case Some(obj) => obj } .runWith(Sink.seq[ObjectKeyAndChecksum]) } - private def readChecksum(key: String)(implicit ec: ExecutionContext): Future[String] = { + private def readChecksum( + key: String + )(implicit ec: ExecutionContext, tc: TraceContext): Future[Option[String]] = { val headRequest = HeadObjectRequest .builder() .bucket(bucketName) .key(key) .build() for { - head <- s3Client.headObject(headRequest).asScala - checksum = head - .metadata() - .asScala - .getOrElse("splice-checksum", throw new RuntimeException("Missing checksum metadata")) + head <- s3Client.headObject(headRequest).asScala.map(Some(_)).recover { case e => + // TODO(#3429): distinguish between "object not found" and other errors, probably want to catch only NoSuchKeyException, and throw everything else + logger + .debug(s"Failed to read checksum for object $key, object may not exist: ${e.getMessage}") + None + } + checksum = head.map( + _.metadata().asScala + .getOrElse("splice-checksum", throw new RuntimeException("Missing checksum metadata")) + ) } yield checksum } diff --git a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/S3UploadTest.scala b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/S3UploadTest.scala index 62d6754e3e..61a0883a79 100644 --- a/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/S3UploadTest.scala +++ b/apps/scan/src/test/scala/org/lfdecentralizedtrust/splice/scan/store/bulk/S3UploadTest.scala @@ -6,12 +6,11 @@ package org.lfdecentralizedtrust.splice.scan.store.bulk import org.apache.pekko.stream.scaladsl.Keep import org.apache.pekko.stream.testkit.scaladsl.{TestSink, TestSource} import org.apache.pekko.util.ByteString -import org.lfdecentralizedtrust.splice.store.{HasS3Mock, StoreTestBase} +import org.lfdecentralizedtrust.splice.store.{HasS3Mock, S3BucketConnection, StoreTestBase} import scala.util.Random import scala.concurrent.duration.* import scala.jdk.CollectionConverters.* - import java.nio.ByteBuffer class S3UploadTest extends StoreTestBase with HasS3Mock { @@ -59,6 +58,7 @@ class S3UploadTest extends StoreTestBase with HasS3Mock { .run() val it = data.iterator + def sendBytes(n: Int) = pub.sendNext(it.getByteString(n)) @@ -105,6 +105,7 @@ class S3UploadTest extends StoreTestBase with HasS3Mock { .run() val it = data.iterator + def sendBytes(n: Int) = pub.sendNext(it.getByteString(n)) @@ -116,4 +117,12 @@ class S3UploadTest extends StoreTestBase with HasS3Mock { succeed } } + + "S3BucketConnection" should { + "Get checksums does not panic on non existing object" in { + val bucketConnection = new S3BucketConnection(s3ConfigMock(), loggerFactory) + val checksum = bucketConnection.getChecksums(Seq("non-existing-object")).futureValue + checksum shouldBe empty + } + } } From f819bd6f0444cf00bfdbda9436c6e72aa07cf55f Mon Sep 17 00:00:00 2001 From: canton-network-da Date: Thu, 20 Aug 2026 16:04:27 -0400 Subject: [PATCH 282/329] Backport PR #6891 (don't OOM on missing projectId with bigQuery.enableStagProdDatastream=true) to main (#6893) --------- Signed-off-by: Stephen Compall Signed-off-by: DA Automation Co-authored-by: Stephen Compall Co-authored-by: DA Automation --- cluster/expected/canton-network/expected.json | 4 ++-- cluster/pulumi/common/src/dump-config-common.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cluster/expected/canton-network/expected.json b/cluster/expected/canton-network/expected.json index ed10ee29e4..c4613dce75 100644 --- a/cluster/expected/canton-network/expected.json +++ b/cluster/expected/canton-network/expected.json @@ -188,8 +188,8 @@ "custom": true, "id": "", "inputs": { - "create": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' create-pub-rep-slot \\\n --private-network-project=\"undefined\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n ", - "delete": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' delete-pub-rep-slot \\\n --private-network-project=\"undefined\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n " + "create": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' create-pub-rep-slot \\\n --private-network-project=\"test-project\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n ", + "delete": "'SPLICE_ROOT/cluster/pulumi/canton-network/bigquery-cloudsql.sh' delete-pub-rep-slot \\\n --private-network-project=\"test-project\" \\\n --compute-region=\"europe-west6\" \\\n --service-account-email=\"undefined\" \\\n --schema-name=\"scan_sv_1\" \\\n --tables-to-replicate-joined=\"update_history_creates, update_history_exercises, scan_verdict_store, scan_verdict_transaction_view_store, app_activity_record_store\" \\\n --postgres-user-name=\"cnadmin\" \\\n --publication-name=\"update_history_datastream_pub\" \\\n --replication-slot-name=\"update_history_datastream_r_slot\" \\\n --replicator-user-name=\"bqdatastream\" \\\n --postgres-instance-name=\"undefined\" \\\n --scan-app-database-name=\"scan_sv_1\" \\\n --flyway-migration-to-wait-for=\"V068__app_activity_record_meta.sql\" \\\n " }, "name": "sv-1-bqdatastream-pub-replicate-slots", "provider": "", diff --git a/cluster/pulumi/common/src/dump-config-common.ts b/cluster/pulumi/common/src/dump-config-common.ts index 0fd783a4b2..1117b23d4a 100644 --- a/cluster/pulumi/common/src/dump-config-common.ts +++ b/cluster/pulumi/common/src/dump-config-common.ts @@ -253,7 +253,7 @@ export async function initDumpConfig({ result: `base64-decoded-mock`, }; case PulumiFunction.GCP_GET_PROJECT: - return { ...args.inputs, name: projectName }; + return { ...args.inputs, name: projectName, projectId: projectName }; case PulumiFunction.GCP_GET_SUB_NETWORK: if (args.inputs.name === `cn-${stackName}net-subnet`) { return { ...args.inputs, id: 'subnet-id' }; From 7fc63dd5ccefe970efd6ea2240e40c5cf0792200 Mon Sep 17 00:00:00 2001 From: Julien Tinguely Date: Fri, 21 Aug 2026 11:13:35 +0200 Subject: [PATCH 283/329] Stop scan connection alerts triggering when value is NaN (#6898) Signed-off-by: Julien Tinguely --- cluster/expected/observability/expected.json | 2 +- .../grafana-alerting/scan_connection_disagreement_alerts.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index ed6653c777..f93f603bad 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -90,7 +90,7 @@ "notification_policies.yaml": "apiVersion: 1\npolicies:\n - orgId: 1\n receiver: cn-ci-channel-notification\n routes:\n - receiver: cn-ci-channel-notification\n object_matchers:\n - - namespace\n - '='\n - sv1-test\n mute_time_intervals:\n - sv1-mute\n - receiver: cn-ci-channel-notification\n object_matchers:\n - - namespace\n - '='\n - sv-test\n mute_time_intervals:\n - sv-runbook-mute\n - receiver: cn-ci-channel-high-prio-notification\n object_matchers:\n - - priority\n - '='\n - high\n group_wait: 30s\n group_interval: 1m\n repeat_interval: 4h\n continue: true\n - receiver: cn-ci-channel-notification\n object_matchers:\n - - team\n - '!='\n - support\n group_wait: 30s\n group_interval: 10m\n repeat_interval: 4h\n", "pruning_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: pruning\n folder: canton-network\n interval: 10m\n rules:\n - uid: ff3sfady2o6bke\n title: Participant pruning\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: daml_pruning_max_event_age\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 720\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 10m\n annotations:\n description: The participant in {{ $labels.namespace }} on migration ID {{ $labels.migration_id }} has not been pruned {{ 30 }} days.\n summary: Participant has not pruned ACS.\n labels:\n \"\": \"\"\n isPaused: false\n - uid: df3sfgne49bswb\n title: Sequencer Pruning\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: daml_sequencer_max_event_age\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 720\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 10m\n annotations:\n description: The sequencer in {{ $labels.namespace }} on migration ID {{ $labels.migration_id }} has not been pruned {{ 30 }} days.\n summary: Sequencer has not been pruned.\n labels:\n \"\": \"\"\n isPaused: false\n - uid: ef3sfj563dq0we\n title: Mediator Pruning\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n disableTextWrap: false\n editorMode: builder\n expr: daml_mediator_max_event_age\n fullMetaSearch: false\n includeNullMetadata: true\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: A\n useBackend: false\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 720\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n noDataState: Alerting\n execErrState: Alerting\n for: 10m\n annotations:\n description: The mediator in {{ $labels.namespace }} on migration ID {{ $labels.migration_id }} has not been pruned {{ 30 }} days.\n summary: Mediator has not been pruned.\n labels:\n \"\": \"\"\n isPaused: false", "scan_bft_sequencers_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: scan bft sequencer reads\n folder: canton-network\n interval: 10m\n rules:\n - uid: aesvbftscanreadfail1\n title: SV failing to read BFT sequencer list from a scan\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n sum by (namespace, job, target_host) (\n histogram_count(increase(daml_http_client_requests_duration_seconds{http_client=\"HttpScanAppClient\", operation=\"ListBftSequencers\", status_code!~\"2..\"}[30m]))\n )\n /\n sum by (namespace, job, target_host) (\n histogram_count(increase(daml_http_client_requests_duration_seconds{http_client=\"HttpScanAppClient\", operation=\"ListBftSequencers\"}[30m]))\n )\n instant: true\n intervalMs: 1000\n legendFormat: '{{namespace}}:{{job}}:{{target_host}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.5\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.5\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: a8a3113a-bccf-4728-b752-dd7a5d6f9bda\n panelId: 2\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: a8a3113a-bccf-4728-b752-dd7a5d6f9bda\n __panelId__: \"2\"\n description: More than 50% of this SV's reads of the BFT sequencer list (/v0/sv-bft-sequencers) from this scan failed (non-2xx response, timeout, or connection error) over the last 30 minutes. The BFT peer reconciler tolerates individual failures safely, but while this scan is unreachable the SV cannot pick up BFT sequencer endpoint changes published by that scan's SV.\n severity: warning\n summary: SV {{ $labels.job }} in namespace {{ $labels.namespace }} failed {{ index $values \"A\" }} of its BFT sequencer list reads from scan {{ $labels.target_host }} in the last 30m.\n labels: {}\n isPaused: false\n", - "scan_connection_disagreement_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: scan connection consensus\n folder: canton-network\n interval: 10m\n rules:\n - uid: aescandisagreeok1\n title: Scan connection returning successful responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n max by (scan_connection, request) (\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"true\", request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\"}[30m]))\n )\n instant: true\n intervalMs: 1000\n legendFormat: '{{scan_connection}}:{{request}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons for this request on this scan connection returned a successful (2xx) response that disagreed with the BFT consensus result over the last 30 minutes.\n severity: critical\n summary: Scan connection {{ $labels.scan_connection }} returned successful responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons for request {{ $labels.request }} in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n - uid: aescandisagreefail1\n title: Scan connection returning failed responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n max by (scan_connection) (\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"false\", http_status!~\"404\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{http_status!~\"404\"}[30m]))\n )\n instant: true\n intervalMs: 1000\n legendFormat: '{{scan_connection}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons on this scan connection returned a failed (non-2xx) response that disagreed with the BFT consensus result over the last 30 minutes. This can indicate the scan node is down.\n severity: warning\n summary: Scan connection {{ $labels.scan_connection }} returned failed responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n", + "scan_connection_disagreement_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: scan connection consensus\n folder: canton-network\n interval: 10m\n rules:\n - uid: aescandisagreeok1\n title: Scan connection returning successful responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n max by (scan_connection, request) (\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"true\", request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{request!~\"getTransferInstructionAcceptContext|getOpenAndIssuingMiningRounds\"}[30m]))\n ) > 0\n instant: true\n intervalMs: 1000\n legendFormat: '{{scan_connection}}:{{request}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons for this request on this scan connection returned a successful (2xx) response that disagreed with the BFT consensus result over the last 30 minutes.\n severity: critical\n summary: Scan connection {{ $labels.scan_connection }} returned successful responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons for request {{ $labels.request }} in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n - uid: aescandisagreefail1\n title: Scan connection returning failed responses that disagree with BFT consensus\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 1800\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: |-\n max by (scan_connection) (\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus=\"disagree\", success=\"false\", http_status!~\"404\"}[30m]))\n /\n sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{http_status!~\"404\"}[30m]))\n ) > 0\n instant: true\n intervalMs: 1000\n legendFormat: '{{scan_connection}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.1\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: last\n type: query\n unloadEvaluator:\n params:\n - 0.1\n - 0\n type: lte\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: cnndx7p\n panelId: 6\n noDataState: OK\n execErrState: OK\n for: 0m\n annotations:\n __dashboardUid__: cnndx7p\n __panelId__: \"6\"\n description: More than 10% of the BFT consensus comparisons on this scan connection returned a failed (non-2xx) response that disagreed with the BFT consensus result over the last 30 minutes. This can indicate the scan node is down.\n severity: warning\n summary: Scan connection {{ $labels.scan_connection }} returned failed responses disagreeing with BFT consensus on {{ index $values \"A\" }} of comparisons in the last 30m (threshold 10%).\n labels: {}\n isPaused: false\n", "sequencer_client_delay_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer client delay\n folder: canton\n interval: 1m\n rules:\n - uid: efe3v7bed6134e\n title: Sequencer Client Delay\n condition: client delay threshold exceeded\n data:\n - refId: client delay\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: min by (namespace, job) (timestamp(daml_sequencer_client_handler_last_sequencing_time_micros) - (daml_sequencer_client_handler_last_sequencing_time_micros>0)/1e6)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: client delay\n - refId: client delay threshold exceeded\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 90\n type: gt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: client delay\n intervalMs: 1000\n maxDataPoints: 43200\n refId: client delay threshold exceeded\n type: threshold\n dashboardUid: ca9df344-c699-4efe-83c2-5fb2639d96d9\n panelId: 3\n noDataState: Alerting\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: ca9df344-c699-4efe-83c2-5fb2639d96d9\n __panelId__: \"3\"\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} is reporting an increased sequencer client delay.\n severity: critical\n labels: {}\n isPaused: false\n", "sequencer_connection_pool_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer connection pool\n folder: canton\n interval: 1m\n rules:\n - uid: dfeb9wma1dk3kc\n title: Health of a connection\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n adhocFilters: []\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (connection, namespace, job) (daml_sequencer_client_sequencer_connection_pool_connection_health{namespace=~\".*\", job=~\".*\"})\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}}:{{job}}:{{connection}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 2\n type: lt\n operator:\n type: and\n query:\n params:\n - C\n reducer:\n params: []\n type: last\n type: query\n datasource:\n type: __expr__\n uid: __expr__\n expression: A\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: threshold\n dashboardUid: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n panelId: 6\n noDataState: NoData\n execErrState: Error\n for: 1m\n annotations:\n __dashboardUid__: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n __panelId__: \"6\"\n description: A failed connection is periodically retried for availability. A fatal subscription is considered invalid and will never be retried.\n summary: The sequencer connection {{ $labels.connection }} of job {{ $labels.job }} in namespace {{ $labels.namespace }} failed validation.\n isPaused: false\n - uid: dfebau7w2r7cwa\n title: Number of active subscriptions in the subscription pool\n condition: C\n data:\n - refId: A\n relativeTimeRange:\n from: 21600\n to: 0\n datasourceUid: prometheus\n model:\n adhocFilters: []\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (namespace, job) (daml_sequencer_client_sequencer_connection_pool_active_subscriptions{namespace=~\".*\",job=~\".*\"})\n instant: true\n interval: \"\"\n intervalMs: 30000\n legendFormat: '{{namespace}}:{{job}}'\n maxDataPoints: 43200\n range: false\n refId: A\n - refId: B\n relativeTimeRange:\n from: 600\n to: 0\n datasourceUid: prometheus\n model:\n datasource:\n type: prometheus\n uid: prometheus\n editorMode: code\n expr: max by (namespace, job) (daml_sequencer_client_sequencer_connection_pool_subscription_threshold{namespace=~\".*\",job=~\".*\"})\n hide: false\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: B\n - refId: C\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: $A-$B\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: C\n type: math\n - refId: D\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n - 0\n type: lt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: C\n hide: false\n intervalMs: 1000\n maxDataPoints: 43200\n refId: D\n type: threshold\n dashboardUid: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n panelId: 5\n noDataState: NoData\n execErrState: Error\n for: 5m\n annotations:\n __dashboardUid__: 50503893-d05f-4ece-9ebf-36fd20ffb06f\n __panelId__: \"5\"\n description: The liveness margin determines how many subscriptions on different sequencers are continuously maintained, beyond the minimum number defined by the trust threshold. In other words, the subscription pool will strive to maintain at all times (trust threshold + liveness margin)-many subscriptions active. This provides tolerance to subscriptions falling, enabling the node to continue operating while some sequencers are down.\n summary: The job {{ $labels.job }} in namespace {{ $labels.namespace }} has {{ index $values \"C\" }} less active subscriptions than the sum of trust threshold in the subscription pool\n severity: critical\n labels: {}\n isPaused: false\n", "sequencer_rate_limit_alerts.yaml": "apiVersion: 1\ngroups:\n - orgId: 1\n name: sequencer-rate-limits\n folder: canton\n interval: 1m\n rules:\n - uid: a3f7c8e1d492\n title: Sequencer Throughput Cap Rejections\n condition: rejection_rate_threshold\n data:\n - refId: rejection_rate\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: sum by (namespace, job)(rate(daml_sequencer_throughput_cap_confirmation_request_rejections_total[5m]))\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: rejection_rate\n - refId: rejection_rate_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: rejection_rate\n hide: false\n refId: rejection_rate_threshold\n type: threshold\n dashboardUid: eb26bc60-6c26-408a-b0de-befc19f6915c\n panelId: 3\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eb26bc60-6c26-408a-b0de-befc19f6915c\n __panelId__: \"3\"\n description: Sequencer throughput cap is rejecting confirmation requests on namespace {{ $labels.namespace }} job {{ $labels.job }}. This means members are exceeding their rate limits.\n severity: warning\n summary: Sequencer throughput cap rejections detected on {{ $labels.namespace }} {{ $labels.job }}\n labels: {}\n isPaused: false\n - uid: b4e8d9f2e5a3\n title: Sequencer Circuit Breaker Open\n condition: circuit_breaker_threshold\n data:\n - refId: circuit_breaker_state\n relativeTimeRange:\n from: 300\n to: 0\n datasourceUid: prometheus\n model:\n editorMode: code\n expr: max by (namespace, job)(daml_sequencer_circuit_breaker_confirmation_request_state)\n instant: true\n intervalMs: 1000\n legendFormat: __auto\n maxDataPoints: 43200\n range: false\n refId: circuit_breaker_state\n - refId: circuit_breaker_threshold\n datasourceUid: __expr__\n model:\n conditions:\n - evaluator:\n params:\n - 0.5\n type: gt\n operator:\n type: and\n query:\n params: []\n reducer:\n params: []\n type: avg\n type: query\n datasource:\n name: Expression\n type: __expr__\n uid: __expr__\n expression: circuit_breaker_state\n hide: false\n refId: circuit_breaker_threshold\n type: threshold\n dashboardUid: eb26bc60-6c26-408a-b0de-befc19f6915c\n panelId: 10\n noDataState: OK\n execErrState: Alerting\n for: 5m\n annotations:\n __dashboardUid__: eb26bc60-6c26-408a-b0de-befc19f6915c\n __panelId__: \"10\"\n description: Sequencer circuit breaker for confirmation requests is open on namespace {{ $labels.namespace }} job {{ $labels.job }}. The sequencer is rejecting all incoming confirmation requests.\n severity: critical\n summary: Sequencer circuit breaker OPEN on {{ $labels.namespace }} {{ $labels.job }}\n labels: {}\n isPaused: false\n", diff --git a/cluster/pulumi/observability/grafana-alerting/scan_connection_disagreement_alerts.yaml b/cluster/pulumi/observability/grafana-alerting/scan_connection_disagreement_alerts.yaml index b537fe47c1..d5012f7f21 100644 --- a/cluster/pulumi/observability/grafana-alerting/scan_connection_disagreement_alerts.yaml +++ b/cluster/pulumi/observability/grafana-alerting/scan_connection_disagreement_alerts.yaml @@ -24,7 +24,7 @@ groups: sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus="disagree", success="true"$SCAN_DISAGREEMENT_FILTER}[30m])) / sum by (namespace, job, node_name, scan_connection, request) (increase(splice_validator_scan_bft_per_connection_consensus_total{$SCAN_DISAGREEMENT_FILTER_BARE}[30m])) - ) + ) > 0 instant: true intervalMs: 1000 legendFormat: '{{scan_connection}}:{{request}}' @@ -94,7 +94,7 @@ groups: sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{consensus="disagree", success="false"$SCAN_DISAGREEMENT_CONNECTION_FILTER}[30m])) / sum by (namespace, job, node_name, scan_connection) (increase(splice_validator_scan_bft_per_connection_consensus_total{$SCAN_DISAGREEMENT_CONNECTION_FILTER_BARE}[30m])) - ) + ) > 0 instant: true intervalMs: 1000 legendFormat: '{{scan_connection}}' From 3e4bb9a094ad7f91f456b64d8f891827911f5657 Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:18:09 +0200 Subject: [PATCH 284/329] Add missing sbt dependency (#6897) Seen here as a failure https://github.com/canton-network/splice/actions/runs/32459976647/job/96705530387?pr=6896 [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- build.sbt | 1 + 1 file changed, 1 insertion(+) diff --git a/build.sbt b/build.sbt index 42963f079d..1fc2d2e91a 100644 --- a/build.sbt +++ b/build.sbt @@ -2399,6 +2399,7 @@ lazy val `apps-app`: Project = `canton-community-app` % "compile->compile;test->test", `canton-community-base`, `canton-community-integration-testing` % "test", + `splice-amulet-test-daml` % "test", `splice-util-featured-app-proxies-daml` % "test", // necessary for token-standard-cli to get `npm install`ed so that TokenStandardCliSanityCheckPlugin can run `apps-common-frontend`, From 3493e52f66ae1d9ba6d3cb3c3d0cb3ee551cf63c Mon Sep 17 00:00:00 2001 From: moritzkiefer-da <45630097+moritzkiefer-da@users.noreply.github.com> Date: Fri, 21 Aug 2026 13:07:29 +0200 Subject: [PATCH 285/329] Avoid racy synchronizer connection query (#6896) fixes https://github.com/DACH-NY/cn-test-failures/issues/9447 and supersedes #6642 Imho this is the proper fix: Avoid making a separate query after the retry when the state may already have changed. [ci] Signed-off-by: moritz.kiefer@digitalasset.com Co-authored-by: moritz.kiefer@digitalasset.com --- .../ParticipantAdminSynchronizerConnection.scala | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ParticipantAdminSynchronizerConnection.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ParticipantAdminSynchronizerConnection.scala index 7da60e8e1a..81e3fd45d4 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ParticipantAdminSynchronizerConnection.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/environment/ParticipantAdminSynchronizerConnection.scala @@ -12,6 +12,7 @@ import com.digitalasset.canton.admin.api.client.data.{ import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.SynchronizerAlias import com.digitalasset.canton.admin.api.client.data +import com.digitalasset.canton.logging.pretty.Pretty import com.digitalasset.canton.participant.synchronizer.SynchronizerConnectionConfig import com.digitalasset.canton.sequencing.SequencerConnectionValidation import com.digitalasset.canton.topology.{PhysicalSynchronizerId, SynchronizerId} @@ -160,6 +161,9 @@ trait ParticipantAdminSynchronizerConnection { ): Future[Unit] = runCmd(ParticipantAdminCommands.SynchronizerConnectivity.DisconnectSynchronizer(alias)) + private implicit val prettyRegisteredSynchronizer: Pretty[RegisteredSynchronizer] = + Pretty.adHocPrettyInstance + def ensureSynchronizerRegisteredWithManualConnect( config: SynchronizerConnectionConfig, retryFor: RetryFor, @@ -169,15 +173,14 @@ trait ParticipantAdminSynchronizerConnection { "manualConnect must be true when trying to register only", ) retryProvider - .ensureThat( + .ensureThatO( retryFor, "synchronizer_registered_no_handshake", s"participant registered ${config.synchronizerAlias}", - isSynchronizerRegistered(config.synchronizerAlias).map(Either.cond(_, (), ())), - (_: Unit) => registerSynchronizer(config), + listSynchronizerConnectionConfig(config.synchronizerAlias).map(_.headOption), + registerSynchronizer(config), logger, ) - .flatMap(_ => getRegisteredSynchronizer(config.synchronizerAlias)) } def ensureSynchronizerRegisteredAndConnected( From 24b1a8ac608f6c69d73ec38cada7e9278003118c Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Fri, 21 Aug 2026 15:13:16 +0200 Subject: [PATCH 286/329] client ip rate limiting - configurable headers (#6905) * client ip rate limiting - configurable headers [ci] Signed-off-by: Nicu Reut --- .../splice/config/RateLimitersConfig.scala | 30 ++-- .../splice/http/ClientIpDirectives.scala | 54 ++---- .../splice/http/HttpRateLimiter.scala | 11 +- .../splice/http/HttpRateLimiterTest.scala | 162 ++++++++---------- cluster/expected/sv-runbook/expected.json | 10 ++ cluster/expected/sv/expected.json | 16 ++ cluster/pulumi/common-sv/src/sv.ts | 5 +- cluster/pulumi/common-sv/src/svApp.ts | 4 +- .../common/src/ratelimit/rateLimitHeaders.ts | 16 ++ cluster/pulumi/sv-runbook/src/installNode.ts | 4 + docs/src/release_notes_upcoming.rst | 12 ++ 11 files changed, 170 insertions(+), 154 deletions(-) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala index e655c163d4..39626cd40f 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/config/RateLimitersConfig.scala @@ -15,20 +15,17 @@ case class RateLimitersConfig( /** Per-operation overrides of the overall `default` rate limiter. */ rateLimiters: Map[String, SpliceRateLimitConfig.WithPerClientIp] = Map.empty, global: SpliceRateLimitConfig.WithPerClientIp = RateLimitersConfig.DefaultGlobal, - /** Name of the HTTP header set by a trusted reverse proxy that carries the real client IP. This header must be set - and any - * client-provided value overwritten - by infrastructure the client cannot bypass, otherwise it - * can be spoofed. When present and parseable as an IP literal it takes precedence over the - * client-controlled `X-Forwarded-For`/`X-Real-Ip` headers. Set to an empty string to disable - * trusting a proxy header and only rely on `X-Forwarded-For`/`X-Real-Ip`/the remote address. + /** Names of the HTTP headers from which the client IP used for per-client-IP rate limiting is + * extracted, in order of precedence: the first header that is present and whose value (or, for + * comma separated lists such as `X-Forwarded-For`, whose first entry) parses as an IP literal + * is used. Set to an empty list to disable per-client-IP rate limiting. + * + * Note that the default headers are client-controlled and can hence be spoofed unless they are + * overwritten by infrastructure the client cannot bypass. In deployments with a trusted reverse + * proxy, configure the (non-spoofable) header set by that proxy instead, e.g. + * `["x-envoy-external-address"]` behind an Envoy proxy. */ - trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, - /** Whether to fall back to the client-controlled `X-Forwarded-For`/`X-Real-Ip` headers when the - * trusted proxy header (see `trustedClientIpHeader`) does not yield a client IP. Enabled by - * default. Set to `false` in deployments where a trusted proxy always sets - * `trustedClientIpHeader`, so that clients cannot influence the extracted IP - and thereby the - * per-client-IP rate limiting - by forging these spoofable headers. - */ - enableClientProvidedIpHeaders: Boolean = true, + clientIpHeaders: Seq[String] = RateLimitersConfig.DefaultClientIpHeaders, ) { def forRateLimiter(name: String): SpliceRateLimitConfig.WithPerClientIp = rateLimiters.getOrElse(name, default) @@ -36,10 +33,11 @@ case class RateLimitersConfig( object RateLimitersConfig { - /** Header set by the Envoy sidecar/ingress (Istio) to the trusted external client address that - * Envoy computes from its trusted-hops configuration. + /** The commonly used client IP headers, in order of precedence. Both are set by clients or + * reverse proxies and are hence only trustworthy if a proxy the client cannot bypass overwrites + * them. */ - val DefaultTrustedClientIpHeader: String = "x-envoy-external-address" + val DefaultClientIpHeaders: Seq[String] = Seq("x-forwarded-for", "x-real-ip") private val DefaultGlobal: SpliceRateLimitConfig.WithPerClientIp = SpliceRateLimitConfig.WithPerClientIp( diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala index 0d9ef4235c..0cea65550d 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/ClientIpDirectives.scala @@ -3,7 +3,7 @@ package org.lfdecentralizedtrust.splice.http -import org.apache.pekko.http.scaladsl.model.headers.{`X-Forwarded-For`, `X-Real-Ip`} +import org.apache.pekko.http.scaladsl.model.headers.`X-Real-Ip` import org.apache.pekko.http.scaladsl.model.RemoteAddress import org.apache.pekko.http.scaladsl.server.Directive1 import org.apache.pekko.http.scaladsl.server.Directives.* @@ -12,46 +12,21 @@ object ClientIpDirectives { /** Extracts the address of the client the request originated from, if it can be determined. * - * The address is taken from the first of the following sources that yields an address: - * 1. the `trustedClientIpHeader` (if configured and parseable as an IP literal), which is set - * by a trusted reverse proxy and hence cannot be spoofed by the client, - * 1. the client-controlled `X-Forwarded-For` header (unless disabled), - * 1. the client-controlled `X-Real-Ip` header (unless disabled), + * The headers in `clientIpHeaders` are tried in order and the first one that is present and + * yields an IP literal determines the address. Note that headers set by the client itself (such + * as `X-Forwarded-For` and `X-Real-Ip`) can be spoofed unless they are overwritten by a reverse + * proxy the client cannot bypass. * - * @param trustedClientIpHeader - * name of the header set by a trusted reverse proxy, matched case-insensitively. An empty name - * disables trusting a proxy header. - * @param enableClientProvidedIpHeaders - * whether to fall back to the client-controlled `X-Forwarded-For`/`X-Real-Ip` headers when the - * trusted proxy header does not yield an address. Set to `false` to only rely on the trusted - * proxy header, so that clients cannot influence the extracted address by forging these headers. + * @param clientIpHeaders + * names of the headers carrying the client IP, in order of precedence. Matched + * case-insensitively, as the configured header names are not required to be lowercase. An + * empty list disables the extraction. */ - def extractClientIp( - trustedClientIpHeader: String, - enableClientProvidedIpHeaders: Boolean, - ): Directive1[Option[RemoteAddress]] = { - val clientProvidedSources: Seq[Directive1[Option[RemoteAddress]]] = - if (enableClientProvidedIpHeaders) Seq(forwardedForClientIp, realIpClientIp) - else Seq.empty - val sources = trustedClientIp(trustedClientIpHeader) +: clientProvidedSources - firstDefined(sources*) - } + def extractClientIp(clientIpHeaders: Seq[String]): Directive1[Option[RemoteAddress]] = + firstDefined(clientIpHeaders.map(_.trim).filter(_.nonEmpty).map(clientIpFromHeader)*) - private def trustedClientIp(headerName: String): Directive1[Option[RemoteAddress]] = { - val trimmedHeaderName = headerName.trim - if (trimmedHeaderName.isEmpty) provide(None) - else - // matched case-insensitively (and locale independently) as the configured header name is not - // required to be lowercase - optionalHeaderValueByName(trimmedHeaderName).map(_.flatMap(parseIpLiteral)) - } - - private val forwardedForClientIp: Directive1[Option[RemoteAddress]] = { - optionalHeaderValuePF { case `X-Forwarded-For`(Seq(address, _*)) => address } - } - - private val realIpClientIp: Directive1[Option[RemoteAddress]] = - optionalHeaderValuePF { case `X-Real-Ip`(address) => address } + private def clientIpFromHeader(headerName: String): Directive1[Option[RemoteAddress]] = + optionalHeaderValueByName(headerName).map(_.flatMap(parseFirstIpLiteral)) /** The value of the first directive that extracts a defined value, [[None]] if there is none. */ private def firstDefined[A]( @@ -64,6 +39,9 @@ object ClientIpDirectives { } } + private def parseFirstIpLiteral(value: String): Option[RemoteAddress] = + value.split(',').headOption.flatMap(parseIpLiteral) + private def parseIpLiteral(value: String): Option[RemoteAddress] = `X-Real-Ip` .parseFromValueString(value.trim) diff --git a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala index 893364a61d..4cf216ee81 100644 --- a/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala +++ b/apps/common/src/main/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiter.scala @@ -32,8 +32,8 @@ class HttpRateLimiter( ]() private val metrics = scala.collection.concurrent.TrieMap[String, SpliceRateLimitMetrics]() - private val trustedClientIpHeader: String = - config.trustedClientIpHeader.trim + private val clientIpHeaders: Seq[String] = + config.clientIpHeaders.map(_.trim).filter(_.nonEmpty) private def metricsFor(service: String): SpliceRateLimitMetrics = metrics.getOrElseUpdate( @@ -97,7 +97,7 @@ class HttpRateLimiter( import org.apache.pekko.http.scaladsl.server.Directives.* HttpRateLimiter - .extractClientIpKey(trustedClientIpHeader, config.enableClientProvidedIpHeaders) + .extractClientIpKey(clientIpHeaders) .flatMap { clientIp => // The per client IP limiters are checked first (and `&&` short-circuits) so that a request // rejected because of its own client IP does not consume budget from the shared overall @@ -134,11 +134,10 @@ object HttpRateLimiter { private[splice] val GlobalService = "global" private[splice] def extractClientIpKey( - trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, - enableClientProvidedIpHeaders: Boolean, + clientIpHeaders: Seq[String] = RateLimitersConfig.DefaultClientIpHeaders ): Directive1[Option[String]] = ClientIpDirectives - .extractClientIp(trustedClientIpHeader, enableClientProvidedIpHeaders) + .extractClientIp(clientIpHeaders) .map(_.collect { case RemoteAddress.IP(ip, _) => rateLimitKey(ip) }) /** Single clients are typically assigned a whole IPv6 /64 (or larger) network, so limiting per diff --git a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala index 37c30d8bd0..da4306e880 100644 --- a/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala +++ b/apps/common/src/test/scala/org/lfdecentralizedtrust/splice/http/HttpRateLimiterTest.scala @@ -24,17 +24,16 @@ import org.lfdecentralizedtrust.splice.util.{ } import org.scalatest.wordspec.AnyWordSpec -import java.net.{Inet6Address, InetAddress} +import java.net.InetAddress class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteTest { "clientIp" should { - "prefer the trusted X-Envoy-External-Address over spoofable headers" in { + "prefer X-Forwarded-For" in { clientIp( HttpRequest() .withHeaders( - RawHeader("X-Envoy-External-Address", "4.4.4.4"), `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), ) @@ -43,87 +42,71 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3")) ) ) - ) should be(Some("4.4.4.4")) + ) should be(Some("1.1.1.1")) } - "ignore a non-IP X-Envoy-External-Address and fall back to the next header" in { + "fall back to X-Real-Ip" in { clientIp( - HttpRequest() - .withHeaders( - RawHeader("X-Envoy-External-Address", "evil.example.com"), - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - ) - ) should be(Some("1.1.1.1")) + HttpRequest().withHeaders(`X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2")))) + ) should be(Some("2.2.2.2")) } - "use a configurable trusted proxy header" in { + "ignore a non-IP value and fall back to the next header" in { clientIp( HttpRequest() .withHeaders( - RawHeader("X-Trusted-Client-Ip", "4.4.4.4"), - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - ), - trustedClientIpHeader = "x-trusted-client-ip", - ) should be(Some("4.4.4.4")) - } - - "match the trusted proxy header case-insensitively" in { - clientIp( - HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "4.4.4.4")), - trustedClientIpHeader = "X-Envoy-External-Address", - ) should be(Some("4.4.4.4")) + RawHeader("X-Forwarded-For", "evil.example.com"), + `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), + ) + ) should be(Some("2.2.2.2")) } - "not trust any proxy header when the trusted header is disabled" in { + "use the first address of a comma separated header value" in { clientIp( - HttpRequest().withHeaders( - RawHeader("X-Envoy-External-Address", "4.4.4.4"), - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - ), - trustedClientIpHeader = "", + HttpRequest().withHeaders(RawHeader("X-Forwarded-For", "1.1.1.1, 2.2.2.2, 3.3.3.3")) ) should be(Some("1.1.1.1")) } - "not fall back to client-provided headers when they are disabled" in { + "use the configured headers in order" in { + val request = HttpRequest().withHeaders( + RawHeader("X-Envoy-External-Address", "4.4.4.4"), + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), + `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), + ) clientIp( - HttpRequest().withHeaders( - RawHeader("X-Envoy-External-Address", "4.4.4.4"), - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), - ), - enableClientProvidedIpHeaders = false, + request, + clientIpHeaders = Seq("x-envoy-external-address", "x-forwarded-for"), ) should be(Some("4.4.4.4")) + clientIp( + request, + clientIpHeaders = Seq("x-real-ip", "x-envoy-external-address"), + ) should be(Some("2.2.2.2")) } - "return None when client-provided headers are disabled and no trusted header is present" in { + "not use headers that are not configured" in { clientIp( HttpRequest().withHeaders( `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), ), - enableClientProvidedIpHeaders = false, + clientIpHeaders = Seq("x-envoy-external-address"), ) should be(None) } - "prefer X-Forwarded-For" in { + "match the configured headers case-insensitively" in { clientIp( - HttpRequest() - .withHeaders( - `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))), - `X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2"))), - ) - .withAttributes( - Map( - AttributeKeys.remoteAddress -> RemoteAddress(InetAddress.getByName("3.3.3.3")) - ) - ) - ) should be(Some("1.1.1.1")) + HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "4.4.4.4")), + clientIpHeaders = Seq("X-Envoy-External-Address"), + ) should be(Some("4.4.4.4")) } - "fall back to X-Real-Ip" in { + "not extract any IP when no headers are configured" in { clientIp( - HttpRequest().withHeaders(`X-Real-Ip`(RemoteAddress(InetAddress.getByName("2.2.2.2")))) - ) should be(Some("2.2.2.2")) + HttpRequest().withHeaders( + `X-Forwarded-For`(RemoteAddress(InetAddress.getByName("1.1.1.1"))) + ), + clientIpHeaders = Seq.empty, + ) should be(None) } "not use the remote address of the transport connection" in { @@ -163,42 +146,25 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT clientIpOf("2001:db9:0:1:1:2:3:4") should not be clientIpOf("2001:db8:0:1:1:2:3:4") } - "ignore the zone id of IPv6 addresses" in { - val scoped = Inet6Address.getByAddress( - null, - InetAddress.getByName("fe80::1:2:3:4").getAddress, - 7, - ) - // sanity check that the zone id is part of the address representation - scoped.getHostAddress should be("fe80:0:0:0:1:2:3:4%7") - clientIpOf(scoped) should be(Some("fe80:0:0:0:0:0:0:0/64")) + "reject IPv6 addresses carrying a zone id" in { + // zone ids are only meaningful locally and are not valid in an IP literal of a header + clientIpOf("fe80::1:2:3:4%7") should be(None) } "use the IPv4 address for IPv4-mapped IPv6 clients" in { - // dual stack sockets can report IPv4 clients as ::ffff:a.b.c.d, those must not end up in a - // single /64 bucket shared by all IPv4 clients - val ipv4Mapped = Inet6Address.getByAddress( - null, - Array[Byte](0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff.toByte, 0xff.toByte, 1, 2, 3, 4), - 0, - ) - ipv4Mapped shouldBe a[Inet6Address] - clientIpOf(ipv4Mapped) should be(Some("1.2.3.4")) - clientIpOf(ipv4Mapped) should be(clientIpOf("1.2.3.4")) - clientIpOf( - Inet6Address.getByAddress( - null, - Array[Byte](0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff.toByte, 0xff.toByte, 4, 3, 2, 1), - 0, - ) - ) should not be clientIpOf(ipv4Mapped) + // clients behind a dual stack proxy can be reported as ::ffff:a.b.c.d, those must not end up + // in a single /64 bucket shared by all IPv4 clients + clientIpOf("::ffff:1.2.3.4") should be(Some("1.2.3.4")) + clientIpOf("::ffff:1.2.3.4") should be(clientIpOf("1.2.3.4")) + clientIpOf("::ffff:4.3.2.1") should not be clientIpOf("::ffff:1.2.3.4") } "apply the same grouping to all client IP sources" in { val expected = Some("2001:db8:0:1:0:0:0:0/64") val address = RemoteAddress(InetAddress.getByName("2001:db8:0:1:1:2:3:4")) clientIp( - HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "2001:db8:0:1:1:2:3:4")) + HttpRequest().withHeaders(RawHeader("X-Envoy-External-Address", "2001:db8:0:1:1:2:3:4")), + clientIpHeaders = Seq("x-envoy-external-address"), ) should be(expected) clientIp( HttpRequest().withHeaders(`X-Forwarded-For`(address)) @@ -267,6 +233,23 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT } } + "not apply the per client IP limiters if no client IP headers are configured" in { + withRoutes( + globalPerClientIp = perClientIp(1), + perClientIpOverrides = Map("testOperation" -> perClientIp(1)), + clientIpHeaders = Seq.empty, + )("testOperation") { fixture => + val route = fixture("testOperation") + (1 to 20).map(_ => call(route, ip = Some("1.1.1.1"))) should contain only StatusCodes.OK + forEvery(Seq("testOperation", HttpRateLimiter.GlobalLimiter)) { limiter => + fixture.requestsRejectedBy( + limiter, + SpliceRateLimiter.PerAttributeLimiterType, + ) should be(0L) + } + } + } + "apply the global per client IP limiter across operations" in { // the same client IP is limited regardless of the operation withRoutes( @@ -456,13 +439,11 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT private def clientIp( request: HttpRequest, - trustedClientIpHeader: String = RateLimitersConfig.DefaultTrustedClientIpHeader, - enableClientProvidedIpHeaders: Boolean = true, + clientIpHeaders: Seq[String] = RateLimitersConfig.DefaultClientIpHeaders, ): Option[String] = { val route = - HttpRateLimiter.extractClientIpKey(trustedClientIpHeader, enableClientProvidedIpHeaders) { - extracted => - complete(extracted.getOrElse[String](HttpRateLimiterTest.NoClientIp)) + HttpRateLimiter.extractClientIpKey(clientIpHeaders) { extracted => + complete(extracted.getOrElse[String](HttpRateLimiterTest.NoClientIp)) } request ~> route ~> check { status should be(StatusCodes.OK) @@ -471,12 +452,7 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT } private def clientIpOf(ip: String): Option[String] = - clientIpOf(InetAddress.getByName(ip)) - - private def clientIpOf(ip: InetAddress): Option[String] = - clientIp( - HttpRequest().withHeaders(`X-Forwarded-For`(RemoteAddress(ip))) - ) + clientIp(HttpRequest().withHeaders(RawHeader("X-Forwarded-For", ip))) private def call(route: Route, ip: Option[String]): StatusCode = { val request = ip match { @@ -494,6 +470,7 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT global: SpliceRateLimitConfig = SpliceRateLimitConfig(ratePerSecond = 1000), globalPerClientIp: PerAttributeRateLimitConfig = PerAttributeRateLimitConfig.Disabled, perClientIpOverrides: Map[String, PerAttributeRateLimitConfig] = Map.empty, + clientIpHeaders: Seq[String] = RateLimitersConfig.DefaultClientIpHeaders, )(operations: String*)(f: HttpRateLimiterTest.Fixture => A): A = { // Any operation with a per client IP override needs its own overall limiter entry so that the // embedded per client IP limiter is used instead of the `default` one. @@ -510,6 +487,7 @@ class HttpRateLimiterTest extends AnyWordSpec with BaseTest with ScalatestRouteT default = withPerClientIp(default, PerAttributeRateLimitConfig.Disabled), rateLimiters = perOperationConfigs, global = withPerClientIp(global, globalPerClientIp), + clientIpHeaders = clientIpHeaders, ), metricsFactory, loggerFactory.getTracedLogger(classOf[HttpRateLimiterTest]), diff --git a/cluster/expected/sv-runbook/expected.json b/cluster/expected/sv-runbook/expected.json index 31362ce6f2..8fd67911cd 100644 --- a/cluster/expected/sv-runbook/expected.json +++ b/cluster/expected/sv-runbook/expected.json @@ -623,6 +623,12 @@ "namespace": "sv", "timeout": 600, "values": { + "additionalEnvVars": [ + { + "name": "ADDITIONAL_CONFIG_CLIENT_IP_HEADERS", + "value": "canton.scan-apps.scan-app.parameters.rate-limiting.client-ip-headers = [\"x-envoy-external-address\"]\n" + } + ], "affinity": { "nodeAffinity": { "requiredDuringSchedulingIgnoredDuringExecution": { @@ -1379,6 +1385,10 @@ { "name": "ADDITIONAL_CONFIG_ADDITIONAL_PACKAGES_TO_UNVET", "value": "canton.sv-apps.sv.additional-packages-to-unvet.\"splice-wallet-payments\" = [\"0.1.15\", \"0.1.16\"]\ncanton.sv-apps.sv.additional-packages-to-unvet.\"splice-amulet\" = [\"0.1.15\"]" + }, + { + "name": "ADDITIONAL_CONFIG_CLIENT_IP_HEADERS", + "value": "canton.sv-apps.sv.parameters.rate-limiting.client-ip-headers = [\"x-envoy-external-address\"]\n" } ], "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomSvAppJvmFlag", diff --git a/cluster/expected/sv/expected.json b/cluster/expected/sv/expected.json index d40a5fe142..7a28d94f97 100644 --- a/cluster/expected/sv/expected.json +++ b/cluster/expected/sv/expected.json @@ -4053,6 +4053,10 @@ { "name": "CUSTOM_MOCK_ENV_VAR_NAME", "value": "CUSTOM_MOCK_ENV_VAR_VALUE" + }, + { + "name": "ADDITIONAL_CONFIG_CLIENT_IP_HEADERS", + "value": "canton.scan-apps.scan-app.parameters.rate-limiting.client-ip-headers = [\"x-envoy-external-address\"]\n" } ], "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomScanAppJvmFlag", @@ -4199,6 +4203,10 @@ { "name": "ADDITIONAL_CONFIG_ADDITIONAL_PACKAGES_TO_UNVET", "value": "canton.sv-apps.sv.additional-packages-to-unvet.\"splice-wallet-payments\" = [\"0.1.15\", \"0.1.16\"]\ncanton.sv-apps.sv.additional-packages-to-unvet.\"splice-amulet\" = [\"0.1.15\"]" + }, + { + "name": "ADDITIONAL_CONFIG_CLIENT_IP_HEADERS", + "value": "canton.sv-apps.sv.parameters.rate-limiting.client-ip-headers = [\"x-envoy-external-address\"]\n" } ], "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomSvAppJvmFlag", @@ -6425,6 +6433,10 @@ { "name": "CUSTOM_MOCK_ENV_VAR_NAME", "value": "CUSTOM_MOCK_ENV_VAR_VALUE" + }, + { + "name": "ADDITIONAL_CONFIG_CLIENT_IP_HEADERS", + "value": "canton.scan-apps.scan-app.parameters.rate-limiting.client-ip-headers = [\"x-envoy-external-address\"]\n" } ], "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomScanAppJvmFlag", @@ -6576,6 +6588,10 @@ { "name": "ADDITIONAL_CONFIG_ADDITIONAL_PACKAGES_TO_UNVET", "value": "canton.sv-apps.sv.additional-packages-to-unvet.\"splice-wallet-payments\" = [\"0.1.15\", \"0.1.16\"]\ncanton.sv-apps.sv.additional-packages-to-unvet.\"splice-amulet\" = [\"0.1.15\"]" + }, + { + "name": "ADDITIONAL_CONFIG_CLIENT_IP_HEADERS", + "value": "canton.sv-apps.sv.parameters.rate-limiting.client-ip-headers = [\"x-envoy-external-address\"]\n" } ], "additionalJvmOptions": "-Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.rmi.port=9010 -Dcom.sun.management.jmxremote.local.only=false -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=127.0.0.1 -DcustomSvAppJvmFlag", diff --git a/cluster/pulumi/common-sv/src/sv.ts b/cluster/pulumi/common-sv/src/sv.ts index b37104203d..854940ff71 100644 --- a/cluster/pulumi/common-sv/src/sv.ts +++ b/cluster/pulumi/common-sv/src/sv.ts @@ -18,6 +18,7 @@ import { DecentralizedSynchronizerMigrationConfig, DecentralizedSynchronizerUpgradeConfig, ExactNamespace, + envoyClientIpHeaderEnvVar, failOnAppVersionMismatch, fetchAndInstallParticipantBootstrapDump, getAdditionalJvmOptions, @@ -772,7 +773,9 @@ function installScan( logLevel: config.logging?.appsLogLevel, apiRequestLogLevel: config.logging?.apiRequestLogLevel, logAsyncFlush: config.logging?.appsAsync, - additionalEnvVars: config.scanApp?.additionalEnvVars || [], + additionalEnvVars: (config.scanApp?.additionalEnvVars || []).concat([ + envoyClientIpHeaderEnvVar('canton.scan-apps.scan-app'), + ]), resources: config.scanApp?.resources, ...(config.bulkStorageBuckets ? { diff --git a/cluster/pulumi/common-sv/src/svApp.ts b/cluster/pulumi/common-sv/src/svApp.ts index 04847f32e2..c1ac0dcf43 100644 --- a/cluster/pulumi/common-sv/src/svApp.ts +++ b/cluster/pulumi/common-sv/src/svApp.ts @@ -5,6 +5,7 @@ import { CLUSTER_HOSTNAME, DecentralizedSynchronizerMigrationConfig, EnvVarConfig, + envoyClientIpHeaderEnvVar, MigrationInfo, pvcSuffix, standardStorageClassName, @@ -109,7 +110,8 @@ export function valuesForSvApp( .concat(bftSequencerConnectionEnvVars) .concat(mediatorPruningConfig) .concat(cantonBftPruningConfig) - .concat(additionalPackagesToUnvetConfig); + .concat(additionalPackagesToUnvetConfig) + .concat([envoyClientIpHeaderEnvVar('canton.sv-apps.sv')]); const synchronizerValues: { synchronizers: object } = { synchronizers: { diff --git a/cluster/pulumi/common/src/ratelimit/rateLimitHeaders.ts b/cluster/pulumi/common/src/ratelimit/rateLimitHeaders.ts index c69c74945c..1c4135982c 100644 --- a/cluster/pulumi/common/src/ratelimit/rateLimitHeaders.ts +++ b/cluster/pulumi/common/src/ratelimit/rateLimitHeaders.ts @@ -12,3 +12,19 @@ export const rateLimitResponseHeaders = [ // added by envoy itself on the local reply it generates when rate limiting 'x-envoy-ratelimited', ]; + +export const envoyExternalAddressHeader = 'x-envoy-external-address'; + +/** + * Overrides the (client-controlled) headers the apps extract the client IP for their per-client-IP + * rate limiting from, so that only the non-spoofable header set by the Envoy sidecar is used. + */ +export function envoyClientIpHeaderEnvVar(appConfigPath: string): { + name: string; + value: string; +} { + return { + name: 'ADDITIONAL_CONFIG_CLIENT_IP_HEADERS', + value: `${appConfigPath}.parameters.rate-limiting.client-ip-headers = ["${envoyExternalAddressHeader}"]\n`, + }; +} diff --git a/cluster/pulumi/sv-runbook/src/installNode.ts b/cluster/pulumi/sv-runbook/src/installNode.ts index 7968944586..e8c4141e01 100644 --- a/cluster/pulumi/sv-runbook/src/installNode.ts +++ b/cluster/pulumi/sv-runbook/src/installNode.ts @@ -50,6 +50,7 @@ import { externalIpRangesFile, clusterNetwork, CnChartVersion, + envoyClientIpHeaderEnvVar, } from '@canton-network/splice-pulumi-common'; import { approvedSvIdentities, @@ -419,6 +420,9 @@ async function installSvAndValidator( enable: true, }, ...synchronizerValues, + additionalEnvVars: (defaultScanValues.additionalEnvVars || []).concat([ + envoyClientIpHeaderEnvVar('canton.scan-apps.scan-app'), + ]), resources: svConfig.scanApp?.resources, pvc: persistentHeapDumpsPvc(), }; diff --git a/docs/src/release_notes_upcoming.rst b/docs/src/release_notes_upcoming.rst index dc39b70538..26ef3ad48f 100644 --- a/docs/src/release_notes_upcoming.rst +++ b/docs/src/release_notes_upcoming.rst @@ -6,3 +6,15 @@ .. NOTE: add your upcoming release notes below this line. They are included in the `release_notes.rst`. release-notes:: Upcoming + + - Scan & SV App + + - The client IP used for per-client-IP HTTP rate limiting is now extracted based on a + configurable, ordered list of headers, ``rate-limiting.client-ip-headers``, which defaults + to ``["x-forwarded-for", "x-real-ip"]``. The first configured header that is present and + whose value parses as an IP literal is used; for comma separated values (as in + ``X-Forwarded-For``) the first entry is taken. Configuring an empty list disables the + extraction, in which case no per-client-IP rate limit is enforced. + + This replaces the ``rate-limiting.trusted-client-ip-header`` and + ``rate-limiting.enable-client-provided-ip-headers`` options, which have been removed. From 8bc2e0756dd6a8da3fa3de75239021934d210801 Mon Sep 17 00:00:00 2001 From: kajalshah-da Date: Fri, 21 Aug 2026 18:24:57 -0400 Subject: [PATCH 287/329] Partition overflow problem solved per issue #6919 [ci] (#6923) * Partition overflow problem solved per issue #6919 [ci] Signed-off-by: Kajal --- cluster/pulumi/common-sv/src/bigQuery.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cluster/pulumi/common-sv/src/bigQuery.ts b/cluster/pulumi/common-sv/src/bigQuery.ts index 92f73ff906..62fcbe674f 100644 --- a/cluster/pulumi/common-sv/src/bigQuery.ts +++ b/cluster/pulumi/common-sv/src/bigQuery.ts @@ -285,7 +285,7 @@ function installDatastream_stag_prod( }, destinationConnectionProfile: destination.name, }, - backfillAll: {}, + backfillNone: {}, // Addressing issue #6919 - partition overflow problem with backfillAll, so using backfillNone for stag-prod datastream ruleSets: tablesToReplicate.map(tableName => ({ objectFilter: { sourceObjectIdentifier: { From ba7f572d1e21d309b40735cad6c6bd1453c4359c Mon Sep 17 00:00:00 2001 From: Robert Autenrieth <31539813+rautenrieth-da@users.noreply.github.com> Date: Mon, 24 Aug 2026 09:46:49 +0200 Subject: [PATCH 288/329] Fix grafana dashboard panel for hidden app reward coupons (#6850) Signed-off-by: Robert Autenrieth --- cluster/expected/observability/expected.json | 2 +- .../canton-network/app-rewards.json | 72 +++++++++++++++---- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/cluster/expected/observability/expected.json b/cluster/expected/observability/expected.json index f93f603bad..e7506b43c5 100644 --- a/cluster/expected/observability/expected.json +++ b/cluster/expected/observability/expected.json @@ -143,7 +143,7 @@ "inputs": { "apiVersion": "v1", "data": { - "app-rewards.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"description\": \"Traffic based app rewards (CIP-104)\",\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 23,\n \"panels\": [],\n \"title\": \"Summary\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of active CalculateRewardsV2 contracts, as seen by the sv app.\\n\\nOne such contract should be created when a round is closed, and should be archived as soon as SVs finish voting on the root hash of the reward merkle tree (usually within a minute).\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 1\n },\n {\n \"color\": \"red\",\n \"value\": 2\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 33,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace,dryRun)((splice_calculate_rewards_v2_active_contracts{namespace=~\\\"$namespace\\\", dryRun=~\\\"$dryRun\\\"} != -1))\",\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"CalculateRewardsV2 contracts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of active ProcessRewardsV2 contracts, as seen by the sv app.\\n\\nA few such contract should be created when the SVs finish voting on the root hash of the reward merkle tree, and they should be archived as the SVs create the corresponding minting allowances. Within a few minutes, there should be no ProcessRewardsV2 contracts left.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 1\n },\n {\n \"color\": \"red\",\n \"value\": 100\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 1\n },\n \"id\": 34,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace, dryRun)(splice_process_rewards_v2_active_contracts{namespace=~\\\"$namespace\\\", dryRun=\\\"$dryRun\\\"} != -1)\",\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"ProcessRewardsV2 contracts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"description\": \"Stacked time series for the number of active RewardCouponV2 contracts, as seen by the sv app. Data is averages across all selected SVs, and disaggregated by contract age into 4 hardcoded buckets.\\n\\nRewardCouponV2 contracts have a TTL of 36h by default, validators may decide to wait a few rounds in order to batch the minting of reward coupons.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 50,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineStyle\": {\n \"fill\": \"solid\"\n },\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 35,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"avg by (bucket, ageLowerBound, ageUpperBound)(splice_reward_coupon_v2_active_contracts{namespace=~\\\"$namespace\\\"} != -1)\",\n \"instant\": false,\n \"legendFormat\": \"{{ageLowerBound}} - {{ageUpperBound}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"RewardCouponV2 contracts, average across all SVs, by contract age\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"description\": \"Leaderboard of parties with hidden reward coupons.\\n\\nHidden coupons are created when a party is due rewards, but has not vetted the required package. The reward coupons are created without the party as observer in that case. As soon as the party vets the required package, the party is added as observer.\\n\\nIf you ever see any parties here, tell their validator node to upgrade or fix their package vetting.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 1\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 9\n },\n \"id\": 32,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"max by (namespace)(splice_scan_reward_computation_reward_coupons_v2_hidden_coupons{namespace=~\\\"$namespace\\\"})\",\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Parties with hidden coupons\",\n \"type\": \"table\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 17\n },\n \"id\": 7,\n \"panels\": [],\n \"title\": \"Ingest verdicts and activity records (ScanVerdictIngestionService in scan app)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested verdict record time and the current wall clock time.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"fixedColor\": \"red\",\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 60\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 18\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\"}) - (splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\"} > 0) / 1e6)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Latency: last ingested verdict lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of ingestion of mediator verdicts per second\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 18\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"rate(splice_scan_verdict_ingestion_count_total{namespace=~\\\"$namespace\\\"}[$__rate_interval])\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": false,\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Traffic: verdicts ingestion rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Average and 99th quantile batch size.\\n\\nVerdicts are streamed in from the mediator, then batched in the scan app. For each batch we do one lookup for traffic summaries in the sequencer, and submit one SQL transaction to store all app activity data.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 40\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 18\n },\n \"id\": 19,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"(histogram_sum(sum by(namespace)(rate(splice_scan_verdict_ingestion_batch_size[10m])))\\n/\\nhistogram_count(sum by(namespace)(rate(splice_scan_verdict_ingestion_batch_size[10m]))))\",\n \"legendFormat\": \"{{namespace}} avg\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Saturation: verdict batch size (10min running average)\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 26\n },\n \"id\": 13,\n \"panels\": [],\n \"title\": \"Calculate reward merkle tree (RewardComputationTrigger in scan app)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time it took to complete a task\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 27\n },\n \"id\": 24,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.5, rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", trigger_name=\\\"RewardComputationTrigger\\\", trigger_type=\\\"taskbased\\\"}[$__rate_interval]))\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: median trigger task duration\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 27\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_trigger_completed_total{trigger_name=~\\\"RewardComputationTrigger\\\",outcome=~\\\"success|failure\\\", namespace=~\\\"$namespace\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}} {{outcome}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Traffic: Trigger runs\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 27\n },\n \"id\": 21,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(errorCodeId, namespace, node_type, trigger_name) (rate(splice_trigger_attempted_total{namespace=~\\\"$namespace\\\", trigger_name=~\\\"RewardComputationTrigger\\\", statusCode!~\\\"OK\\\"}[$__rate_interval])) > 0\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Errors: Failed trigger attempts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested record time and the current wallclock time.\\n\\nNote that the last ingested record time metric only updates when the store ingests a new transaction so if there is no activity, the last ingested record time will not advance.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 60\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 35\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbScanRewardsReferenceStore\\\"}) - (splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbScanRewardsReferenceStore\\\"} > 0) / 1e3)\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: DbScanRewardsReferenceStore lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches in the latest computed round. Median value from all selected scan apps.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 4,\n \"x\": 8,\n \"y\": 35\n },\n \"id\": 22,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\n \"median\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"quantile(0.5,splice_scan_reward_computation_batches_created_count{namespace=~\\\"$namespace\\\"})\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batches\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of activity records in the latest computed round. Median value from all selected scan apps.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 4,\n \"x\": 12,\n \"y\": 35\n },\n \"id\": 16,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"quantile(0.5, splice_scan_reward_computation_activity_records_count{namespace=~\\\"$namespace\\\"})\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Activity records\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of parties with activity in the latest computed round. Median value from all selected scan apps\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 4,\n \"x\": 16,\n \"y\": 35\n },\n \"id\": 18,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"quantile(0.5, splice_scan_reward_computation_active_parties_count{namespace=~\\\"$namespace\\\"})\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Active parties\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of parties with rewards in the latest computed round. Median value from all selected scan apps.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 4,\n \"x\": 20,\n \"y\": 35\n },\n \"id\": 17,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"quantile(0.5, splice_scan_reward_computation_rewarded_parties_count{namespace=~\\\"$namespace\\\"})\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rewarded parties\",\n \"type\": \"stat\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 43\n },\n \"id\": 3,\n \"panels\": [],\n \"title\": \"Voting on root hash (CalculateRewardsTrigger in sv app)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time it took to complete a task\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 44\n },\n \"id\": 25,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.5, rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", trigger_name=~\\\"CalculateRewards.*Trigger\\\", dryRun=~\\\"$dryRun\\\", trigger_type=\\\"taskbased\\\"}[$__rate_interval]))\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: median trigger task duration\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested record time and the current wallclock time.\\n\\nNote that the last ingested record time metric only updates when the store ingests a new transaction so if there is no activity, the last ingested record time will not advance. For a party performing reward collection, e.g., the validator operator party you expect at least one transaction every round so the lag should not go above 20min.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 60\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 44\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbSvDsoStore\\\"}) - (splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbSvDsoStore\\\"} > 0) / 1e3)\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: DbSvDsoStore lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"the time it took between the closing of a round, and this SV's confirmation for the CalculateRewardsV2 contract's processing\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 44\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"histogram_avg(rate(splice_calculate_rewards_v2_processing_delay_duration_seconds{namespace=~\\\"$namespace\\\", dryRun=~\\\"$dryRun\\\"}[$__rate_interval]))\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: time between round end and confirming root hash\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 52\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_trigger_completed_total{trigger_name=~\\\"ProcessRewardsTrigger.*\\\", dryRun=\\\"$dryRun\\\", outcome=~\\\"success|failure\\\", namespace=~\\\"$namespace\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}} {{dryRun}} {{outcome}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Traffic: trigger runs\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 52\n },\n \"id\": 26,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by(statusCode, namespace, node_type, trigger_name) (rate(splice_trigger_attempted_total{namespace=~\\\"$namespace\\\", trigger_name=~\\\"CalculateRewards.*Trigger\\\", dryRun=\\\"$dryRun\\\", statusCode!~\\\"OK\\\"}[$__rate_interval]))\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{dryRun}} {{statusCode}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Errors: Failed trigger attempts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of BFT reads of the root hash. BFT reads happens if this SV node started ingesting verdicts after the round on which is being voted.\\n\\nI.e., BFT reads should only happen after the SV node was onboarded or after its verdict store data was reset.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 52\n },\n \"id\": 27,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"rate(splice_calculate_rewards_v2_root_hash_bft_reads{namespace=~\\\"$namespace\\\", dryRun=~\\\"$dryRun\\\"}[$__rate_interval])\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate of reading root hash from other scans\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 60\n },\n \"id\": 5,\n \"panels\": [],\n \"title\": \"Creating minting allowances (ProcessRewardsTrigger in sv app)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time it took to complete a task\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 61\n },\n \"id\": 29,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.5, rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", trigger_name=~\\\"ProcessRewards.*Trigger\\\", dryRun=~\\\"$dryRun\\\", trigger_type=\\\"taskbased\\\"}[$__rate_interval]))\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: median trigger task duration\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested record time and the current wallclock time.\\n\\nNote that the last ingested record time metric only updates when the store ingests a new transaction so if there is no activity, the last ingested record time will not advance. For a party performing reward collection, e.g., the validator operator party you expect at least one transaction every round so the lag should not go above 20min.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 60\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 61\n },\n \"id\": 30,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbSvDsoStore\\\"}) - (splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbSvDsoStore\\\"} > 0) / 1e3)\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: DbSvDsoStore lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time it took between the closing of a round, and this SV's processing of a ProcessRewardsV2 contract for that round\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 61\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"histogram_avg(rate(splice_process_rewards_v2_processing_delay_duration_seconds{namespace=~\\\"$namespace\\\", dryRun=~\\\"$dryRun\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: time between round end and processing a reward batch\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 69\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_trigger_completed_total{trigger_name=~\\\"ProcessRewards.*Trigger\\\", dryRun=~\\\"$dryRun\\\", outcome=~\\\"success|failure\\\", namespace=~\\\"$namespace\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}} {{dryRun}} {{outcome}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Traffic: trigger runs\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Note: this is a delegate based trigger. It is normal that this trigger fails due to contention as multiple SVs attempt to complete the same task at the same time. Example errors:\\n\\n- FAILED_PRECONDITION: UNKNOWN_CONTRACT_SYNCHRONIZERS: The following contracts have been archived\\n- NOT_FOUND: LOCAL_VERDICT_INACTIVE_CONTRACTS: Rejected transaction is referring to inactive contracts\\n- ABORTED: LOCAL_VERDICT_LOCKED_CONTRACTS: Rejected transaction is referring to locked contracts\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 69\n },\n \"id\": 28,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by(statusCode, namespace, node_type, trigger_name) (rate(splice_trigger_attempted_total{namespace=~\\\"$namespace\\\", trigger_name=~\\\"ProcessRewards.*Trigger\\\", dryRun=~\\\"$dryRun\\\", statusCode!~\\\"OK\\\"}[$__rate_interval])) > 0\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{true}} {{statusCode}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Errors: Failed trigger attempts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of BFT reads of reward data. BFT reads happens if this SV node's RewardComputationTrigger did not (yet) calculate rewards for this round.\\n\\nBFT reads here mean the SV is behind on calculating rewards, but still participates in creating minting allowance contracts.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 69\n },\n \"id\": 31,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"rate(splice_process_rewards_v2_batch_bft_reads_total{namespace=~\\\"$namespace\\\", dryRun=\\\"$dryRun\\\"}[$__rate_interval])\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate of reading reward data from other scans\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_calculate_rewards_v2_processing_delay_duration_seconds,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_calculate_rewards_v2_processing_delay_duration_seconds,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": true,\n \"current\": {\n \"text\": [\n \"false\"\n ],\n \"value\": [\n \"false\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_calculate_rewards_v2_processing_delay_duration_seconds,dryRun)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"dryRun\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_calculate_rewards_v2_processing_delay_duration_seconds,dryRun)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Traffic-based app rewards (CIP-104)\",\n \"uid\": \"cnx7gsn\",\n \"version\": 41,\n \"weekStart\": \"\"\n}\n", + "app-rewards.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"description\": \"Traffic based app rewards (CIP-104)\",\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 23,\n \"panels\": [],\n \"title\": \"Summary\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of active CalculateRewardsV2 contracts, as seen by the sv app.\\n\\nOne such contract should be created when a round is closed, and should be archived as soon as SVs finish voting on the root hash of the reward merkle tree (usually within a minute).\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 1\n },\n {\n \"color\": \"red\",\n \"value\": 2\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 33,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace,dryRun)((splice_calculate_rewards_v2_active_contracts{namespace=~\\\"$namespace\\\", dryRun=~\\\"$dryRun\\\"} != -1))\",\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"CalculateRewardsV2 contracts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of active ProcessRewardsV2 contracts, as seen by the sv app.\\n\\nA few such contract should be created when the SVs finish voting on the root hash of the reward merkle tree, and they should be archived as the SVs create the corresponding minting allowances. Within a few minutes, there should be no ProcessRewardsV2 contracts left.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 1\n },\n {\n \"color\": \"red\",\n \"value\": 100\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 1\n },\n \"id\": 34,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace, dryRun)(splice_process_rewards_v2_active_contracts{namespace=~\\\"$namespace\\\", dryRun=\\\"$dryRun\\\"} != -1)\",\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"ProcessRewardsV2 contracts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"description\": \"Stacked time series for the number of active RewardCouponV2 contracts, as seen by the sv app. Data is averages across all selected SVs, and disaggregated by contract age into 4 hardcoded buckets.\\n\\nRewardCouponV2 contracts have a TTL of 36h by default, validators may decide to wait a few rounds in order to batch the minting of reward coupons.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 50,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineStyle\": {\n \"fill\": \"solid\"\n },\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"normal\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 9\n },\n \"id\": 35,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"avg by (bucket, ageLowerBound, ageUpperBound)(splice_reward_coupon_v2_active_contracts{namespace=~\\\"$namespace\\\"} != -1)\",\n \"instant\": false,\n \"legendFormat\": \"{{ageLowerBound}} - {{ageUpperBound}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"RewardCouponV2 contracts, average across all SVs, by contract age\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"uid\": \"prometheus\"\n },\n \"description\": \"Leaderboard of parties with hidden reward coupons.\\n\\nHidden coupons are created when a party is due rewards, but has not vetted the required package. The reward coupons are created without the party as observer in that case. As soon as the party vets the required package, the party is added as observer.\\n\\nIf you ever see any parties here, tell their validator node to upgrade or fix their package vetting.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 1\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Value\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 100\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Time\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 200\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 9\n },\n \"id\": 32,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"enablePagination\": true,\n \"showHeader\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"max by (party)(splice_reward_coupons_v2_hidden_coupons{namespace=~\\\"$namespace\\\"})\",\n \"format\": \"table\",\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Parties with hidden coupons\",\n \"transformations\": [\n {\n \"id\": \"sortBy\",\n \"options\": {\n \"fields\": {},\n \"sort\": [\n {\n \"desc\": true,\n \"field\": \"Value\"\n }\n ]\n }\n },\n {\n \"id\": \"filterFieldsByName\",\n \"options\": {\n \"include\": {\n \"names\": [\n \"party\",\n \"Value\"\n ]\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 17\n },\n \"id\": 7,\n \"panels\": [],\n \"title\": \"Ingest verdicts and activity records (ScanVerdictIngestionService in scan app)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested verdict record time and the current wall clock time.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"fixedColor\": \"red\",\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 60\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 18\n },\n \"id\": 10,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\"}) - (splice_scan_verdict_ingestion_last_record_time_us{namespace=~\\\"$namespace\\\"} > 0) / 1e6)\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Latency: last ingested verdict lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of ingestion of mediator verdicts per second\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 18\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": true,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"rate(splice_scan_verdict_ingestion_count_total{namespace=~\\\"$namespace\\\"}[$__rate_interval])\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": false,\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Traffic: verdicts ingestion rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Average and 99th quantile batch size.\\n\\nVerdicts are streamed in from the mediator, then batched in the scan app. For each batch we do one lookup for traffic summaries in the sequencer, and submit one SQL transaction to store all app activity data.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 40\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 18\n },\n \"id\": 19,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"(histogram_sum(sum by(namespace)(rate(splice_scan_verdict_ingestion_batch_size[10m])))\\n/\\nhistogram_count(sum by(namespace)(rate(splice_scan_verdict_ingestion_batch_size[10m]))))\",\n \"legendFormat\": \"{{namespace}} avg\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Saturation: verdict batch size (10min running average)\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 26\n },\n \"id\": 13,\n \"panels\": [],\n \"title\": \"Calculate reward merkle tree (RewardComputationTrigger in scan app)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time it took to complete a task\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 27\n },\n \"id\": 24,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.5, rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", trigger_name=\\\"RewardComputationTrigger\\\", trigger_type=\\\"taskbased\\\"}[$__rate_interval]))\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: median trigger task duration\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 27\n },\n \"id\": 14,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_trigger_completed_total{trigger_name=~\\\"RewardComputationTrigger\\\",outcome=~\\\"success|failure\\\", namespace=~\\\"$namespace\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}} {{outcome}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Traffic: Trigger runs\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 27\n },\n \"id\": 21,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by(errorCodeId, namespace, node_type, trigger_name) (rate(splice_trigger_attempted_total{namespace=~\\\"$namespace\\\", trigger_name=~\\\"RewardComputationTrigger\\\", statusCode!~\\\"OK\\\"}[$__rate_interval])) > 0\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Errors: Failed trigger attempts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested record time and the current wallclock time.\\n\\nNote that the last ingested record time metric only updates when the store ingests a new transaction so if there is no activity, the last ingested record time will not advance.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 60\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 35\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbScanRewardsReferenceStore\\\"}) - (splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbScanRewardsReferenceStore\\\"} > 0) / 1e3)\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: DbScanRewardsReferenceStore lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of batches in the latest computed round. Median value from all selected scan apps.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 4,\n \"x\": 8,\n \"y\": 35\n },\n \"id\": 22,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\"median\"],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"quantile(0.5,splice_scan_reward_computation_batches_created_count{namespace=~\\\"$namespace\\\"})\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Batches\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of activity records in the latest computed round. Median value from all selected scan apps.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 4,\n \"x\": 12,\n \"y\": 35\n },\n \"id\": 16,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\"lastNotNull\"],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"quantile(0.5, splice_scan_reward_computation_activity_records_count{namespace=~\\\"$namespace\\\"})\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Activity records\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of parties with activity in the latest computed round. Median value from all selected scan apps\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 4,\n \"x\": 16,\n \"y\": 35\n },\n \"id\": 18,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\"lastNotNull\"],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"quantile(0.5, splice_scan_reward_computation_active_parties_count{namespace=~\\\"$namespace\\\"})\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Active parties\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Number of parties with rewards in the latest computed round. Median value from all selected scan apps.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 4,\n \"x\": 20,\n \"y\": 35\n },\n \"id\": 17,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\"lastNotNull\"],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"quantile(0.5, splice_scan_reward_computation_rewarded_parties_count{namespace=~\\\"$namespace\\\"})\",\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rewarded parties\",\n \"type\": \"stat\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 43\n },\n \"id\": 3,\n \"panels\": [],\n \"title\": \"Voting on root hash (CalculateRewardsTrigger in sv app)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time it took to complete a task\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 44\n },\n \"id\": 25,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.5, rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", trigger_name=~\\\"CalculateRewards.*Trigger\\\", dryRun=~\\\"$dryRun\\\", trigger_type=\\\"taskbased\\\"}[$__rate_interval]))\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: median trigger task duration\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested record time and the current wallclock time.\\n\\nNote that the last ingested record time metric only updates when the store ingests a new transaction so if there is no activity, the last ingested record time will not advance. For a party performing reward collection, e.g., the validator operator party you expect at least one transaction every round so the lag should not go above 20min.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 60\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 44\n },\n \"id\": 12,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbSvDsoStore\\\"}) - (splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbSvDsoStore\\\"} > 0) / 1e3)\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: DbSvDsoStore lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"the time it took between the closing of a round, and this SV's confirmation for the CalculateRewardsV2 contract's processing\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 44\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"histogram_avg(rate(splice_calculate_rewards_v2_processing_delay_duration_seconds{namespace=~\\\"$namespace\\\", dryRun=~\\\"$dryRun\\\"}[$__rate_interval]))\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: time between round end and confirming root hash\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 52\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_trigger_completed_total{trigger_name=~\\\"ProcessRewardsTrigger.*\\\", dryRun=\\\"$dryRun\\\", outcome=~\\\"success|failure\\\", namespace=~\\\"$namespace\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}} {{dryRun}} {{outcome}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Traffic: trigger runs\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 52\n },\n \"id\": 26,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by(statusCode, namespace, node_type, trigger_name) (rate(splice_trigger_attempted_total{namespace=~\\\"$namespace\\\", trigger_name=~\\\"CalculateRewards.*Trigger\\\", dryRun=\\\"$dryRun\\\", statusCode!~\\\"OK\\\"}[$__rate_interval]))\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{dryRun}} {{statusCode}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Errors: Failed trigger attempts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of BFT reads of the root hash. BFT reads happens if this SV node started ingesting verdicts after the round on which is being voted.\\n\\nI.e., BFT reads should only happen after the SV node was onboarded or after its verdict store data was reset.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 52\n },\n \"id\": 27,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"rate(splice_calculate_rewards_v2_root_hash_bft_reads{namespace=~\\\"$namespace\\\", dryRun=~\\\"$dryRun\\\"}[$__rate_interval])\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate of reading root hash from other scans\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 60\n },\n \"id\": 5,\n \"panels\": [],\n \"title\": \"Creating minting allowances (ProcessRewardsTrigger in sv app)\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time it took to complete a task\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 61\n },\n \"id\": 29,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.5, rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", trigger_name=~\\\"ProcessRewards.*Trigger\\\", dryRun=~\\\"$dryRun\\\", trigger_type=\\\"taskbased\\\"}[$__rate_interval]))\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: median trigger task duration\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time difference between the last ingested record time and the current wallclock time.\\n\\nNote that the last ingested record time metric only updates when the store ingests a new transaction so if there is no activity, the last ingested record time will not advance. For a party performing reward collection, e.g., the validator operator party you expect at least one transaction every round so the lag should not go above 20min.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"yellow\",\n \"value\": 60\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 61\n },\n \"id\": 30,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"(timestamp(splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbSvDsoStore\\\"}) - (splice_store_last_ingested_record_time_ms{namespace=~\\\"$namespace\\\",store_name=~\\\"DbSvDsoStore\\\"} > 0) / 1e3)\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: DbSvDsoStore lag\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time it took between the closing of a round, and this SV's processing of a ProcessRewardsV2 contract for that round\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 61\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"expr\": \"histogram_avg(rate(splice_process_rewards_v2_processing_delay_duration_seconds{namespace=~\\\"$namespace\\\", dryRun=~\\\"$dryRun\\\"}[$__rate_interval]))\",\n \"legendFormat\": \"{{namespace}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency: time between round end and processing a reward batch\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 69\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_trigger_completed_total{trigger_name=~\\\"ProcessRewards.*Trigger\\\", dryRun=~\\\"$dryRun\\\", outcome=~\\\"success|failure\\\", namespace=~\\\"$namespace\\\"}[$__rate_interval])\",\n \"legendFormat\": \"{{namespace}} {{dryRun}} {{outcome}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Traffic: trigger runs\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Note: this is a delegate based trigger. It is normal that this trigger fails due to contention as multiple SVs attempt to complete the same task at the same time. Example errors:\\n\\n- FAILED_PRECONDITION: UNKNOWN_CONTRACT_SYNCHRONIZERS: The following contracts have been archived\\n- NOT_FOUND: LOCAL_VERDICT_INACTIVE_CONTRACTS: Rejected transaction is referring to inactive contracts\\n- ABORTED: LOCAL_VERDICT_LOCKED_CONTRACTS: Rejected transaction is referring to locked contracts\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 8,\n \"y\": 69\n },\n \"id\": 28,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by(statusCode, namespace, node_type, trigger_name) (rate(splice_trigger_attempted_total{namespace=~\\\"$namespace\\\", trigger_name=~\\\"ProcessRewards.*Trigger\\\", dryRun=~\\\"$dryRun\\\", statusCode!~\\\"OK\\\"}[$__rate_interval])) > 0\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{true}} {{statusCode}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Errors: Failed trigger attempts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Rate of BFT reads of reward data. BFT reads happens if this SV node's RewardComputationTrigger did not (yet) calculate rewards for this round.\\n\\nBFT reads here mean the SV is behind on calculating rewards, but still participates in creating minting allowance contracts.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 16,\n \"y\": 69\n },\n \"id\": 31,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"rate(splice_process_rewards_v2_batch_bft_reads_total{namespace=~\\\"$namespace\\\", dryRun=\\\"$dryRun\\\"}[$__rate_interval])\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{dryRun}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Rate of reading reward data from other scans\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_calculate_rewards_v2_processing_delay_duration_seconds,namespace)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_calculate_rewards_v2_processing_delay_duration_seconds,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"allowCustomValue\": true,\n \"current\": {\n \"text\": [\n \"false\"\n ],\n \"value\": [\n \"false\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_calculate_rewards_v2_processing_delay_duration_seconds,dryRun)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"dryRun\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_calculate_rewards_v2_processing_delay_duration_seconds,dryRun)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Traffic-based app rewards (CIP-104)\",\n \"uid\": \"cnx7gsn\",\n \"version\": 41,\n \"weekStart\": \"\"\n}\n", "automations.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"description\": \"Track the trigger automations being run by Splice Apps\",\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 30,\n \"panels\": [],\n \"title\": \"Automation Service Health\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"noValue\": \"0\",\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 1\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 5,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 34,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"count by (namespace, job, automation_service) (splice_automation_background_service_health{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\"} == 1)\",\n \"instant\": true,\n \"legendFormat\": \"{{namespace}} {{job}} {{automation_service}}\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Unhealthy services\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Current health of registered automation background services.\\n-1 = no reading yet (or error reading health), 0 = healthy, 1 = unhealthy.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"color-background\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [\n {\n \"options\": {\n \"0\": {\n \"color\": \"green\",\n \"index\": 1,\n \"text\": \"healthy\"\n },\n \"1\": {\n \"color\": \"red\",\n \"index\": 2,\n \"text\": \"unhealthy\"\n },\n \"-1\": {\n \"color\": \"text\",\n \"index\": 0,\n \"text\": \"unknown\"\n }\n },\n \"type\": \"value\"\n }\n ],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"text\",\n \"value\": 0\n },\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 1\n }\n ]\n }\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Time\"\n },\n \"properties\": [\n {\n \"id\": \"custom.hideFrom.viz\",\n \"value\": true\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"exported_service\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 785\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Node\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 109\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"Namespace\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 110\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"automation_service\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 296\n }\n ]\n },\n {\n \"matcher\": {\n \"id\": \"byName\",\n \"options\": \"App\"\n },\n \"properties\": [\n {\n \"id\": \"custom.width\",\n \"value\": 94\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 19,\n \"x\": 5,\n \"y\": 1\n },\n \"id\": 31,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"showHeader\": true,\n \"sortBy\": [\n {\n \"desc\": true,\n \"displayName\": \"Status\"\n }\n ]\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"splice_automation_background_service_health{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\"}\",\n \"format\": \"table\",\n \"instant\": true,\n \"legendFormat\": \"__auto\",\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Background service health\",\n \"transformations\": [\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Time\": true,\n \"__name__\": true,\n \"container\": true,\n \"endpoint\": true,\n \"instance\": true,\n \"job\": true,\n \"migration\": true,\n \"otel_scope_name\": true,\n \"pod\": true,\n \"prometheus\": true,\n \"service\": true\n },\n \"includeByName\": {},\n \"indexByName\": {},\n \"renameByName\": {\n \"Value\": \"Status\",\n \"instance\": \"Instance\",\n \"namespace\": \"Namespace\",\n \"node_name\": \"Node\",\n \"node_type\": \"App\",\n \"service\": \"Service\"\n }\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"Health of automation background services over time (-1 unknown, 0 healthy, 1 unhealthy).\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 10,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"stepAfter\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [\n {\n \"options\": {\n \"0\": {\n \"index\": 1,\n \"text\": \"healthy\"\n },\n \"1\": {\n \"index\": 2,\n \"text\": \"unhealthy\"\n },\n \"-1\": {\n \"index\": 0,\n \"text\": \"unknown\"\n }\n },\n \"type\": \"value\"\n }\n ],\n \"max\": 1,\n \"min\": -1,\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 1\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 10\n },\n \"id\": 32,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"splice_automation_background_service_health{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\"}\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{node_type}} {{automation_service}} {{exported_service}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Background service health over time\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 20\n },\n \"id\": 33,\n \"panels\": [],\n \"title\": \"Runs\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"runs / second\"\n },\n \"overrides\": [\n {\n \"matcher\": {\n \"id\": \"byRegexp\",\n \"options\": \"/.*failure/\"\n },\n \"properties\": [\n {\n \"id\": \"custom.fillOpacity\",\n \"value\": 50\n }\n ]\n }\n ]\n },\n \"gridPos\": {\n \"h\": 10,\n \"w\": 24,\n \"x\": 0,\n \"y\": 21\n },\n \"id\": 18,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"rate(splice_trigger_completed_total{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_name=~\\\"$trigger_name\\\", outcome=~\\\"success|failure\\\"}[$__rate_interval])\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": false,\n \"instant\": false,\n \"legendFormat\": \"{{node_name}} {{trigger_name}} {{outcome}} \",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Runs\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 3,\n \"panels\": [],\n \"repeat\": \"trigger_type\",\n \"title\": \"Triggers $trigger_type\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The rate of successful iterations completed\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"noValue\": \"0\",\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 4,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 5,\n \"options\": {\n \"colorMode\": \"none\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"center\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum(histogram_sum(rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_name=~\\\"$trigger_name\\\", trigger_type=\\\"$trigger_type\\\"}[$__rate_interval])))\",\n \"instant\": true,\n \"range\": false,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Traffic\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The rate at which the trigger(s) are failing with retryable errors\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 4,\n \"x\": 4,\n \"y\": 32\n },\n \"id\": 7,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_retries_failures{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_name=~\\\"$trigger_name\\\", trigger_type=\\\"$trigger_type\\\", error_kind=\\\"transient\\\"}[$__rate_interval]))\",\n \"instant\": false,\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Retries\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The rate at which trigger(s) are failing with fatal/non-retryable errors\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 4,\n \"x\": 8,\n \"y\": 32\n },\n \"id\": 8,\n \"options\": {\n \"colorMode\": \"value\",\n \"graphMode\": \"area\",\n \"justifyMode\": \"auto\",\n \"orientation\": \"auto\",\n \"percentChangeColorMode\": \"standard\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showPercentChange\": false,\n \"textMode\": \"auto\",\n \"wideLayout\": true\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_retries_failures{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_name=~\\\"$trigger_name\\\", trigger_type=\\\"$trigger_type\\\", error_kind=\\\"fatal\\\"}[$__rate_interval]))\",\n \"instant\": false,\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Errors\",\n \"type\": \"stat\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"The time it took to perform an iteration\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 12,\n \"x\": 12,\n \"y\": 32\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"histogram_quantile(0.95, rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_name=~\\\"$trigger_name\\\", trigger_type=\\\"$trigger_type\\\"}[$__rate_interval]))\",\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{node_type}} {{service}} {{trigger_name}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Latency\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"continuous-RdYlGr\"\n },\n \"custom\": {\n \"align\": \"auto\",\n \"cellOptions\": {\n \"type\": \"auto\"\n },\n \"footer\": {\n \"reducers\": []\n },\n \"inspect\": false\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 90\n },\n {\n \"color\": \"#6ED0E0\",\n \"value\": 100\n },\n {\n \"color\": \"#EF843C\",\n \"value\": 110\n },\n {\n \"color\": \"#E24D42\",\n \"value\": 120\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 8,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 10,\n \"options\": {\n \"cellHeight\": \"sm\",\n \"frameIndex\": 0,\n \"showHeader\": true,\n \"sortBy\": []\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by(trigger_name) (histogram_count(rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_type=\\\"$trigger_type\\\"}[$__rate_interval])))\",\n \"format\": \"time_series\",\n \"instant\": false,\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Top 5 Active Triggers\",\n \"transformations\": [\n {\n \"id\": \"reduce\",\n \"options\": {\n \"includeTimeField\": false,\n \"labelsToFields\": true,\n \"mode\": \"seriesToRows\",\n \"reducers\": [\n \"sum\"\n ]\n }\n },\n {\n \"id\": \"sortBy\",\n \"options\": {\n \"fields\": {},\n \"sort\": [\n {\n \"desc\": true,\n \"field\": \"Total\"\n }\n ]\n }\n },\n {\n \"id\": \"organize\",\n \"options\": {\n \"excludeByName\": {\n \"Field\": true\n },\n \"indexByName\": {},\n \"renameByName\": {\n \"Total\": \"Traffic\",\n \"trigger_name\": \"Trigger\"\n }\n }\n },\n {\n \"id\": \"limit\",\n \"options\": {\n \"limitField\": 5\n }\n }\n ],\n \"type\": \"table\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"How busy triggers are within a certain time window\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"percentunit\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 16,\n \"x\": 8,\n \"y\": 39\n },\n \"id\": 9,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by(trigger_name, instance, node_type, trigger_type) (histogram_sum(rate(splice_trigger_latency_duration_seconds{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_name=~\\\"$trigger_name\\\", trigger_type=\\\"$trigger_type\\\"}[$__rate_interval])))\",\n \"instant\": false,\n \"legendFormat\": \"{{node_type}} {{instance}} {{trigger_name}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Saturation\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"description\": \"How busy triggers are within a certain time window\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n }\n ]\n },\n \"unit\": \"ops\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 47\n },\n \"id\": 23,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"sum by(trigger_name, instance, node_type, trigger_type, outcome) (rate(splice_trigger_completed_total{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_name=~\\\"$trigger_name\\\", trigger_type=\\\"$trigger_type\\\"}[$__rate_interval]))\",\n \"instant\": false,\n \"legendFormat\": \"{{node_type}} {{instance}} {{trigger_name}} {{outcome}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Completed\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 55\n },\n \"id\": 22,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_trigger_attempted_total{isDsoDelegateTrigger=~\\\"$isDsoDelegateTrigger\\\", trigger_name=~\\\"$trigger_name\\\", namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"total attempts\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum(rate(splice_trigger_attempted_total{statusCode!~\\\"OK\\\", isDsoDelegateTrigger=~\\\"$isDsoDelegateTrigger\\\", namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_name=~\\\"$trigger_name\\\", contentionFailure=\\\"true\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"legendFormat\": \"total contentions\",\n \"range\": true,\n \"refId\": \"B\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Trigger attempts\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 63\n },\n \"id\": 20,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"last\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by(errorCodeId, namespace, node_type, trigger_name) (rate(splice_trigger_attempted_total{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\", trigger_name=~\\\"$trigger_name\\\", isDsoDelegateTrigger=~\\\"$isDsoDelegateTrigger\\\", contentionFailure=\\\"true\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Failed trigger attempts with contention errors\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 24,\n \"x\": 0,\n \"y\": 71\n },\n \"id\": 21,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"last\"\n ],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by (errorCodeId, namespace, node_type, trigger_name) (rate(splice_trigger_attempted_total{namespace=~\\\"$namespace\\\",node_type=~\\\"$app\\\",trigger_name=~\\\"$trigger_name\\\",isDsoDelegateTrigger=~\\\"$isDsoDelegateTrigger\\\", statusCode!~\\\"OK\\\", contentionFailure=\\\"false\\\"}[$__rate_interval]))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"B\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Failed trigger attempts with non-contention errors\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"\",\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"sv-1\",\n \"value\": \"sv-1\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_trigger_completed_total,namespace)\",\n \"includeAll\": true,\n \"label\": \"Namespace\",\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_trigger_completed_total,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"scan\",\n \"value\": \"scan\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_trigger_completed_total{namespace=~\\\"$namespace\\\"},node_type)\",\n \"includeAll\": true,\n \"label\": \"App\",\n \"name\": \"app\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_trigger_completed_total{namespace=~\\\"$namespace\\\"},node_type)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(splice_trigger_completed_total{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\"},trigger_name)\",\n \"includeAll\": true,\n \"label\": \"Trigger Name\",\n \"name\": \"trigger_name\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(splice_trigger_completed_total{namespace=~\\\"$namespace\\\", node_type=~\\\"$app\\\"},trigger_name)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": [\n \"taskbased\",\n \"polling\"\n ],\n \"value\": [\n \"taskbased\",\n \"polling\"\n ]\n },\n \"includeAll\": false,\n \"multi\": true,\n \"name\": \"trigger_type\",\n \"options\": [],\n \"query\": \"taskbased, polling\",\n \"type\": \"custom\",\n \"valuesFormat\": \"csv\"\n },\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": \"$__all\"\n },\n \"definition\": \"label_values(isDsoDelegateTrigger)\",\n \"includeAll\": true,\n \"label\": \"isDsoDelegateTrigger\",\n \"name\": \"isDsoDelegateTrigger\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(isDsoDelegateTrigger)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-3h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Automations\",\n \"uid\": \"a3e1385f-6f03-46d9-908c-34aca0f507a6\",\n \"version\": 2,\n \"weekStart\": \"\"\n}\n", "catchup.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"grafana\",\n \"uid\": \"-- Grafana --\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"type\": \"dashboard\"\n }\n ]\n },\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"links\": [],\n \"panels\": [\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 16,\n \"panels\": [],\n \"title\": \"Client Delay\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"description\": \"Delay on event processing of a sequencer, compared to the collective\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"log\": 2,\n \"type\": \"log\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"ms\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 24,\n \"x\": 0,\n \"y\": 1\n },\n \"id\": 1,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"min by (namespace, job) (daml_sequencer_block_delay{namespace=~\\\"$namespace\\\", component=\\\"sequencer\\\",job=~\\\"global-domain-$migration-sequencer\\\"})\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Client Delay\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"description\": \"Catchup speed of the sequencer in the last 5min\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"log\": 2,\n \"type\": \"log\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"ms\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 11,\n \"w\": 24,\n \"x\": 0,\n \"y\": 12\n },\n \"id\": 18,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"exemplar\": false,\n \"expr\": \"max by (namespace, job) (delta(daml_sequencer_block_delay{namespace=~\\\"$namespace\\\", component=\\\"sequencer\\\",job=~\\\"global-domain-$migration-sequencer\\\"}[5m])) / 5\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"processing time {{namespace}}-{{job}} / min\",\n \"range\": true,\n \"refId\": \"B\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Catchup Speed\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"description\": \"Delay on event processing of the participant, compared to the sequencers it is connected to.\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"log\": 2,\n \"type\": \"log\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 23\n },\n \"id\": 3,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace) (timestamp(daml_sequencer_client_handler_last_sequencing_time_micros{namespace=~\\\"$namespace\\\",component=\\\"participant\\\"}) - ((daml_sequencer_client_handler_last_sequencing_time_micros{namespace=~\\\"$namespace\\\",component=\\\"participant\\\"} > 0) / 1e6))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}} {{node}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Participant Client Delay\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"description\": \"Delay on event processing of a mediator, compared to its corresponding sequencer\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"log\": 2,\n \"type\": \"log\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"s\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 23\n },\n \"id\": 2,\n \"options\": {\n \"legend\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true,\n \"sortBy\": \"Last *\",\n \"sortDesc\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"max by (namespace, job) (timestamp(daml_sequencer_client_handler_last_sequencing_time_micros{namespace=~\\\"$namespace\\\",component=\\\"mediator\\\",job=~\\\"global-domain-$migration-mediator\\\"}) - ((daml_sequencer_client_handler_last_sequencing_time_micros{namespace=~\\\"$namespace\\\",component=\\\"mediator\\\",job=~\\\"global-domain-$migration-mediator\\\"} > 0) / 1e6)) \",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Mediator Client Delay\",\n \"type\": \"timeseries\"\n },\n {\n \"collapsed\": false,\n \"gridPos\": {\n \"h\": 1,\n \"w\": 24,\n \"x\": 0,\n \"y\": 31\n },\n \"id\": 10,\n \"panels\": [],\n \"title\": \"CometBFT\",\n \"type\": \"row\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"$DS\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 24,\n \"x\": 0,\n \"y\": 32\n },\n \"id\": 11,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": false\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"$DS\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"sum by(namespace, job) (rate(daml_sequencer_block_events_total{namespace=~\\\"$namespace\\\",job=~\\\"global-domain-$migration-sequencer\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}} {{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer processing event Rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"$DS\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 7,\n \"w\": 24,\n \"x\": 0,\n \"y\": 39\n },\n \"id\": 7,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": false\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"$DS\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"rate((daml_sequencer_block_height{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-sequencer\\\"} > 0)[$__rate_interval:])\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}} {{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer processing Block Rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"$DS\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"decimals\": 2,\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n },\n \"unit\": \"cps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 6,\n \"w\": 24,\n \"x\": 0,\n \"y\": 46\n },\n \"id\": 6,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": false\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"$DS\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"rate(cometbft_consensus_height{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-cometbft-cometbft-rpc\\\"}[$__rate_interval])\",\n \"format\": \"time_series\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"interval\": \"\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Cometbft producing Block Rate\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 52\n },\n \"id\": 8,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"daml_sequencer_block_height{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-sequencer\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"Sequencer Cometbft height \",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 52\n },\n \"id\": 4,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"cometbft_consensus_latest_block_height{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-cometbft-cometbft-rpc\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}-{{chain_id}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"CometBFT Block Height\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 0,\n \"y\": 60\n },\n \"id\": 5,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"single\",\n \"sort\": \"none\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"code\",\n \"expr\": \"cometbft_consensus_block_syncing{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-cometbft-cometbft-rpc\\\"}\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}-{{job}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"CometBFT Block Syncing\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 8,\n \"w\": 12,\n \"x\": 12,\n \"y\": 60\n },\n \"id\": 15,\n \"options\": {\n \"minVizHeight\": 75,\n \"minVizWidth\": 75,\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"lastNotNull\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showThresholdLabels\": false,\n \"showThresholdMarkers\": true,\n \"sizing\": \"auto\"\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"exemplar\": false,\n \"expr\": \"max by(namespace, migration) (label_replace(cometbft_consensus_latest_block_height{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-cometbft-cometbft-rpc\\\"}, \\\"migration\\\", \\\"$1\\\", \\\"job\\\", \\\"global-domain-(\\\\\\\\d)-.*\\\")) - max by(namespace, migration) (label_replace(daml_sequencer_block_height{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-sequencer\\\"}, \\\"migration\\\", \\\"$1\\\", \\\"job\\\", \\\"global-domain-(\\\\\\\\d)-.*\\\"))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": true,\n \"legendFormat\": \"{{namespace}}, migration={{migration}}\",\n \"range\": false,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"CometBFT blocks to process\",\n \"type\": \"gauge\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"barWidthFactor\": 0.6,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"auto\",\n \"showValues\": false,\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"off\"\n }\n },\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": 0\n },\n {\n \"color\": \"red\",\n \"value\": 80\n }\n ]\n }\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 9,\n \"w\": 24,\n \"x\": 0,\n \"y\": 68\n },\n \"id\": 17,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"list\",\n \"placement\": \"bottom\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"hideZeros\": false,\n \"mode\": \"multi\",\n \"sort\": \"desc\"\n }\n },\n \"pluginVersion\": \"12.4.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"${DS}\"\n },\n \"disableTextWrap\": false,\n \"editorMode\": \"builder\",\n \"exemplar\": false,\n \"expr\": \"max by(namespace, migration) (label_replace(cometbft_consensus_latest_block_height{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-cometbft-cometbft-rpc\\\"}, \\\"migration\\\", \\\"$1\\\", \\\"job\\\", \\\"global-domain-(\\\\\\\\d)-.*\\\")) - max by(namespace, migration) (label_replace(daml_sequencer_block_height{namespace=~\\\"$namespace\\\", job=~\\\"global-domain-$migration-sequencer\\\"}, \\\"migration\\\", \\\"$1\\\", \\\"job\\\", \\\"global-domain-(\\\\\\\\d)-.*\\\"))\",\n \"fullMetaSearch\": false,\n \"includeNullMetadata\": true,\n \"instant\": false,\n \"legendFormat\": \"{{namespace}}, migration={{migration}}\",\n \"range\": true,\n \"refId\": \"A\",\n \"useBackend\": false\n }\n ],\n \"title\": \"CometBFT blocks to process\",\n \"type\": \"timeseries\"\n }\n ],\n \"preload\": false,\n \"refresh\": \"30s\",\n \"schemaVersion\": 42,\n \"tags\": [],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"text\": \"Prometheus\",\n \"value\": \"prometheus\"\n },\n \"includeAll\": false,\n \"name\": \"DS\",\n \"options\": [],\n \"query\": \"prometheus\",\n \"refresh\": 1,\n \"regex\": \"\",\n \"type\": \"datasource\"\n },\n {\n \"current\": {\n \"text\": [\n \"sv\",\n \"sv-1\"\n ],\n \"value\": [\n \"sv\",\n \"sv-1\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_client_handler_delay,namespace)\",\n \"includeAll\": true,\n \"label\": \"Namespace\",\n \"multi\": true,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"query\": \"label_values(daml_sequencer_client_handler_delay,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"regexApplyTo\": \"value\",\n \"type\": \"query\"\n },\n {\n \"current\": {\n \"text\": \"All\",\n \"value\": [\n \"$__all\"\n ]\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"definition\": \"label_values(daml_sequencer_client_handler_delay{namespace=~\\\"$namespace\\\"},job)\",\n \"includeAll\": true,\n \"multi\": true,\n \"name\": \"migration\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(daml_sequencer_client_handler_delay{namespace=~\\\"$namespace\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"/global-domain-(?\\\\d+)-sequencer/g\",\n \"regexApplyTo\": \"value\",\n \"sort\": 1,\n \"type\": \"query\"\n }\n ]\n },\n \"time\": {\n \"from\": \"now-1h\",\n \"to\": \"now\"\n },\n \"timepicker\": {},\n \"timezone\": \"\",\n \"title\": \"Global Domain Catchup\",\n \"uid\": \"ca9df344-c699-4efe-83c2-5fb2639d96d9\",\n \"version\": 1,\n \"weekStart\": \"\"\n}\n", "cometbft-network-status.json": "{\n \"annotations\": {\n \"list\": [\n {\n \"builtIn\": 1,\n \"datasource\": {\n \"type\": \"datasource\",\n \"uid\": \"grafana\"\n },\n \"enable\": true,\n \"hide\": true,\n \"iconColor\": \"rgba(0, 211, 255, 1)\",\n \"name\": \"Annotations & Alerts\",\n \"target\": {\n \"limit\": 100,\n \"matchAny\": false,\n \"tags\": [],\n \"type\": \"dashboard\"\n },\n \"type\": \"dashboard\"\n }\n ]\n },\n \"description\": \"Health status of the CometBFT P2P Network as measured by incoming / outgoing \",\n \"editable\": true,\n \"fiscalYearStartMonth\": 0,\n \"graphTooltip\": 0,\n \"id\": 1283,\n \"links\": [],\n \"liveNow\": false,\n \"panels\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"$DS\"\n },\n \"description\": \"Total peer-wise rate of bytes sent or received over all channels over the P2P network\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"thresholds\"\n },\n \"fieldMinMax\": false,\n \"mappings\": [],\n \"min\": 0,\n \"thresholds\": {\n \"mode\": \"percentage\",\n \"steps\": [\n {\n \"color\": \"red\",\n \"value\": null\n },\n {\n \"color\": \"#EAB839\",\n \"value\": 1\n },\n {\n \"color\": \"green\",\n \"value\": 10\n }\n ]\n },\n \"unit\": \"KBs\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 13,\n \"w\": 24,\n \"x\": 0,\n \"y\": 0\n },\n \"id\": 66,\n \"options\": {\n \"minVizHeight\": 75,\n \"minVizWidth\": 75,\n \"orientation\": \"auto\",\n \"reduceOptions\": {\n \"calcs\": [\n \"last\"\n ],\n \"fields\": \"\",\n \"values\": false\n },\n \"showThresholdLabels\": false,\n \"showThresholdMarkers\": true,\n \"sizing\": \"auto\"\n },\n \"pluginVersion\": \"11.1.0\",\n \"targets\": [\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"prometheus\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (peer_id) (rate(cometbft_p2p_peer_receive_bytes_total{namespace=\\\"$namespace\\\", chain_id=\\\"$chain_id\\\", job=\\\"$instance\\\"}[$__rate_interval]) / 1000) + sum by (peer_id) (rate(cometbft_p2p_peer_send_bytes_total{namespace=\\\"$namespace\\\", chain_id=\\\"$chain_id\\\", job=\\\"$instance\\\"}[$__rate_interval]) / 1000)\",\n \"instant\": false,\n \"legendFormat\": \"__auto\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Total Network Throughput (current)\",\n \"type\": \"gauge\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"$DS\"\n },\n \"description\": \"Peer-wise rate of bytes received over all channels over the P2P network\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"never\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"fieldMinMax\": false,\n \"links\": [],\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 2000\n }\n ]\n },\n \"unit\": \"binBps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 24,\n \"x\": 0,\n \"y\": 13\n },\n \"id\": 59,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"asc\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"$DS\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (peer_id) (rate(cometbft_p2p_peer_receive_bytes_total{namespace=\\\"$namespace\\\", chain_id=\\\"$chain_id\\\", job=\\\"$instance\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{peer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Incoming Network Throughput\",\n \"type\": \"timeseries\"\n },\n {\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"$DS\"\n },\n \"description\": \"Peer-wise rate of bytes sent over all channels over the P2P network\",\n \"fieldConfig\": {\n \"defaults\": {\n \"color\": {\n \"mode\": \"palette-classic\"\n },\n \"custom\": {\n \"axisBorderShow\": false,\n \"axisCenteredZero\": false,\n \"axisColorMode\": \"text\",\n \"axisLabel\": \"\",\n \"axisPlacement\": \"auto\",\n \"barAlignment\": 0,\n \"drawStyle\": \"line\",\n \"fillOpacity\": 0,\n \"gradientMode\": \"none\",\n \"hideFrom\": {\n \"legend\": false,\n \"tooltip\": false,\n \"viz\": false\n },\n \"insertNulls\": false,\n \"lineInterpolation\": \"linear\",\n \"lineWidth\": 1,\n \"pointSize\": 5,\n \"scaleDistribution\": {\n \"type\": \"linear\"\n },\n \"showPoints\": \"never\",\n \"spanNulls\": false,\n \"stacking\": {\n \"group\": \"A\",\n \"mode\": \"none\"\n },\n \"thresholdsStyle\": {\n \"mode\": \"dashed\"\n }\n },\n \"fieldMinMax\": false,\n \"links\": [],\n \"mappings\": [],\n \"thresholds\": {\n \"mode\": \"absolute\",\n \"steps\": [\n {\n \"color\": \"green\",\n \"value\": null\n },\n {\n \"color\": \"red\",\n \"value\": 2000\n }\n ]\n },\n \"unit\": \"binBps\"\n },\n \"overrides\": []\n },\n \"gridPos\": {\n \"h\": 12,\n \"w\": 24,\n \"x\": 0,\n \"y\": 25\n },\n \"id\": 65,\n \"options\": {\n \"legend\": {\n \"calcs\": [],\n \"displayMode\": \"table\",\n \"placement\": \"right\",\n \"showLegend\": true\n },\n \"tooltip\": {\n \"mode\": \"multi\",\n \"sort\": \"asc\"\n }\n },\n \"pluginVersion\": \"10.1.5\",\n \"targets\": [\n {\n \"datasource\": {\n \"uid\": \"$DS\"\n },\n \"editorMode\": \"code\",\n \"expr\": \"sum by (peer_id) (rate(cometbft_p2p_peer_send_bytes_total{namespace=\\\"$namespace\\\", chain_id=\\\"$chain_id\\\", job=\\\"$instance\\\"}[$__rate_interval]))\",\n \"format\": \"time_series\",\n \"intervalFactor\": 1,\n \"legendFormat\": \"{{peer_id}}\",\n \"range\": true,\n \"refId\": \"A\"\n }\n ],\n \"title\": \"Outgoing Network Throughput\",\n \"type\": \"timeseries\"\n }\n ],\n \"refresh\": \"\",\n \"schemaVersion\": 39,\n \"tags\": [\n \"Blockchain\"\n ],\n \"templating\": {\n \"list\": [\n {\n \"current\": {\n \"selected\": false,\n \"text\": \"default\",\n \"value\": \"default\"\n },\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"Datasource\",\n \"multi\": false,\n \"name\": \"DS\",\n \"options\": [],\n \"query\": \"prometheus\",\n \"queryValue\": \"\",\n \"refresh\": 1,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"type\": \"datasource\"\n },\n {\n \"current\": {\n \"selected\": false,\n \"text\": \"sv-1\",\n \"value\": \"sv-1\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"$DS\"\n },\n \"definition\": \"label_values(cometbft_p2p_message_receive_bytes_total,namespace)\",\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"namespace\",\n \"multi\": false,\n \"name\": \"namespace\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(cometbft_p2p_message_receive_bytes_total,namespace)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 2,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 1,\n \"tagValuesQuery\": \"\",\n \"tagsQuery\": \"\",\n \"type\": \"query\",\n \"useTags\": false\n },\n {\n \"current\": {\n \"selected\": true,\n \"text\": \"cidaily-0-0.2.0-snapshot.20240725.6534.0.v112d3fc6\",\n \"value\": \"cidaily-0-0.2.0-snapshot.20240725.6534.0.v112d3fc6\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"$DS\"\n },\n \"definition\": \"label_values(cometbft_p2p_message_receive_bytes_total,chain_id)\",\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"Chain ID\",\n \"multi\": false,\n \"name\": \"chain_id\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(cometbft_p2p_message_receive_bytes_total,chain_id)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 0,\n \"tagValuesQuery\": \"\",\n \"tagsQuery\": \"\",\n \"type\": \"query\",\n \"useTags\": false\n },\n {\n \"allValue\": \"\",\n \"current\": {\n \"selected\": false,\n \"text\": \"global-domain-0-cometbft-cometbft-rpc\",\n \"value\": \"global-domain-0-cometbft-cometbft-rpc\"\n },\n \"datasource\": {\n \"type\": \"prometheus\",\n \"uid\": \"$DS\"\n },\n \"definition\": \"label_values(cometbft_p2p_message_receive_bytes_total{chain_id=\\\"$chain_id\\\"},job)\",\n \"hide\": 0,\n \"includeAll\": false,\n \"label\": \"Instance\",\n \"multi\": false,\n \"name\": \"instance\",\n \"options\": [],\n \"query\": {\n \"qryType\": 1,\n \"query\": \"label_values(cometbft_p2p_message_receive_bytes_total{chain_id=\\\"$chain_id\\\"},job)\",\n \"refId\": \"PrometheusVariableQueryEditor-VariableQuery\"\n },\n \"refresh\": 1,\n \"regex\": \"\",\n \"skipUrlSync\": false,\n \"sort\": 5,\n \"tagValuesQuery\": \"\",\n \"tagsQuery\": \"\",\n \"type\": \"query\",\n \"useTags\": false\n }\n ]\n },\n \"time\": {\n \"from\": \"now-12h\",\n \"to\": \"now\"\n },\n \"timepicker\": {\n \"refresh_intervals\": [\n \"5s\",\n \"10s\",\n \"30s\",\n \"1m\",\n \"5m\",\n \"15m\",\n \"30m\",\n \"1h\",\n \"2h\",\n \"1d\"\n ],\n \"time_options\": [\n \"5m\",\n \"15m\",\n \"1h\",\n \"6h\",\n \"12h\",\n \"24h\",\n \"2d\",\n \"7d\",\n \"30d\"\n ]\n },\n \"timezone\": \"\",\n \"title\": \"CometBFT Network Status\",\n \"uid\": \"ddsuu1wnxwetcd\",\n \"version\": 7,\n \"weekStart\": \"\"\n}\n", diff --git a/cluster/pulumi/observability/grafana-dashboards/canton-network/app-rewards.json b/cluster/pulumi/observability/grafana-dashboards/canton-network/app-rewards.json index 8ea7846361..d648b9e431 100644 --- a/cluster/pulumi/observability/grafana-dashboards/canton-network/app-rewards.json +++ b/cluster/pulumi/observability/grafana-dashboards/canton-network/app-rewards.json @@ -360,7 +360,32 @@ ] } }, - "overrides": [] + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "custom.width", + "value": 100 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Time" + }, + "properties": [ + { + "id": "custom.width", + "value": 200 + } + ] + } + ] }, "gridPos": { "h": 8, @@ -371,6 +396,7 @@ "id": 32, "options": { "cellHeight": "sm", + "enablePagination": true, "showHeader": true }, "pluginVersion": "12.4.0", @@ -378,7 +404,8 @@ { "editorMode": "code", "exemplar": false, - "expr": "max by (namespace)(splice_scan_reward_computation_reward_coupons_v2_hidden_coupons{namespace=~\"$namespace\"})", + "expr": "max by (party)(splice_reward_coupons_v2_hidden_coupons{namespace=~\"$namespace\"})", + "format": "table", "instant": true, "legendFormat": "__auto", "range": false, @@ -386,6 +413,31 @@ } ], "title": "Parties with hidden coupons", + "transformations": [ + { + "id": "sortBy", + "options": { + "fields": {}, + "sort": [ + { + "desc": true, + "field": "Value" + } + ] + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": [ + "party", + "Value" + ] + } + } + } + ], "type": "table" }, { @@ -1149,9 +1201,7 @@ "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "median" - ], + "calcs": ["median"], "fields": "", "values": false }, @@ -1210,9 +1260,7 @@ "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -1271,9 +1319,7 @@ "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, @@ -1332,9 +1378,7 @@ "orientation": "auto", "percentChangeColorMode": "standard", "reduceOptions": { - "calcs": [ - "lastNotNull" - ], + "calcs": ["lastNotNull"], "fields": "", "values": false }, From 99e962c4f4162e783d50ca4b9cf4202ddd4befb7 Mon Sep 17 00:00:00 2001 From: Nicu Reut Date: Mon, 24 Aug 2026 12:55:42 +0200 Subject: [PATCH 289/329] Enable minimal whitelisting by default (#6928) [static] Signed-off-by: Nicu Reut --- cluster/pulumi/infra/src/config.ts | 2 +- cluster/pulumi/infra/src/whitelisting/index.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cluster/pulumi/infra/src/config.ts b/cluster/pulumi/infra/src/config.ts index 636937c790..3d42081c5d 100644 --- a/cluster/pulumi/infra/src/config.ts +++ b/cluster/pulumi/infra/src/config.ts @@ -53,7 +53,7 @@ export const InfraConfigSchema = z.object({ enableIngressAccessLogging: z.boolean(), enableClusterAccessLogging: z.boolean().default(false), enablePublicTokenRegistry: z.boolean().default(false), - enableGeneralIpWhitelist: z.boolean().default(true), + enableGeneralIpWhitelist: z.boolean().default(false), istiodValues: z.object({}).catchall(z.any()).default({}), sequencerFlowControl: z.object({ initialStreamWindowSize: z.int(), diff --git a/cluster/pulumi/infra/src/whitelisting/index.ts b/cluster/pulumi/infra/src/whitelisting/index.ts index f912f3b2bb..d532ec1470 100644 --- a/cluster/pulumi/infra/src/whitelisting/index.ts +++ b/cluster/pulumi/infra/src/whitelisting/index.ts @@ -13,8 +13,9 @@ export function installAppWhitelisting( ): pulumi.Output[] { if (infraConfig.istio.enableGeneralIpWhitelist) { return []; + } else { + return [configureScanAndSvAppWhitelist(namespace), ...configureSequencerWhitelist(namespace)]; } - return [configureScanAndSvAppWhitelist(namespace), ...configureSequencerWhitelist(namespace)]; } export function configureIstioGatewayPolicies( From b959ffbb39b0e23cf33f9f8a97939be0c05854e4 Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 24 Aug 2026 13:51:36 +0200 Subject: [PATCH 290/329] Final Implementation Initiate Proposal Flow (#6410) Signed-off-by: Puneet Bharti Co-authored-by: Puneet Bharti --- apps/sv/frontend/index.html | 2 +- .../components/copyable-identifier.test.tsx | 84 +++++- ...allocated-unclaimed-activity-form.test.tsx | 10 +- .../grant-revoke-featured-app-form.test.tsx | 19 +- .../forms/offboard-sv-form.test.tsx | 12 +- .../forms/set-amulet-rules-form.test.tsx | 10 +- .../forms/set-dso-rules-form.test.tsx | 5 +- ...update-sv-reward-weight-form-test.test.tsx | 7 +- .../governance/governance-page.test.tsx | 4 +- .../governance/governance-sorting.test.tsx | 4 +- .../proposal-details-content.test.tsx | 86 +++++- .../governance/proposal-summary.test.tsx | 166 +++++------- .../src/__tests__/layout/sv-top-nav.test.tsx | 35 +++ apps/sv/frontend/src/components/Layout.tsx | 2 +- .../components/beta/CopyableIdentifier.tsx | 129 +++++++-- .../src/components/beta/CopyableUrl.tsx | 89 +++++-- .../src/components/beta/MemberIdentifier.tsx | 11 +- .../src/components/beta/identifierStyles.ts | 33 ++- .../form-components/ConfigField.tsx | 62 ++++- .../components/form-components/DateField.tsx | 25 +- .../form-components/FormControls.tsx | 89 ++++--- .../form-components/ProposalSummaryField.tsx | 37 ++- .../form-components/ProposalTypeField.tsx | 13 +- .../form-components/SelectField.tsx | 9 +- .../components/form-components/TextField.tsx | 28 +- ...UnallocatedUnclaimedActivityRecordForm.tsx | 42 ++- .../src/components/forms/FormLayout.tsx | 32 ++- .../forms/GrantRevokeFeaturedAppForm.tsx | 50 +++- .../src/components/forms/OffboardSvForm.tsx | 50 +++- .../src/components/forms/SelectAction.tsx | 2 +- .../forms/SetAmuletConfigRulesForm.tsx | 105 +++++--- .../forms/SetDsoConfigRulesForm.tsx | 106 +++++--- .../forms/UpdateFeaturedAppForm.tsx | 24 +- .../forms/UpdateSvRewardWeightForm.tsx | 46 +++- .../governance/ActionRequiredSection.tsx | 8 +- .../governance/CancelProposalDialog.tsx | 96 +++++++ .../governance/ConfigValuesChanges.tsx | 19 +- .../governance/InitiateProposalHeader.tsx | 43 +++ .../governance/InitiateProposalLayout.tsx | 24 ++ .../governance/ProposalDetailsContent.tsx | 194 +++++++++++--- .../governance/ProposalListingSection.tsx | 12 +- .../governance/ProposalReviewField.tsx | 63 +++++ .../components/governance/ProposalSummary.tsx | 249 +++++++++--------- .../governance/ProposalVoteForm.tsx | 12 +- .../proposal-details/DetailItem.tsx | 9 +- .../src/components/layout/SvNavLink.tsx | 55 ++-- .../src/components/layout/SvTopNav.tsx | 65 ++++- .../src/constants/createProposalLayout.ts | 67 +++++ .../src/constants/formButtonStyles.ts | 70 +++++ .../src/hooks/useHorizontalScrollMetrics.ts | 6 +- .../sv/frontend/src/routes/createProposal.tsx | 10 +- apps/sv/frontend/src/routes/governance.tsx | 2 +- apps/sv/frontend/src/theme/tokens.ts | 3 - apps/sv/frontend/src/themes/fieldStyles.ts | 5 + .../src/utils/buildDsoConfigChanges.ts | 24 +- apps/sv/frontend/src/utils/constants.ts | 28 +- 56 files changed, 1861 insertions(+), 631 deletions(-) create mode 100644 apps/sv/frontend/src/__tests__/layout/sv-top-nav.test.tsx create mode 100644 apps/sv/frontend/src/components/governance/CancelProposalDialog.tsx create mode 100644 apps/sv/frontend/src/components/governance/InitiateProposalHeader.tsx create mode 100644 apps/sv/frontend/src/components/governance/InitiateProposalLayout.tsx create mode 100644 apps/sv/frontend/src/components/governance/ProposalReviewField.tsx create mode 100644 apps/sv/frontend/src/constants/createProposalLayout.ts create mode 100644 apps/sv/frontend/src/constants/formButtonStyles.ts diff --git a/apps/sv/frontend/index.html b/apps/sv/frontend/index.html index c2f95b8233..32252600a0 100644 --- a/apps/sv/frontend/index.html +++ b/apps/sv/frontend/index.html @@ -7,7 +7,7 @@ diff --git a/apps/sv/frontend/src/__tests__/components/copyable-identifier.test.tsx b/apps/sv/frontend/src/__tests__/components/copyable-identifier.test.tsx index 0f2d6ec304..90a415352d 100644 --- a/apps/sv/frontend/src/__tests__/components/copyable-identifier.test.tsx +++ b/apps/sv/frontend/src/__tests__/components/copyable-identifier.test.tsx @@ -27,26 +27,80 @@ describe('CopyableIdentifier', () => { expect(screen.getByTestId('contract-id-scroll')).toHaveStyle({ overflowX: 'auto' }); }); - test('shows a scroll track below the identifier when content overflows', async () => { + test('keeps the copy button adjacent to short identifiers', () => { + render( +

+ +
+ ); + + expect(screen.getByTestId('short-id')).toHaveStyle({ + display: 'inline-flex', + width: 'fit-content', + }); + }); + + test('compact scroll keeps #1785 scrolling at the Figma width', async () => { render( - + ); const scroll = screen.getByTestId('contract-id-scroll'); + expect(scroll).toHaveStyle({ overflowX: 'auto', maxWidth: '270px' }); + expect(screen.getByTestId('contract-id-value')).toHaveTextContent(LONG_CONTRACT_ID); + Object.defineProperty(scroll, 'scrollWidth', { configurable: true, value: 400 }); Object.defineProperty(scroll, 'clientWidth', { configurable: true, value: 100 }); + Object.defineProperty(scroll, 'scrollLeft', { configurable: true, value: 0 }); fireEvent.scroll(scroll); await waitFor(() => { + expect(screen.queryByTestId('contract-id-ellipsis-cue')).not.toBeInTheDocument(); expect(screen.getByTestId('contract-id-scroll-track')).toBeInTheDocument(); }); + }); + + test('fullWidth fills the parent and keeps scrolling', () => { + render( +
+ +
+ ); + + expect(screen.getByTestId('contract-id')).toHaveStyle({ width: '100%', display: 'flex' }); + expect(screen.getByTestId('contract-id-scroll')).toHaveStyle({ overflowX: 'auto' }); + expect(screen.getByTestId('contract-id-value')).toHaveTextContent(LONG_CONTRACT_ID); + }); - expect(screen.getByTestId('contract-id-scroll-track')).toHaveStyle({ - opacity: '0', - height: '0px', + test('trims long identifiers to the Figma ellipsis width without a narrow parent', () => { + render( + + ); + + expect(screen.getByTestId('contract-id-value')).toHaveTextContent(LONG_CONTRACT_ID); + expect(screen.getByTestId('contract-id-value')).toHaveAttribute('title', LONG_CONTRACT_ID); + expect(screen.getByTestId('contract-id-ellipsis')).toHaveStyle({ + overflow: 'hidden', + maxWidth: '270px', }); + expect(screen.getByTestId('contract-id-value')).toHaveStyle({ textOverflow: 'ellipsis' }); }); }); @@ -60,6 +114,26 @@ describe('MemberIdentifier', () => { expect(screen.getByTestId('member-value')).not.toHaveTextContent('...'); expect(screen.getByTestId('member-scroll')).toHaveStyle({ overflowX: 'auto' }); }); + + test('supports ellipsis overflow for compact layouts', () => { + render( + + + + ); + + expect(screen.getByTestId('member-value')).toHaveTextContent(LONG_PARTY_ID); + expect(screen.getByTestId('member-value')).toHaveAttribute('title', LONG_PARTY_ID); + expect(screen.getByTestId('member-ellipsis')).toHaveStyle({ overflow: 'hidden' }); + expect(screen.getByTestId('member-value')).toHaveStyle({ textOverflow: 'ellipsis' }); + expect(screen.queryByTestId('member-scroll')).not.toBeInTheDocument(); + }); }); describe('common PartyId', () => { diff --git a/apps/sv/frontend/src/__tests__/governance/forms/create-unallocated-unclaimed-activity-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/create-unallocated-unclaimed-activity-form.test.tsx index 5c13445a01..67858a367d 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/create-unallocated-unclaimed-activity-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/create-unallocated-unclaimed-activity-form.test.tsx @@ -10,7 +10,11 @@ import { describe, expect, test } from 'vitest'; import App from '../../../App'; import { CreateUnallocatedUnclaimedActivityRecordForm } from '../../../components/forms/CreateUnallocatedUnclaimedActivityRecordForm'; import { SvConfigProvider } from '../../../utils'; -import { PROPOSAL_SUMMARY_SUBTITLE, PROPOSAL_SUMMARY_TITLE } from '../../../utils/constants'; +import { + CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE, + PROPOSAL_REVIEW_TITLE, + PROPOSAL_SUMMARY_SUBTITLE, +} from '../../../utils/constants'; import { Wrapper } from '../../helpers'; import { svPartyId } from '../../mocks/constants'; import { server, svUrl } from '../../setup/setup'; @@ -47,7 +51,7 @@ describe('Create Unallocated Unclaimed Activity Record Form', () => { expect( screen.getByTestId('create-unallocated-unclaimed-activity-record-form') ).toBeInTheDocument(); - expect(screen.getByText('Proposal type')).toBeInTheDocument(); + expect(screen.getByText(CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE)).toBeInTheDocument(); const actionInput = screen.getByTestId('create-unallocated-unclaimed-activity-record-action'); expect(actionInput).toBeInTheDocument(); @@ -368,7 +372,7 @@ describe('Create Unallocated Unclaimed Activity Record Form', () => { await user.click(submitButton); - expect(screen.getByText(PROPOSAL_SUMMARY_TITLE)).toBeInTheDocument(); + expect(screen.getByText(PROPOSAL_REVIEW_TITLE)).toBeInTheDocument(); }); test('should show error on form if submission fails', async () => { diff --git a/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx index 1566a5ff9e..aaca03db31 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/grant-revoke-featured-app-form.test.tsx @@ -13,7 +13,12 @@ import dayjs from 'dayjs'; import { GrantRevokeFeaturedAppForm } from '../../../components/forms/GrantRevokeFeaturedAppForm'; import { server, svUrl } from '../../setup/setup'; import { http, HttpResponse } from 'msw'; -import { PROPOSAL_SUMMARY_SUBTITLE, PROPOSAL_SUMMARY_TITLE } from '../../../utils/constants'; +import { + CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE, + CREATE_PROPOSAL_LABEL_PROVIDER_PARTY_ID, + PROPOSAL_REVIEW_TITLE, + PROPOSAL_SUMMARY_SUBTITLE, +} from '../../../utils/constants'; describe('SV user can', () => { test('login and see the SV party ID', async () => { @@ -45,7 +50,7 @@ describe('Grant Featured App Form', () => { ); expect(screen.getByTestId('grant-featured-app-form')).toBeInTheDocument(); - expect(screen.getByText('Proposal type')).toBeInTheDocument(); + expect(screen.getByText(CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE)).toBeInTheDocument(); const actionInput = screen.getByTestId('grant-featured-app-action'); expect(actionInput).toBeInTheDocument(); @@ -69,7 +74,7 @@ describe('Grant Featured App Form', () => { const providerInput = screen.getByTestId('grant-featured-app-idValue-title'); expect(providerInput).toBeInTheDocument(); - expect(providerInput.textContent).toBe('Provider Party ID'); + expect(providerInput.textContent).toBe(CREATE_PROPOSAL_LABEL_PROVIDER_PARTY_ID); expect(screen.getByText('Review Proposal')).toBeInTheDocument(); }); @@ -212,7 +217,7 @@ describe('Grant Featured App Form', () => { await user.click(submitButton); - expect(screen.getByText(PROPOSAL_SUMMARY_TITLE)).toBeInTheDocument(); + expect(screen.getByText(PROPOSAL_REVIEW_TITLE)).toBeInTheDocument(); }); test('activity weight is optional and is sent to backend as null when left blank', async () => { @@ -391,7 +396,7 @@ describe('Revoke Featured App Form', () => { ); expect(screen.getByTestId('revoke-featured-app-form')).toBeInTheDocument(); - expect(screen.getByText('Proposal type')).toBeInTheDocument(); + expect(screen.getByText(CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE)).toBeInTheDocument(); const actionInput = screen.getByTestId('revoke-featured-app-action'); expect(actionInput).toBeInTheDocument(); @@ -411,7 +416,7 @@ describe('Revoke Featured App Form', () => { const partyIdTitle = screen.getByTestId('revoke-featured-app-partyId-title'); expect(partyIdTitle).toBeInTheDocument(); - expect(partyIdTitle.textContent).toBe('Provider Party ID'); + expect(partyIdTitle.textContent).toBe(CREATE_PROPOSAL_LABEL_PROVIDER_PARTY_ID); const rightCidDropdown = screen.getByTestId('revoke-featured-app-rightCid-dropdown'); expect(rightCidDropdown).toBeInTheDocument(); @@ -544,7 +549,7 @@ describe('Revoke Featured App Form', () => { await user.click(submitButton); - expect(screen.getByText(PROPOSAL_SUMMARY_TITLE)).toBeInTheDocument(); + expect(screen.getByText(PROPOSAL_REVIEW_TITLE)).toBeInTheDocument(); expect(screen.getByTestId('revokeProviderPartyId-title').textContent).toBe('Provider Party ID'); expect(screen.getByTestId('revokeProviderPartyId-field').textContent).toBe( 'a-party-id::1014912492' diff --git a/apps/sv/frontend/src/__tests__/governance/forms/offboard-sv-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/offboard-sv-form.test.tsx index 3d95c438be..d16b28c494 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/offboard-sv-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/offboard-sv-form.test.tsx @@ -13,7 +13,12 @@ import dayjs from 'dayjs'; import { OffboardSvForm } from '../../../components/forms/OffboardSvForm'; import { server, svUrl } from '../../setup/setup'; import { http, HttpResponse } from 'msw'; -import { PROPOSAL_SUMMARY_SUBTITLE, PROPOSAL_SUMMARY_TITLE } from '../../../utils/constants'; +import { + CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE, + PROPOSAL_REVIEW_TITLE, + PROPOSAL_SUMMARY_PLACEHOLDER, + PROPOSAL_SUMMARY_SUBTITLE, +} from '../../../utils/constants'; describe('SV user can', () => { test('login and see the SV party ID', async () => { @@ -45,7 +50,7 @@ describe('Offboard SV Form', () => { ); expect(screen.getByTestId('offboard-sv-form')).toBeInTheDocument(); - expect(screen.getByText('Proposal type')).toBeInTheDocument(); + expect(screen.getByText(CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE)).toBeInTheDocument(); const actionInput = screen.getByTestId('offboard-sv-action'); expect(actionInput).toBeInTheDocument(); @@ -54,6 +59,7 @@ describe('Offboard SV Form', () => { const summaryInput = screen.getByTestId('offboard-sv-summary'); expect(summaryInput).toBeInTheDocument(); expect(summaryInput.getAttribute('value')).toBeNull(); + expect(summaryInput.getAttribute('placeholder')).toBe(PROPOSAL_SUMMARY_PLACEHOLDER); const summarySubtitle = screen.getByTestId('offboard-sv-summary-subtitle'); expect(summarySubtitle).toBeInTheDocument(); @@ -231,7 +237,7 @@ describe('Offboard SV Form', () => { await user.click(submitButton); - expect(screen.getByText(PROPOSAL_SUMMARY_TITLE)).toBeInTheDocument(); + expect(screen.getByText(PROPOSAL_REVIEW_TITLE)).toBeInTheDocument(); }); test('should show error on form if submission fails', async () => { diff --git a/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx index d12a78ac96..112720c447 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/set-amulet-rules-form.test.tsx @@ -13,7 +13,11 @@ import { SetAmuletConfigRulesForm } from '../../../components/forms/SetAmuletCon import dayjs from 'dayjs'; import { dateTimeFormatISO } from '@canton-network/splice-common-frontend-utils'; import { server, svUrl } from '../../setup/setup'; -import { PROPOSAL_SUMMARY_SUBTITLE, PROPOSAL_SUMMARY_TITLE } from '../../../utils/constants'; +import { + CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE, + PROPOSAL_REVIEW_TITLE, + PROPOSAL_SUMMARY_SUBTITLE, +} from '../../../utils/constants'; describe('SV user can', () => { test('login and see the SV party ID', async () => { @@ -45,7 +49,7 @@ describe('Set Amulet Config Rules Form', () => { ); expect(screen.getByTestId('set-amulet-config-rules-form')).toBeInTheDocument(); - expect(screen.getByText('Proposal type')).toBeInTheDocument(); + expect(screen.getByText(CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE)).toBeInTheDocument(); const actionInput = screen.getByTestId('set-amulet-config-rules-action'); expect(actionInput).toBeInTheDocument(); @@ -325,7 +329,7 @@ describe('Set Amulet Config Rules Form', () => { await user.click(submitButton); - expect(screen.getByText(PROPOSAL_SUMMARY_TITLE)).toBeInTheDocument(); + expect(screen.getByText(PROPOSAL_REVIEW_TITLE)).toBeInTheDocument(); expect(screen.queryByText('JSON')).not.toBeInTheDocument(); expect(screen.getByTestId('json-diff-toggle')).toHaveTextContent('Show JSON'); }); diff --git a/apps/sv/frontend/src/__tests__/governance/forms/set-dso-rules-form.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/set-dso-rules-form.test.tsx index 2825f1d132..fbf46ec324 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/set-dso-rules-form.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/set-dso-rules-form.test.tsx @@ -12,6 +12,7 @@ import { http, HttpResponse } from 'msw'; import { describe, expect, test } from 'vitest'; import App from '../../../App'; import { SetDsoConfigRulesForm } from '../../../components/forms/SetDsoConfigRulesForm'; +import { CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE } from '../../../utils/constants'; import { SvConfigProvider } from '../../../utils'; import { Wrapper } from '../../helpers'; import { svPartyId } from '../../mocks/constants'; @@ -47,7 +48,7 @@ describe('Set DSO Config Rules Form', () => { ); expect(screen.getByTestId('set-dso-config-rules-form')).toBeInTheDocument(); - expect(screen.getByText('Proposal type')).toBeInTheDocument(); + expect(screen.getByText(CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE)).toBeInTheDocument(); const actionInput = screen.getByTestId('set-dso-config-rules-action'); expect(actionInput).toBeInTheDocument(); @@ -267,7 +268,7 @@ describe('Set DSO Config Rules Form', () => { await user.click(submitButton); - expect(screen.getByText('Proposal Summary')).toBeInTheDocument(); + expect(screen.getByText('Proposal Review')).toBeInTheDocument(); expect(screen.queryByText('JSON')).not.toBeInTheDocument(); expect(screen.getByTestId('json-diff-toggle')).toHaveTextContent('Show JSON'); }); diff --git a/apps/sv/frontend/src/__tests__/governance/forms/update-sv-reward-weight-form-test.test.tsx b/apps/sv/frontend/src/__tests__/governance/forms/update-sv-reward-weight-form-test.test.tsx index a9bd4985ee..2d6892d4bb 100644 --- a/apps/sv/frontend/src/__tests__/governance/forms/update-sv-reward-weight-form-test.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/forms/update-sv-reward-weight-form-test.test.tsx @@ -13,7 +13,10 @@ import { dateTimeFormatISO } from '@canton-network/splice-common-frontend-utils' import dayjs from 'dayjs'; import { server, svUrl } from '../../setup/setup'; import { http, HttpResponse } from 'msw'; -import { PROPOSAL_SUMMARY_SUBTITLE } from '../../../utils/constants'; +import { + CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE, + PROPOSAL_SUMMARY_SUBTITLE, +} from '../../../utils/constants'; describe('SV user can', () => { test('login and see the SV party ID', async () => { @@ -45,7 +48,7 @@ describe('Update Super Validator Reward Weight Form', () => { ); expect(screen.getByTestId('update-sv-reward-weight-form')).toBeInTheDocument(); - expect(screen.getByText('Proposal type')).toBeInTheDocument(); + expect(screen.getByText(CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE)).toBeInTheDocument(); const actionInput = screen.getByTestId('update-sv-reward-weight-action'); expect(actionInput).toBeInTheDocument(); diff --git a/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx b/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx index fa85a645a9..adf2aaf9b0 100644 --- a/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/governance-page.test.tsx @@ -183,7 +183,7 @@ describe('Governance Page', () => { await user.click(viewDetailsLink); - const proposalDetails = screen.getByTestId('proposal-details-title'); + const proposalDetails = screen.getByTestId('proposal-details-proposal-details'); expect(proposalDetails).toBeInTheDocument(); }); @@ -201,7 +201,7 @@ describe('Governance Page', () => { await user.click(viewDetailsLink); - const proposalDetails = screen.getByTestId('proposal-details-title'); + const proposalDetails = screen.getByTestId('proposal-details-proposal-details'); expect(proposalDetails).toBeInTheDocument(); const action = screen.getByTestId('proposal-details-action-value'); diff --git a/apps/sv/frontend/src/__tests__/governance/governance-sorting.test.tsx b/apps/sv/frontend/src/__tests__/governance/governance-sorting.test.tsx index fe1307b030..f593de50f4 100644 --- a/apps/sv/frontend/src/__tests__/governance/governance-sorting.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/governance-sorting.test.tsx @@ -62,7 +62,7 @@ describe('Governance Page Sorting', () => { }); }); - describe('Inflight Votes Section', () => { + describe('In-flight Proposals Section', () => { const baseData: Omit< ProposalListingData, 'actionName' | 'contractId' | 'voteTakesEffect' | 'votingThresholdDeadline' | 'voteStats' @@ -120,7 +120,7 @@ describe('Governance Page Sorting', () => { render( , @@ -160,16 +168,21 @@ describe('Proposal Details Content', () => { ); - const pageTitle = screen.getByTestId('proposal-details-title'); - expect(pageTitle.textContent).toMatch(/Proposal Details/); + expect(screen.getByTestId('proposal-details-title')).toHaveTextContent('Proposal Details'); + const backToAllVotes = screen.getByTestId('proposal-details-back-to-all-votes'); + expect(backToAllVotes).toHaveTextContent('Back to all votes'); + expect(backToAllVotes).toHaveAttribute('href', '/governance/proposals'); + + const proposalDetailsSection = screen.getByTestId('proposal-details-proposal-details'); + expect(proposalDetailsSection).toBeInTheDocument(); + // Figma starts at Action — no duplicate inner "Proposal Details" heading + expect( + within(proposalDetailsSection).queryByRole('heading', { name: 'Proposal Details' }) + ).toBeNull(); const action = screen.getByTestId('proposal-details-action-value'); expect(action.textContent).toMatch(/Offboard Member/); - expect(screen.getByTestId('proposal-details-contractid-label').textContent).toBe( - VOTE_PROPOSAL_CONTRACT_ID_LABEL - ); - const offboardSection = screen.getByTestId('proposal-details-offboard-member-section'); expect(offboardSection).toBeInTheDocument(); @@ -178,7 +191,13 @@ describe('Proposal Details Content', () => { ); expect(memberInput).toBeInTheDocument(); expect(memberInput.textContent).toBe('sv2'); + expect( + within(offboardSection).getByTestId('proposal-details-member-party-id-scroll') + ).toHaveStyle({ overflowX: 'auto', maxWidth: '270px' }); + expect(screen.getByTestId('proposal-details-summary-label').textContent).toBe( + PROPOSAL_SUMMARY_TITLE + ); const summary = screen.getByTestId('proposal-details-summary-value'); expect(summary.textContent).toMatch(/Summary of the proposal/); @@ -186,6 +205,30 @@ describe('Proposal Details Content', () => { const url = screen.getByTestId('proposal-details-url'); expect(url.textContent).toMatch(/https:\/\/example.com/); + expect(screen.getByTestId('proposal-details-url-scroll')).toHaveStyle({ + overflowX: 'auto', + maxWidth: '346px', + }); + + // Figma Offboard details order: Action → Member → Proposal Summary → Supporting URL → Contract ID + expect(screen.getByTestId('proposal-details-contractid-label').textContent).toBe( + VOTE_PROPOSAL_CONTRACT_ID_LABEL + ); + expect(screen.getByTestId('proposal-details-contractid-id-scroll')).toHaveStyle({ + overflowX: 'auto', + maxWidth: '270px', + }); + const contractIdLabel = screen.getByTestId('proposal-details-contractid-label'); + expect( + action.compareDocumentPosition(offboardSection) & Node.DOCUMENT_POSITION_FOLLOWING + ).toBeTruthy(); + expect( + offboardSection.compareDocumentPosition(summary) & Node.DOCUMENT_POSITION_FOLLOWING + ).toBeTruthy(); + expect(summary.compareDocumentPosition(url) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy(); + expect( + url.compareDocumentPosition(contractIdLabel) & Node.DOCUMENT_POSITION_FOLLOWING + ).toBeTruthy(); const votingInformationSection = screen.getByTestId('proposal-details-voting-information'); expect(votingInformationSection).toBeInTheDocument(); @@ -195,6 +238,23 @@ describe('Proposal Details Content', () => { ); expect(requesterInput).toBeInTheDocument(); expect(requesterInput.textContent).toBe('sv1'); + expect( + within(votingInformationSection).getByTestId('proposal-details-requester-party-id-scroll') + ).toHaveStyle({ overflowX: 'auto', maxWidth: '270px' }); + + expect(screen.getByTestId('proposal-details-created-at-label').textContent).toBe( + PROPOSAL_CREATED_LABEL + ); + expect(screen.getByTestId('proposal-details-created-at-value').textContent).toBe( + '2025-01-01 13:00' + ); + + expect(screen.getByTestId('proposal-details-threshold-deadline-label').textContent).toBe( + THRESHOLD_DEADLINE_LABEL + ); + expect(screen.getByTestId('proposal-details-effective-at-label').textContent).toBe( + EFFECTIVE_AT_LABEL + ); const votingClosesIso = within(votingInformationSection).getByTestId( 'proposal-details-voting-closes-value' @@ -217,7 +277,9 @@ describe('Proposal Details Content', () => { expect(screen.getByTestId('your-vote-form')).toBeInTheDocument(); expect(screen.getByTestId('your-vote-url-input')).toBeInTheDocument(); - expect(screen.getByTestId('your-vote-reason-input')).toBeInTheDocument(); + const reasonInput = screen.getByTestId('your-vote-reason-input'); + expect(reasonInput).toBeInTheDocument(); + expect(reasonInput.getAttribute('placeholder')).toBe(VOTE_REASON_PLACEHOLDER); expect(screen.getByTestId('your-vote-accept')).toBeInTheDocument(); expect(screen.getByTestId('your-vote-reject')).toBeInTheDocument(); }); @@ -753,6 +815,14 @@ describe('Proposal Details > Votes & Voting', () => { expect(acceptedVotesTab.getAttribute('aria-selected')).toBe('false'); expect(rejectedVotesTab.getAttribute('aria-selected')).toBe('false'); expect(noVoteVotesTab.getAttribute('aria-selected')).toBe('false'); + + const voterScroll = screen.getAllByTestId('proposal-details-voter-party-id-scroll'); + expect(voterScroll.length).toBeGreaterThan(0); + expect(voterScroll[0]).toHaveStyle({ overflowX: 'auto' }); + // Votes rows fill width and scroll — no fixed 270px cap. + expect(screen.getAllByTestId('proposal-details-voter-party-id')[0]).toHaveStyle({ + width: '100%', + }); }); test('should filter votes by tabs', async () => { diff --git a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx index 74beb26805..7eca1496df 100644 --- a/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx +++ b/apps/sv/frontend/src/__tests__/governance/proposal-summary.test.tsx @@ -3,6 +3,13 @@ import { render, screen } from '@testing-library/react'; import { describe, expect, test } from 'vitest'; import { ProposalSummary } from '../../components/governance/ProposalSummary'; +import { + CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE, + EFFECTIVE_AT_LABEL, + PROPOSAL_REVIEW_TITLE, + SUPPORTING_URL_LABEL, + THRESHOLD_DEADLINE_LABEL, +} from '../../utils/constants'; import { ConfigChange } from '../../utils/types'; const url = 'https://example.com'; @@ -10,6 +17,29 @@ const summary = 'Summary of the proposal'; const expiryDate = '2025-09-25 11:00'; const effectiveDate = '2025-09-26 11:00'; +/** Shared labels for the post-rebase ProposalSummary / ProposalReviewField chrome. */ +const REVIEW_LABELS = { + title: PROPOSAL_REVIEW_TITLE, + action: CREATE_PROPOSAL_LABEL_PROPOSAL_TYPE, + expiryDate: THRESHOLD_DEADLINE_LABEL, + effectiveDate: EFFECTIVE_AT_LABEL, + summary: 'Proposal Summary', + url: SUPPORTING_URL_LABEL, +} as const; + +function expectCommonReviewFields(actionName: string) { + expect(screen.getByTestId('proposal-review-title').textContent).toBe(REVIEW_LABELS.title); + expect(screen.getByTestId('action-title').textContent).toBe(REVIEW_LABELS.action); + expect(screen.getByTestId('action-field').textContent).toBe(actionName); + expect(screen.getByTestId('url-title').textContent).toBe(REVIEW_LABELS.url); + expect(screen.getByTestId('url-field').textContent).toBe(url); + expect(screen.getByTestId('summary-title').textContent).toBe(REVIEW_LABELS.summary); + expect(screen.getByTestId('summary-field').textContent).toBe(summary); + expect(screen.getByTestId('expiryDate-title').textContent).toBe(REVIEW_LABELS.expiryDate); + expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); + expect(screen.getByTestId('effectiveDate-title').textContent).toBe(REVIEW_LABELS.effectiveDate); +} + describe('Review Proposal Component', () => { test('should render review proposal component for offboard member', () => { const actionName = 'Offboard Member'; @@ -29,23 +59,12 @@ describe('Review Proposal Component', () => { /> ); - expect(screen.getByTestId('action-title').textContent).toBe('Action'); - expect(screen.getByTestId('action-field').textContent).toBe(actionName); - - expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); - expect(screen.getByTestId('url-field').textContent).toBe(url); - - expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); - expect(screen.getByTestId('summary-field').textContent).toBe(summary); - - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); - expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); - - expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); + expectCommonReviewFields(actionName); expect(screen.getByTestId('effectiveDate-field').textContent).toBe(effectiveDate); - expect(screen.getByTestId('offboardMember-title').textContent).toBe('Offboard Member'); - expect(screen.getByTestId('offboardMember-field').textContent).toBe(offboardMember); + expect(screen.getByTestId('offboardMember-title').textContent).toBe('Member'); + expect(screen.getByTestId('offboardMember-party-id-value').textContent).toBe(offboardMember); + expect(screen.getByTestId('offboardMember-party-id-copy-button')).toBeInTheDocument(); }); test('should render review proposal component for offboard member at Threshold', () => { @@ -66,7 +85,7 @@ describe('Review Proposal Component', () => { /> ); - expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); + expect(screen.getByTestId('effectiveDate-title').textContent).toBe(EFFECTIVE_AT_LABEL); expect(screen.getByTestId('effectiveDate-field').textContent).toBe('Threshold'); }); @@ -93,21 +112,16 @@ describe('Review Proposal Component', () => { /> ); - expect(screen.getByTestId('action-title').textContent).toBe('Action'); - expect(screen.getByTestId('action-field').textContent).toBe(actionName); - - expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); - expect(screen.getByTestId('url-field').textContent).toBe(url); - - expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); - expect(screen.getByTestId('summary-field').textContent).toBe(summary); - - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); - expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); - - expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); + expectCommonReviewFields(actionName); expect(screen.getByTestId('effectiveDate-field').textContent).toBe(effectiveDate); + expect(screen.getByTestId('svRewardWeightMember-title').textContent).toBe('Member'); + expect(screen.getByTestId('svRewardWeightMember-party-id-value').textContent).toBe( + svRewardWeightMember + ); + expect(screen.getByTestId('svRewardWeightMember-party-id-copy-button')).toBeInTheDocument(); + + expect(screen.getByTestId('configChange-title').textContent).toBe('Proposed Changes'); expect(screen.getByTestId('config-change-field-label').textContent).toBe(title); expect(screen.getByTestId('config-change-current-value').textContent).toBe(currentWeight); expect(screen.getByTestId('config-change-new-value').textContent).toBe(svRewardWeight); @@ -133,23 +147,12 @@ describe('Review Proposal Component', () => { /> ); - expect(screen.getByTestId('action-title').textContent).toBe('Action'); - expect(screen.getByTestId('action-field').textContent).toBe(actionName); - - expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); - expect(screen.getByTestId('url-field').textContent).toBe(url); - - expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); - expect(screen.getByTestId('summary-field').textContent).toBe(summary); - - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); - expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); - - expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); + expectCommonReviewFields(actionName); expect(screen.getByTestId('effectiveDate-field').textContent).toBe(effectiveDate); expect(screen.getByTestId('grantRight-title').textContent).toBe('Provider Party ID'); - expect(screen.getByTestId('grantRight-field').textContent).toBe(provider); + expect(screen.getByTestId('grantRight-party-id-value').textContent).toBe(provider); + expect(screen.getByTestId('grantRight-party-id-copy-button')).toBeInTheDocument(); expect(screen.getByTestId('grantRightActivityWeight-title').textContent).toBe( 'Activity Weight' @@ -177,23 +180,14 @@ describe('Review Proposal Component', () => { /> ); - expect(screen.getByTestId('action-title').textContent).toBe('Action'); - expect(screen.getByTestId('action-field').textContent).toBe(actionName); - - expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); - expect(screen.getByTestId('url-field').textContent).toBe(url); - - expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); - expect(screen.getByTestId('summary-field').textContent).toBe(summary); - - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); - expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); - - expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); + expectCommonReviewFields(actionName); expect(screen.getByTestId('effectiveDate-field').textContent).toBe(effectiveDate); expect(screen.getByTestId('revokeProviderPartyId-title').textContent).toBe('Provider Party ID'); - expect(screen.getByTestId('revokeProviderPartyId-field').textContent).toBe(providerPartyId); + expect(screen.getByTestId('revokeProviderPartyId-party-id-value').textContent).toBe( + providerPartyId + ); + expect(screen.getByTestId('revokeProviderPartyId-party-id-copy-button')).toBeInTheDocument(); expect(screen.getByTestId('revokeRight-title').textContent).toBe( 'Featured Application Contract ID' @@ -225,33 +219,27 @@ describe('Review Proposal Component', () => { /> ); - expect(screen.getByTestId('action-title').textContent).toBe('Action'); - expect(screen.getByTestId('action-field').textContent).toBe(actionName); - - expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); - expect(screen.getByTestId('url-field').textContent).toBe(url); - - expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); - expect(screen.getByTestId('summary-field').textContent).toBe(summary); - - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); - expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); - - expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); + expectCommonReviewFields(actionName); expect(screen.getByTestId('effectiveDate-field').textContent).toBe(effectiveDate); expect(screen.getByTestId('updateProviderPartyId-title').textContent).toBe('Provider Party ID'); - expect(screen.getByTestId('updateProviderPartyId-field').textContent).toBe(providerPartyId); + expect(screen.getByTestId('updateProviderPartyId-party-id-value').textContent).toBe( + providerPartyId + ); + expect(screen.getByTestId('updateProviderPartyId-party-id-copy-button')).toBeInTheDocument(); expect(screen.getByTestId('updateRight-title').textContent).toBe( 'Featured Application Contract ID' ); expect(screen.getByTestId('updateRight-field').textContent).toBe(rightCid); + expect(screen.getByTestId('updateActivityWeight-title').textContent).toBe('Proposed Changes'); expect(screen.getByTestId('config-change-current-value').textContent).toBe( currentActivityWeight ); expect(screen.getByTestId('config-change-new-value').textContent).toBe(newActivityWeight); + + expect(screen.queryByTestId('updateReason-field')).not.toBeInTheDocument(); }); test('should render review proposal component for dso rules config', () => { @@ -288,22 +276,12 @@ describe('Review Proposal Component', () => { /> ); - expect(screen.getByTestId('action-title').textContent).toBe('Action'); - expect(screen.getByTestId('action-field').textContent).toBe(actionName); - - expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); - expect(screen.getByTestId('url-field').textContent).toBe(url); - - expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); - expect(screen.getByTestId('summary-field').textContent).toBe(summary); - - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); - expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); - - expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); + expectCommonReviewFields(actionName); expect(screen.getByTestId('effectiveDate-field').textContent).toBe(effectiveDate); - expect(screen.getByText('Proposed Changes')).toBeDefined(); + expect(screen.getByTestId('configChange-title').textContent).toBe( + 'Proposed Configuration Changes' + ); expect(screen.getByText(numThresholdTitle)).toBeDefined(); expect(screen.getByText(voteCooldownTitle)).toBeDefined(); @@ -360,22 +338,12 @@ describe('Review Proposal Component', () => { /> ); - expect(screen.getByTestId('action-title').textContent).toBe('Action'); - expect(screen.getByTestId('action-field').textContent).toBe(actionName); - - expect(screen.getByTestId('url-title').textContent).toBe('Supporting URL'); - expect(screen.getByTestId('url-field').textContent).toBe(url); - - expect(screen.getByTestId('summary-title').textContent).toBe('Summary'); - expect(screen.getByTestId('summary-field').textContent).toBe(summary); - - expect(screen.getByTestId('expiryDate-title').textContent).toBe('Quorum Threshold Deadline'); - expect(screen.getByTestId('expiryDate-field').textContent).toBe(expiryDate); - - expect(screen.getByTestId('effectiveDate-title').textContent).toBe('Effective Date'); + expectCommonReviewFields(actionName); expect(screen.getByTestId('effectiveDate-field').textContent).toBe(effectiveDate); - expect(screen.getByText('Proposed Changes')).toBeDefined(); + expect(screen.getByTestId('configChange-title').textContent).toBe( + 'Proposed Configuration Changes' + ); expect(screen.getByText(feeTitle)).toBeDefined(); expect(screen.getByText(feeRateTitle)).toBeDefined(); diff --git a/apps/sv/frontend/src/__tests__/layout/sv-top-nav.test.tsx b/apps/sv/frontend/src/__tests__/layout/sv-top-nav.test.tsx new file mode 100644 index 0000000000..4f0d14f221 --- /dev/null +++ b/apps/sv/frontend/src/__tests__/layout/sv-top-nav.test.tsx @@ -0,0 +1,35 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { render, screen } from '@testing-library/react'; +import { MemoryRouter } from 'react-router'; +import { describe, expect, test, vi } from 'vitest'; + +import SvTopNav from '../../components/layout/SvTopNav'; + +const navLinks = [ + { name: 'Global Synchronizer Information', path: '/dso' }, + { name: 'Governance', path: '/governance' }, + { name: 'Teluma Price', path: '/amulet-price' }, + { name: 'Validators', path: '/validator-onboarding' }, +]; + +describe('SvTopNav', () => { + test('renders brand, centered nav cluster, and logout', () => { + render( + + + + ); + + expect(screen.getByTestId('app-title')).toHaveTextContent('Supervalidator Operations'); + expect(screen.getByTestId('sv-top-nav-links')).toBeInTheDocument(); + expect(screen.getByTestId('navlink-dso')).toBeInTheDocument(); + expect(screen.getByTestId('navlink-governance')).toBeInTheDocument(); + expect(screen.getByTestId('logout-button')).toBeInTheDocument(); + + const row = screen.getByTestId('sv-top-nav'); + expect(row).toHaveStyle({ display: 'flex' }); + expect(screen.getByTestId('sv-top-nav-spacer-start')).toBeInTheDocument(); + expect(screen.getByTestId('sv-top-nav-spacer-end')).toBeInTheDocument(); + }); +}); diff --git a/apps/sv/frontend/src/components/Layout.tsx b/apps/sv/frontend/src/components/Layout.tsx index bbfca185e7..c119706a6f 100644 --- a/apps/sv/frontend/src/components/Layout.tsx +++ b/apps/sv/frontend/src/components/Layout.tsx @@ -59,7 +59,7 @@ const Layout: React.FC = ({ children }) => { } const navLinks: SvNavLinkItem[] = [ - { name: 'Global Synchronizer Information', path: '/dso' }, + { name: 'Global Synchronizer Information', path: '/dso', alsoActiveFor: ['/'] }, { name: 'Governance', path: '/governance', diff --git a/apps/sv/frontend/src/components/beta/CopyableIdentifier.tsx b/apps/sv/frontend/src/components/beta/CopyableIdentifier.tsx index 1896cf0d47..a8b60c012a 100644 --- a/apps/sv/frontend/src/components/beta/CopyableIdentifier.tsx +++ b/apps/sv/frontend/src/components/beta/CopyableIdentifier.tsx @@ -5,76 +5,153 @@ import { Box, Chip, IconButton, Typography } from '@mui/material'; import { useRef } from 'react'; import { useHorizontalScrollMetrics } from '../../hooks/useHorizontalScrollMetrics'; -import { scrollContainerSx, scrollTextSx, scrollThumbSx, scrollTrackSx } from './identifierStyles'; +import { + ellipsisContainerSx, + ellipsisTextSx, + IDENTIFIER_COMPACT_MAX_WIDTH_PX, + scrollContainerSx, + scrollTextSx, + scrollThumbSx, + scrollTrackSx, +} from './identifierStyles'; export type CopyableIdentifierSize = 'small' | 'large'; +export type CopyableIdentifierOverflow = 'scroll' | 'ellipsis'; interface CopyableIdentifierProps { value: string; copyValue?: string; badge?: string; size: CopyableIdentifierSize; + overflow?: CopyableIdentifierOverflow; + /** + * Caps the text slot (Figma ~270px). With `overflow="scroll"`, the ID stays + * horizontally scrollable inside the cap (#1785 + Figma width). With + * `overflow="ellipsis"`, CSS ellipsis is used instead. + */ + maxWidth?: number; + /** + * Fill the parent width: party-ID text flexes/scrolls; copy + badge stay fixed. + * Used by Votes rows (ID + reason shrink; status stays right-aligned). + */ + fullWidth?: boolean; + /** When true, only the (scrollable) value is rendered — caller places copy / badge. */ + hideCopy?: boolean; 'data-testid': string; } +/** Gap between party-ID text and copy / You accessories. */ +const IDENTIFIER_ACCESSORY_GAP = '8px'; + const CopyableIdentifier: React.FC = ({ value, copyValue, badge, size, + overflow = 'scroll', + maxWidth, + fullWidth = false, + hideCopy = false, 'data-testid': testId, }) => { const scrollRef = useRef(null); - const metrics = useHorizontalScrollMetrics(scrollRef, [value]); - const fontSize = size === 'small' ? '14px' : '18px'; + const metrics = useHorizontalScrollMetrics(scrollRef, [value, maxWidth, fullWidth]); + const fontSize = size === 'small' ? '14px' : '16px'; + const isEllipsis = overflow === 'ellipsis'; + const compactMaxWidth = maxWidth ?? (isEllipsis ? IDENTIFIER_COMPACT_MAX_WIDTH_PX : undefined); + const showAccessories = !hideCopy; return ( - - + + {value} - {metrics.canScroll && ( + {!isEllipsis && metrics.canScroll && ( )} - { - e.stopPropagation(); - e.preventDefault(); - navigator.clipboard.writeText(copyValue ?? value); - }} - > - - - {badge !== undefined && ( - + {showAccessories && ( + <> + { + e.stopPropagation(); + e.preventDefault(); + navigator.clipboard.writeText(copyValue ?? value); + }} + > + + + {badge !== undefined && ( + + )} + )} ); diff --git a/apps/sv/frontend/src/components/beta/CopyableUrl.tsx b/apps/sv/frontend/src/components/beta/CopyableUrl.tsx index 827bfa9a7f..273aa0416a 100644 --- a/apps/sv/frontend/src/components/beta/CopyableUrl.tsx +++ b/apps/sv/frontend/src/components/beta/CopyableUrl.tsx @@ -4,8 +4,16 @@ import { ContentCopy } from '@mui/icons-material'; import { Box, IconButton, Link } from '@mui/material'; import { sanitizeUrl } from '@canton-network/splice-common-frontend-utils'; +import { useRef } from 'react'; +import { useHorizontalScrollMetrics } from '../../hooks/useHorizontalScrollMetrics'; import type { CopyableIdentifierSize } from './CopyableIdentifier'; +import { + scrollContainerSx, + scrollThumbSx, + scrollTrackSx, + URL_COMPACT_MAX_WIDTH_PX, +} from './identifierStyles'; interface CopyableUrlProps { url: string; @@ -13,41 +21,76 @@ interface CopyableUrlProps { 'data-testid': string; } -function abbreviateUrl(url: string, maxLength = 50): string { - if (url.length <= maxLength) { - return url; - } - return `${url.slice(0, maxLength)}...`; -} - const CopyableUrl: React.FC = ({ url, size, 'data-testid': testId }) => { const sanitizedUrl = sanitizeUrl(url); + const fontSize = size === 'small' ? '14px' : '16px'; + const scrollRef = useRef(null); + const metrics = useHorizontalScrollMetrics(scrollRef, [sanitizedUrl]); return ( - - + - {abbreviateUrl(sanitizedUrl)} - + + + {sanitizedUrl} + + + {metrics.canScroll && ( + + + + )} + navigator.clipboard.writeText(sanitizedUrl)} > - + ); diff --git a/apps/sv/frontend/src/components/beta/MemberIdentifier.tsx b/apps/sv/frontend/src/components/beta/MemberIdentifier.tsx index 80a50ac7ac..b51cbe8cba 100644 --- a/apps/sv/frontend/src/components/beta/MemberIdentifier.tsx +++ b/apps/sv/frontend/src/components/beta/MemberIdentifier.tsx @@ -2,12 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 import CopyableIdentifier from './CopyableIdentifier'; -import type { CopyableIdentifierSize } from './CopyableIdentifier'; +import type { CopyableIdentifierOverflow, CopyableIdentifierSize } from './CopyableIdentifier'; interface MemberIdentifierProps { partyId: string; isYou: boolean; size: CopyableIdentifierSize; + overflow?: CopyableIdentifierOverflow; + maxWidth?: number; + fullWidth?: boolean; 'data-testid': string; } @@ -15,6 +18,9 @@ const MemberIdentifier: React.FC = ({ partyId, isYou, size, + overflow, + maxWidth, + fullWidth, 'data-testid': testId, }) => ( = ({ copyValue={partyId} badge={isYou ? 'You' : undefined} size={size} + overflow={overflow} + maxWidth={maxWidth} + fullWidth={fullWidth} data-testid={testId} /> ); diff --git a/apps/sv/frontend/src/components/beta/identifierStyles.ts b/apps/sv/frontend/src/components/beta/identifierStyles.ts index 6b3ea34c67..ec59d01c9d 100644 --- a/apps/sv/frontend/src/components/beta/identifierStyles.ts +++ b/apps/sv/frontend/src/components/beta/identifierStyles.ts @@ -20,15 +20,42 @@ export const scrollContainerSx: SxProps = { export const scrollTextSx: SxProps = { display: 'inline-block', - width: 'max-content', - minWidth: '100%', whiteSpace: 'nowrap', textOverflow: 'clip', + // Intrinsic width for overflow scroll; parent must clip (minmax(0,1fr) / overflow). + width: 'max-content', +}; + +export const ellipsisContainerSx: SxProps = { + minWidth: 0, + // Figma truncated ID text slot (e.g. Vote proposal contract id Group 461): 270px + maxWidth: 270, + width: '100%', + overflow: 'hidden', +}; + +/** Figma Group 461 truncated ID text width — also used to cap scrollable compact IDs. */ +export const IDENTIFIER_COMPACT_MAX_WIDTH_PX = 270; + +/** Figma Supporting URL value slot (`552:960`). */ +export const URL_COMPACT_MAX_WIDTH_PX = 346; + +export const ellipsisTextSx: SxProps = { + display: 'block', + overflow: 'hidden', + textOverflow: 'ellipsis', + whiteSpace: 'nowrap', + maxWidth: '100%', }; export const scrollableIdentifierFieldSx: SxProps = { fontFamily: 'Source Code Pro, monospace', - ...scrollTextSx, + display: 'inline-block', + width: 'max-content', + minWidth: '100%', + maxWidth: '100%', + whiteSpace: 'nowrap', + textOverflow: 'clip', }; const scrollableInputTextSx = { diff --git a/apps/sv/frontend/src/components/form-components/ConfigField.tsx b/apps/sv/frontend/src/components/form-components/ConfigField.tsx index de91c9786b..ea6dc53a57 100644 --- a/apps/sv/frontend/src/components/form-components/ConfigField.tsx +++ b/apps/sv/frontend/src/components/form-components/ConfigField.tsx @@ -17,6 +17,10 @@ import { useFieldContext } from '../../hooks/formContext'; import type { ConfigChange, PendingConfigFieldInfo } from '../../utils/types'; import { nextScheduledSynchronizerUpgradeFormat } from '@canton-network/splice-common-frontend-utils'; import { configFieldFieldSx, configFieldInputSx } from '../../themes/fieldStyles'; +import { + CREATE_PROPOSAL_CONFIG_INPUT_WIDTH, + CREATE_PROPOSAL_FIELD_BODY_SX, +} from '../../constants/createProposalLayout'; dayjs.extend(relativeTime); @@ -83,27 +87,54 @@ export const ConfigField: React.FC = props => { <> - - + + {configChange.label} {configChange.fieldName} - + {configChange.options ? (