From 925d604b6f5633a216d0f59faa69c6e64842a5ba Mon Sep 17 00:00:00 2001 From: canton-network-da Date: Mon, 30 Sep 2024 06:07:13 -0400 Subject: [PATCH] Update Splice from CCI (#46) Signed-off-by: DA Automation Co-authored-by: DA Automation --- LATEST_RELEASE | 2 +- VERSION | 2 +- apps/ans/frontend/package.json | 1 + apps/ans/frontend/vite.config.ts | 2 +- apps/app-manager/frontend/package.json | 1 + apps/app-manager/frontend/vite.config.ts | 2 +- ...ScanFrontendTimeBasedIntegrationTest.scala | 42 +- .../tests/SvFrontendIntegrationTest.scala | 23 +- ...SvTimeBasedOnboardingIntegrationTest.scala | 4 +- .../ValidatorLicensesFrontendTestUtil.scala | 32 ++ .../tests/WalletAmuletMetricsTest.scala | 52 -- .../integration/tests/WalletMetricsTest.scala | 111 ++++ apps/common/frontend-test-utils/package.json | 14 +- apps/common/frontend-test-utils/src/index.ts | 3 +- .../src/mocks/handlers/dso-info-handler.ts | 521 ++++++++++++++++++ .../handlers/validator-licenses-handler.ts | 40 ++ apps/common/frontend-test-utils/tsconfig.json | 4 +- .../frontend-test-vite-utils/.eslintrc.json | 3 + .../frontend-test-vite-utils/.prettierrc.cjs | 3 + .../frontend-test-vite-utils/package.json | 34 ++ .../frontend-test-vite-utils/src/index.ts | 1 + .../src/vitest.common.conf.ts | 0 .../tsconfig.cjs.json | 0 .../tsconfig.esm.json | 0 .../frontend-test-vite-utils/tsconfig.json | 10 + .../tsconfig.types.json | 0 .../write-package-jsons.js | 0 .../src/components/ValidatorLicenses.tsx | 114 ++++ apps/common/frontend/src/components/index.ts | 3 + apps/common/frontend/vite.config.ts | 2 +- .../src/main/openapi/common-internal.yaml | 15 + .../network/automation/AssignTrigger.scala | 2 + .../automation/DomainIngestionService.scala | 2 + .../network/automation/PollingTrigger.scala | 3 +- .../network/automation/TaskbasedTrigger.scala | 2 +- .../automation/TransferFollowTrigger.scala | 2 + .../com/daml/network/automation/Trigger.scala | 2 + .../network/automation/TriggerMetrics.scala | 10 + .../network/automation/UnassignTrigger.scala | 2 + .../http/HttpValidatorLicensesHandler.scala | 45 ++ apps/package-lock.json | 31 ++ apps/package.json | 1 + apps/scan/frontend/src/App.tsx | 2 + .../src/__tests__/mocks/handlers/scan-api.ts | 3 + .../scan/frontend/src/__tests__/scan.test.tsx | 31 +- apps/scan/frontend/src/components/Layout.tsx | 1 + apps/scan/frontend/src/hooks/index.ts | 2 + .../src/hooks/useValidatorLicenses.tsx | 28 + .../src/routes/scanValidatorLicenses.tsx | 29 + apps/scan/frontend/vite.config.ts | 2 +- apps/scan/src/main/openapi/scan-internal.yaml | 25 + .../scan/admin/http/HttpScanHandler.scala | 23 +- apps/splitwell/frontend/package.json | 1 + apps/splitwell/frontend/vite.config.ts | 2 +- apps/sv/frontend/package.json | 1 + .../frontend/src/__tests__/mocks/constants.ts | 517 +---------------- .../src/__tests__/mocks/handlers/sv-api.ts | 51 +- .../src/components/ValidatorLicenses.tsx | 110 +--- .../src/hooks/useValidatorLicenses.tsx | 10 +- apps/sv/frontend/vite.config.ts | 2 +- apps/sv/src/main/openapi/sv-internal.yaml | 17 +- .../sv/admin/http/HttpSvAdminHandler.scala | 29 +- .../daml/network/sv/store/SvDsoStore.scala | 9 - apps/wallet/frontend/package.json | 1 + apps/wallet/frontend/vite.config.ts | 2 +- .../AcceptedTransferOfferTrigger.scala | 4 + .../automation/AmuletMetricsTrigger.scala | 4 + .../AutoAcceptTransferOffersTrigger.scala | 4 + ...CollectRewardsAndMergeAmuletsTrigger.scala | 2 + .../CompleteBuyTrafficRequestTrigger.scala | 4 + .../ExpireAcceptedTransferOfferTrigger.scala | 2 + .../ExpireAppPaymentRequestsTrigger.scala | 2 + .../ExpireBuyTrafficRequestsTrigger.scala | 2 + .../ExpireTransferOfferTrigger.scala | 2 + .../SubscriptionReadyForPaymentTrigger.scala | 2 + .../automation/WalletSweepTrigger.scala | 2 + build.sbt | 15 +- 77 files changed, 1270 insertions(+), 816 deletions(-) create mode 100644 apps/app/src/test/scala/com/daml/network/integration/tests/ValidatorLicensesFrontendTestUtil.scala delete mode 100644 apps/app/src/test/scala/com/daml/network/integration/tests/WalletAmuletMetricsTest.scala create mode 100644 apps/app/src/test/scala/com/daml/network/integration/tests/WalletMetricsTest.scala create mode 100644 apps/common/frontend-test-utils/src/mocks/handlers/dso-info-handler.ts create mode 100644 apps/common/frontend-test-utils/src/mocks/handlers/validator-licenses-handler.ts create mode 100644 apps/common/frontend-test-vite-utils/.eslintrc.json create mode 100644 apps/common/frontend-test-vite-utils/.prettierrc.cjs create mode 100644 apps/common/frontend-test-vite-utils/package.json create mode 100644 apps/common/frontend-test-vite-utils/src/index.ts rename apps/common/{frontend-test-utils => frontend-test-vite-utils}/src/vitest.common.conf.ts (100%) rename apps/common/{frontend-test-utils => frontend-test-vite-utils}/tsconfig.cjs.json (100%) rename apps/common/{frontend-test-utils => frontend-test-vite-utils}/tsconfig.esm.json (100%) create mode 100644 apps/common/frontend-test-vite-utils/tsconfig.json rename apps/common/{frontend-test-utils => frontend-test-vite-utils}/tsconfig.types.json (100%) rename apps/common/{frontend-test-utils => frontend-test-vite-utils}/write-package-jsons.js (100%) create mode 100644 apps/common/frontend/src/components/ValidatorLicenses.tsx create mode 100644 apps/common/src/main/scala/com/daml/network/http/HttpValidatorLicensesHandler.scala create mode 100644 apps/scan/frontend/src/hooks/useValidatorLicenses.tsx create mode 100644 apps/scan/frontend/src/routes/scanValidatorLicenses.tsx diff --git a/LATEST_RELEASE b/LATEST_RELEASE index 0c62199f..71790396 100644 --- a/LATEST_RELEASE +++ b/LATEST_RELEASE @@ -1 +1 @@ -0.2.1 +0.2.3 diff --git a/VERSION b/VERSION index ee1372d3..abd41058 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.2 +0.2.4 diff --git a/apps/ans/frontend/package.json b/apps/ans/frontend/package.json index 8f5fda48..523d876d 100644 --- a/apps/ans/frontend/package.json +++ b/apps/ans/frontend/package.json @@ -46,6 +46,7 @@ "@types/uuid": "8.3.4", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "happy-dom": "^11.0.0", "prettier": "2.8.4", "typescript": "4.9.5", diff --git a/apps/ans/frontend/vite.config.ts b/apps/ans/frontend/vite.config.ts index bb344050..5134e25b 100644 --- a/apps/ans/frontend/vite.config.ts +++ b/apps/ans/frontend/vite.config.ts @@ -1,7 +1,7 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import react from '@vitejs/plugin-react'; -import { vitest_common_conf } from 'common-test-utils'; +import { vitest_common_conf } from 'common-test-vite-utils'; import { defineConfig, loadEnv, mergeConfig } from 'vite'; import viteTsconfigPaths from 'vite-tsconfig-paths'; diff --git a/apps/app-manager/frontend/package.json b/apps/app-manager/frontend/package.json index d91d57ce..8e5ceba1 100644 --- a/apps/app-manager/frontend/package.json +++ b/apps/app-manager/frontend/package.json @@ -37,6 +37,7 @@ "@typescript-eslint/parser": "5.52.0", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "eslint": "8.34.0", "eslint-config-prettier": "8.6.0", "eslint-plugin-import": "2.27.5", diff --git a/apps/app-manager/frontend/vite.config.ts b/apps/app-manager/frontend/vite.config.ts index 12d27d94..7755a015 100644 --- a/apps/app-manager/frontend/vite.config.ts +++ b/apps/app-manager/frontend/vite.config.ts @@ -1,5 +1,5 @@ import react from '@vitejs/plugin-react'; -import { vitest_common_conf } from 'common-test-utils'; +import { vitest_common_conf } from 'common-test-vite-utils'; import { defineConfig, loadEnv, mergeConfig } from 'vite'; import viteTsconfigPaths from 'vite-tsconfig-paths'; diff --git a/apps/app/src/test/scala/com/daml/network/integration/tests/ScanFrontendTimeBasedIntegrationTest.scala b/apps/app/src/test/scala/com/daml/network/integration/tests/ScanFrontendTimeBasedIntegrationTest.scala index d4479707..6b7efbc2 100644 --- a/apps/app/src/test/scala/com/daml/network/integration/tests/ScanFrontendTimeBasedIntegrationTest.scala +++ b/apps/app/src/test/scala/com/daml/network/integration/tests/ScanFrontendTimeBasedIntegrationTest.scala @@ -26,7 +26,9 @@ class ScanFrontendTimeBasedIntegrationTest with TimeTestUtil with SynchronizerFeesTestUtil with TriggerTestUtil - with VotesFrontendTestUtil { + with VotesFrontendTestUtil + with ValidatorLicensesFrontendTestUtil + with SvTestUtil { val amuletPrice = 2 @@ -593,5 +595,43 @@ class ScanFrontendTimeBasedIntegrationTest } } + "see the validator licenses" in { implicit env => + withFrontEnd("scan-ui") { implicit webDriver => + actAndCheck( + "Go to Scan UI main page", + go to s"http://localhost:${scanUIPort}", + )( + "Switch to the validator licenses tab", + _ => { + inside(find(id("navlink-/validator-licenses"))) { case Some(navlink) => + navlink.underlying.click() + } + }, + ) + + val licenseRows = getLicensesTableRows + val newValidatorParty = allocateRandomSvParty("validatorX") + val newSecret = sv1Backend.devNetOnboardValidatorPrepare() + + actAndCheck( + "onboard new validator using the secret", + sv1Backend.onboardValidator( + newValidatorParty, + newSecret, + s"${newValidatorParty.uid.identifier}@example.com", + ), + )( + "a new validator row is added", + _ => { + checkLastValidatorLicenseRow( + licenseRows.size.toLong, + sv1Backend.getDsoInfo().svParty, + newValidatorParty, + ) + }, + ) + } + } + } } diff --git a/apps/app/src/test/scala/com/daml/network/integration/tests/SvFrontendIntegrationTest.scala b/apps/app/src/test/scala/com/daml/network/integration/tests/SvFrontendIntegrationTest.scala index 785bc242..1b6924cd 100644 --- a/apps/app/src/test/scala/com/daml/network/integration/tests/SvFrontendIntegrationTest.scala +++ b/apps/app/src/test/scala/com/daml/network/integration/tests/SvFrontendIntegrationTest.scala @@ -30,7 +30,8 @@ class SvFrontendIntegrationTest with SvFrontendTestUtil with FrontendLoginUtil with WalletTestUtil - with VotesFrontendTestUtil { + with VotesFrontendTestUtil + with ValidatorLicensesFrontendTestUtil { override def environmentDefinition : BaseEnvironmentDefinition[EnvironmentImpl, SpliceTestConsoleEnvironment] = @@ -172,7 +173,7 @@ class SvFrontendIntegrationTest }, ) - val licenseRows = findAll(className("validator-licenses-table-row")).toList + val licenseRows = getLicensesTableRows val newValidatorParty = allocateRandomSvParty("validatorX") actAndCheck( @@ -185,19 +186,11 @@ class SvFrontendIntegrationTest )( "a new validator row is added", _ => { - val newLicenseRows = findAll(className("validator-licenses-table-row")).toList - newLicenseRows should have size (licenseRows.size + 1L) - val row: Element = inside(newLicenseRows) { case row :: _ => - row - } - val sponsor = - seleniumText(row.childElement(className("validator-licenses-sponsor"))) - - val validator = - seleniumText(row.childElement(className("validator-licenses-validator"))) - - sponsor shouldBe sv1Backend.getDsoInfo().svParty.toProtoPrimitive - validator shouldBe newValidatorParty.toProtoPrimitive + checkLastValidatorLicenseRow( + licenseRows.size.toLong, + sv1Backend.getDsoInfo().svParty, + newValidatorParty, + ) }, ) } diff --git a/apps/app/src/test/scala/com/daml/network/integration/tests/SvTimeBasedOnboardingIntegrationTest.scala b/apps/app/src/test/scala/com/daml/network/integration/tests/SvTimeBasedOnboardingIntegrationTest.scala index 3c9adb72..e210e249 100644 --- a/apps/app/src/test/scala/com/daml/network/integration/tests/SvTimeBasedOnboardingIntegrationTest.scala +++ b/apps/app/src/test/scala/com/daml/network/integration/tests/SvTimeBasedOnboardingIntegrationTest.scala @@ -21,6 +21,7 @@ import com.daml.network.sv.util.SvUtil import com.daml.network.util.TriggerTestUtil import java.time.Duration as JavaDuration +import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters.* import scala.util.Random @@ -57,7 +58,8 @@ class SvTimeBasedOnboardingIntegrationTest sv4Backend.start() } clue("An `SvOnboardingRequest` contract is created") { - eventually()( + // Increased timeout, because SV4 takes a while to start up + eventually(timeUntilSuccess = 60.seconds)( // The onboarding is requested by SV4 during SvApp init. sv1Backend.participantClientWithAdminToken.ledger_api_extensions.acs .filterJava(splice.svonboarding.SvOnboardingRequest.COMPANION)( diff --git a/apps/app/src/test/scala/com/daml/network/integration/tests/ValidatorLicensesFrontendTestUtil.scala b/apps/app/src/test/scala/com/daml/network/integration/tests/ValidatorLicensesFrontendTestUtil.scala new file mode 100644 index 00000000..7135364b --- /dev/null +++ b/apps/app/src/test/scala/com/daml/network/integration/tests/ValidatorLicensesFrontendTestUtil.scala @@ -0,0 +1,32 @@ +package com.daml.network.integration.tests + +import com.digitalasset.canton.topology.PartyId +import org.scalatest.Assertion + +trait ValidatorLicensesFrontendTestUtil { self: FrontendIntegrationTestWithSharedEnvironment => + + def getLicensesTableRows(implicit webDriver: WebDriverType) = { + findAll(className("validator-licenses-table-row")).toList + } + + def checkLastValidatorLicenseRow( + previousSize: Long, + expectedSponsor: PartyId, + expectedValidator: PartyId, + )(implicit webDriver: WebDriverType): Assertion = { + val newLicenseRows = findAll(className("validator-licenses-table-row")).toList + newLicenseRows should have size (previousSize + 1L) + val row: Element = inside(newLicenseRows) { case row :: _ => + row + } + val sponsor = + seleniumText(row.childElement(className("validator-licenses-sponsor"))) + + val validator = + seleniumText(row.childElement(className("validator-licenses-validator"))) + + sponsor shouldBe expectedSponsor.toProtoPrimitive + validator shouldBe expectedValidator.toProtoPrimitive + } + +} diff --git a/apps/app/src/test/scala/com/daml/network/integration/tests/WalletAmuletMetricsTest.scala b/apps/app/src/test/scala/com/daml/network/integration/tests/WalletAmuletMetricsTest.scala deleted file mode 100644 index 1d2257f2..00000000 --- a/apps/app/src/test/scala/com/daml/network/integration/tests/WalletAmuletMetricsTest.scala +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.daml.network.integration.tests - -import com.daml.network.integration.EnvironmentDefinition -import com.daml.network.integration.tests.SpliceTests.IntegrationTestWithSharedEnvironment -import com.daml.network.util.{WalletTestUtil} -import com.digitalasset.canton.HasExecutionContext -import com.digitalasset.canton.metrics.MetricValue - -class WalletAmuletMetricsTest - extends IntegrationTestWithSharedEnvironment - with HasExecutionContext - with WalletTestUtil - with WalletTxLogTestUtil { - - override def environmentDefinition: EnvironmentDefinition = { - EnvironmentDefinition - .simpleTopology1Sv(this.getClass.getSimpleName) - } - - "Unlocked coin metrics" should { - "update when tapping coin" in { implicit env => - val aliceUserParty = onboardWalletUser(aliceWalletClient, aliceValidatorBackend) - val before = aliceValidatorBackend.metrics - .get( - "cn.wallet.unlocked-amulet-balance", - Map("owner" -> aliceUserParty.toString), - ) - .select[MetricValue.DoublePoint] - .value - .value - before shouldBe 0 - actAndCheck( - "alice taps 100 coin", - aliceWalletClient.tap(100.0), - )( - "metrics update to reflect new coins", - _ => { - val after = aliceValidatorBackend.metrics - .get("cn.wallet.unlocked-amulet-balance", Map("owner" -> aliceUserParty.toString)) - .select[MetricValue.DoublePoint] - .value - .value - val tapCC = walletUsdToAmulet(100.0) - BigDecimal(after) should beWithin(tapCC - smallAmount, tapCC) - }, - ) - } - } -} diff --git a/apps/app/src/test/scala/com/daml/network/integration/tests/WalletMetricsTest.scala b/apps/app/src/test/scala/com/daml/network/integration/tests/WalletMetricsTest.scala new file mode 100644 index 00000000..66b634f8 --- /dev/null +++ b/apps/app/src/test/scala/com/daml/network/integration/tests/WalletMetricsTest.scala @@ -0,0 +1,111 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml.network.integration.tests + +import com.daml.network.integration.EnvironmentDefinition +import com.daml.network.integration.tests.SpliceTests.IntegrationTestWithSharedEnvironment +import com.daml.network.util.{WalletTestUtil} +import com.digitalasset.canton.HasExecutionContext +import com.digitalasset.canton.metrics.MetricValue + +class WalletMetricsTest + extends IntegrationTestWithSharedEnvironment + with HasExecutionContext + with WalletTestUtil + with WalletTxLogTestUtil { + + override def environmentDefinition: EnvironmentDefinition = { + EnvironmentDefinition + .simpleTopology1Sv(this.getClass.getSimpleName) + } + + "Unlocked coin metrics" should { + "update when tapping coin" in { implicit env => + val aliceUserParty = onboardWalletUser(aliceWalletClient, aliceValidatorBackend) + val before = aliceValidatorBackend.metrics + .get( + "cn.wallet.unlocked-amulet-balance", + Map("owner" -> aliceUserParty.toString), + ) + .select[MetricValue.DoublePoint] + .value + .value + before shouldBe 0 + actAndCheck( + "alice taps 100 coin", + aliceWalletClient.tap(100.0), + )( + "metrics update to reflect new coins", + _ => { + val after = aliceValidatorBackend.metrics + .get("cn.wallet.unlocked-amulet-balance", Map("owner" -> aliceUserParty.toString)) + .select[MetricValue.DoublePoint] + .value + .value + val tapCC = walletUsdToAmulet(100.0) + BigDecimal(after) should beWithin(tapCC - smallAmount, tapCC) + }, + ) + } + } + + "User wallet automation metrics" should { + "are labeled with the party ID of the wallet user" in { implicit env => + val aliceUserParty = onboardWalletUser(aliceWalletClient, aliceValidatorBackend) + val bobUserParty = onboardWalletUser(bobWalletClient, bobValidatorBackend) + aliceWalletClient.tap(100.0) + p2pTransfer(aliceWalletClient, bobWalletClient, bobUserParty, 50.0) + + // Polling triggers + // Not exhaustive, only triggers configured to run (e.g., no WalletSweepTrigger) + Seq( + "AmuletMetricsTrigger", + "CollectRewardsAndMergeAmuletsTrigger", + "DomainIngestionService", + "ExpireAcceptedTransferOfferTrigger", + "ExpireAppPaymentRequestsTrigger", + "ExpireBuyTrafficRequestsTrigger", + "ExpireTransferOfferTrigger", + "SubscriptionReadyForPaymentTrigger", + "TransferFollowTrigger", + ).foreach(triggerName => + clue(s"$triggerName should report polling iterations correctly") { + aliceValidatorBackend.metrics + .get( + "cn.trigger.iterations", + Map( + "trigger_name" -> triggerName, + "party" -> aliceUserParty.toString, + ), + ) + .select[MetricValue.LongPoint] + .value + // We always do one iteration right after startup + .value should be > 0L + } + ) + + // Task-based triggers + // Not exhaustive, only triggers that are invoked during init and transfers + Seq( + "AcceptedTransferOfferTrigger", + "DomainIngestionService", + ).foreach(triggerName => + clue(s"$triggerName should report task completions correctly") { + aliceValidatorBackend.metrics + .get( + "cn.trigger.completed", + Map( + "trigger_name" -> triggerName, + "party" -> aliceUserParty.toString, + ), + ) + .select[MetricValue.LongPoint] + .value + .value should be > 0L + } + ) + } + } +} diff --git a/apps/common/frontend-test-utils/package.json b/apps/common/frontend-test-utils/package.json index d9eb6cdd..aa2edcaa 100644 --- a/apps/common/frontend-test-utils/package.json +++ b/apps/common/frontend-test-utils/package.json @@ -2,14 +2,9 @@ "name": "common-test-utils", "version": "0.1.0", "private": true, - "types": "./lib/types/index.d.ts", + "types": ".lib/index.d.ts", "exports": { - ".": { - "types": "./lib/types/index.d.ts", - "import": "./lib/esm/index.js", - "require": "./lib/cjs/index.js", - "default": "./lib/esm/index.js" - } + ".": "./lib/index.js" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "4.0.0", @@ -21,14 +16,13 @@ "typescript": "4.9.5" }, "scripts": { - "build": "npm run build:tsc && node ./write-package-jsons.js", - "build:tsc": "tsc -b ./tsconfig.cjs.json ./tsconfig.esm.json ./tsconfig.types.json", + "build": "tsc", "check": "npm run format:check && npm run lint:check", "fix": "npm run format:fix && npm run lint:fix", "format:check": "prettier --check -- src", "format:fix": "prettier --write -- src", "lint:check": "eslint --ignore-pattern src/com/* --max-warnings=0 -- src", "lint:fix": "eslint --ignore-pattern src/com/* --fix --max-warnings=0 -- src", - "start": "nodemon -V -e ts,json -i lib/ -x \"npm run build\"" + "start": "tsc --watch" } } diff --git a/apps/common/frontend-test-utils/src/index.ts b/apps/common/frontend-test-utils/src/index.ts index c6b4ed8b..0dafc271 100644 --- a/apps/common/frontend-test-utils/src/index.ts +++ b/apps/common/frontend-test-utils/src/index.ts @@ -1 +1,2 @@ -export * from './vitest.common.conf'; +export { validatorLicensesHandler } from './mocks/handlers/validator-licenses-handler'; +export { dsoInfoHandler, dsoInfo } from './mocks/handlers/dso-info-handler'; diff --git a/apps/common/frontend-test-utils/src/mocks/handlers/dso-info-handler.ts b/apps/common/frontend-test-utils/src/mocks/handlers/dso-info-handler.ts new file mode 100644 index 00000000..46eeb011 --- /dev/null +++ b/apps/common/frontend-test-utils/src/mocks/handlers/dso-info-handler.ts @@ -0,0 +1,521 @@ +import { rest, RestHandler } from 'msw'; + +// Obtained via `curl https://sv.sv-2.cimain.network.canton.global/api/sv/v0/dso` +// ...and then npmFix made it look nice. +// You'll need to update this on template changes to DsoRules and AmuletRules. +export const dsoInfo = { + sv_user: 'OBpJ9oTyOLuAKF0H2hhzdSFUICt0diIn@clients', + sv_party_id: + 'Digital-Asset-2::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', + dso_party_id: 'DSO::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + voting_threshold: 3, + latest_mining_round: { + contract: { + template_id: + '218bd1d12914957ff65c2f26f3e752337f10b643b2115af712e287e06dc248ca:Splice.Round:OpenMiningRound', + contract_id: + '00c5e96485ac00043b7e0b576faefe6ef597b9a279f07807eea3b18a2789c65e1cca021220ab83da15af4b90ea1042477b33ea68cebd055e07480608c3f96152b1c49f7106', + payload: { + issuingFor: { + microseconds: '450000000', + }, + issuanceConfig: { + validatorRewardPercentage: '0.5', + amuletToIssuePerYear: '40000000000.0', + unfeaturedAppRewardCap: '0.6', + appRewardPercentage: '0.15', + validatorFaucetCap: '2.85', + featuredAppRewardCap: '100.0', + validatorRewardCap: '0.2', + }, + opensAt: '2024-01-09T19:20:43.133736Z', + transferConfigUsd: { + holdingFee: { + rate: '0.0000048225', + }, + maxNumInputs: '100', + lockHolderFee: { + fee: '0.005', + }, + createFee: { + fee: '0.03', + }, + maxNumLockHolders: '50', + transferFee: { + initialRate: '0.01', + steps: [ + { + _1: '100.0', + _2: '0.001', + }, + { + _1: '1000.0', + _2: '0.0001', + }, + { + _1: '1000000.0', + _2: '0.00001', + }, + ], + }, + maxNumOutputs: '100', + }, + targetClosesAt: '2024-01-09T19:25:43.133736Z', + amuletPrice: '1.0', + tickDuration: { + microseconds: '150000000', + }, + dso: 'DSO::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + round: { + number: '3', + }, + }, + created_event_blob: + 'CgNkZXYS8QYKRQDF6WSFrAAEO34LV2+u/m71l7miefB4B+6jsYonicZeHMoCEiCrg9oVr0uQ6hBCR3sz6mjOvQVeB0gGCMP5YVKxxJ9xBhJeCkAyMThiZDFkMTI5MTQ5NTdmZjY1YzJmMjZmM2U3NTIzMzdmMTBiNjQzYjIxMTVhZjcxMmUyODdlMDZkYzI0OGNhEgJDQxIFUm91bmQaD09wZW5NaW5pbmdSb3VuZBrJBArGBBJNEktSSVNWQzo6MTIyMGE1NTVlY2NlZWQ3ZmVmNDQ1YzdlYzMzM2MxNDQ0OWQ5ODFmYjY1OTViZTIxOGM1ZDcwMWVlZjVlYTYzYTFiY2ESChIICgYSBBICKAYSEBIOMgwxLjAwMDAwMDAwMDASCxIJSSgD6jWIDgYAEgsSCUkopstHiA4GABIOEgwKChIIEgYogNKTrQMSiQIShgIKgwISFhIUChISEBIOMgwwLjAzMDAwMDAwMDASFhIUChISEBIOMgwwLjAwMDAwNDgyMjUSpAESoQEKngESEBIOMgwwLjAxMDAwMDAwMDASiQEShgEigwEKKAomEhISEDIOMTAwLjAwMDAwMDAwMDASEBIOMgwwLjAwMTAwMDAwMDAKKQonEhMSETIPMTAwMC4wMDAwMDAwMDAwEhASDjIMMC4wMDAxMDAwMDAwCiwKKhIWEhQyEjEwMDAwMDAuMDAwMDAwMDAwMBIQEg4yDDAuMDAwMDEwMDAwMBIWEhQKEhIQEg4yDDAuMDA1MDAwMDAwMBIFEgMoyAESBRIDKMgBEgQSAihkEpABEo0BCooBEhoSGDIWNDAwMDAwMDAwMDAuMDAwMDAwMDAwMBIQEg4yDDAuNTAwMDAwMDAwMBIQEg4yDDAuMTUwMDAwMDAwMBIQEg4yDDAuMjAwMDAwMDAwMBISEhAyDjEwMC4wMDAwMDAwMDAwEhASDjIMMC42MDAwMDAwMDAwEhASDjIMMi44NTAwMDAwMDAwEg4SDAoKEggSBiiAxoaPASpJU1ZDOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYTmoMfksiA4GAEIoCiYKJAgBEiCgTuxgdiWHx5vZWf1A6G5VedSrPWvqJqL4OyX06xBIUw==', + created_at: '2024-01-09T19:18:13.133736Z', + }, + domain_id: + 'global-domain::1220fb89d62774bd5b3fd8a11c1b22c8c5453e8286c3cf7add515c98d7bca192ef18', + }, + amulet_rules: { + contract: { + template_id: + '218bd1d12914957ff65c2f26f3e752337f10b643b2115af712e287e06dc248ca:Splice.AmuletRules:AmuletRules', + contract_id: + '0084bd1732e6ef1757c2755a83d6acfdc9bf68688b7f55d19d4586008ee3228bceca02122028af9a1d4fff115e10a40adb65d08dcd69463ad6bf5c3055e12d1b960bbad9da', + payload: { + dso: 'DSO::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + configSchedule: { + initialValue: getAmuletConfig('0.03'), + futureValues: [ + { + _1: '2023-03-15T08:35:00Z', + _2: getAmuletConfig('0.003'), + }, + { + _1: '2024-03-15T08:35:00Z', + _2: getAmuletConfig('4815162342'), + }, + { + _1: '2524-03-19T08:35:00Z', + _2: getAmuletConfig('0.03'), + }, + ], + }, + isDevNet: true, + }, + created_event_blob: + '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', + created_at: '2024-01-09T19:15:33.960325Z', + }, + domain_id: + 'global-domain::1220fb89d62774bd5b3fd8a11c1b22c8c5453e8286c3cf7add515c98d7bca192ef18', + }, + dso_rules: { + contract: { + template_id: + 'b71a4deb943e8f7f27bb7a384c1b8da8a88f5cd40f92d5b1b56b97f1cb379f27:Splice.DsoRules:DsoRules', + contract_id: + '00cb5ebc4580a0806e202a295fff32ac769d4a1ba969c0d83773ae98dc4ff9c246ca021220d0f926a6d088171f7f38abbee000b9e3e6d098d76632e1374e34c8d43b702519', + payload: { + epoch: '0', + initialTrafficState: [ + [ + 'MED::mediator::1220919a6e8c9ddd3b07ef36e698e79686dcf5e4c6b32affb57b5a910cc75f7b66b4', + { + consumedTraffic: '0', + }, + ], + [ + 'PAR::participant::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', + { + consumedTraffic: '0', + }, + ], + ], + offboardedSvs: [], + config: { + svOnboardingRequestTimeout: { + microseconds: '3600000000', + }, + numUnclaimedRewardsThreshold: '10', + actionConfirmationTimeout: { + microseconds: '3600000000', + }, + dsoDelegateInactiveTimeout: { + microseconds: '70000000', + }, + voteRequestTimeout: { + microseconds: '604800000000', + }, + synchronizerNodeConfigLimits: { + cometBft: { + maxNumSequencingKeys: '2', + maxNodeIdLength: '50', + maxNumCometBftNodes: '2', + maxPubKeyLength: '256', + maxNumGovernanceKeys: '2', + }, + }, + numMemberTrafficContractsThreshold: '5', + initialTrafficGrant: '1000000', + svOnboardingConfirmedTimeout: { + microseconds: '3600000000', + }, + decentralizedSynchronizer: { + synchronizers: [ + [ + 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + { + state: 'DS_Operational', + cometBftGenesisJson: + 'TODO(#4900): share CometBFT genesis.json of founding SV node via DsoRules config.', + }, + ], + ], + lastSynchronizerId: + 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + activeSynchronizerId: + 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + }, + maxTextLength: '1024', + }, + dsoDelegate: + 'Digital-Asset-2::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', + isDevNet: true, + svs: [ + [ + 'Digital-Asset-2::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', + { + numRewardCouponsMissed: '0', + name: 'Digital-Asset-2', + joinedAsOfRound: { + number: '0', + }, + svRewardWeight: '10', + participantId: + 'PAR::participant-1::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', + synchronizerNodes: [ + [ + 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + { + cometBft: { + nodes: [ + [ + '5af57aa83abcec085c949323ed8538108757be9c', + { + validatorPubKey: 'gpkwc1WCttL8ZATBIPWIBRCrb0eV4JwMCnjRa56REPw=', + votingPower: '1', + }, + ], + ], + governanceKeys: [ + { + pubKey: 'm16haLzv/d/Ok04Sm39ABk0f0HsSWYNZxrIUiyQ+cK8=', + }, + ], + sequencingKeys: [], + }, + sequencer: { + migrationId: '0', + sequencerId: + 'SEQ::sequencer::12209f0d96157ae83871bd347d2fe22fe0b982dfbfe50016f7cf6dcfdfcd4eb8e132', + url: 'https://sequencer-0.sv-2.cimain.network.canton.global', + availableAfter: '2024-01-09T19:15:31.137243Z', + }, + mediator: { + mediatorId: + 'MED::mediator::1220919a6e8c9ddd3b07ef36e698e79686dcf5e4c6b32affb57b5a910cc75f7b66b4', + }, + }, + ], + ], + lastReceivedRewardFor: { + number: '-1', + }, + }, + ], + [ + 'Digital-Asset-Eng-2::12205ab9210b258422a251d6148b031d71147405948c92bf9c4bc78029c5479aed75', + { + numRewardCouponsMissed: '0', + name: 'Digital-Asset-Eng-2', + joinedAsOfRound: { + number: '0', + }, + svRewardWeight: '12345', + participantId: + 'PAR::participant-2::12205ab9210b258422a251d6148b031d71147405948c92bf9c4bc78029c5479aed75', + synchronizerNodes: [ + [ + 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + { + cometBft: { + nodes: [ + [ + 'c36b3bbd969d993ba0b4809d1f587a3a341f22c1', + { + validatorPubKey: 'BVSM9/uPGLU7lJj72SUw1a261z2L6Yy2XKLhpUvbxqE=', + votingPower: '1', + }, + ], + ], + governanceKeys: [ + { + pubKey: 'm16haLzv/d/Ok04Sm39ABk0f0HsSWYNZxrIUiyQ+cK8=', + }, + ], + sequencingKeys: [], + }, + sequencer: { + migrationId: '0', + sequencerId: + 'SEQ::sequencer::12207eec8b03a668493a6068a755e2eb32084d9b588717049690cdbecb6558fd325c', + url: 'https://sequencer-0.sv-2-eng.cimain.network.canton.global', + availableAfter: '2024-01-09T19:19:10.755996Z', + }, + mediator: { + mediatorId: + 'MED::mediator::122046b485a233b81f6018831c983a532d125aad8784df8f0c0a9478d66c46292d60', + }, + }, + ], + ], + lastReceivedRewardFor: { + number: '-1', + }, + }, + ], + [ + 'Digital-Asset-Eng-3::12203cb019c9986425861c91685d9b7c0068cf48abb8dff8e20f166501f7f677dce7', + { + numRewardCouponsMissed: '0', + name: 'Digital-Asset-Eng-3', + joinedAsOfRound: { + number: '0', + }, + svRewardWeight: '12345', + participantId: + 'PAR::participant-3::12203cb019c9986425861c91685d9b7c0068cf48abb8dff8e20f166501f7f677dce7', + synchronizerNodes: [ + [ + 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + { + cometBft: { + nodes: [ + [ + '0d8e87c54d199e85548ccec123c9d92966ec458c', + { + validatorPubKey: 'dxm4n1MRP/GuSEkJIwbdB4zVcGAeacohFKNtbKK8oRA=', + votingPower: '1', + }, + ], + ], + governanceKeys: [ + { + pubKey: 'm16haLzv/d/Ok04Sm39ABk0f0HsSWYNZxrIUiyQ+cK8=', + }, + ], + sequencingKeys: [], + }, + sequencer: { + migrationId: '0', + sequencerId: + 'SEQ::sequencer::12207a75f3778b60414df3bb876e4a7c84976f35640b4daf30894189bc73e5c7fe38', + url: 'https://sequencer-0.sv-3-eng.cimain.network.canton.global', + availableAfter: '2024-01-09T19:19:14.361604Z', + }, + mediator: { + mediatorId: + 'MED::mediator::1220ed81687df57e0f6be850bcd546b1751a0b4ad8a8d8c5eeef99ddac8405d49fab', + }, + }, + ], + ], + lastReceivedRewardFor: { + number: '-1', + }, + }, + ], + [ + 'Digital-Asset-Eng-4::122070fc4bb3519a4f39f5919b5a166e30794733e56ad9fba2157f7208ff458f7dc7', + { + numRewardCouponsMissed: '0', + name: 'Digital-Asset-Eng-4', + joinedAsOfRound: { + number: '0', + }, + svRewardWeight: '12345', + participantId: + 'PAR::participant-4::122070fc4bb3519a4f39f5919b5a166e30794733e56ad9fba2157f7208ff458f7dc7', + synchronizerNodes: [ + [ + 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + { + cometBft: { + nodes: [ + [ + 'ee738517c030b42c3ff626d9f80b41dfc4b1a3b8', + { + validatorPubKey: '2umZdUS97a6VUXMGsgKJ/VbQbanxWaFUxK1QimhlEjo=', + votingPower: '1', + }, + ], + ], + governanceKeys: [ + { + pubKey: 'm16haLzv/d/Ok04Sm39ABk0f0HsSWYNZxrIUiyQ+cK8=', + }, + ], + sequencingKeys: [], + }, + sequencer: { + migrationId: '0', + sequencerId: + 'SEQ::sequencer::12208d47d9361a0e84ee10648c611fc2436885ab5193aa67fa5d9eefc7929f732e96', + url: 'https://sequencer-0.sv-4-eng.cimain.network.canton.global', + availableAfter: '2024-01-09T19:18:43.522097Z', + }, + mediator: { + mediatorId: + 'MED::mediator::12208c13beecbd916771ce198d9d0f048b243ed99c6e39e34bdec5b32fbb7a51bab4', + }, + }, + ], + ], + lastReceivedRewardFor: { + number: '-1', + }, + }, + ], + ], + dso: 'DSO::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', + }, + created_event_blob: + 'CgNkZXYSnyEKRQDLXrxFgKCAbiAqKV//Mqx2nUobqWnA2DdzrpjcT/nCRsoCEiDQ+Sam0IgXH384q77gALnj5tCY12Yy4TdONMjUO3AlGRJaCkBiNzFhNGRlYjk0M2U4ZjdmMjdiYjdhMzg0YzFiOGRhOGE4OGY1Y2Q0MGY5MmQ1YjFiNTZiOTdmMWNiMzc5ZjI3EgJDThIIU3ZjUnVsZXMaCFN2Y1J1bGVzGvseCvgeEk0SS1JJU1ZDOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYRIEEgIoABKhFxKeF5IBmhcK4gUKW1JZQ2FudG9uLUZvdW5kYXRpb24tMTo6MTIyMGVkNTQ4ZWZiY2MyMmJiNTA5N2JkNWE5ODMwM2QxZDY0YWI1MTlmOTU2OGNkYzE2NzZlZjE2MzBkYTFmYTY4MzISggUK/wQSFxIVQhNDYW50b24tRm91bmRhdGlvbi0xEgoSCAoGEgQSAigAEgoSCAoGEgQSAigBEgQSAigAEgQSAigUEr8EErwEkgG4BAq1BApVQlNnbG9iYWwtZG9tYWluOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYRLbAwrYAxK5ARK2AQqzARJvEm2SAWoKaAoqQig1YWY1N2FhODNhYmNlYzA4NWM5NDkzMjNlZDg1MzgxMDg3NTdiZTljEjoKOBIwEi5CLGdwa3djMVdDdHRMOFpBVEJJUFdJQlJDcmIwZVY0SndNQ25qUmE1NlJFUHc9EgQSAigCEjoSOCI2CjQKMhIwEi5CLG0xNmhhTHp2L2QvT2swNFNtMzlBQmswZjBIc1NXWU5aeHJJVWl5UStjSzg9EgQSAiIAErYBErMBcrABCq0BCqoBElgSVkJUU0VROjpzZXF1ZW5jZXI6OjEyMjA5ZjBkOTYxNTdhZTgzODcxYmQzNDdkMmZlMjJmZTBiOTgyZGZiZmU1MDAxNmY3Y2Y2ZGNmZGZjZDRlYjhlMTMyEj0SO0I5aHR0cHM6Ly9zZXF1ZW5jZXItMC5zdi0xLnN2Yy5jaW1haW4ubmV0d29yay5jYW50b24uZ2xvYmFsEg8SDXILCglJ21JRI4gOBgASYRJfcl0KWwpZElcSVUJTTUVEOjptZWRpYXRvcjo6MTIyMDkxOWE2ZThjOWRkZDNiMDdlZjM2ZTY5OGU3OTY4NmRjZjVlNGM2YjMyYWZmYjU3YjVhOTEwY2M3NWY3YjY2YjQK5AUKW1JZQ2FudG9uLUZvdW5kYXRpb24tMjo6MTIyMDVhYjkyMTBiMjU4NDIyYTI1MWQ2MTQ4YjAzMWQ3MTE0NzQwNTk0OGM5MmJmOWM0YmM3ODAyOWM1NDc5YWVkNzUShAUKgQUSFxIVQhNDYW50b24tRm91bmRhdGlvbi0yEgoSCAoGEgQSAigAEgoSCAoGEgQSAigBEgQSAigAEgYSBCjywAESvwQSvASSAbgECrUEClVCU2dsb2JhbC1kb21haW46OjEyMjBhNTU1ZWNjZWVkN2ZlZjQ0NWM3ZWMzMzNjMTQ0NDlkOTgxZmI2NTk1YmUyMThjNWQ3MDFlZWY1ZWE2M2ExYmNhEtsDCtgDErkBErYBCrMBEm8SbZIBagpoCipCKGMzNmIzYmJkOTY5ZDk5M2JhMGI0ODA5ZDFmNTg3YTNhMzQxZjIyYzESOgo4EjASLkIsQlZTTTkvdVBHTFU3bEpqNzJTVXcxYTI2MXoyTDZZeTJYS0xocFV2YnhxRT0SBBICKAISOhI4IjYKNAoyEjASLkIsbTE2aGFMenYvZC9PazA0U20zOUFCazBmMEhzU1dZTlp4cklVaXlRK2NLOD0SBBICIgAStgESswFysAEKrQEKqgESWBJWQlRTRVE6OnNlcXVlbmNlcjo6MTIyMDdlZWM4YjAzYTY2ODQ5M2E2MDY4YTc1NWUyZWIzMjA4NGQ5YjU4ODcxNzA0OTY5MGNkYmVjYjY1NThmZDMyNWMSPRI7QjlodHRwczovL3NlcXVlbmNlci0wLnN2LTIuc3ZjLmNpbWFpbi5uZXR3b3JrLmNhbnRvbi5nbG9iYWwSDxINcgsKCUmccGgwiA4GABJhEl9yXQpbClkSVxJVQlNNRUQ6Om1lZGlhdG9yOjoxMjIwNDZiNDg1YTIzM2I4MWY2MDE4ODMxYzk4M2E1MzJkMTI1YWFkODc4NGRmOGYwYzBhOTQ3OGQ2NmM0NjI5MmQ2MArkBQpbUllDYW50b24tRm91bmRhdGlvbi0zOjoxMjIwM2NiMDE5Yzk5ODY0MjU4NjFjOTE2ODVkOWI3YzAwNjhjZjQ4YWJiOGRmZjhlMjBmMTY2NTAxZjdmNjc3ZGNlNxKEBQqBBRIXEhVCE0NhbnRvbi1Gb3VuZGF0aW9uLTMSChIICgYSBBICKAASChIICgYSBBICKAESBBICKAASBhIEKPLAARK/BBK8BJIBuAQKtQQKVUJTZ2xvYmFsLWRvbWFpbjo6MTIyMGE1NTVlY2NlZWQ3ZmVmNDQ1YzdlYzMzM2MxNDQ0OWQ5ODFmYjY1OTViZTIxOGM1ZDcwMWVlZjVlYTYzYTFiY2ES2wMK2AMSuQEStgEKswESbxJtkgFqCmgKKkIoMGQ4ZTg3YzU0ZDE5OWU4NTU0OGNjZWMxMjNjOWQ5Mjk2NmVjNDU4YxI6CjgSMBIuQixkeG00bjFNUlAvR3VTRWtKSXdiZEI0elZjR0FlYWNvaEZLTnRiS0s4b1JBPRIEEgIoAhI6EjgiNgo0CjISMBIuQixtMTZoYUx6di9kL09rMDRTbTM5QUJrMGYwSHNTV1lOWnhySVVpeVErY0s4PRIEEgIiABK2ARKzAXKwAQqtAQqqARJYElZCVFNFUTo6c2VxdWVuY2VyOjoxMjIwN2E3NWYzNzc4YjYwNDE0ZGYzYmI4NzZlNGE3Yzg0OTc2ZjM1NjQwYjRkYWYzMDg5NDE4OWJjNzNlNWM3ZmUzOBI9EjtCOWh0dHBzOi8vc2VxdWVuY2VyLTAuc3YtMy5zdmMuY2ltYWluLm5ldHdvcmsuY2FudG9uLmdsb2JhbBIPEg1yCwoJSQR1nzCIDgYAEmESX3JdClsKWRJXElVCU01FRDo6bWVkaWF0b3I6OjEyMjBlZDgxNjg3ZGY1N2UwZjZiZTg1MGJjZDU0NmIxNzUxYTBiNGFkOGE4ZDhjNWVlZWY5OWRkYWM4NDA1ZDQ5ZmFiCuQFCltSWUNhbnRvbi1Gb3VuZGF0aW9uLTQ6OjEyMjA3MGZjNGJiMzUxOWE0ZjM5ZjU5MTliNWExNjZlMzA3OTQ3MzNlNTZhZDlmYmEyMTU3ZjcyMDhmZjQ1OGY3ZGM3EoQFCoEFEhcSFUITQ2FudG9uLUZvdW5kYXRpb24tNBIKEggKBhIEEgIoABIKEggKBhIEEgIoARIEEgIoABIGEgQo8sABEr8EErwEkgG4BAq1BApVQlNnbG9iYWwtZG9tYWluOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYRLbAwrYAxK5ARK2AQqzARJvEm2SAWoKaAoqQihlZTczODUxN2MwMzBiNDJjM2ZmNjI2ZDlmODBiNDFkZmM0YjFhM2I4EjoKOBIwEi5CLDJ1bVpkVVM5N2E2VlVYTUdzZ0tKL1ZiUWJhbnhXYUZVeEsxUWltaGxFam89EgQSAigCEjoSOCI2CjQKMhIwEi5CLG0xNmhhTHp2L2QvT2swNFNtMzlBQmswZjBIc1NXWU5aeHJJVWl5UStjSzg9EgQSAiIAErYBErMBcrABCq0BCqoBElgSVkJUU0VROjpzZXF1ZW5jZXI6OjEyMjA4ZDQ3ZDkzNjFhMGU4NGVlMTA2NDhjNjExZmMyNDM2ODg1YWI1MTkzYWE2N2ZhNWQ5ZWVmYzc5MjlmNzMyZTk2Ej0SO0I5aHR0cHM6Ly9zZXF1ZW5jZXItMC5zdi00LnN2Yy5jaW1haW4ubmV0d29yay5jYW50b24uZ2xvYmFsEg8SDXILCglJMeLILogOBgASYRJfcl0KWwpZElcSVUJTTUVEOjptZWRpYXRvcjo6MTIyMDhjMTNiZWVjYmQ5MTY3NzFjZTE5OGQ5ZDBmMDQ4YjI0M2VkOTljNmUzOWUzNGJkZWM1YjMyZmJiN2E1MWJhYjQSBRIDkgEAEl0SW1JZQ2FudG9uLUZvdW5kYXRpb24tMTo6MTIyMGVkNTQ4ZWZiY2MyMmJiNTA5N2JkNWE5ODMwM2QxZDY0YWI1MTlmOTU2OGNkYzE2NzZlZjE2MzBkYTFmYTY4MzISvQQSugQKtwQSBBICKBQSBBICKAoSDhIMCgoSCBIGKICMjZ4CEg4SDAoKEggSBiiAkJ3pGhIOEgwKChIIEgYogJCd6RoSDxINCgsSCRIHKICAnY6aIxINEgsKCRIHEgUogPbgQhIpEicKJRIjEiEKHxIEEgIoBBIEEgIoBBIEEgIoBBIEEgIoZBIFEgMogAQSBRIDKIAQEgYSBCiAiXoSDhIMCgoSCBIGKIDIzrQNEo4DEosDCogDEtMBEtABkgHMAQrJAQpVQlNnbG9iYWwtZG9tYWluOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYRJwCm4SFRITigEQEg5EU19PcGVyYXRpb25hbBJVElNCUVRPRE8oIzQ5MDApOiBzaGFyZSBDb21ldEJGVCBnZW5lc2lzLmpzb24gb2YgZm91bmRpbmcgU1Ygbm9kZSB2aWEgU3ZjUnVsZXMgY29uZmlnLhJXElVCU2dsb2JhbC1kb21haW46OjEyMjBhNTU1ZWNjZWVkN2ZlZjQ0NWM3ZWMzMzNjMTQ0NDlkOTgxZmI2NTk1YmUyMThjNWQ3MDFlZWY1ZWE2M2ExYmNhElcSVUJTZ2xvYmFsLWRvbWFpbjo6MTIyMGE1NTVlY2NlZWQ3ZmVmNDQ1YzdlYzMzM2MxNDQ0OWQ5ODFmYjY1OTViZTIxOGM1ZDcwMWVlZjVlYTYzYTFiY2ES0AESzQGSAckBCmEKVUJTTUVEOjptZWRpYXRvcjo6MTIyMDkxOWE2ZThjOWRkZDNiMDdlZjM2ZTY5OGU3OTY4NmRjZjVlNGM2YjMyYWZmYjU3YjVhOTEwY2M3NWY3YjY2YjQSCAoGEgQSAigACmQKWEJWUEFSOjpwYXJ0aWNpcGFudDo6MTIyMGVkNTQ4ZWZiY2MyMmJiNTA5N2JkNWE5ODMwM2QxZDY0YWI1MTlmOTU2OGNkYzE2NzZlZjE2MzBkYTFmYTY4MzISCAoGEgQSAigAEgQSAlgBKklTVkM6OjEyMjBhNTU1ZWNjZWVkN2ZlZjQ0NWM3ZWMzMzNjMTQ0NDlkOTgxZmI2NTk1YmUyMThjNWQ3MDFlZWY1ZWE2M2ExYmNhOdM1DC2IDgYAQigKJgokCAESIPKYoAaliKrlh2j2l0JzyRfKBAw67ACJ8Gt89rH9+3cc', + created_at: '2024-01-09T19:18:14.379987Z', + }, + domain_id: + 'global-domain::1220fb89d62774bd5b3fd8a11c1b22c8c5453e8286c3cf7add515c98d7bca192ef18', + }, + sv_node_states: [], // TODO(tech-debt): add better mock data +}; + +function getAmuletConfig(createFee: string) { + return { + packageConfig: { + amulet: '0.1.0', + walletPayments: '0.1.0', + dsoGovernance: '0.1.0', + validatorLifecycle: '0.1.0', + amuletNameService: '0.1.0', + wallet: '0.1.0', + }, + tickDuration: { microseconds: '150000000' }, + transferConfig: { + holdingFee: { rate: '0.0000048225' }, + extraFeaturedAppRewardAmount: '1.0', + maxNumInputs: '100', + lockHolderFee: { fee: '0.005' }, + createFee: { fee: createFee }, + maxNumLockHolders: '50', + transferFee: { + initialRate: '0.01', + steps: [ + { _1: '100.0', _2: '0.001' }, + { _1: '1000.0', _2: '0.0001' }, + { _1: '1000000.0', _2: '0.00001' }, + ], + }, + maxNumOutputs: '100', + }, + decentralizedSynchronizer: { + requiredSynchronizers: { + map: [ + [ + 'global-synchronizer::1220d12352e0839d9aac0a1c0c05b0eaaeb44f0aa19958cca2db37ae22c7817949a7', + {}, + ], + ], + }, + activeSynchronizer: + 'global-synchronizer::1220d12352e0839d9aac0a1c0c05b0eaaeb44f0aa19958cca2db37ae22c7817949a7', + fees: { + baseRateTrafficLimits: { + burstAmount: '2000000', + burstWindow: { microseconds: '600000000' }, + }, + extraTrafficPrice: '1.0', + readVsWriteScalingFactor: '4', + minTopupAmount: '10000000', + }, + }, + issuanceCurve: { + initialValue: { + validatorRewardPercentage: '0.5', + amuletToIssuePerYear: '40000000000.0', + unfeaturedAppRewardCap: '0.6', + appRewardPercentage: '0.15', + featuredAppRewardCap: '100.0', + validatorRewardCap: '0.2', + optValidatorFaucetCap: null, + }, + futureValues: [ + { + _1: { microseconds: '15768000000000' }, + _2: { + validatorRewardPercentage: '0.12', + amuletToIssuePerYear: '20000000000.0', + unfeaturedAppRewardCap: '0.6', + appRewardPercentage: '0.4', + featuredAppRewardCap: '100.0', + validatorRewardCap: '0.2', + optValidatorFaucetCap: null, + }, + }, + { + _1: { microseconds: '47304000000000' }, + _2: { + validatorRewardPercentage: '0.18', + amuletToIssuePerYear: '10000000000.0', + unfeaturedAppRewardCap: '0.6', + appRewardPercentage: '0.62', + featuredAppRewardCap: '100.0', + validatorRewardCap: '0.2', + optValidatorFaucetCap: null, + }, + }, + { + _1: { microseconds: '157680000000000' }, + _2: { + validatorRewardPercentage: '0.21', + amuletToIssuePerYear: '5000000000.0', + unfeaturedAppRewardCap: '0.6', + appRewardPercentage: '0.69', + featuredAppRewardCap: '100.0', + validatorRewardCap: '0.2', + optValidatorFaucetCap: null, + }, + }, + { + _1: { microseconds: '315360000000000' }, + _2: { + validatorRewardPercentage: '0.2', + amuletToIssuePerYear: '2500000000.0', + unfeaturedAppRewardCap: '0.6', + appRewardPercentage: '0.75', + featuredAppRewardCap: '100.0', + validatorRewardCap: '0.2', + optValidatorFaucetCap: null, + }, + }, + ], + }, + }; +} + +export function dsoInfoHandler(baseUrl: string): RestHandler { + return rest.get(`${baseUrl}/v0/dso`, (_, res, ctx) => { + return res(ctx.json(dsoInfo)); + }); +} diff --git a/apps/common/frontend-test-utils/src/mocks/handlers/validator-licenses-handler.ts b/apps/common/frontend-test-utils/src/mocks/handlers/validator-licenses-handler.ts new file mode 100644 index 00000000..335662df --- /dev/null +++ b/apps/common/frontend-test-utils/src/mocks/handlers/validator-licenses-handler.ts @@ -0,0 +1,40 @@ +import { rest, RestHandler } from 'msw'; + +import { ValidatorLicense } from '@daml.js/splice-amulet-0.1.5/lib/Splice/ValidatorLicense'; + +export function validatorLicensesHandler(baseUrl: string): RestHandler { + return rest.get(`${baseUrl}/v0/admin/validator/licenses`, (req, res, ctx) => { + const n = parseInt(req.url.searchParams.get('limit')!); + const after = req.url.searchParams.get('after'); + const from = after ? parseInt(after) + 1 : 0; + const aTimestamp = '2024-09-26T16:15:36Z'; + const validatorLicenses = Array.from({ length: n }, (_, i) => { + const id = (i + from).toString(); + const validatorLicense: ValidatorLicense = { + dso: 'dso', + validator: `validator::${id}`, + sponsor: 'sponsor', + faucetState: { + firstReceivedFor: { number: '1' }, + lastReceivedFor: { number: '10' }, + numCouponsMissed: '1', + }, + metadata: { version: '1', lastUpdatedAt: aTimestamp, contactPoint: 'nowhere' }, + lastActiveAt: aTimestamp, + }; + return { + contract_id: id, + created_at: aTimestamp, + created_event_blob: '', + payload: validatorLicense, + template_id: ValidatorLicense.templateId, + }; + }); + return res( + ctx.json({ + validator_licenses: validatorLicenses, + next_page_token: from + n, + }) + ); + }); +} diff --git a/apps/common/frontend-test-utils/tsconfig.json b/apps/common/frontend-test-utils/tsconfig.json index fdaf7bd1..921672a9 100644 --- a/apps/common/frontend-test-utils/tsconfig.json +++ b/apps/common/frontend-test-utils/tsconfig.json @@ -3,8 +3,8 @@ "compilerOptions": { "rootDir": "src", "outDir": "lib", - "moduleResolution": "node", - "module": "esnext" + "isolatedModules": false, + "types": ["node", "vite/client"] }, "include": ["src"] } diff --git a/apps/common/frontend-test-vite-utils/.eslintrc.json b/apps/common/frontend-test-vite-utils/.eslintrc.json new file mode 100644 index 00000000..33ddc1df --- /dev/null +++ b/apps/common/frontend-test-vite-utils/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "../../eslintrc.cjs" +} diff --git a/apps/common/frontend-test-vite-utils/.prettierrc.cjs b/apps/common/frontend-test-vite-utils/.prettierrc.cjs new file mode 100644 index 00000000..68d6cd2e --- /dev/null +++ b/apps/common/frontend-test-vite-utils/.prettierrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + ...require('../../prettierrc.cjs'), +}; diff --git a/apps/common/frontend-test-vite-utils/package.json b/apps/common/frontend-test-vite-utils/package.json new file mode 100644 index 00000000..6ba48dc4 --- /dev/null +++ b/apps/common/frontend-test-vite-utils/package.json @@ -0,0 +1,34 @@ +{ + "name": "common-test-vite-utils", + "version": "0.1.0", + "private": true, + "types": "./lib/types/index.d.ts", + "exports": { + ".": { + "types": "./lib/types/index.d.ts", + "import": "./lib/esm/index.js", + "require": "./lib/cjs/index.js", + "default": "./lib/esm/index.js" + } + }, + "devDependencies": { + "@trivago/prettier-plugin-sort-imports": "4.0.0", + "@types/node": "18.11.0", + "eslint": "8.34.0", + "msw": "^1.2.5", + "nodemon": "^3.0.1", + "prettier": "2.8.4", + "typescript": "4.9.5" + }, + "scripts": { + "build": "npm run build:tsc && node ./write-package-jsons.js", + "build:tsc": "tsc -b ./tsconfig.cjs.json ./tsconfig.esm.json ./tsconfig.types.json", + "check": "npm run format:check && npm run lint:check", + "fix": "npm run format:fix && npm run lint:fix", + "format:check": "prettier --check -- src", + "format:fix": "prettier --write -- src", + "lint:check": "eslint --ignore-pattern src/com/* --max-warnings=0 -- src", + "lint:fix": "eslint --ignore-pattern src/com/* --fix --max-warnings=0 -- src", + "start": "nodemon -V -e ts,json -i lib/ -x \"npm run build\"" + } +} diff --git a/apps/common/frontend-test-vite-utils/src/index.ts b/apps/common/frontend-test-vite-utils/src/index.ts new file mode 100644 index 00000000..c6b4ed8b --- /dev/null +++ b/apps/common/frontend-test-vite-utils/src/index.ts @@ -0,0 +1 @@ +export * from './vitest.common.conf'; diff --git a/apps/common/frontend-test-utils/src/vitest.common.conf.ts b/apps/common/frontend-test-vite-utils/src/vitest.common.conf.ts similarity index 100% rename from apps/common/frontend-test-utils/src/vitest.common.conf.ts rename to apps/common/frontend-test-vite-utils/src/vitest.common.conf.ts diff --git a/apps/common/frontend-test-utils/tsconfig.cjs.json b/apps/common/frontend-test-vite-utils/tsconfig.cjs.json similarity index 100% rename from apps/common/frontend-test-utils/tsconfig.cjs.json rename to apps/common/frontend-test-vite-utils/tsconfig.cjs.json diff --git a/apps/common/frontend-test-utils/tsconfig.esm.json b/apps/common/frontend-test-vite-utils/tsconfig.esm.json similarity index 100% rename from apps/common/frontend-test-utils/tsconfig.esm.json rename to apps/common/frontend-test-vite-utils/tsconfig.esm.json diff --git a/apps/common/frontend-test-vite-utils/tsconfig.json b/apps/common/frontend-test-vite-utils/tsconfig.json new file mode 100644 index 00000000..fdaf7bd1 --- /dev/null +++ b/apps/common/frontend-test-vite-utils/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "lib", + "moduleResolution": "node", + "module": "esnext" + }, + "include": ["src"] +} diff --git a/apps/common/frontend-test-utils/tsconfig.types.json b/apps/common/frontend-test-vite-utils/tsconfig.types.json similarity index 100% rename from apps/common/frontend-test-utils/tsconfig.types.json rename to apps/common/frontend-test-vite-utils/tsconfig.types.json diff --git a/apps/common/frontend-test-utils/write-package-jsons.js b/apps/common/frontend-test-vite-utils/write-package-jsons.js similarity index 100% rename from apps/common/frontend-test-utils/write-package-jsons.js rename to apps/common/frontend-test-vite-utils/write-package-jsons.js diff --git a/apps/common/frontend/src/components/ValidatorLicenses.tsx b/apps/common/frontend/src/components/ValidatorLicenses.tsx new file mode 100644 index 00000000..3799ea88 --- /dev/null +++ b/apps/common/frontend/src/components/ValidatorLicenses.tsx @@ -0,0 +1,114 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { UseInfiniteQueryResult, UseQueryResult } from '@tanstack/react-query'; +import { DateDisplay, DsoInfo, Loading, PartyId, ViewMoreButton } from 'common-frontend'; +import { Contract } from 'common-frontend-utils'; +import React from 'react'; + +import { Chip, Stack, Table, TableContainer, TableHead, Typography } from '@mui/material'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableRow from '@mui/material/TableRow'; + +import { ValidatorLicense } from '@daml.js/splice-amulet-0.1.5/lib/Splice/ValidatorLicense'; +import { Party } from '@daml/types'; + +export interface ValidatorLicensesPage { + validatorLicenses: Contract[]; + after?: number; +} + +interface ValidatorLicensesProps { + validatorLicensesQuery: UseInfiniteQueryResult; + dsoInfosQuery: UseQueryResult; +} + +const ValidatorLicenses: React.FC = ({ + validatorLicensesQuery, + dsoInfosQuery, +}) => { + if (validatorLicensesQuery.isLoading || dsoInfosQuery.isLoading) { + return ; + } + + if (validatorLicensesQuery.isError || dsoInfosQuery.isError) { + return

Error, something went wrong.

; + } + + const loadedValidatorLicenses = validatorLicensesQuery.data.pages.flatMap( + page => page.validatorLicenses + ); + + return ( + + + Validator Licenses + + + + + + Created at + Validator + Sponsor + + + + {loadedValidatorLicenses.map(license => { + return ( + + ); + })} + +
+
+ validatorLicensesQuery.fetchNextPage()} + disabled={!validatorLicensesQuery.hasNextPage} + idSuffix="validator-licenses" + /> +
+ ); +}; + +interface LicenseRowProps { + validator: Party; + sponsor: Party; + createdAt: Date; + sv: Party; +} + +const LicenseRow: React.FC = ({ validator, sponsor, createdAt, sv }) => { + const sponsoredByThisSv = sponsor === sv; + return ( + + + + + + + + + + + {sponsoredByThisSv && } + + + + ); +}; + +export default ValidatorLicenses; diff --git a/apps/common/frontend/src/components/index.ts b/apps/common/frontend/src/components/index.ts index 02dde989..7fa641c4 100644 --- a/apps/common/frontend/src/components/index.ts +++ b/apps/common/frontend/src/components/index.ts @@ -20,6 +20,7 @@ import LoginFailed from './LoginFailed'; import PartyId from './PartyId'; import RateDisplay from './RateDisplay'; import TitledTable from './TitledTable'; +import ValidatorLicenses, { ValidatorLicensesPage } from './ValidatorLicenses'; import ViewMoreButton from './ViewMoreButton'; import { TransferButton, SubscriptionButton } from './WalletButtons'; import { @@ -65,4 +66,6 @@ export { useVotesHooks, ListVoteRequests, ViewMoreButton, + ValidatorLicenses, + ValidatorLicensesPage, }; diff --git a/apps/common/frontend/vite.config.ts b/apps/common/frontend/vite.config.ts index e6535899..246f18c2 100644 --- a/apps/common/frontend/vite.config.ts +++ b/apps/common/frontend/vite.config.ts @@ -1,7 +1,7 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import react from '@vitejs/plugin-react'; -import { vitest_common_conf } from 'common-test-utils'; +import { vitest_common_conf } from 'common-test-vite-utils'; import { defineConfig, loadEnv, mergeConfig } from 'vite'; import viteTsconfigPaths from 'vite-tsconfig-paths'; diff --git a/apps/common/src/main/openapi/common-internal.yaml b/apps/common/src/main/openapi/common-internal.yaml index 02284dba..21b48d92 100644 --- a/apps/common/src/main/openapi/common-internal.yaml +++ b/apps/common/src/main/openapi/common-internal.yaml @@ -221,3 +221,18 @@ components: type: array items: type: object + + ListValidatorLicensesResponse: + type: object + required: + - validator_licenses + properties: + validator_licenses: + type: array + items: + $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/schemas/Contract" + next_page_token: + type: integer + format: int64 + description: | + When requesting the next page of results, pass this as URL query parameter `after`. \ No newline at end of file diff --git a/apps/common/src/main/scala/com/daml/network/automation/AssignTrigger.scala b/apps/common/src/main/scala/com/daml/network/automation/AssignTrigger.scala index dd8074b3..e7cf37ba 100644 --- a/apps/common/src/main/scala/com/daml/network/automation/AssignTrigger.scala +++ b/apps/common/src/main/scala/com/daml/network/automation/AssignTrigger.scala @@ -27,6 +27,8 @@ class AssignTrigger( store ) { + override protected def extraMetricLabels = Seq("party" -> partyId.toString) + override protected def completeTask( unassign: ReassignmentEvent.Unassign )(implicit tc: TraceContext): Future[TaskOutcome] = diff --git a/apps/common/src/main/scala/com/daml/network/automation/DomainIngestionService.scala b/apps/common/src/main/scala/com/daml/network/automation/DomainIngestionService.scala index 5a09805c..4c2f93c6 100644 --- a/apps/common/src/main/scala/com/daml/network/automation/DomainIngestionService.scala +++ b/apps/common/src/main/scala/com/daml/network/automation/DomainIngestionService.scala @@ -22,6 +22,8 @@ class DomainIngestionService( quiet = true, ) { + override protected def extraMetricLabels = Seq("party" -> sink.ingestionFilter.toString) + override def completeTask( task: PeriodicTaskTrigger.PeriodicTask )(implicit tc: TraceContext): Future[TaskOutcome] = diff --git a/apps/common/src/main/scala/com/daml/network/automation/PollingTrigger.scala b/apps/common/src/main/scala/com/daml/network/automation/PollingTrigger.scala index b0e6b193..44601c00 100644 --- a/apps/common/src/main/scala/com/daml/network/automation/PollingTrigger.scala +++ b/apps/common/src/main/scala/com/daml/network/automation/PollingTrigger.scala @@ -30,7 +30,7 @@ trait PollingTrigger extends Trigger with FlagCloseableAsync { private implicit val mc: MetricsContext = MetricsContext( "trigger_name" -> this.getClass.getSimpleName, "trigger_type" -> "polling", - ) + ).withExtraLabels(extraMetricLabels*) protected def isRewardOperationTrigger: Boolean = false @@ -68,6 +68,7 @@ trait PollingTrigger extends Trigger with FlagCloseableAsync { runningTaskFinishedVar = Some(Promise()) // TODO(#8526) refactor for better latency reporting val latencyTimer = metrics.latency.startAsync() + metrics.iterations.mark() waitForReadyToWork() .flatMap(_ => performWorkIfAvailable()) .transform { performedWork => diff --git a/apps/common/src/main/scala/com/daml/network/automation/TaskbasedTrigger.scala b/apps/common/src/main/scala/com/daml/network/automation/TaskbasedTrigger.scala index 39a4c589..d6304d33 100644 --- a/apps/common/src/main/scala/com/daml/network/automation/TaskbasedTrigger.scala +++ b/apps/common/src/main/scala/com/daml/network/automation/TaskbasedTrigger.scala @@ -29,7 +29,7 @@ abstract class TaskbasedTrigger[T: Pretty]( private implicit val mc: MetricsContext = MetricsContext( "trigger_name" -> this.getClass.getSimpleName(), "trigger_type" -> "taskbased", - ) + ).withExtraLabels(extraMetricLabels*) /** How to complete a task. * diff --git a/apps/common/src/main/scala/com/daml/network/automation/TransferFollowTrigger.scala b/apps/common/src/main/scala/com/daml/network/automation/TransferFollowTrigger.scala index 046bc9e2..de93fea3 100644 --- a/apps/common/src/main/scala/com/daml/network/automation/TransferFollowTrigger.scala +++ b/apps/common/src/main/scala/com/daml/network/automation/TransferFollowTrigger.scala @@ -36,6 +36,8 @@ class TransferFollowTrigger( LubTask ] { + override protected def extraMetricLabels = Seq("party" -> partyId.toString) + override def retrieveTasks()(implicit tc: TraceContext) = retrieve(tc) override protected def completeTask( diff --git a/apps/common/src/main/scala/com/daml/network/automation/Trigger.scala b/apps/common/src/main/scala/com/daml/network/automation/Trigger.scala index cd805a4c..2a721fa0 100644 --- a/apps/common/src/main/scala/com/daml/network/automation/Trigger.scala +++ b/apps/common/src/main/scala/com/daml/network/automation/Trigger.scala @@ -22,6 +22,8 @@ trait Trigger extends FlagCloseable with NamedLogging with Spanning with HasHeal protected def metrics: TriggerMetrics = new TriggerMetrics(context.metricsFactory) + protected def extraMetricLabels: Seq[(String, String)] = Seq.empty + protected def timeouts: ProcessingTimeout = context.timeouts protected def loggerFactory: NamedLoggerFactory = context.loggerFactory diff --git a/apps/common/src/main/scala/com/daml/network/automation/TriggerMetrics.scala b/apps/common/src/main/scala/com/daml/network/automation/TriggerMetrics.scala index 9bf1029d..8d6cf58a 100644 --- a/apps/common/src/main/scala/com/daml/network/automation/TriggerMetrics.scala +++ b/apps/common/src/main/scala/com/daml/network/automation/TriggerMetrics.scala @@ -23,6 +23,16 @@ class TriggerMetrics( ) ) + val iterations: Meter = metricsFactory.meter( + MetricInfo( + name = prefix :+ "iterations", + summary = "How often a polling trigger was run", + description = + "This metric measures the number of individual polling iterations processed by the trigger.", + qualification = Traffic, + ) + )(MetricsContext.Empty) + val completed: Meter = metricsFactory.meter( MetricInfo( name = prefix :+ "completed", diff --git a/apps/common/src/main/scala/com/daml/network/automation/UnassignTrigger.scala b/apps/common/src/main/scala/com/daml/network/automation/UnassignTrigger.scala index f8334998..2609f3ff 100644 --- a/apps/common/src/main/scala/com/daml/network/automation/UnassignTrigger.scala +++ b/apps/common/src/main/scala/com/daml/network/automation/UnassignTrigger.scala @@ -38,6 +38,8 @@ class UnassignTrigger[C <: ContractTypeCompanion[_, TCid, _, T], TCid <: Contrac companion, ) { + override protected def extraMetricLabels = Seq("party" -> partyId.toString) + override protected def completeTask( task: AssignedContract[ TCid, diff --git a/apps/common/src/main/scala/com/daml/network/http/HttpValidatorLicensesHandler.scala b/apps/common/src/main/scala/com/daml/network/http/HttpValidatorLicensesHandler.scala new file mode 100644 index 00000000..7276089b --- /dev/null +++ b/apps/common/src/main/scala/com/daml/network/http/HttpValidatorLicensesHandler.scala @@ -0,0 +1,45 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.daml.network.http + +import com.daml.network.codegen.java.splice.validatorlicense as vl +import com.daml.network.http.v0.definitions +import com.daml.network.http.v0.definitions.ListValidatorLicensesResponse +import com.daml.network.store.{AppStore, PageLimit, SortOrder} +import com.digitalasset.canton.logging.NamedLogging +import com.digitalasset.canton.tracing.{Spanning, TraceContext} +import io.opentelemetry.api.trace.Tracer + +import scala.concurrent.{ExecutionContext, Future} + +trait HttpValidatorLicensesHandler extends Spanning with NamedLogging { + + protected val validatorLicensesStore: AppStore + protected val workflowId: String + protected implicit val tracer: Tracer + + def listValidatorLicenses(after: Option[Long], limit: Option[Int])(implicit + tc: TraceContext, + ec: ExecutionContext, + ): Future[ListValidatorLicensesResponse] = { + withSpan(s"$workflowId.listValidatorLicenses") { _ => _ => + for { + resultsInPage <- validatorLicensesStore.multiDomainAcsStore + .listContractsPaginated( + vl.ValidatorLicense.COMPANION, + after, + limit.fold(PageLimit.Max)(PageLimit.tryCreate), + SortOrder.Descending, + ) + .map(_.mapResultsInPage(_.contract)) + } yield { + definitions.ListValidatorLicensesResponse( + resultsInPage.resultsInPage.map(_.toHttp).toVector, + resultsInPage.nextPageToken, + ) + } + } + } + +} diff --git a/apps/package-lock.json b/apps/package-lock.json index 9c1c96c2..04a1e11b 100644 --- a/apps/package-lock.json +++ b/apps/package-lock.json @@ -6,6 +6,7 @@ "": { "workspaces": [ "common/frontend-test-utils", + "common/frontend-test-vite-utils", "common/frontend-typeface-termina", "common/frontend", "common/frontend/utils", @@ -70,6 +71,7 @@ "@types/uuid": "8.3.4", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "happy-dom": "^11.0.0", "prettier": "2.8.4", "typescript": "4.9.5", @@ -260,6 +262,7 @@ "@typescript-eslint/parser": "5.52.0", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "eslint": "8.34.0", "eslint-config-prettier": "8.6.0", "eslint-plugin-import": "2.27.5", @@ -632,6 +635,7 @@ "@types/uuid": "8.3.4", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "happy-dom": "^11.0.0", "prettier": "2.8.4", "typescript": "4.9.5", @@ -701,6 +705,25 @@ "dev": true, "license": "MIT" }, + "common/frontend-test-vite-utils": { + "name": "common-test-vite-utils", + "version": "0.1.0", + "devDependencies": { + "@trivago/prettier-plugin-sort-imports": "4.0.0", + "@types/node": "18.11.0", + "eslint": "8.34.0", + "msw": "^1.2.5", + "nodemon": "^3.0.1", + "prettier": "2.8.4", + "typescript": "4.9.5" + } + }, + "common/frontend-test-vite-utils/node_modules/@types/node": { + "version": "18.11.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.0.tgz", + "integrity": "sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w==", + "dev": true + }, "common/frontend-typeface-termina": { "name": "common-typeface-termina", "version": "1.0.0", @@ -2776,6 +2799,7 @@ "@types/uuid": "8.3.4", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "happy-dom": "^11.0.0", "prettier": "2.8.4", "typescript": "4.9.5", @@ -8053,6 +8077,10 @@ "resolved": "common/frontend-test-utils", "link": true }, + "node_modules/common-test-vite-utils": { + "resolved": "common/frontend-test-vite-utils", + "link": true + }, "node_modules/common-typeface-termina": { "resolved": "common/frontend-typeface-termina", "link": true @@ -14169,6 +14197,7 @@ "@types/uuid": "8.3.4", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "happy-dom": "^11.0.0", "prettier": "2.8.4", "typescript": "4.9.5", @@ -14344,6 +14373,7 @@ "@types/uuid": "8.3.4", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "happy-dom": "^11.0.0", "prettier": "2.8.4", "typescript": "4.9.5", @@ -14618,6 +14648,7 @@ "@typescript-eslint/parser": "5.52.0", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "eslint": "8.34.0", "eslint-config-prettier": "8.6.0", "eslint-plugin-import": "2.27.5", diff --git a/apps/package.json b/apps/package.json index 6379265a..6fb042b1 100644 --- a/apps/package.json +++ b/apps/package.json @@ -1,6 +1,7 @@ { "workspaces": [ "common/frontend-test-utils", + "common/frontend-test-vite-utils", "common/frontend-typeface-termina", "common/frontend", "common/frontend/utils", diff --git a/apps/scan/frontend/src/App.tsx b/apps/scan/frontend/src/App.tsx index 22732e85..346980c0 100644 --- a/apps/scan/frontend/src/App.tsx +++ b/apps/scan/frontend/src/App.tsx @@ -23,6 +23,7 @@ import AppLeaderboard from './routes/appLeaderboard'; import SynchronizerFeesLeaderboard from './routes/domainFeesLeaderboard'; import DsoWithContexts from './routes/dso'; import Root from './routes/root'; +import ScanValidatorLicenses from './routes/scanValidatorLicenses'; import ValidatorFaucetsLeaderboard from './routes/validatorFaucetsLeaderboard'; import ValidatorLeaderboard from './routes/validatorLeaderboard'; import { useScanConfig } from './utils'; @@ -60,6 +61,7 @@ const router = createBrowserRouter( } /> } /> + } /> ) ); diff --git a/apps/scan/frontend/src/__tests__/mocks/handlers/scan-api.ts b/apps/scan/frontend/src/__tests__/mocks/handlers/scan-api.ts index 683be97d..c8631365 100644 --- a/apps/scan/frontend/src/__tests__/mocks/handlers/scan-api.ts +++ b/apps/scan/frontend/src/__tests__/mocks/handlers/scan-api.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 { validatorLicensesHandler, dsoInfoHandler } from 'common-test-utils'; import { RestHandler, rest } from 'msw'; import { ErrorResponse, @@ -20,6 +21,7 @@ import { config } from '../../setup/config'; const amuletNameServiceAcronym = config.spliceInstanceNames.nameServiceNameAcronym; export const buildScanMock = (scanUrl: string): RestHandler[] => [ + dsoInfoHandler(scanUrl), rest.get(`${scanUrl}/v0/dso-party-id`, (_, res, ctx) => { return res( ctx.json({ @@ -273,4 +275,5 @@ export const buildScanMock = (scanUrl: string): RestHandler[] => [ } } ), + validatorLicensesHandler(scanUrl), ]; diff --git a/apps/scan/frontend/src/__tests__/scan.test.tsx b/apps/scan/frontend/src/__tests__/scan.test.tsx index ec78a432..963fa0e6 100644 --- a/apps/scan/frontend/src/__tests__/scan.test.tsx +++ b/apps/scan/frontend/src/__tests__/scan.test.tsx @@ -1,6 +1,7 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import { fireEvent, render, screen, within } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { test, expect } from 'vitest'; import App from '../App'; @@ -9,22 +10,22 @@ import { config } from './setup/config'; const spliceInstanceNames = config.spliceInstanceNames; -test('home screen shows up', async () => { - render( +const AppWithConfig: React.FC = () => { + return ( ); +}; + +test('home screen shows up', async () => { + render(); const a = await screen.findByText(`${spliceInstanceNames.amuletName} Scan`); expect(a).toBeDefined(); }); test('recent activity link from tab opens a tab', async () => { - render( - - - - ); + render(); const appLeaderboardLink = screen.getByRole('tab', { name: 'App Leaderboard' }); fireEvent.click(appLeaderboardLink); expect(screen.queryAllByTestId('activity-table')).toHaveLength(0); @@ -57,3 +58,19 @@ test('recent activity looks up entries', async () => { ); expect(ansNameElement).toBeDefined(); }); + +test('validator licenses are displayed and paginable', async () => { + const user = userEvent.setup(); + render(); + await user.click(screen.getByText('Validators')); + + expect(await screen.findByText('Validator Licenses')).toBeDefined(); + + expect(await screen.findByDisplayValue('validator::1')).toBeDefined(); + expect(screen.queryByText('validator::15')).toBeNull(); + + expect(await screen.findByText('View More')).toBeDefined(); + await user.click(screen.getByText('View More')); + + expect(await screen.findByDisplayValue('validator::15')).toBeDefined(); +}); diff --git a/apps/scan/frontend/src/components/Layout.tsx b/apps/scan/frontend/src/components/Layout.tsx index e4c253ac..ab57d31c 100644 --- a/apps/scan/frontend/src/components/Layout.tsx +++ b/apps/scan/frontend/src/components/Layout.tsx @@ -23,6 +23,7 @@ const Layout: React.FC = (props: LayoutProps) => { { name: `${config.spliceInstanceNames.amuletName} Activity`, path: '/' }, { name: 'Network Info', path: '/dso' }, { name: 'Governance', path: '/governance' }, + { name: 'Validators', path: '/validator-licenses' }, ]} /> diff --git a/apps/scan/frontend/src/hooks/index.ts b/apps/scan/frontend/src/hooks/index.ts index 9c8d6698..622eee27 100644 --- a/apps/scan/frontend/src/hooks/index.ts +++ b/apps/scan/frontend/src/hooks/index.ts @@ -3,6 +3,7 @@ import { useDsoInfos } from './useDsoInfos'; import { useListVoteRequestResult, useListDsoRulesVoteRequests } from './useListVoteRequests'; import { useListVotes } from './useListVotes'; +import { useValidatorLicenses } from './useValidatorLicenses'; import { useVoteRequest } from './useVoteRequest'; export { @@ -11,4 +12,5 @@ export { useListDsoRulesVoteRequests, useListVotes, useVoteRequest, + useValidatorLicenses, }; diff --git a/apps/scan/frontend/src/hooks/useValidatorLicenses.tsx b/apps/scan/frontend/src/hooks/useValidatorLicenses.tsx new file mode 100644 index 00000000..0d8ced21 --- /dev/null +++ b/apps/scan/frontend/src/hooks/useValidatorLicenses.tsx @@ -0,0 +1,28 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { UseInfiniteQueryResult, useInfiniteQuery } from '@tanstack/react-query'; +import { ValidatorLicensesPage } from 'common-frontend'; +import { Contract, PollingStrategy } from 'common-frontend-utils'; +import { useScanClient } from 'common-frontend/scan-api'; + +import { ValidatorLicense } from '@daml.js/splice-amulet/lib/Splice/ValidatorLicense/module'; + +export const useValidatorLicenses = ( + limit: number +): UseInfiniteQueryResult => { + const scanClient = useScanClient(); + return useInfiniteQuery({ + refetchInterval: PollingStrategy.FIXED, + queryKey: ['listValidatorLicenses', limit], + queryFn: async ({ pageParam }) => { + const page = await scanClient.listValidatorLicenses(pageParam, limit); + const validatorLicenses = page.validator_licenses.map(c => + Contract.decodeOpenAPI(c, ValidatorLicense) + ); + return { validatorLicenses, after: page.next_page_token }; + }, + getNextPageParam: lastPage => { + return lastPage && lastPage.after; + }, + }); +}; diff --git a/apps/scan/frontend/src/routes/scanValidatorLicenses.tsx b/apps/scan/frontend/src/routes/scanValidatorLicenses.tsx new file mode 100644 index 00000000..8933dd43 --- /dev/null +++ b/apps/scan/frontend/src/routes/scanValidatorLicenses.tsx @@ -0,0 +1,29 @@ +// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 +import { ValidatorLicenses } from 'common-frontend'; +import React from 'react'; + +import { Box } from '@mui/material'; +import Container from '@mui/material/Container'; + +import Layout from '../components/Layout'; +import { useDsoInfos, useValidatorLicenses } from '../hooks'; + +const ScanValidatorLicenses: React.FC = () => { + const validatorLicensesQuery = useValidatorLicenses(10); + const dsoInfosQuery = useDsoInfos(); + return ( + + + + + + + + ); +}; + +export default ScanValidatorLicenses; diff --git a/apps/scan/frontend/vite.config.ts b/apps/scan/frontend/vite.config.ts index 8b81772f..5c355fc2 100644 --- a/apps/scan/frontend/vite.config.ts +++ b/apps/scan/frontend/vite.config.ts @@ -1,7 +1,7 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import react from '@vitejs/plugin-react'; -import { vitest_common_conf } from 'common-test-utils'; +import { vitest_common_conf } from 'common-test-vite-utils'; import { defineConfig, loadEnv, mergeConfig } from 'vite'; import viteTsconfigPaths from 'vite-tsconfig-paths'; diff --git a/apps/scan/src/main/openapi/scan-internal.yaml b/apps/scan/src/main/openapi/scan-internal.yaml index 92c662a6..643d6616 100644 --- a/apps/scan/src/main/openapi/scan-internal.yaml +++ b/apps/scan/src/main/openapi/scan-internal.yaml @@ -902,6 +902,31 @@ paths: application/json: schema: "$ref": "../../../../common/src/main/openapi/common-internal.yaml#/components/schemas/ListDsoRulesVoteResultsResponse" + /v0/admin/validator/licenses: + get: + tags: [ scan ] + x-jvm-package: scan + operationId: "listValidatorLicenses" + parameters: + - name: "after" + in: "query" + required: false + schema: + type: integer + format: int64 + - name: "limit" + in: "query" + required: false + schema: + type: integer + format: int32 + responses: + "200": + description: ok + content: + application/json: + schema: + "$ref": "../../../../common/src/main/openapi/common-internal.yaml#/components/schemas/ListValidatorLicensesResponse" components: schemas: diff --git a/apps/scan/src/main/scala/com/daml/network/scan/admin/http/HttpScanHandler.scala b/apps/scan/src/main/scala/com/daml/network/scan/admin/http/HttpScanHandler.scala index 601b152f..ba5a7973 100644 --- a/apps/scan/src/main/scala/com/daml/network/scan/admin/http/HttpScanHandler.scala +++ b/apps/scan/src/main/scala/com/daml/network/scan/admin/http/HttpScanHandler.scala @@ -60,9 +60,9 @@ import com.daml.network.http.v0.definitions.TransactionHistoryResponseItem.Trans Mint, Transfer, } -import com.daml.network.http.{HttpVotesHandler, UrlValidator} +import com.daml.network.http.{HttpValidatorLicensesHandler, HttpVotesHandler, UrlValidator} import com.daml.network.scan.dso.DsoAnsResolver -import com.daml.network.store.{PageLimit, SortOrder} +import com.daml.network.store.{AppStore, PageLimit, SortOrder, VotesStore} import com.digitalasset.canton.config.NonNegativeFiniteDuration import com.digitalasset.canton.time.Clock @@ -82,9 +82,12 @@ class HttpScanHandler( ec: ExecutionContextExecutor, protected val tracer: Tracer, ) extends v0.ScanHandler[TraceContext] - with HttpVotesHandler { - protected val workflowId = this.getClass.getSimpleName - protected val votesStore = store + with HttpVotesHandler + with HttpValidatorLicensesHandler { + + override protected val workflowId: String = this.getClass.getSimpleName + override protected val votesStore: VotesStore = store + override protected val validatorLicensesStore: AppStore = store def getDsoPartyId( response: v0.ScanResource.GetDsoPartyIdResponse.type @@ -553,6 +556,16 @@ class HttpScanHandler( } } + override def listValidatorLicenses( + respond: ScanResource.ListValidatorLicensesResponse.type + )(after: Option[Long], limit: Option[Int])( + extracted: TraceContext + ): Future[ScanResource.ListValidatorLicensesResponse] = { + this + .listValidatorLicenses(after, limit)(extracted, ec) + .map(ScanResource.ListValidatorLicensesResponse.OK) + } + // TODO: (#7809) Add caching for sequencers per domain override def listDsoSequencers( respond: v0.ScanResource.ListDsoSequencersResponse.type diff --git a/apps/splitwell/frontend/package.json b/apps/splitwell/frontend/package.json index c9eb63b3..62cd9124 100644 --- a/apps/splitwell/frontend/package.json +++ b/apps/splitwell/frontend/package.json @@ -43,6 +43,7 @@ "@types/uuid": "8.3.4", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "happy-dom": "^11.0.0", "prettier": "2.8.4", "typescript": "4.9.5", diff --git a/apps/splitwell/frontend/vite.config.ts b/apps/splitwell/frontend/vite.config.ts index bb344050..5134e25b 100644 --- a/apps/splitwell/frontend/vite.config.ts +++ b/apps/splitwell/frontend/vite.config.ts @@ -1,7 +1,7 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import react from '@vitejs/plugin-react'; -import { vitest_common_conf } from 'common-test-utils'; +import { vitest_common_conf } from 'common-test-vite-utils'; import { defineConfig, loadEnv, mergeConfig } from 'vite'; import viteTsconfigPaths from 'vite-tsconfig-paths'; diff --git a/apps/sv/frontend/package.json b/apps/sv/frontend/package.json index cff57e2e..ce8eec32 100644 --- a/apps/sv/frontend/package.json +++ b/apps/sv/frontend/package.json @@ -45,6 +45,7 @@ "@types/uuid": "8.3.4", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "happy-dom": "^11.0.0", "prettier": "2.8.4", "typescript": "4.9.5", diff --git a/apps/sv/frontend/src/__tests__/mocks/constants.ts b/apps/sv/frontend/src/__tests__/mocks/constants.ts index d05b8d16..d53c96d8 100644 --- a/apps/sv/frontend/src/__tests__/mocks/constants.ts +++ b/apps/sv/frontend/src/__tests__/mocks/constants.ts @@ -1,7 +1,8 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import * as jtv from '@mojotech/json-type-validation'; -import { GetDsoInfoResponse, ListDsoRulesVoteResultsResponse } from 'sv-openapi'; +import { dsoInfo } from 'common-test-utils'; +import { ListDsoRulesVoteResultsResponse } from 'sv-openapi'; import { AmuletRules } from '@daml.js/splice-amulet/lib/Splice/AmuletRules'; import { DsoRules } from '@daml.js/splice-dso-governance/lib/Splice/DsoRules/module'; @@ -197,520 +198,6 @@ export const voteResults: ListDsoRulesVoteResultsResponse = { ], }; -// Obtained via `curl https://sv.sv-2.cimain.network.canton.global/api/sv/v0/dso` -// ...and then npmFix made it look nice. -// You'll need to update this on template changes to DsoRules and AmuletRules. -export const dsoInfo: GetDsoInfoResponse = { - sv_user: 'OBpJ9oTyOLuAKF0H2hhzdSFUICt0diIn@clients', - sv_party_id: - 'Digital-Asset-2::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', - dso_party_id: 'DSO::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - voting_threshold: 3, - latest_mining_round: { - contract: { - template_id: - '218bd1d12914957ff65c2f26f3e752337f10b643b2115af712e287e06dc248ca:Splice.Round:OpenMiningRound', - contract_id: - '00c5e96485ac00043b7e0b576faefe6ef597b9a279f07807eea3b18a2789c65e1cca021220ab83da15af4b90ea1042477b33ea68cebd055e07480608c3f96152b1c49f7106', - payload: { - issuingFor: { - microseconds: '450000000', - }, - issuanceConfig: { - validatorRewardPercentage: '0.5', - amuletToIssuePerYear: '40000000000.0', - unfeaturedAppRewardCap: '0.6', - appRewardPercentage: '0.15', - validatorFaucetCap: '2.85', - featuredAppRewardCap: '100.0', - validatorRewardCap: '0.2', - }, - opensAt: '2024-01-09T19:20:43.133736Z', - transferConfigUsd: { - holdingFee: { - rate: '0.0000048225', - }, - maxNumInputs: '100', - lockHolderFee: { - fee: '0.005', - }, - createFee: { - fee: '0.03', - }, - maxNumLockHolders: '50', - transferFee: { - initialRate: '0.01', - steps: [ - { - _1: '100.0', - _2: '0.001', - }, - { - _1: '1000.0', - _2: '0.0001', - }, - { - _1: '1000000.0', - _2: '0.00001', - }, - ], - }, - maxNumOutputs: '100', - }, - targetClosesAt: '2024-01-09T19:25:43.133736Z', - amuletPrice: '1.0', - tickDuration: { - microseconds: '150000000', - }, - dso: 'DSO::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - round: { - number: '3', - }, - }, - created_event_blob: - 'CgNkZXYS8QYKRQDF6WSFrAAEO34LV2+u/m71l7miefB4B+6jsYonicZeHMoCEiCrg9oVr0uQ6hBCR3sz6mjOvQVeB0gGCMP5YVKxxJ9xBhJeCkAyMThiZDFkMTI5MTQ5NTdmZjY1YzJmMjZmM2U3NTIzMzdmMTBiNjQzYjIxMTVhZjcxMmUyODdlMDZkYzI0OGNhEgJDQxIFUm91bmQaD09wZW5NaW5pbmdSb3VuZBrJBArGBBJNEktSSVNWQzo6MTIyMGE1NTVlY2NlZWQ3ZmVmNDQ1YzdlYzMzM2MxNDQ0OWQ5ODFmYjY1OTViZTIxOGM1ZDcwMWVlZjVlYTYzYTFiY2ESChIICgYSBBICKAYSEBIOMgwxLjAwMDAwMDAwMDASCxIJSSgD6jWIDgYAEgsSCUkopstHiA4GABIOEgwKChIIEgYogNKTrQMSiQIShgIKgwISFhIUChISEBIOMgwwLjAzMDAwMDAwMDASFhIUChISEBIOMgwwLjAwMDAwNDgyMjUSpAESoQEKngESEBIOMgwwLjAxMDAwMDAwMDASiQEShgEigwEKKAomEhISEDIOMTAwLjAwMDAwMDAwMDASEBIOMgwwLjAwMTAwMDAwMDAKKQonEhMSETIPMTAwMC4wMDAwMDAwMDAwEhASDjIMMC4wMDAxMDAwMDAwCiwKKhIWEhQyEjEwMDAwMDAuMDAwMDAwMDAwMBIQEg4yDDAuMDAwMDEwMDAwMBIWEhQKEhIQEg4yDDAuMDA1MDAwMDAwMBIFEgMoyAESBRIDKMgBEgQSAihkEpABEo0BCooBEhoSGDIWNDAwMDAwMDAwMDAuMDAwMDAwMDAwMBIQEg4yDDAuNTAwMDAwMDAwMBIQEg4yDDAuMTUwMDAwMDAwMBIQEg4yDDAuMjAwMDAwMDAwMBISEhAyDjEwMC4wMDAwMDAwMDAwEhASDjIMMC42MDAwMDAwMDAwEhASDjIMMi44NTAwMDAwMDAwEg4SDAoKEggSBiiAxoaPASpJU1ZDOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYTmoMfksiA4GAEIoCiYKJAgBEiCgTuxgdiWHx5vZWf1A6G5VedSrPWvqJqL4OyX06xBIUw==', - created_at: '2024-01-09T19:18:13.133736Z', - }, - domain_id: - 'global-domain::1220fb89d62774bd5b3fd8a11c1b22c8c5453e8286c3cf7add515c98d7bca192ef18', - }, - amulet_rules: { - contract: { - template_id: - '218bd1d12914957ff65c2f26f3e752337f10b643b2115af712e287e06dc248ca:Splice.AmuletRules:AmuletRules', - contract_id: - '0084bd1732e6ef1757c2755a83d6acfdc9bf68688b7f55d19d4586008ee3228bceca02122028af9a1d4fff115e10a40adb65d08dcd69463ad6bf5c3055e12d1b960bbad9da', - payload: { - dso: 'DSO::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - configSchedule: { - initialValue: getAmuletConfig('0.03'), - futureValues: [ - { - _1: '2023-03-15T08:35:00Z', - _2: getAmuletConfig('0.003'), - }, - { - _1: '2024-03-15T08:35:00Z', - _2: getAmuletConfig('4815162342'), - }, - { - _1: '2524-03-19T08:35:00Z', - _2: getAmuletConfig('0.03'), - }, - ], - }, - isDevNet: true, - }, - created_event_blob: - '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', - created_at: '2024-01-09T19:15:33.960325Z', - }, - domain_id: - 'global-domain::1220fb89d62774bd5b3fd8a11c1b22c8c5453e8286c3cf7add515c98d7bca192ef18', - }, - dso_rules: { - contract: { - template_id: - 'b71a4deb943e8f7f27bb7a384c1b8da8a88f5cd40f92d5b1b56b97f1cb379f27:Splice.DsoRules:DsoRules', - contract_id: - '00cb5ebc4580a0806e202a295fff32ac769d4a1ba969c0d83773ae98dc4ff9c246ca021220d0f926a6d088171f7f38abbee000b9e3e6d098d76632e1374e34c8d43b702519', - payload: { - epoch: '0', - initialTrafficState: [ - [ - 'MED::mediator::1220919a6e8c9ddd3b07ef36e698e79686dcf5e4c6b32affb57b5a910cc75f7b66b4', - { - consumedTraffic: '0', - }, - ], - [ - 'PAR::participant::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', - { - consumedTraffic: '0', - }, - ], - ], - offboardedSvs: [], - config: { - svOnboardingRequestTimeout: { - microseconds: '3600000000', - }, - numUnclaimedRewardsThreshold: '10', - actionConfirmationTimeout: { - microseconds: '3600000000', - }, - dsoDelegateInactiveTimeout: { - microseconds: '70000000', - }, - voteRequestTimeout: { - microseconds: '604800000000', - }, - synchronizerNodeConfigLimits: { - cometBft: { - maxNumSequencingKeys: '2', - maxNodeIdLength: '50', - maxNumCometBftNodes: '2', - maxPubKeyLength: '256', - maxNumGovernanceKeys: '2', - }, - }, - numMemberTrafficContractsThreshold: '5', - initialTrafficGrant: '1000000', - svOnboardingConfirmedTimeout: { - microseconds: '3600000000', - }, - decentralizedSynchronizer: { - synchronizers: [ - [ - 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - { - state: 'DS_Operational', - cometBftGenesisJson: - 'TODO(#4900): share CometBFT genesis.json of founding SV node via DsoRules config.', - }, - ], - ], - lastSynchronizerId: - 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - activeSynchronizerId: - 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - }, - maxTextLength: '1024', - }, - dsoDelegate: - 'Digital-Asset-2::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', - isDevNet: true, - svs: [ - [ - 'Digital-Asset-2::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', - { - numRewardCouponsMissed: '0', - name: 'Digital-Asset-2', - joinedAsOfRound: { - number: '0', - }, - svRewardWeight: '10', - participantId: - 'PAR::participant-1::1220ed548efbcc22bb5097bd5a98303d1d64ab519f9568cdc1676ef1630da1fa6832', - synchronizerNodes: [ - [ - 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - { - cometBft: { - nodes: [ - [ - '5af57aa83abcec085c949323ed8538108757be9c', - { - validatorPubKey: 'gpkwc1WCttL8ZATBIPWIBRCrb0eV4JwMCnjRa56REPw=', - votingPower: '1', - }, - ], - ], - governanceKeys: [ - { - pubKey: 'm16haLzv/d/Ok04Sm39ABk0f0HsSWYNZxrIUiyQ+cK8=', - }, - ], - sequencingKeys: [], - }, - sequencer: { - migrationId: '0', - sequencerId: - 'SEQ::sequencer::12209f0d96157ae83871bd347d2fe22fe0b982dfbfe50016f7cf6dcfdfcd4eb8e132', - url: 'https://sequencer-0.sv-2.cimain.network.canton.global', - availableAfter: '2024-01-09T19:15:31.137243Z', - }, - mediator: { - mediatorId: - 'MED::mediator::1220919a6e8c9ddd3b07ef36e698e79686dcf5e4c6b32affb57b5a910cc75f7b66b4', - }, - }, - ], - ], - lastReceivedRewardFor: { - number: '-1', - }, - }, - ], - [ - 'Digital-Asset-Eng-2::12205ab9210b258422a251d6148b031d71147405948c92bf9c4bc78029c5479aed75', - { - numRewardCouponsMissed: '0', - name: 'Digital-Asset-Eng-2', - joinedAsOfRound: { - number: '0', - }, - svRewardWeight: '12345', - participantId: - 'PAR::participant-2::12205ab9210b258422a251d6148b031d71147405948c92bf9c4bc78029c5479aed75', - synchronizerNodes: [ - [ - 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - { - cometBft: { - nodes: [ - [ - 'c36b3bbd969d993ba0b4809d1f587a3a341f22c1', - { - validatorPubKey: 'BVSM9/uPGLU7lJj72SUw1a261z2L6Yy2XKLhpUvbxqE=', - votingPower: '1', - }, - ], - ], - governanceKeys: [ - { - pubKey: 'm16haLzv/d/Ok04Sm39ABk0f0HsSWYNZxrIUiyQ+cK8=', - }, - ], - sequencingKeys: [], - }, - sequencer: { - migrationId: '0', - sequencerId: - 'SEQ::sequencer::12207eec8b03a668493a6068a755e2eb32084d9b588717049690cdbecb6558fd325c', - url: 'https://sequencer-0.sv-2-eng.cimain.network.canton.global', - availableAfter: '2024-01-09T19:19:10.755996Z', - }, - mediator: { - mediatorId: - 'MED::mediator::122046b485a233b81f6018831c983a532d125aad8784df8f0c0a9478d66c46292d60', - }, - }, - ], - ], - lastReceivedRewardFor: { - number: '-1', - }, - }, - ], - [ - 'Digital-Asset-Eng-3::12203cb019c9986425861c91685d9b7c0068cf48abb8dff8e20f166501f7f677dce7', - { - numRewardCouponsMissed: '0', - name: 'Digital-Asset-Eng-3', - joinedAsOfRound: { - number: '0', - }, - svRewardWeight: '12345', - participantId: - 'PAR::participant-3::12203cb019c9986425861c91685d9b7c0068cf48abb8dff8e20f166501f7f677dce7', - synchronizerNodes: [ - [ - 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - { - cometBft: { - nodes: [ - [ - '0d8e87c54d199e85548ccec123c9d92966ec458c', - { - validatorPubKey: 'dxm4n1MRP/GuSEkJIwbdB4zVcGAeacohFKNtbKK8oRA=', - votingPower: '1', - }, - ], - ], - governanceKeys: [ - { - pubKey: 'm16haLzv/d/Ok04Sm39ABk0f0HsSWYNZxrIUiyQ+cK8=', - }, - ], - sequencingKeys: [], - }, - sequencer: { - migrationId: '0', - sequencerId: - 'SEQ::sequencer::12207a75f3778b60414df3bb876e4a7c84976f35640b4daf30894189bc73e5c7fe38', - url: 'https://sequencer-0.sv-3-eng.cimain.network.canton.global', - availableAfter: '2024-01-09T19:19:14.361604Z', - }, - mediator: { - mediatorId: - 'MED::mediator::1220ed81687df57e0f6be850bcd546b1751a0b4ad8a8d8c5eeef99ddac8405d49fab', - }, - }, - ], - ], - lastReceivedRewardFor: { - number: '-1', - }, - }, - ], - [ - 'Digital-Asset-Eng-4::122070fc4bb3519a4f39f5919b5a166e30794733e56ad9fba2157f7208ff458f7dc7', - { - numRewardCouponsMissed: '0', - name: 'Digital-Asset-Eng-4', - joinedAsOfRound: { - number: '0', - }, - svRewardWeight: '12345', - participantId: - 'PAR::participant-4::122070fc4bb3519a4f39f5919b5a166e30794733e56ad9fba2157f7208ff458f7dc7', - synchronizerNodes: [ - [ - 'global-synchronizer::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - { - cometBft: { - nodes: [ - [ - 'ee738517c030b42c3ff626d9f80b41dfc4b1a3b8', - { - validatorPubKey: '2umZdUS97a6VUXMGsgKJ/VbQbanxWaFUxK1QimhlEjo=', - votingPower: '1', - }, - ], - ], - governanceKeys: [ - { - pubKey: 'm16haLzv/d/Ok04Sm39ABk0f0HsSWYNZxrIUiyQ+cK8=', - }, - ], - sequencingKeys: [], - }, - sequencer: { - migrationId: '0', - sequencerId: - 'SEQ::sequencer::12208d47d9361a0e84ee10648c611fc2436885ab5193aa67fa5d9eefc7929f732e96', - url: 'https://sequencer-0.sv-4-eng.cimain.network.canton.global', - availableAfter: '2024-01-09T19:18:43.522097Z', - }, - mediator: { - mediatorId: - 'MED::mediator::12208c13beecbd916771ce198d9d0f048b243ed99c6e39e34bdec5b32fbb7a51bab4', - }, - }, - ], - ], - lastReceivedRewardFor: { - number: '-1', - }, - }, - ], - ], - dso: 'DSO::1220a555ecceed7fef445c7ec333c14449d981fb6595be218c5d701eef5ea63a1bca', - }, - created_event_blob: - 'CgNkZXYSnyEKRQDLXrxFgKCAbiAqKV//Mqx2nUobqWnA2DdzrpjcT/nCRsoCEiDQ+Sam0IgXH384q77gALnj5tCY12Yy4TdONMjUO3AlGRJaCkBiNzFhNGRlYjk0M2U4ZjdmMjdiYjdhMzg0YzFiOGRhOGE4OGY1Y2Q0MGY5MmQ1YjFiNTZiOTdmMWNiMzc5ZjI3EgJDThIIU3ZjUnVsZXMaCFN2Y1J1bGVzGvseCvgeEk0SS1JJU1ZDOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYRIEEgIoABKhFxKeF5IBmhcK4gUKW1JZQ2FudG9uLUZvdW5kYXRpb24tMTo6MTIyMGVkNTQ4ZWZiY2MyMmJiNTA5N2JkNWE5ODMwM2QxZDY0YWI1MTlmOTU2OGNkYzE2NzZlZjE2MzBkYTFmYTY4MzISggUK/wQSFxIVQhNDYW50b24tRm91bmRhdGlvbi0xEgoSCAoGEgQSAigAEgoSCAoGEgQSAigBEgQSAigAEgQSAigUEr8EErwEkgG4BAq1BApVQlNnbG9iYWwtZG9tYWluOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYRLbAwrYAxK5ARK2AQqzARJvEm2SAWoKaAoqQig1YWY1N2FhODNhYmNlYzA4NWM5NDkzMjNlZDg1MzgxMDg3NTdiZTljEjoKOBIwEi5CLGdwa3djMVdDdHRMOFpBVEJJUFdJQlJDcmIwZVY0SndNQ25qUmE1NlJFUHc9EgQSAigCEjoSOCI2CjQKMhIwEi5CLG0xNmhhTHp2L2QvT2swNFNtMzlBQmswZjBIc1NXWU5aeHJJVWl5UStjSzg9EgQSAiIAErYBErMBcrABCq0BCqoBElgSVkJUU0VROjpzZXF1ZW5jZXI6OjEyMjA5ZjBkOTYxNTdhZTgzODcxYmQzNDdkMmZlMjJmZTBiOTgyZGZiZmU1MDAxNmY3Y2Y2ZGNmZGZjZDRlYjhlMTMyEj0SO0I5aHR0cHM6Ly9zZXF1ZW5jZXItMC5zdi0xLnN2Yy5jaW1haW4ubmV0d29yay5jYW50b24uZ2xvYmFsEg8SDXILCglJ21JRI4gOBgASYRJfcl0KWwpZElcSVUJTTUVEOjptZWRpYXRvcjo6MTIyMDkxOWE2ZThjOWRkZDNiMDdlZjM2ZTY5OGU3OTY4NmRjZjVlNGM2YjMyYWZmYjU3YjVhOTEwY2M3NWY3YjY2YjQK5AUKW1JZQ2FudG9uLUZvdW5kYXRpb24tMjo6MTIyMDVhYjkyMTBiMjU4NDIyYTI1MWQ2MTQ4YjAzMWQ3MTE0NzQwNTk0OGM5MmJmOWM0YmM3ODAyOWM1NDc5YWVkNzUShAUKgQUSFxIVQhNDYW50b24tRm91bmRhdGlvbi0yEgoSCAoGEgQSAigAEgoSCAoGEgQSAigBEgQSAigAEgYSBCjywAESvwQSvASSAbgECrUEClVCU2dsb2JhbC1kb21haW46OjEyMjBhNTU1ZWNjZWVkN2ZlZjQ0NWM3ZWMzMzNjMTQ0NDlkOTgxZmI2NTk1YmUyMThjNWQ3MDFlZWY1ZWE2M2ExYmNhEtsDCtgDErkBErYBCrMBEm8SbZIBagpoCipCKGMzNmIzYmJkOTY5ZDk5M2JhMGI0ODA5ZDFmNTg3YTNhMzQxZjIyYzESOgo4EjASLkIsQlZTTTkvdVBHTFU3bEpqNzJTVXcxYTI2MXoyTDZZeTJYS0xocFV2YnhxRT0SBBICKAISOhI4IjYKNAoyEjASLkIsbTE2aGFMenYvZC9PazA0U20zOUFCazBmMEhzU1dZTlp4cklVaXlRK2NLOD0SBBICIgAStgESswFysAEKrQEKqgESWBJWQlRTRVE6OnNlcXVlbmNlcjo6MTIyMDdlZWM4YjAzYTY2ODQ5M2E2MDY4YTc1NWUyZWIzMjA4NGQ5YjU4ODcxNzA0OTY5MGNkYmVjYjY1NThmZDMyNWMSPRI7QjlodHRwczovL3NlcXVlbmNlci0wLnN2LTIuc3ZjLmNpbWFpbi5uZXR3b3JrLmNhbnRvbi5nbG9iYWwSDxINcgsKCUmccGgwiA4GABJhEl9yXQpbClkSVxJVQlNNRUQ6Om1lZGlhdG9yOjoxMjIwNDZiNDg1YTIzM2I4MWY2MDE4ODMxYzk4M2E1MzJkMTI1YWFkODc4NGRmOGYwYzBhOTQ3OGQ2NmM0NjI5MmQ2MArkBQpbUllDYW50b24tRm91bmRhdGlvbi0zOjoxMjIwM2NiMDE5Yzk5ODY0MjU4NjFjOTE2ODVkOWI3YzAwNjhjZjQ4YWJiOGRmZjhlMjBmMTY2NTAxZjdmNjc3ZGNlNxKEBQqBBRIXEhVCE0NhbnRvbi1Gb3VuZGF0aW9uLTMSChIICgYSBBICKAASChIICgYSBBICKAESBBICKAASBhIEKPLAARK/BBK8BJIBuAQKtQQKVUJTZ2xvYmFsLWRvbWFpbjo6MTIyMGE1NTVlY2NlZWQ3ZmVmNDQ1YzdlYzMzM2MxNDQ0OWQ5ODFmYjY1OTViZTIxOGM1ZDcwMWVlZjVlYTYzYTFiY2ES2wMK2AMSuQEStgEKswESbxJtkgFqCmgKKkIoMGQ4ZTg3YzU0ZDE5OWU4NTU0OGNjZWMxMjNjOWQ5Mjk2NmVjNDU4YxI6CjgSMBIuQixkeG00bjFNUlAvR3VTRWtKSXdiZEI0elZjR0FlYWNvaEZLTnRiS0s4b1JBPRIEEgIoAhI6EjgiNgo0CjISMBIuQixtMTZoYUx6di9kL09rMDRTbTM5QUJrMGYwSHNTV1lOWnhySVVpeVErY0s4PRIEEgIiABK2ARKzAXKwAQqtAQqqARJYElZCVFNFUTo6c2VxdWVuY2VyOjoxMjIwN2E3NWYzNzc4YjYwNDE0ZGYzYmI4NzZlNGE3Yzg0OTc2ZjM1NjQwYjRkYWYzMDg5NDE4OWJjNzNlNWM3ZmUzOBI9EjtCOWh0dHBzOi8vc2VxdWVuY2VyLTAuc3YtMy5zdmMuY2ltYWluLm5ldHdvcmsuY2FudG9uLmdsb2JhbBIPEg1yCwoJSQR1nzCIDgYAEmESX3JdClsKWRJXElVCU01FRDo6bWVkaWF0b3I6OjEyMjBlZDgxNjg3ZGY1N2UwZjZiZTg1MGJjZDU0NmIxNzUxYTBiNGFkOGE4ZDhjNWVlZWY5OWRkYWM4NDA1ZDQ5ZmFiCuQFCltSWUNhbnRvbi1Gb3VuZGF0aW9uLTQ6OjEyMjA3MGZjNGJiMzUxOWE0ZjM5ZjU5MTliNWExNjZlMzA3OTQ3MzNlNTZhZDlmYmEyMTU3ZjcyMDhmZjQ1OGY3ZGM3EoQFCoEFEhcSFUITQ2FudG9uLUZvdW5kYXRpb24tNBIKEggKBhIEEgIoABIKEggKBhIEEgIoARIEEgIoABIGEgQo8sABEr8EErwEkgG4BAq1BApVQlNnbG9iYWwtZG9tYWluOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYRLbAwrYAxK5ARK2AQqzARJvEm2SAWoKaAoqQihlZTczODUxN2MwMzBiNDJjM2ZmNjI2ZDlmODBiNDFkZmM0YjFhM2I4EjoKOBIwEi5CLDJ1bVpkVVM5N2E2VlVYTUdzZ0tKL1ZiUWJhbnhXYUZVeEsxUWltaGxFam89EgQSAigCEjoSOCI2CjQKMhIwEi5CLG0xNmhhTHp2L2QvT2swNFNtMzlBQmswZjBIc1NXWU5aeHJJVWl5UStjSzg9EgQSAiIAErYBErMBcrABCq0BCqoBElgSVkJUU0VROjpzZXF1ZW5jZXI6OjEyMjA4ZDQ3ZDkzNjFhMGU4NGVlMTA2NDhjNjExZmMyNDM2ODg1YWI1MTkzYWE2N2ZhNWQ5ZWVmYzc5MjlmNzMyZTk2Ej0SO0I5aHR0cHM6Ly9zZXF1ZW5jZXItMC5zdi00LnN2Yy5jaW1haW4ubmV0d29yay5jYW50b24uZ2xvYmFsEg8SDXILCglJMeLILogOBgASYRJfcl0KWwpZElcSVUJTTUVEOjptZWRpYXRvcjo6MTIyMDhjMTNiZWVjYmQ5MTY3NzFjZTE5OGQ5ZDBmMDQ4YjI0M2VkOTljNmUzOWUzNGJkZWM1YjMyZmJiN2E1MWJhYjQSBRIDkgEAEl0SW1JZQ2FudG9uLUZvdW5kYXRpb24tMTo6MTIyMGVkNTQ4ZWZiY2MyMmJiNTA5N2JkNWE5ODMwM2QxZDY0YWI1MTlmOTU2OGNkYzE2NzZlZjE2MzBkYTFmYTY4MzISvQQSugQKtwQSBBICKBQSBBICKAoSDhIMCgoSCBIGKICMjZ4CEg4SDAoKEggSBiiAkJ3pGhIOEgwKChIIEgYogJCd6RoSDxINCgsSCRIHKICAnY6aIxINEgsKCRIHEgUogPbgQhIpEicKJRIjEiEKHxIEEgIoBBIEEgIoBBIEEgIoBBIEEgIoZBIFEgMogAQSBRIDKIAQEgYSBCiAiXoSDhIMCgoSCBIGKIDIzrQNEo4DEosDCogDEtMBEtABkgHMAQrJAQpVQlNnbG9iYWwtZG9tYWluOjoxMjIwYTU1NWVjY2VlZDdmZWY0NDVjN2VjMzMzYzE0NDQ5ZDk4MWZiNjU5NWJlMjE4YzVkNzAxZWVmNWVhNjNhMWJjYRJwCm4SFRITigEQEg5EU19PcGVyYXRpb25hbBJVElNCUVRPRE8oIzQ5MDApOiBzaGFyZSBDb21ldEJGVCBnZW5lc2lzLmpzb24gb2YgZm91bmRpbmcgU1Ygbm9kZSB2aWEgU3ZjUnVsZXMgY29uZmlnLhJXElVCU2dsb2JhbC1kb21haW46OjEyMjBhNTU1ZWNjZWVkN2ZlZjQ0NWM3ZWMzMzNjMTQ0NDlkOTgxZmI2NTk1YmUyMThjNWQ3MDFlZWY1ZWE2M2ExYmNhElcSVUJTZ2xvYmFsLWRvbWFpbjo6MTIyMGE1NTVlY2NlZWQ3ZmVmNDQ1YzdlYzMzM2MxNDQ0OWQ5ODFmYjY1OTViZTIxOGM1ZDcwMWVlZjVlYTYzYTFiY2ES0AESzQGSAckBCmEKVUJTTUVEOjptZWRpYXRvcjo6MTIyMDkxOWE2ZThjOWRkZDNiMDdlZjM2ZTY5OGU3OTY4NmRjZjVlNGM2YjMyYWZmYjU3YjVhOTEwY2M3NWY3YjY2YjQSCAoGEgQSAigACmQKWEJWUEFSOjpwYXJ0aWNpcGFudDo6MTIyMGVkNTQ4ZWZiY2MyMmJiNTA5N2JkNWE5ODMwM2QxZDY0YWI1MTlmOTU2OGNkYzE2NzZlZjE2MzBkYTFmYTY4MzISCAoGEgQSAigAEgQSAlgBKklTVkM6OjEyMjBhNTU1ZWNjZWVkN2ZlZjQ0NWM3ZWMzMzNjMTQ0NDlkOTgxZmI2NTk1YmUyMThjNWQ3MDFlZWY1ZWE2M2ExYmNhOdM1DC2IDgYAQigKJgokCAESIPKYoAaliKrlh2j2l0JzyRfKBAw67ACJ8Gt89rH9+3cc', - created_at: '2024-01-09T19:18:14.379987Z', - }, - domain_id: - 'global-domain::1220fb89d62774bd5b3fd8a11c1b22c8c5453e8286c3cf7add515c98d7bca192ef18', - }, - sv_node_states: [], // TODO(tech-debt): add better mock data -}; - -function getAmuletConfig(createFee: string) { - return { - packageConfig: { - amulet: '0.1.0', - walletPayments: '0.1.0', - dsoGovernance: '0.1.0', - validatorLifecycle: '0.1.0', - amuletNameService: '0.1.0', - wallet: '0.1.0', - }, - tickDuration: { microseconds: '150000000' }, - transferConfig: { - holdingFee: { rate: '0.0000048225' }, - extraFeaturedAppRewardAmount: '1.0', - maxNumInputs: '100', - lockHolderFee: { fee: '0.005' }, - createFee: { fee: createFee }, - maxNumLockHolders: '50', - transferFee: { - initialRate: '0.01', - steps: [ - { _1: '100.0', _2: '0.001' }, - { _1: '1000.0', _2: '0.0001' }, - { _1: '1000000.0', _2: '0.00001' }, - ], - }, - maxNumOutputs: '100', - }, - decentralizedSynchronizer: { - requiredSynchronizers: { - map: [ - [ - 'global-synchronizer::1220d12352e0839d9aac0a1c0c05b0eaaeb44f0aa19958cca2db37ae22c7817949a7', - {}, - ], - ], - }, - activeSynchronizer: - 'global-synchronizer::1220d12352e0839d9aac0a1c0c05b0eaaeb44f0aa19958cca2db37ae22c7817949a7', - fees: { - baseRateTrafficLimits: { - burstAmount: '2000000', - burstWindow: { microseconds: '600000000' }, - }, - extraTrafficPrice: '1.0', - readVsWriteScalingFactor: '4', - minTopupAmount: '10000000', - }, - }, - issuanceCurve: { - initialValue: { - validatorRewardPercentage: '0.5', - amuletToIssuePerYear: '40000000000.0', - unfeaturedAppRewardCap: '0.6', - appRewardPercentage: '0.15', - featuredAppRewardCap: '100.0', - validatorRewardCap: '0.2', - optValidatorFaucetCap: null, - }, - futureValues: [ - { - _1: { microseconds: '15768000000000' }, - _2: { - validatorRewardPercentage: '0.12', - amuletToIssuePerYear: '20000000000.0', - unfeaturedAppRewardCap: '0.6', - appRewardPercentage: '0.4', - featuredAppRewardCap: '100.0', - validatorRewardCap: '0.2', - optValidatorFaucetCap: null, - }, - }, - { - _1: { microseconds: '47304000000000' }, - _2: { - validatorRewardPercentage: '0.18', - amuletToIssuePerYear: '10000000000.0', - unfeaturedAppRewardCap: '0.6', - appRewardPercentage: '0.62', - featuredAppRewardCap: '100.0', - validatorRewardCap: '0.2', - optValidatorFaucetCap: null, - }, - }, - { - _1: { microseconds: '157680000000000' }, - _2: { - validatorRewardPercentage: '0.21', - amuletToIssuePerYear: '5000000000.0', - unfeaturedAppRewardCap: '0.6', - appRewardPercentage: '0.69', - featuredAppRewardCap: '100.0', - validatorRewardCap: '0.2', - optValidatorFaucetCap: null, - }, - }, - { - _1: { microseconds: '315360000000000' }, - _2: { - validatorRewardPercentage: '0.2', - amuletToIssuePerYear: '2500000000.0', - unfeaturedAppRewardCap: '0.6', - appRewardPercentage: '0.75', - featuredAppRewardCap: '100.0', - validatorRewardCap: '0.2', - optValidatorFaucetCap: null, - }, - }, - ], - }, - }; -} - // Sanity check / guard against template changes const result = jtv.Result.andThen( 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 66d1811b..7d9cd091 100644 --- a/apps/sv/frontend/src/__tests__/mocks/handlers/sv-api.ts +++ b/apps/sv/frontend/src/__tests__/mocks/handlers/sv-api.ts @@ -1,27 +1,20 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import dayjs from 'dayjs'; -import { RestHandler, rest } from 'msw'; +import { validatorLicensesHandler, dsoInfoHandler } from 'common-test-utils'; +import { rest, RestHandler } from 'msw'; import { - Contract, ErrorResponse, - GetDsoInfoResponse, ListDsoRulesVoteRequestsResponse, ListDsoRulesVoteResultsResponse, - ListValidatorLicensesResponse, } from 'sv-openapi'; -import { ValidatorLicense } from '@daml.js/splice-amulet-0.1.5/lib/Splice/ValidatorLicense'; - -import { dsoInfo, voteResults } from '../constants'; +import { voteResults } from '../constants'; export const buildSvMock = (svUrl: string): RestHandler[] => [ rest.get(`${svUrl}/v0/admin/authorization`, (_, res, ctx) => { return res(ctx.status(200)); }), - rest.get(`${svUrl}/v0/dso`, (_, res, ctx) => { - return res(ctx.json(dsoInfo)); - }), + dsoInfoHandler(svUrl), rest.get(`${svUrl}/v0/admin/sv/voterequests`, (_, res, ctx) => { return res( ctx.json({ @@ -57,39 +50,5 @@ export const buildSvMock = (svUrl: string): RestHandler[] => [ }) ); }), - rest.get(`${svUrl}/v0/admin/validator/licenses`, (req, res, ctx) => { - const n = parseInt(req.url.searchParams.get('limit')!); - const after = req.url.searchParams.get('after'); - const from = after ? parseInt(after) + 1 : 0; - const now = dayjs(); - const validatorLicenses = Array.from({ length: n }, (_, i) => { - const id = (i + from).toString(); - const validatorLicense: ValidatorLicense = { - dso: 'dso', - validator: `validator::${id}`, - sponsor: 'sponsor', - faucetState: { - firstReceivedFor: { number: '1' }, - lastReceivedFor: { number: '10' }, - numCouponsMissed: '1', - }, - metadata: { version: '1', lastUpdatedAt: now.toISOString(), contactPoint: 'nowhere' }, - lastActiveAt: now.toISOString(), - }; - const contract: Contract = { - contract_id: id, - created_at: now.toISOString(), - created_event_blob: '', - payload: validatorLicense, - template_id: ValidatorLicense.templateId, - }; - return contract; - }); - return res( - ctx.json({ - validator_licenses: validatorLicenses, - next_page_token: from + n, - }) - ); - }), + validatorLicensesHandler(svUrl), ]; diff --git a/apps/sv/frontend/src/components/ValidatorLicenses.tsx b/apps/sv/frontend/src/components/ValidatorLicenses.tsx index 727b7787..8f5ac396 100644 --- a/apps/sv/frontend/src/components/ValidatorLicenses.tsx +++ b/apps/sv/frontend/src/components/ValidatorLicenses.tsx @@ -1,114 +1,30 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 -import { DateDisplay, Loading, PartyId, SvClientProvider, ViewMoreButton } from 'common-frontend'; +import { SvClientProvider, ValidatorLicenses } from 'common-frontend'; import React from 'react'; -import { Chip, Stack, Table, TableContainer, TableHead, Typography } from '@mui/material'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableRow from '@mui/material/TableRow'; - -import { Party } from '@daml/types'; - import { useDsoInfos } from '../contexts/SvContext'; import { useValidatorLicenses } from '../hooks/useValidatorLicenses'; import { useSvConfig } from '../utils'; -const ValidatorLicenses: React.FC = () => { - const validatorLicensesQuery = useValidatorLicenses(10); - const dsoInfosQuery = useDsoInfos(); - - if (validatorLicensesQuery.isLoading || dsoInfosQuery.isLoading) { - return ; - } - - if (validatorLicensesQuery.isError || dsoInfosQuery.isError) { - return

Error, something went wrong.

; - } - - const loadedValidatorLicenses = validatorLicensesQuery.data.pages.flatMap( - page => page.validatorLicenses - ); - - return ( - - - Validator Licenses - - - - - - Created at - Validator - Sponsor - - - - {loadedValidatorLicenses.map(license => { - return ( - - ); - })} - -
-
- validatorLicensesQuery.fetchNextPage()} - disabled={!validatorLicensesQuery.hasNextPage} - idSuffix="validator-licenses" - /> -
- ); -}; - -interface LicenseRowProps { - validator: Party; - sponsor: Party; - createdAt: Date; - sv: Party; -} - -const LicenseRow: React.FC = ({ validator, sponsor, createdAt, sv }) => { - const sponsoredByThisSv = sponsor === sv; - return ( - - - - - - - - - - - {sponsoredByThisSv && } - - - - ); -}; - const ValidatorLicensesWithContexts: React.FC = () => { const config = useSvConfig(); return ( - + ); }; +const ValidatorLicensesWithQueries: React.FC = () => { + const validatorLicensesQuery = useValidatorLicenses(10); + const dsoInfosQuery = useDsoInfos(); + return ( + + ); +}; + export default ValidatorLicensesWithContexts; diff --git a/apps/sv/frontend/src/hooks/useValidatorLicenses.tsx b/apps/sv/frontend/src/hooks/useValidatorLicenses.tsx index 9b359f07..eadd4b84 100644 --- a/apps/sv/frontend/src/hooks/useValidatorLicenses.tsx +++ b/apps/sv/frontend/src/hooks/useValidatorLicenses.tsx @@ -1,17 +1,13 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import { UseInfiniteQueryResult, useInfiniteQuery } from '@tanstack/react-query'; +import { ValidatorLicensesPage } from 'common-frontend'; import { Contract, PollingStrategy } from 'common-frontend-utils'; import { ValidatorLicense } from '@daml.js/splice-amulet/lib/Splice/ValidatorLicense/module'; import { useSvAdminClient } from '../contexts/SvAdminServiceContext'; -interface ValidatorLicensesPage { - validatorLicenses: Contract[]; - after?: number; -} - export const useValidatorLicenses = ( limit: number ): UseInfiniteQueryResult => { @@ -24,9 +20,7 @@ export const useValidatorLicenses = ( const validatorLicenses = page.validator_licenses.map(c => Contract.decodeOpenAPI(c, ValidatorLicense) ); - return validatorLicenses.length === 0 - ? undefined - : { validatorLicenses, after: page.next_page_token }; + return { validatorLicenses, after: page.next_page_token }; }, getNextPageParam: lastPage => { return lastPage && lastPage.after; diff --git a/apps/sv/frontend/vite.config.ts b/apps/sv/frontend/vite.config.ts index 8b81772f..5c355fc2 100644 --- a/apps/sv/frontend/vite.config.ts +++ b/apps/sv/frontend/vite.config.ts @@ -1,7 +1,7 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import react from '@vitejs/plugin-react'; -import { vitest_common_conf } from 'common-test-utils'; +import { vitest_common_conf } from 'common-test-vite-utils'; import { defineConfig, loadEnv, mergeConfig } from 'vite'; import viteTsconfigPaths from 'vite-tsconfig-paths'; diff --git a/apps/sv/src/main/openapi/sv-internal.yaml b/apps/sv/src/main/openapi/sv-internal.yaml index b0ee0766..b0c22ba9 100644 --- a/apps/sv/src/main/openapi/sv-internal.yaml +++ b/apps/sv/src/main/openapi/sv-internal.yaml @@ -71,7 +71,7 @@ paths: content: application/json: schema: - "$ref": "#/components/schemas/ListValidatorLicensesResponse" + "$ref": "../../../../common/src/main/openapi/common-internal.yaml#/components/schemas/ListValidatorLicensesResponse" /v0/admin/domain/cometbft/status: get: tags: [sv] @@ -663,21 +663,6 @@ components: items: $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/schemas/Contract" - ListValidatorLicensesResponse: - type: object - required: - - validator_licenses - properties: - validator_licenses: - type: array - items: - $ref: "../../../../common/src/main/openapi/common-external.yaml#/components/schemas/Contract" - next_page_token: - type: integer - format: int64 - description: | - When requesting the next page of results, pass this as URL query parameter `after`. - PrepareValidatorOnboardingRequest: type: object required: diff --git a/apps/sv/src/main/scala/com/daml/network/sv/admin/http/HttpSvAdminHandler.scala b/apps/sv/src/main/scala/com/daml/network/sv/admin/http/HttpSvAdminHandler.scala index 6bf4886f..4af4677b 100644 --- a/apps/sv/src/main/scala/com/daml/network/sv/admin/http/HttpSvAdminHandler.scala +++ b/apps/sv/src/main/scala/com/daml/network/sv/admin/http/HttpSvAdminHandler.scala @@ -14,11 +14,11 @@ import com.daml.network.environment.{ SequencerAdminConnection, SpliceStatus, } -import com.daml.network.http.HttpVotesHandler +import com.daml.network.http.{HttpValidatorLicensesHandler, HttpVotesHandler} import com.daml.network.http.v0.{definitions, sv_admin as v0} import com.daml.network.http.v0.definitions.TriggerDomainMigrationDumpRequest import com.daml.network.http.v0.sv_admin.SvAdminResource -import com.daml.network.store.{AppStoreWithIngestion, PageLimit} +import com.daml.network.store.{AppStore, AppStoreWithIngestion, VotesStore} import com.daml.network.sv.{LocalSynchronizerNode, SvApp} import com.daml.network.sv.cometbft.CometBftClient import com.daml.network.sv.config.SvAppBackendConfig @@ -62,15 +62,17 @@ class HttpSvAdminHandler( protected val tracer: Tracer, templateJsonDecoder: TemplateJsonDecoder, ) extends v0.SvAdminHandler[TracedUser] - with HttpVotesHandler { + with HttpVotesHandler + with HttpValidatorLicensesHandler { implicit private val loggingContext: ErrorLoggingContext = ErrorLoggingContext.fromTracedLogger(logger)(TraceContext.empty) - protected val workflowId = this.getClass.getSimpleName + override protected val workflowId: String = this.getClass.getSimpleName private val svStore = svStoreWithIngestion.store private val dsoStore = dsoStoreWithIngestion.store - protected val votesStore = dsoStore + override protected val votesStore: VotesStore = dsoStore + override protected val validatorLicensesStore: AppStore = dsoStore def listOngoingValidatorOnboardings( respond: v0.SvAdminResource.ListOngoingValidatorOnboardingsResponse.type @@ -92,20 +94,9 @@ class HttpSvAdminHandler( )(after: Option[Long], limit: Option[Int])( tuser: TracedUser ): Future[SvAdminResource.ListValidatorLicensesResponse] = { - implicit val TracedUser(_, traceContext) = tuser - withSpan(s"$workflowId.listValidatorLicenses") { _ => _ => - for { - resultsInPage <- dsoStore.listValidatorLicenses( - limit.fold(PageLimit.Max)(PageLimit.tryCreate), - after, - ) - } yield { - definitions.ListValidatorLicensesResponse( - resultsInPage.resultsInPage.map(_.toHttp).toVector, - resultsInPage.nextPageToken, - ) - } - } + this + .listValidatorLicenses(after, limit)(tuser.traceContext, ec) + .map(SvAdminResource.ListValidatorLicensesResponse.OK) } def prepareValidatorOnboarding( diff --git a/apps/sv/src/main/scala/com/daml/network/sv/store/SvDsoStore.scala b/apps/sv/src/main/scala/com/daml/network/sv/store/SvDsoStore.scala index 86ffc257..6495271b 100644 --- a/apps/sv/src/main/scala/com/daml/network/sv/store/SvDsoStore.scala +++ b/apps/sv/src/main/scala/com/daml/network/sv/store/SvDsoStore.scala @@ -732,15 +732,6 @@ trait SvDsoStore tc: TraceContext ): Future[QueryResult[Option[Contract[vl.ValidatorLicense.ContractId, vl.ValidatorLicense]]]] - /** List all ValidatorLicenses */ - def listValidatorLicenses(limit: PageLimit, after: Option[Long])(implicit - tc: TraceContext - ): Future[ResultsPage[Contract[vl.ValidatorLicense.ContractId, vl.ValidatorLicense]]] = { - multiDomainAcsStore - .listContractsPaginated(vl.ValidatorLicense.COMPANION, after, limit, SortOrder.Descending) - .map(_.mapResultsInPage(_.contract)) - } - def listValidatorLicensePerValidator(validator: String, limit: Limit)(implicit tc: TraceContext ): Future[Seq[Contract[ValidatorLicense.ContractId, ValidatorLicense]]] diff --git a/apps/wallet/frontend/package.json b/apps/wallet/frontend/package.json index 8ed4d2bf..89199f34 100644 --- a/apps/wallet/frontend/package.json +++ b/apps/wallet/frontend/package.json @@ -40,6 +40,7 @@ "@typescript-eslint/parser": "5.52.0", "@vitejs/plugin-react": "^4.0.4", "common-test-utils": "^0.1.0", + "common-test-vite-utils": "^0.1.0", "eslint": "8.34.0", "eslint-config-prettier": "8.6.0", "eslint-plugin-import": "2.27.5", diff --git a/apps/wallet/frontend/vite.config.ts b/apps/wallet/frontend/vite.config.ts index 8b81772f..5c355fc2 100644 --- a/apps/wallet/frontend/vite.config.ts +++ b/apps/wallet/frontend/vite.config.ts @@ -1,7 +1,7 @@ // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 import react from '@vitejs/plugin-react'; -import { vitest_common_conf } from 'common-test-utils'; +import { vitest_common_conf } from 'common-test-vite-utils'; import { defineConfig, loadEnv, mergeConfig } from 'vite'; import viteTsconfigPaths from 'vite-tsconfig-paths'; diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AcceptedTransferOfferTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AcceptedTransferOfferTrigger.scala index 2dfdb8c1..d26ee771 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AcceptedTransferOfferTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AcceptedTransferOfferTrigger.scala @@ -46,6 +46,10 @@ class AcceptedTransferOfferTrigger( transferOffersCodegen.AcceptedTransferOffer.COMPANION, ) { + override protected def extraMetricLabels = Seq( + "party" -> store.key.endUserParty.toString + ) + // Override the default source, as we can only auto-complete accepted offers if we are the sender override protected def source(implicit traceContext: TraceContext): Source[AssignedContract[ transferOffersCodegen.AcceptedTransferOffer.ContractId, diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AmuletMetricsTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AmuletMetricsTrigger.scala index a6605176..f05db32a 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AmuletMetricsTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AmuletMetricsTrigger.scala @@ -24,6 +24,10 @@ class AmuletMetricsTrigger( mat: Materializer, ) extends PollingTrigger { + override protected def extraMetricLabels = Seq( + "party" -> userWalletStore.key.endUserParty.toString + ) + val amuletMetrics = new AmuletMetrics(userWalletStore.key.endUserParty, context.metricsFactory) override def performWorkIfAvailable()(implicit diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AutoAcceptTransferOffersTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AutoAcceptTransferOffersTrigger.scala index 45f06bb5..cec548f8 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AutoAcceptTransferOffersTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/AutoAcceptTransferOffersTrigger.scala @@ -40,6 +40,10 @@ class AutoAcceptTransferOffersTrigger( transferOffersCodegen.TransferOffer, ](store, transferOffersCodegen.TransferOffer.COMPANION) { + override protected def extraMetricLabels = Seq( + "party" -> store.key.endUserParty.toString + ) + override protected def completeTask( transferOffer: AssignedContract[ transferOffersCodegen.TransferOffer.ContractId, diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/CollectRewardsAndMergeAmuletsTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/CollectRewardsAndMergeAmuletsTrigger.scala index 154ffc99..5f75a123 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/CollectRewardsAndMergeAmuletsTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/CollectRewardsAndMergeAmuletsTrigger.scala @@ -36,6 +36,8 @@ class CollectRewardsAndMergeAmuletsTrigger( val mat: Materializer, ) extends PollingTrigger { + override protected def extraMetricLabels = Seq("party" -> store.key.endUserParty.toString) + override def isRewardOperationTrigger: Boolean = true override def performWorkIfAvailable()(implicit traceContext: TraceContext): Future[Boolean] = diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/CompleteBuyTrafficRequestTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/CompleteBuyTrafficRequestTrigger.scala index 7866fe2c..555136ad 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/CompleteBuyTrafficRequestTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/CompleteBuyTrafficRequestTrigger.scala @@ -43,6 +43,10 @@ class CompleteBuyTrafficRequestTrigger( trafficRequestCodegen.BuyTrafficRequest.COMPANION, ) { + override protected def extraMetricLabels = Seq( + "party" -> store.key.endUserParty.toString + ) + override def completeTask( trafficRequest: AssignedContract[ trafficRequestCodegen.BuyTrafficRequest.ContractId, diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireAcceptedTransferOfferTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireAcceptedTransferOfferTrigger.scala index c47800f2..c10c7e56 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireAcceptedTransferOfferTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireAcceptedTransferOfferTrigger.scala @@ -38,6 +38,8 @@ class ExpireAcceptedTransferOfferTrigger( transferOffersCodegen.AcceptedTransferOffer.COMPANION, ) { + override protected def extraMetricLabels = Seq("party" -> store.key.endUserParty.toString) + override protected def completeTask( task: ScheduledTaskTrigger.ReadyTask[ AssignedContract[ diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireAppPaymentRequestsTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireAppPaymentRequestsTrigger.scala index ed631eda..4d1d768f 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireAppPaymentRequestsTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireAppPaymentRequestsTrigger.scala @@ -37,6 +37,8 @@ class ExpireAppPaymentRequestsTrigger( paymentCodegen.AppPaymentRequest.COMPANION, ) { + override protected def extraMetricLabels = Seq("party" -> store.key.endUserParty.toString) + override protected def completeTask( task: ScheduledTaskTrigger.ReadyTask[ AssignedContract[ diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireBuyTrafficRequestsTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireBuyTrafficRequestsTrigger.scala index 6da2784b..3ec2d965 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireBuyTrafficRequestsTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireBuyTrafficRequestsTrigger.scala @@ -32,6 +32,8 @@ class ExpireBuyTrafficRequestsTrigger( trafficRequestCodegen.BuyTrafficRequest.COMPANION, ) { + override protected def extraMetricLabels = Seq("party" -> store.key.endUserParty.toString) + override protected def completeTask( task: ScheduledTaskTrigger.ReadyTask[ AssignedContract[ diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireTransferOfferTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireTransferOfferTrigger.scala index 80212ec3..7c744d3b 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireTransferOfferTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/ExpireTransferOfferTrigger.scala @@ -38,6 +38,8 @@ class ExpireTransferOfferTrigger( transferOffersCodegen.TransferOffer.COMPANION, ) { + override protected def extraMetricLabels = Seq("party" -> store.key.endUserParty.toString) + override protected def completeTask( task: ScheduledTaskTrigger.ReadyTask[ AssignedContract[ diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/SubscriptionReadyForPaymentTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/SubscriptionReadyForPaymentTrigger.scala index 06094279..46107bbf 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/SubscriptionReadyForPaymentTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/SubscriptionReadyForPaymentTrigger.scala @@ -42,6 +42,8 @@ class SubscriptionReadyForPaymentTrigger( subsCodegen.SubscriptionIdleState.COMPANION, ) { + override protected def extraMetricLabels = Seq("party" -> store.key.endUserParty.toString) + override protected def completeTask( task: ScheduledTaskTrigger.ReadyTask[ AssignedContract[ diff --git a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/WalletSweepTrigger.scala b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/WalletSweepTrigger.scala index 119f36d4..8ec91d90 100644 --- a/apps/wallet/src/main/scala/com/daml/network/wallet/automation/WalletSweepTrigger.scala +++ b/apps/wallet/src/main/scala/com/daml/network/wallet/automation/WalletSweepTrigger.scala @@ -44,6 +44,8 @@ class WalletSweepTrigger( mat: Materializer, ) extends PollingParallelTaskExecutionTrigger[WalletSweepTrigger.Task] { + override protected def extraMetricLabels = Seq("party" -> store.key.endUserParty.toString) + override protected def retrieveTasks()(implicit tc: TraceContext ): Future[Seq[WalletSweepTrigger.Task]] = { diff --git a/build.sbt b/build.sbt index 9ead2a4f..6c53dc1a 100644 --- a/build.sbt +++ b/build.sbt @@ -735,6 +735,12 @@ lazy val `apps-common-frontend` = { "common-frontend", log, ) + BuildCommon.TS.runWorkspaceCommand( + npmRootDir.value, + "build", + "common-test-vite-utils", + log, + ) BuildCommon.TS.runWorkspaceCommand( npmRootDir.value, "build", @@ -771,7 +777,14 @@ lazy val `apps-common-frontend` = { val log = streams.value.log (Test / compile).value npmInstall.value - for (workspace <- Seq("common-frontend-utils", "common-frontend", "common-test-utils")) + for ( + workspace <- Seq( + "common-test-vite-utils", + "common-frontend-utils", + "common-frontend", + "common-test-utils", + ) + ) BuildCommon.TS.runWorkspaceCommand(npmRootDir.value, "build", workspace, log) runCommand( Seq("npm", "run", "test:sbt", "--workspaces", "--if-present"),