Skip to content

Conversation

@dfordivam
Copy link
Contributor

@dfordivam dfordivam commented Oct 28, 2025

Fixes #2904

  • Adds support for granting ValidatorLicense via SV UI and console.
  • Show weight + License kind in ValidatorLicense UI
    • Weight will only be shown if it is non-null
    • For kind I have chosen to show a check-mark only when the license is of a non-validator party, as showing a check-mark for every validator party would make the UI too noisy.
  • Scan API fixes: ValidatorReceivedFaucets
  • And includes scala code fixes for the daml changes introduced in Daml: Add weight, kind to ValidatorLicense, and SV grant license support #2903

Pull Request Checklist

Cluster Testing

  • If a cluster test is required, comment /cluster_test on this PR to request it, and ping someone with access to the DA-internal system to approve it.
  • If a hard-migration test is required (from the latest release), comment /hdm_test on this PR to request it, and ping someone with access to the DA-internal system to approve it.

PR Guidelines

  • Include any change that might be observable by our partners or affect their deployment in the release notes.
  • Specify fixed issues with Fixes #n, and mention issues worked on using #n
  • Include a screenshot for frontend-related PRs - see README or use your favorite screenshot tool

Merge Guidelines

  • Make the git commit message look sensible when squash-merging on GitHub (most likely: just copy your PR description).

@dfordivam dfordivam changed the title S Support for granting ValidatorLicense via SV UI Oct 28, 2025
@dfordivam
Copy link
Contributor Author

Screenshots

  1. New fields in /validator-onboarding for granting license.
    And Validator Licenses table shows "Weight" and "Non-operator" columns
Screenshot 2025-10-28 at 11-52-50 Super Validator Operations
  1. Confirmation prompt when trying to grant a license
Screenshot 2025-10-28 at 11-53-30 Super Validator Operations
  1. New License visible in table, along with check-mark for "Non-operator".
Screenshot 2025-10-28 at 11-54-08 Super Validator Operations
  1. Error shown when party is invalid
Screenshot 2025-10-28 at 11-54-40 Super Validator Operations
  1. Button is disabled when UI detects a typo
Screenshot 2025-10-28 at 11-55-17 Super Validator Operations
  1. Scan-app's /validator-licenses also shows new columns
Screenshot 2025-10-28 at 11-56-24 Amulet Scan

@isegall-da
Copy link
Contributor

@dfordivam I'm confused. I thought we agreed that we're breaking the work into a series of small PRs that one can effectively review. It's very hard to review thoroughly a PR of this magnitude that's a code dump of your complete work.
Specifically, if you have fixes to another PR that's still open, please apply the fixes there instead of including them here, so that every PR is a self-contained and complete unit of work.

@dfordivam
Copy link
Contributor Author

@isegall-da I would have preferred to change the "base" branch, such that the daml changes included in this branch are not shown in this PR. (I could not set the base to my branch as it is on a fork).
But I guess it would be better to simply remove the daml commits; at least for the review purpose the daml changes need not be present in this PR.

@dfordivam dfordivam force-pushed the dn/cip-0073-grant-validator-license branch from 3c79212 to 1e3dc30 Compare October 28, 2025 09:41
@isegall-da
Copy link
Contributor

@isegall-da I would have preferred to change the "base" branch, such that the daml changes included in this branch are not shown in this PR. (I could not set the base to my branch as it is on a fork). But I guess it would be better to simply remove the daml commits; at least for the review purpose the daml changes need not be present in this PR.

Why can't you base the branch on your own branch? You can stack multiple PRs one on top of the other, thus breaking the work into chunks that a reviewer can digest and review effectively.

@isegall-da
Copy link
Contributor

Alternatively, you can rework the git history in the branch such that it's a series of self-contained commits that one can review one at a time.

@dfordivam
Copy link
Contributor Author

Why can't you base the branch on your own branch? You can stack multiple PRs one on top of the other, thus breaking the work into chunks that a reviewer can digest and review effectively.

So its just a Github limitation, my branch is on the fork, and the base branch has to be on hyperledger-labs/splice.
The simplest solution in this scenario is to give me access to push branch to this repo. Other workarounds are a bit more complicated or have limitations, as breaking the work such that it does not use commits present in other PRs would limit how much I can practically breakdown changes into reviewable chunks.

@meiersi-da
Copy link
Contributor

I'll review tomorrow morning my time.

@meiersi-da
Copy link
Contributor

@dfordivam : on

image

wdyt about adding 'default: 1.0' when the weight is not set?

I'd also suggest to invert the double-negation: call the column "Operator" and add a checkmark if its an operator license.

Copy link
Contributor

@meiersi-da meiersi-da left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work @dfordivam !

