Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions daml/splice-amulet/daml/Splice/DecentralizedSynchronizer.daml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,51 @@ instance HasCheckedFetch MemberTraffic ForDso where
instance HasCheckedFetch MemberTraffic ForMemberTraffic where
contractGroupId MemberTraffic{..} = ForMemberTraffic with ..

-- Dedicated-synchronizer registration (PoC)
------------------------------------------------
--
-- A "dedicated synchronizer" is a non-global synchronizer (stood up with the Splice software)
-- whose traffic we want to fund by burning CC on the global synchronizer. Before any CC can be
-- burned for such a synchronizer it is registered once, by DSO governance, via
-- `DsoRules_RegisterSynchronizer` (splice-dso-governance), which creates this contract.
--
-- The registration binds a stable synchronizer id to the `operator` party that runs it. Two
-- consumers read it:
-- * `AmuletRules_BuyDedicatedSyncTraffic` (splice-amulet) authorizes a CC burn for a
-- synchronizer id only if a matching `RegisteredSynchronizer` is supplied (via explicit
-- disclosure), and uses `operator` as the observer of the resulting traffic record.
-- * the `operator` itself, which observes this contract and thus learns it is registered.
--
-- This template lives in splice-amulet (not splice-dso-governance) so that `AmuletRules` can
-- `fetch` it: splice-dso-governance depends on splice-amulet, not the reverse.
template RegisteredSynchronizer with

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Crux of rung 1: this DSO-signed contract binds a dedicated synchronizer id to its operator party. It is created only by a governance vote (DsoRules_RegisterSynchronizer) and observed by the operator. It lives in splice-amulet rather than splice-dso-governance because the rung-2 AmuletRules_BuyDedicatedSyncTraffic choice must fetch it, and splice-dso-governance depends on splice-amulet (one-way), not the reverse.

dso : Party
-- ^ The DSO of the global synchronizer; sole signatory. A DSO vote creates this contract.
synchronizerId : Text
-- ^ Id of the dedicated synchronizer this registration authorizes CC-funded traffic for.
operator : Party
-- ^ The party operating the dedicated synchronizer. Observes this registration and every
-- traffic purchase for its synchronizer (see `DedicatedSyncTraffic`).
where
signatory dso
observer operator

ensure not (T.isEmpty synchronizerId)

-- | Public fetch choice, mirroring `OpenMiningRound_Fetch`: lets a buyer who is neither
-- signatory nor observer read this contract inside `AmuletRules_BuyDedicatedSyncTraffic`
-- when it is passed via explicit disclosure. Returns the contract unchanged.
nonconsuming choice RegisteredSynchronizer_Fetch : RegisteredSynchronizer

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Public fetch choice, copied from OpenMiningRound_Fetch. It lets the buyer -- who is neither signatory nor observer -- read this contract inside the rung-2 buy choice when it is supplied via explicit disclosure. Returns the contract unchanged.

with
p : Party -- ^ the reader (the buyer/provider); authorizes the fetch
controller p
do pure this

-- Group-id check for `fetchPublicReferenceData`: a `RegisteredSynchronizer` belongs to its DSO,
-- so reads are validated against the expected DSO party (mirrors the `MemberTraffic`/`ForDso` instance).
instance HasCheckedFetch RegisteredSynchronizer ForDso where
contractGroupId RegisteredSynchronizer{..} = ForDso with ..

instance Patchable BaseRateTrafficLimits where
patch new base current = BaseRateTrafficLimits with
burstAmount = patch new.burstAmount base.burstAmount current.burstAmount
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
-- Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0

-- | PoC (dedicated-synchronizer traffic): register a dedicated synchronizer through a DSO
-- governance vote and assert the resulting `RegisteredSynchronizer` contract.
--
-- This is the first rung of the ladder: it only proves that governance can create the
-- registry entry that a later CC-funded traffic purchase will read. Buying traffic against a
-- registered synchronizer is exercised in the splice-amulet test (next PR).
module Splice.Scripts.TestRegisterSynchronizer where

import DA.Assert

import Daml.Script

