Skip to content

Commit f8e75e9

Browse files
[ci] processed comments from review
Signed-off-by: Julien Tinguely <[email protected]>
1 parent cea9a8d commit f8e75e9

File tree

11 files changed

+98
-86
lines changed

11 files changed

+98
-86
lines changed

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ jobs:
163163
with:
164164
runs_on: self-hosted-k8s-medium
165165
test_names_file: 'test-full-class-names-signatures.log'
166-
start_canton_options: -B "scripts/bootstrap/bootstrap-canton-with-deprecated-keys.sc"
166+
start_canton_options: -B "scripts/bootstrap/bootstrap-canton-with-unsigned-keys.sc"
167167
parallelism: 1
168168
test_name: signatures
169169
with_gcp_creds: false

apps/common/src/main/scala/org/lfdecentralizedtrust/splice/setup/NodeInitializer.scala

Lines changed: 43 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ class NodeInitializer(
154154
} yield ()
155155
}
156156

157-
def rotateLocalCantonNodesOTKIfNeeded(
157+
def rotateCantonNodesOTKIfNeeded(
158158
identifierName: String,
159159
nodeIdentity: UniqueIdentifier => Member & NodeIdentity,
160160
synchronizerId: SynchronizerId,
@@ -347,39 +347,43 @@ class NodeInitializer(
347347
_ = logger.info(s"AuthorizedStore snapshot is imported")
348348
} yield ()
349349

350+
// This method rotates OwnerToKeyMapping signing public keys that are not signed
350351
private def rotateOwnerToKeyMappingNotSignedByKeys(
351352
id: UniqueIdentifier,
352353
nodeIdentity: UniqueIdentifier => Member & NodeIdentity,
353354
synchronizerId: SynchronizerId,
354-
)(implicit tc: TraceContext, ec: ExecutionContext): Future[Unit] =
355+
)(implicit tc: TraceContext, ec: ExecutionContext): Future[Unit] = {
356+
val member = nodeIdentity(id)
355357
for {
356-
fullTxHistory <- connection.listAllTransactions(
358+
nsTxHistory <- connection.listAllTransactions(
357359
store = TopologyStoreId.SynchronizerStore(synchronizerId),
358360
timeQuery = TimeQuery.Range(None, None),
359361
includeMappings = Set(OwnerToKeyMapping.code),
362+
filterNamespace = Some(member.namespace),
360363
)
361-
ownerToKeyMappingTxHistory = fullTxHistory.filter(_.transaction.mapping match {
362-
case mapping: OwnerToKeyMapping if mapping.member == nodeIdentity(id) => true
364+
ownerToKeyMappings = nsTxHistory.filter(_.transaction.mapping match {
365+
case mapping: OwnerToKeyMapping if mapping.member == member => true
363366
case _ => false
364367
})
365368
_ <-
366-
if (ownerToKeyMappingTxHistory.isEmpty) {
369+
if (ownerToKeyMappings.isEmpty) {
367370
Future.unit
368371
} else {
369-
performKeyRotation(ownerToKeyMappingTxHistory, nodeIdentity(id))
372+
performKeyRotation(ownerToKeyMappings, nodeIdentity(id))
370373
}
371374
} yield ()
375+
}
372376

373377
private def performKeyRotation(
374-
ownerToKeyMappingTxHistory: Seq[StoredTopologyTransaction[TopologyChangeOp, TopologyMapping]],
378+
ownerToKeyMappings: Seq[StoredTopologyTransaction[TopologyChangeOp, TopologyMapping]],
375379
member: Member,
376380
)(implicit tc: TraceContext, ec: ExecutionContext): Future[Unit] = {
377-
val allOtkSignatures = ownerToKeyMappingTxHistory
381+
val allOtkSignatures = ownerToKeyMappings
378382
.map(_.transaction)
379383
.flatMap(_.signatures)
380384
.map(_.signedBy)
381385
.distinct
382-
val latestKeys = ownerToKeyMappingTxHistory
386+
val currentKeys = ownerToKeyMappings
383387
.map(_.transaction)
384388
.sortBy(_.transaction.serial)
385389
.lastOption
@@ -388,38 +392,37 @@ class NodeInitializer(
388392
case mapping: OwnerToKeyMapping =>
389393
mapping.keys.forgetNE
390394
case _ =>
391-
throw new IllegalStateException("Latest transaction is not an OwnerToKeyMapping type.")
395+
throw new IllegalStateException("Latest transaction is not of OwnerToKeyMapping type.")
392396
}
393-
val (_, toRotate) = latestKeys.map(_.id).partition(allOtkSignatures.contains)
394-
for {
395-
_ <-
396-
if (toRotate.nonEmpty) {
397-
logger.info(s"The following keys with missing signature need to be rotated: $toRotate")
398-
val rotatedKeys = latestKeys.map {
399-
case key: SigningPublicKey if toRotate.contains(key.id) =>
400-
connection.generateKeyPair(
401-
key.keySpec.name,
402-
key.usage,
403-
)
404-
case key => Future.successful(key)
405-
}
406-
for {
407-
newKeys <- Future.sequence(rotatedKeys)
408-
_ <- connection.ensureOwnerToKeyMapping(
409-
member = member,
410-
keys = NonEmpty.mk(
411-
Seq,
412-
newKeys.headOption.getOrElse(throw new IllegalStateException("newKeys is empty.")),
413-
newKeys.drop(1)*
414-
),
415-
retryFor = RetryFor.Automation,
397+
val toRotate = currentKeys.map(_.id).filterNot(allOtkSignatures.contains)
398+
if (toRotate.nonEmpty) {
399+
logger.info(s"The following keys with missing signature need to be rotated: $toRotate")
400+
for {
401+
newKeys <- Future.traverse(currentKeys) {
402+
case key: SigningPublicKey if toRotate.contains(key.id) =>
403+
connection.generateKeyPair(
404+
key.keySpec.name,
405+
key.usage,
416406
)
417-
} yield logger.info(
418-
s"Rotating OTK mapping keys that did not sign the OTK topology transaction."
419-
)
420-
} else {
421-
Future.unit
407+
case key => Future.successful(key)
422408
}
423-
} yield ()
409+
newKeysNE <- NonEmpty.from(newKeys) match {
410+
case Some(ne) => Future.successful(ne)
411+
case None =>
412+
Future.failed(
413+
new IllegalStateException("newKeys collection cannot be empty after rotation.")
414+
)
415+
}
416+
_ <- connection.ensureOwnerToKeyMapping(
417+
member = member,
418+
keys = newKeysNE,
419+
retryFor = RetryFor.Automation,
420+
)
421+
} yield logger.info(
422+
s"Rotating OTK mapping keys that did not sign the OTK topology transaction."
423+
)
424+
} else {
425+
Future.unit
426+
}
424427
}
425428
}

apps/common/src/main/scala/org/lfdecentralizedtrust/splice/setup/ParticipantInitializer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ class ParticipantInitializer(
118118

119119
def ensureInitializedWithRotatedOTK(synchronizerId: SynchronizerId): Future[Unit] =
120120
for {
121-
_ <- nodeInitializer.rotateLocalCantonNodesOTKIfNeeded(
121+
_ <- nodeInitializer.rotateCantonNodesOTKIfNeeded(
122122
identifierName,
123123
ParticipantId.apply,
124124
synchronizerId,

apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/NodeInitializerUtil.scala

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ import org.lfdecentralizedtrust.splice.store.{
2525
import org.lfdecentralizedtrust.splice.sv.LocalSynchronizerNode
2626
import org.lfdecentralizedtrust.splice.sv.automation.{SvDsoAutomationService, SvSvAutomationService}
2727
import org.lfdecentralizedtrust.splice.sv.cometbft.{CometBftNode, CometBftRequestSigner}
28-
import org.lfdecentralizedtrust.splice.sv.config.SvAppBackendConfig
28+
import org.lfdecentralizedtrust.splice.sv.config.{SvAppBackendConfig, SvCantonIdentifierConfig}
2929
import org.lfdecentralizedtrust.splice.sv.store.{SvDsoStore, SvStore, SvSvStore}
3030
import org.lfdecentralizedtrust.splice.util.TemplateJsonDecoder
3131
import com.digitalasset.canton.lifecycle.CloseContext
32-
import com.digitalasset.canton.logging.NamedLogging
32+
import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging}
3333
import com.digitalasset.canton.resource.Storage
3434
import com.digitalasset.canton.time.Clock
3535
import com.digitalasset.canton.topology.{ParticipantId, PartyId, SynchronizerId}
@@ -345,6 +345,33 @@ trait NodeInitializerUtil extends NamedLogging with Spanning with SynchronizerNo
345345
} yield initialRound
346346
}
347347

348+
// Ensure OTK mappings contain only signing keys that are signed
349+
protected def ensureCantonNodesOTKRotatedIfNeeded(
350+
skipSynchronizerInitialization: Boolean,
351+
cantonIdentifierConfig: SvCantonIdentifierConfig,
352+
localSynchronizerNode: Option[LocalSynchronizerNode],
353+
clock: Clock,
354+
loggerFactory: NamedLoggerFactory,
355+
retryProvider: RetryProvider,
356+
decentralizedSynchronizerId: SynchronizerId,
357+
)(implicit tc: TraceContext, ec: ExecutionContext): Future[Unit] =
358+
localSynchronizerNode match {
359+
case Some(synchronizerNode) if !skipSynchronizerInitialization =>
360+
SynchronizerNodeInitializer.rotateCantonNodesOTKIfNeeded(
361+
cantonIdentifierConfig,
362+
synchronizerNode,
363+
clock,
364+
loggerFactory,
365+
retryProvider,
366+
decentralizedSynchronizerId,
367+
)
368+
case _ =>
369+
logger.info(
370+
"Skipping OTK keys rotation because skipSynchronizerInitialization is enabled"
371+
)
372+
Future.unit
373+
}
374+
348375
private def setInitialRound(connection: BaseLedgerConnection, initialRound: Long)(implicit
349376
ec: ExecutionContext,
350377
tc: TraceContext,

apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/SynchronizerNodeInitializer.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ object SynchronizerNodeInitializer {
6060
} yield ()
6161
}
6262

63-
def rotateLocalCantonNodesOTKIfNeeded(
63+
def rotateCantonNodesOTKIfNeeded(
6464
identifierConfig: SvCantonIdentifierConfig,
6565
synchronizerNode: LocalSynchronizerNode,
6666
clock: Clock,
@@ -75,12 +75,12 @@ object SynchronizerNodeInitializer {
7575
retryProvider,
7676
)
7777
for {
78-
_ <- synchronizerNodeInitializer.sequencerInitializer.rotateLocalCantonNodesOTKIfNeeded(
78+
_ <- synchronizerNodeInitializer.sequencerInitializer.rotateCantonNodesOTKIfNeeded(
7979
identifierConfig.sequencer,
8080
SequencerId.apply,
8181
synchronizerId,
8282
)
83-
_ <- synchronizerNodeInitializer.mediatorInitializer.rotateLocalCantonNodesOTKIfNeeded(
83+
_ <- synchronizerNodeInitializer.mediatorInitializer.rotateCantonNodesOTKIfNeeded(
8484
identifierConfig.mediator,
8585
MediatorId.apply,
8686
synchronizerId,

apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/joining/JoiningNodeInitializer.scala

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -323,24 +323,15 @@ class JoiningNodeInitializer(
323323
)
324324
Future.unit
325325
}
326-
_ <-
327-
if (!config.skipSynchronizerInitialization) {
328-
localSynchronizerNode.traverse(lsn =>
329-
SynchronizerNodeInitializer.rotateLocalCantonNodesOTKIfNeeded(
330-
cantonIdentifierConfig,
331-
lsn,
332-
clock,
333-
loggerFactory,
334-
retryProvider,
335-
decentralizedSynchronizerId,
336-
)
337-
)
338-
} else {
339-
logger.info(
340-
"Skipping OTK keys rotation because skipSynchronizerInitialization is enabled"
341-
)
342-
Future.unit
343-
}
326+
_ <- ensureCantonNodesOTKRotatedIfNeeded(
327+
config.skipSynchronizerInitialization,
328+
cantonIdentifierConfig,
329+
localSynchronizerNode,
330+
clock,
331+
loggerFactory,
332+
retryProvider,
333+
decentralizedSynchronizerId,
334+
)
344335
_ <- onboard(
345336
decentralizedSynchronizerId,
346337
dsoAutomation,

apps/sv/src/main/scala/org/lfdecentralizedtrust/splice/sv/onboarding/sv1/SV1Initializer.scala

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -167,22 +167,6 @@ class SV1Initializer(
167167
} else {
168168
bootstrapDomain(localSynchronizerNode)
169169
}
170-
_ <-
171-
if (!config.skipSynchronizerInitialization) {
172-
SynchronizerNodeInitializer.rotateLocalCantonNodesOTKIfNeeded(
173-
cantonIdentifierConfig,
174-
localSynchronizerNode,
175-
clock,
176-
loggerFactory,
177-
retryProvider,
178-
synchronizerId,
179-
)
180-
} else {
181-
logger.info(
182-
"Skipping OTK keys rotation because skipSynchronizerInitialization is enabled"
183-
)
184-
Future.unit
185-
}
186170
_ = logger.info("Domain is bootstrapped, connecting sv1 participant to domain")
187171
internalSequencerApi = localSynchronizerNode.sequencerInternalConfig
188172
_ <- participantAdminConnection.ensureDomainRegisteredAndConnected(
@@ -212,6 +196,16 @@ class SV1Initializer(
212196
retryFor = RetryFor.WaitingOnInitDependency,
213197
)
214198
_ = logger.info("Participant connected to domain")
199+
_ <- ensureCantonNodesOTKRotatedIfNeeded(
200+
config.skipSynchronizerInitialization,
201+
cantonIdentifierConfig,
202+
Some(localSynchronizerNode),
203+
clock,
204+
loggerFactory,
205+
retryProvider,
206+
synchronizerId,
207+
)
208+
_ = logger.info("Synchronizer rotated OTK keys that were not signed")
215209
(dsoParty, svParty, _) <- (
216210
setupDsoParty(synchronizerId, initConnection, namespace),
217211
SetupUtil.setupSvParty(

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1854,7 +1854,7 @@ checkErrors := {
18541854

18551855
splitAndCheckCantonLogFile("canton", usesSimtime = false)
18561856
splitAndCheckCantonLogFile("canton-simtime", usesSimtime = true)
1857-
splitAndCheckCantonLogFile("canton-signatures", usesSimtime = false)
1857+
splitAndCheckCantonLogFile("canton-missing-signatures", usesSimtime = false)
18581858
import better.files._
18591859
val dir = File("log/")
18601860
if (dir.exists())

scripts/bootstrap/bootstrap-canton-with-deprecated-keys.sc renamed to scripts/bootstrap/bootstrap-canton-with-unsigned-keys.sc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,6 @@ bootstrapDomainWithUnsignedKeys(
171171
aliceParticipant,
172172
)
173173

174-
sv1Participant.synchronizers.connect_local(globalSequencerSv1, "global")
175-
aliceParticipant.synchronizers.connect_local(globalSequencerSv1, "global")
176-
177174
// These user allocations are only there
178175
// for local testing. Our tests allocate their own users.
179176
println(s"Allocating users for local testing...")

0 commit comments

Comments
 (0)