Skip to content

Commit 01384d1

Browse files
committed
Fix merging of ValidatorLicenses
Signed-off-by: Divam <[email protected]>
1 parent cce6eb0 commit 01384d1

File tree

2 files changed

+181
-8
lines changed

2 files changed

+181
-8
lines changed

daml/splice-dso-governance-test/daml/Splice/Scripts/TestOnboarding.daml

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,157 @@ test_MergeValidatorLicense = do
589589
[(validatorLicenseSv2Cid, _)] <- queryFilter @ValidatorLicense dso (\license -> license.validator == sv2)
590590
submitMultiMustFail [sv1] [dso] $ exerciseCmd rulesCid (DsoRules_MergeValidatorLicense [validatorLicenseCid3, validatorLicenseSv2Cid] (Some sv1))
591591

592+
-- Test weight merging: min weight should be used
593+
v2 <- allocateParty "v2"
594+
validatorLicenseW1 <- submit dso $ createCmd ValidatorLicense with
595+
validator = v2
596+
sponsor = sv1
597+
dso
598+
faucetState = Some FaucetState with
599+
firstReceivedFor = Round 0
600+
lastReceivedFor = Round 1
601+
numCouponsMissed = 0
602+
metadata = None
603+
lastActiveAt = Some now
604+
weight = None
605+
kind = Some OperatorLicense
606+
607+
validatorLicenseW2 <- submit dso $ createCmd ValidatorLicense with
608+
validator = v2
609+
sponsor = sv2
610+
dso
611+
faucetState = Some FaucetState with
612+
firstReceivedFor = Round 0
613+
lastReceivedFor = Round 3
614+
numCouponsMissed = 0
615+
metadata = None
616+
lastActiveAt = Some now
617+
weight = Some 2.0
618+
kind = Some OperatorLicense
619+
620+
validatorLicenseW3 <- submit dso $ createCmd ValidatorLicense with
621+
validator = v2
622+
sponsor = sv1
623+
dso
624+
faucetState = Some FaucetState with
625+
firstReceivedFor = Round 0
626+
lastReceivedFor = Round 2
627+
numCouponsMissed = 0
628+
metadata = None
629+
lastActiveAt = Some now
630+
weight = Some 1.5
631+
kind = Some OperatorLicense
632+
633+
DsoRules_MergeValidatorLicenseResult mergedWeightLicense <- submitMulti [sv1] [dso] $ exerciseCmd rulesCid (DsoRules_MergeValidatorLicense [validatorLicenseW1, validatorLicenseW2, validatorLicenseW3] (Some sv1))
634+
[(_, mergedLicense)] <- queryFilter @ValidatorLicense dso (\license -> license.validator == v2)
635+
-- The merged license should have the minimum weight (0.5) and the highest lastReceivedFor (Round 3)
636+
mergedLicense.weight === Some 1.5
637+
mergedLicense.faucetState === Some FaucetState with
638+
firstReceivedFor = Round 0
639+
lastReceivedFor = Round 3
640+
numCouponsMissed = 0
641+
642+
-- Test kind merging: Some OperatorLicense if any license has None or Some OperatorLicense
643+
v3 <- allocateParty "v3"
644+
645+
-- Test case 1: mixing None and Some NonOperatorLicense -> should result in Some OperatorLicense
646+
validatorLicenseK1 <- submit dso $ createCmd ValidatorLicense with
647+
validator = v3
648+
sponsor = sv1
649+
dso
650+
faucetState = Some FaucetState with
651+
firstReceivedFor = Round 0
652+
lastReceivedFor = Round 1
653+
numCouponsMissed = 0
654+
metadata = None
655+
lastActiveAt = Some now
656+
weight = None
657+
kind = None -- None should be treated as OperatorLicense
658+
659+
validatorLicenseK2 <- submit dso $ createCmd ValidatorLicense with
660+
validator = v3
661+
sponsor = sv2
662+
dso
663+
faucetState = Some FaucetState with
664+
firstReceivedFor = Round 0
665+
lastReceivedFor = Round 2
666+
numCouponsMissed = 0
667+
metadata = None
668+
lastActiveAt = Some now
669+
weight = None
670+
kind = Some NonOperatorLicense
671+
672+
DsoRules_MergeValidatorLicenseResult mergedKind1 <- submitMulti [sv1] [dso] $ exerciseCmd rulesCid (DsoRules_MergeValidatorLicense [validatorLicenseK1, validatorLicenseK2] (Some sv1))
673+
[(_, mergedLicense1)] <- queryFilter @ValidatorLicense dso (\license -> license.validator == v3)
674+
-- Should be OperatorLicense because one license had None
675+
mergedLicense1.kind === Some OperatorLicense
676+
677+
-- Test case 2: mixing Some OperatorLicense and Some NonOperatorLicense -> should result in Some OperatorLicense
678+
v4 <- allocateParty "v4"
679+
validatorLicenseK3 <- submit dso $ createCmd ValidatorLicense with
680+
validator = v4
681+
sponsor = sv1
682+
dso
683+
faucetState = Some FaucetState with
684+
firstReceivedFor = Round 0
685+
lastReceivedFor = Round 1
686+
numCouponsMissed = 0
687+
metadata = None
688+
lastActiveAt = Some now
689+
weight = None
690+
kind = Some OperatorLicense
691+
692+
validatorLicenseK4 <- submit dso $ createCmd ValidatorLicense with
693+
validator = v4
694+
sponsor = sv2
695+
dso
696+
faucetState = Some FaucetState with
697+
firstReceivedFor = Round 0
698+
lastReceivedFor = Round 2
699+
numCouponsMissed = 0
700+
metadata = None
701+
lastActiveAt = Some now
702+
weight = None
703+
kind = Some NonOperatorLicense
704+
705+
DsoRules_MergeValidatorLicenseResult mergedKind2 <- submitMulti [sv1] [dso] $ exerciseCmd rulesCid (DsoRules_MergeValidatorLicense [validatorLicenseK3, validatorLicenseK4] (Some sv1))
706+
[(_, mergedLicense2)] <- queryFilter @ValidatorLicense dso (\license -> license.validator == v4)
707+
-- Should be OperatorLicense to prevent downgrading
708+
mergedLicense2.kind === Some OperatorLicense
709+
710+
-- Test case 3: all licenses are Some NonOperatorLicense -> should result in Some NonOperatorLicense
711+
v5 <- allocateParty "v5"
712+
validatorLicenseK5 <- submit dso $ createCmd ValidatorLicense with
713+
validator = v5
714+
sponsor = sv1
715+
dso
716+
faucetState = Some FaucetState with
717+
firstReceivedFor = Round 0
718+
lastReceivedFor = Round 1
719+
numCouponsMissed = 0
720+
metadata = None
721+
lastActiveAt = Some now
722+
weight = None
723+
kind = Some NonOperatorLicense
724+
725+
validatorLicenseK6 <- submit dso $ createCmd ValidatorLicense with
726+
validator = v5
727+
sponsor = sv2
728+
dso
729+
faucetState = Some FaucetState with
730+
firstReceivedFor = Round 0
731+
lastReceivedFor = Round 2
732+
numCouponsMissed = 0
733+
metadata = None
734+
lastActiveAt = Some now
735+
weight = None
736+
kind = Some NonOperatorLicense
737+
738+
DsoRules_MergeValidatorLicenseResult mergedKind3 <- submitMulti [sv1] [dso] $ exerciseCmd rulesCid (DsoRules_MergeValidatorLicense [validatorLicenseK5, validatorLicenseK6] (Some sv1))
739+
[(_, mergedLicense3)] <- queryFilter @ValidatorLicense dso (\license -> license.validator == v5)
740+
-- Should be NonOperatorLicense since all licenses are NonOperatorLicense
741+
mergedLicense3.kind === Some NonOperatorLicense
742+
592743
pure()
593744