-- The governance action + its argument record.
import Splice.DsoRules
-- The registry contract created by the vote. It lives in splice-amulet (AmuletRules must be
-- able to fetch it), and splice-dso-governance-test data-depends on splice-amulet.
import Splice.DecentralizedSynchronizer (RegisteredSynchronizer)

-- Shared helpers that spin up a 4-SV network and drive a vote end-to-end.
import Splice.Scripts.DsoTestUtils

-- | Register a dedicated synchronizer via a supermajority SV vote, then verify the registration:
-- signed by the DSO, naming the operator, and observed by that operator.
test_RegisterSynchronizer_viaVote : Script ()
test_RegisterSynchronizer_viaVote = do
-- 4-SV network; `dso` is the DSO party, `sv1..sv4` are the Super Validators.
(app, dso, (sv1, sv2, sv3, sv4)) <- initMainNet

-- The party that will operate the dedicated synchronizer.
operator <- allocateParty "dedicatedSyncOperator"
let dedicatedSyncId = "dedicated-sync::1220dededededededededededededededededededededededededededededede"

-- Supermajority SV vote to register the dedicated synchronizer. `initiateAndAcceptVote` routes
-- through DsoRules_RequestVote -> DsoRules_CastVote (x3) -> DsoRules_CloseVoteRequest, which on
-- acceptance calls executeActionRequiringConfirmation, dispatching to DsoRules_RegisterSynchronizer.
initiateAndAcceptVote app [sv1, sv2, sv3, sv4] $

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Drives the full governance path (DsoRules_RequestVote -> DsoRules_CastVote x3 -> DsoRules_CloseVoteRequest -> executeActionRequiringConfirmation), so registration is exercised exactly as in production rather than by calling the choice in isolation.

ARC_DsoRules with
dsoAction = SRARC_RegisterSynchronizer DsoRules_RegisterSynchronizer with
synchronizerId = dedicatedSyncId
operator

-- The registration now exists on-ledger with exactly the fields we voted for.
registrations <- query @RegisteredSynchronizer dso
case registrations of
[(_cid, reg)] -> do
reg.dso === dso
reg.synchronizerId === dedicatedSyncId
reg.operator === operator
_ -> abort $ "expected exactly one RegisteredSynchronizer, got " <> show (length registrations)

-- The operator is an observer, so it can see its own registration.
operatorView <- query @RegisteredSynchronizer operator
length operatorView === 1
30 changes: 29 additions & 1 deletion daml/splice-dso-governance/daml/Splice/DsoRules.daml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import Splice.Round
import Splice.Types
import Splice.ValidatorLicense
import Splice.Wallet.Subscriptions
import Splice.DecentralizedSynchronizer (MemberTraffic)
-- `RegisteredSynchronizer` (PoC): created by `DsoRules_RegisterSynchronizer` below; the template
-- itself lives in splice-amulet because AmuletRules must be able to fetch it.
import Splice.DecentralizedSynchronizer (MemberTraffic, RegisteredSynchronizer(..))

import qualified Splice.CometBft as CometBft
import Splice.Ans
Expand Down Expand Up @@ -117,6 +119,10 @@ data DsoRules_ActionRequiringConfirmation
-- ^ Create TransferCommandCounter contract for the given sender if it does not already exist
| SRARC_CreateUnallocatedUnclaimedActivityRecord DsoRules_CreateUnallocatedUnclaimedActivityRecord
-- ^ Voted action to create an UnallocatedUnclaimedActivityRecord contract.
| SRARC_RegisterSynchronizer DsoRules_RegisterSynchronizer

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

New variant on the closed DsoRules_ActionRequiringConfirmation sum. Adding a variant forces every exhaustive match to handle it; I verified there is exactly one such match (the dispatcher below) -- actionRequiringConfirmationEffectiveAt matches at the ARC_* level, not on these SRARC_* variants.