Looks good up to the testing changes and small pieces of missing functionality. Let me know when you've addressed that so I can do a second review.

}

@Help.Summary("Grant a ValidatorLicense to a validator party (via admin API)")
def grantValidatorLicense(partyId: PartyId): Unit =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about setting a non-standard weight?

weight:
description: |
Weight of the activity records produced by this license. Determined
through SV votes. If not set, weight is 1.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wdyt about making the field required and always setting the default value. Thereby limiting the propagation of the default? No need for the clients to implement the defaulting, or is there?

Copy link
Contributor Author

@dfordivam dfordivam Nov 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By always returning the default value, instead of missing field/null, we are losing information which may be useful for the clients. I can set it if required.

kind:
description: |
Type of license: OperatorLicense or NonOperatorLicense. If not set,
kind is OperatorLicense
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here: resolve the default on the server.

Optional.empty(), // optTotalValidatorFaucetCoupons (deprecated)
Optional.of(
java.math.BigDecimal.valueOf(validatorLivenessActivityRecords)
), // optTotalValidatorLivenessActivityRecords
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find the corresponding change to the store method to retrieve the sum of weights. Am I looking in the wrong place?

I'd have expected:

  • an adjusted SvDsoStoreTest
  • an adjusted integration test for validator faucet coupons that tests a non-default weight license

GrantValidatorLicenseRequest:
type: object
required:
- party_id
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weight

}
import scala.jdk.OptionConverters.*

class SvValidatorLicenseIntegrationTest extends SvIntegrationTestBase {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This start a 4SV topology, which is unnecessarily expensive for this kind of test. I know there are other tests that do the same, but they should ideally also be adjusted.

Having had a peek at the tests we have I suggest: you merge your new test with the one in https://github.com/hyperledger-labs/splice/blob/main/apps/app/src/test/scala/org/lfdecentralizedtrust/splice/integration/tests/SvMergeDuplicatedValidatorLicenseIntegrationTest.scala (probably want to use your name), but use the environment setup from there:

override def environmentDefinition
: org.lfdecentralizedtrust.splice.integration.EnvironmentDefinition =
EnvironmentDefinition
.simpleTopology1Sv(this.getClass.getSimpleName)
.addConfigTransforms((_, config) =>
updateAutomationConfig(ConfigurableApp.Sv)(
_.withPausedTrigger[MergeValidatorLicenseContractsTrigger]
)(config)
)


val dsoParty = sv1Backend.getDsoInfo().dsoParty
val sv1Party = sv1Backend.getDsoInfo().svParty
val validatorParty = allocateRandomSvParty("test-validator", Some(100))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that sounds off. This party gets allocated on sv1, which means we might not discover problems due to non-local reads.

I'd suggest to instead just grant the license to alice

class SvValidatorLicenseIntegrationTest extends SvIntegrationTestBase {

"grant validator license via admin API" in { implicit env =>
initDso()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
initDso()

having to manually start nodes should only be required in special circumstances ==> it's a bit of a test smell.

val validatorParty = allocateRandomSvParty("test-validator", Some(100))

// Verify no license exists initially
val initialLicenses = sv1Backend.participantClientWithAdminToken.ledger_api_extensions.acs
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

read through alice's node

</Button>
</DisableConditionally>

{grantLicenseMutation.isError && (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isegall-da : the code looks OK, but I have done too little frontend work to be sure. Could you have a peek or delegate having a peek?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're missing weight also in the UI, but given that the endpoint doesn't have one I guess that makes sense for now (happy for it to be added in a followup PR). Otherwise, looks good

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting a weight when granting license is prohibited by the CIP, the weight always defaults to none. Hence daml, scala and UI does not have an option to specify it.


const VALID_PARTY_ID_REGEX = /^[^-]+-[^-]+-\d+$/;
const VALID_PARTY_HINT_REGEX = /^[^-]+-[^-]+-\d+$/;
const VALID_PARTY_ID_REGEX = /^[a-zA-Z0-9_-]+::[a-zA-Z0-9_-]+$/;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@meiersi-da is the fingerprint part of of constant length? Can we strengthen this check to be an exact match on it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it currently is of constant length and hex encoded, but I wouldn't be surprised if it changes in the future. Would you be OK with having to change this regex in that case?

Probably makes sense to check here, as dropping a fingerprint digit is quite easy when manually copying a party-id. I'll ask the Canton team about the guarantees.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think I'd be inclined to have this assert tightly on the current constraints, and potentially relax that in the future if needed. Quite easy to get a copy-paste of a long blob wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dfordivam : I've checked with the Canton team. Here's the regex they propose to use:
^[A-Za-z0-9_-]{1,185}::[A-Fa-f0-9]{68}$ please switch to that one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SV should be able grant a ValidatorLicense to any arbitrary party

5 participants