594745
testBootstrapDevNetWithNonZeroRound : Script ()

daml/splice-dso-governance/daml/Splice/DsoRules.daml

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ import DA.Action (void, unless)
99
import DA.Assert
1010
import DA.Either (partitionEithers)
1111
import DA.Exception
12-
import DA.Foldable (forA_, all)
12+
import DA.Foldable (forA_, all, minimum)
1313
import DA.List as List hiding (all)
14-
import DA.Optional (isNone, fromSome, fromOptional, isSome)
14+
import DA.Optional (isNone, fromSome, fromOptional, isSome, catOptionals)
1515
import qualified DA.Map as Map
1616
import qualified DA.Set as Set
1717
import qualified DA.Text as T
@@ -986,14 +986,16 @@ template DsoRules with
986986
kind = Some OperatorLicense
987987
return DsoRules_OnboardValidatorResult with ..
988988

989+
-- Grant ValidatorLicense to a party that is not an validator node operator
990+
---------------------------------------------------------------------------
991+
989992
nonconsuming choice DsoRules_GrantValidatorLicense : DsoRules_GrantValidatorLicenseResult
990993
with
991994
sponsor : Party
992995
validator : Party
993996
controller sponsor
994997
do
995998
require "Sponsor is an SV" (sponsor `Map.member` svs)
996-
-- create license for a validator that is not an operator
997999
now <- getTime
9981000
validatorLicense <- create ValidatorLicense with
9991001
dso
@@ -1007,7 +1009,10 @@ template DsoRules with
10071009
return DsoRules_GrantValidatorLicenseResult with ..
10081010