-- ^ PoC (dedicated-synchronizer traffic): voted action to register a dedicated (non-global)
-- synchronizer by binding its id to an operator party, so its traffic can be funded in CC.
-- Executed via a DSO vote; see the `DsoRules_RegisterSynchronizer` choice.
| SRARC_CreateBootstrapExternalPartyConfigStateInstruction DsoRules_CreateBootstrapExternalPartyConfigStateInstruction
-- ^ Create BootstrapExternalPartyConfigStateInstruction
| SRARC_UpdateFeaturedAppRight DsoRules_UpdateFeaturedAppRight
Expand Down Expand Up @@ -332,6 +338,10 @@ data DsoRules_AmuletRules_ConvertFeaturedAppActivityMarkersResult = DsoRules_Amu
data DsoRules_CreateUnallocatedUnclaimedActivityRecordResult = DsoRules_CreateUnallocatedUnclaimedActivityRecordResult with
unallocatedUnclaimedActivityRecordCid : ContractId UnallocatedUnclaimedActivityRecord

-- | Result of `DsoRules_RegisterSynchronizer`: the cid of the created registration.
data DsoRules_RegisterSynchronizerResult = DsoRules_RegisterSynchronizerResult with
registeredSynchronizerCid : ContractId RegisteredSynchronizer

data DsoRules_AllocateUnallocatedUnclaimedActivityRecordResult = DsoRules_AllocateUnallocatedUnclaimedActivityRecordResult with
unclaimedActivityRecordCid : ContractId UnclaimedActivityRecord
optUnclaimedRewardCid : Optional (ContractId UnclaimedReward)
Expand Down Expand Up @@ -1680,6 +1690,22 @@ template DsoRules with
expiresAt
pure $ DsoRules_CreateUnallocatedUnclaimedActivityRecordResult with ..

-- | PoC (dedicated-synchronizer traffic): governed registration of a dedicated synchronizer.
-- Creates a `RegisteredSynchronizer` (in splice-amulet) binding `synchronizerId` to its
-- `operator`. This is the only way the registry is populated; `AmuletRules_BuyDedicatedSyncTraffic`
-- then reads that contract (via explicit disclosure) to authorize CC burns for the synchronizer.
-- `controller dso` + `nonconsuming`: a vote reaches this via `executeActionRequiringConfirmation`,
-- and the `DsoRules` contract is not archived by exercising it.
nonconsuming choice DsoRules_RegisterSynchronizer : DsoRules_RegisterSynchronizerResult

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Governed create, mirroring DsoRules_CreateUnallocatedUnclaimedActivityRecord. controller dso + nonconsuming: it is reached only via an accepted vote through executeActionRequiringConfirmation, and does not archive the DsoRules contract. It creates the splice-amulet RegisteredSynchronizer, which is allowed because splice-dso-governance depends on splice-amulet.

with
synchronizerId : Text -- ^ stable id of the dedicated synchronizer to register
operator : Party -- ^ the party operating that synchronizer
controller dso
do
require "synchronizerId is non-empty" (not (T.isEmpty synchronizerId))
registeredSynchronizerCid <- create RegisteredSynchronizer with dso; synchronizerId; operator
pure $ DsoRules_RegisterSynchronizerResult with ..

nonconsuming choice DsoRules_AllocateUnallocatedUnclaimedActivityRecord : DsoRules_AllocateUnallocatedUnclaimedActivityRecordResult
with
unallocatedUnclaimedActivityRecordCid : ContractId UnallocatedUnclaimedActivityRecord
Expand Down Expand Up @@ -1816,6 +1842,8 @@ executeActionRequiringConfirmation dso dsoRulesCid amuletRulesCid act = case act
SRARC_CreateExternalPartyAmuletRules choiceArg -> void $ exercise dsoRulesCid choiceArg
SRARC_CreateTransferCommandCounter choiceArg -> void $ exercise dsoRulesCid choiceArg
SRARC_CreateUnallocatedUnclaimedActivityRecord choiceArg -> void $ exercise dsoRulesCid choiceArg
-- PoC: an accepted vote to register a dedicated synchronizer runs the choice on DsoRules.
SRARC_RegisterSynchronizer choiceArg -> void $ exercise dsoRulesCid choiceArg

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Dispatch arm: an accepted SRARC_RegisterSynchronizer vote exercises the choice on DsoRules. This is the single exhaustive match over the DsoRules action variants.

SRARC_CreateBootstrapExternalPartyConfigStateInstruction choiceArg -> void $ exercise dsoRulesCid choiceArg
ARC_AnsEntryContext with .. -> do
void $ fetchChecked (ForDso with dso) ansEntryContextCid
Expand Down