10091011
nonconsuming choice DsoRules_MergeValidatorLicense : DsoRules_MergeValidatorLicenseResult
1010-
-- ^ Note: removes the old duplicated licenses and creates a new one with the highest lastReceivedFor round
1012+
-- ^ Note: removes the old duplicated licenses and creates a new one with merged fields:
1013+
-- - lastReceivedFor: max value among all licenses
1014+
-- - weight: min value among all licenses
1015+
-- - kind: Some OperatorLicense if any license has None or Some OperatorLicense
10111016
-- There should never be duplicates going forward.
10121017
with
10131018
validatorLicenseCids : [ContractId ValidatorLicense]
@@ -1017,13 +1022,30 @@ template DsoRules with
10171022
require "Number of validatorLicense contracts to merge is >= 2" (length validatorLicenseCids >= 2)
10181023
validatorLicenses <- forA validatorLicenseCids $ fetchAndArchive (ForDso this.dso)
10191024
require "All validatorLicenses map to the same validator" ( length (dedup (map (.validator) validatorLicenses)) == 1)
1020-
-- We don't attempt to merge the fields in the validator licence since there is no good
1021-
-- way to do so and none of the fields other than lastReceivedFor round are crucial and for that
1022-
-- taking the max is the correct merge.
1023-
cid <- create $ maximumOn (\license ->
1025+
1026+
-- Merge the licenses:
1027+
-- 1. Use the max lastReceivedFor for faucetState (take the license with max lastReceivedFor as base)
1028+
-- 2. Use the min weight (treating None as 1.0)
1029+
-- 3. Use Some OperatorLicense for kind if any license has None or Some OperatorLicense
1030+
let baseLicense = maximumOn (\license ->
10241031
case license.faucetState of
10251032
None -> Round 0
10261033
Some r -> r.lastReceivedFor) validatorLicenses
1034+
1035+
-- Calculate merged weight: use minimum weight among all licenses, or None if all are None
1036+
let weights = catOptionals $ map (.weight) validatorLicenses
1037+
let mergedWeight = if null weights then None else Some (minimum weights)
1038+
1039+
-- Calculate merged kind: use OperatorLicense if any license has None or Some OperatorLicense
1040+
let hasOperatorLicense license =
1041+
isNone license.kind || license.kind == Some OperatorLicense
1042+
let mergedKind = if any hasOperatorLicense validatorLicenses
1043+
then Some OperatorLicense
1044+
else Some NonOperatorLicense
1045+
1046+
cid <- create baseLicense with
1047+
weight = mergedWeight
1048+
kind = mergedKind
10271049
pure $ DsoRules_MergeValidatorLicenseResult cid
10281050

10291051
-- SV onboarding

0 commit comments

Comments
 (0)