Participant Node Migration with a New Namespace

To migrate a non-KMS participant to a KMS-enabled one you need to make sure you have this new participant connected to a KMS-compatible sync domain (e.g. running JCE with KMS-supported encryption and signing keys). Afterwards, you transfer all parties, active contracts and DARs from the old participant to the new one. Since, in this case, you do not transfer the identities from the old node to the new one. You need to re-write your contracts to refer to the new party ids. With this method, you can easily migrate your old participant that runs an older protocol version to a new participant with KMS enabled that can be running a more recent protocol version. Furthermore, you do not need to safe-keep the old node’s root namespace key, because your participant namespace changes. However, for it to work a single operator must control all the contracts or all participant operators have to agree on this rewrite.

Warning

Currently this migration only works if you are migrating from an old participant node running protocol version 3. This only works for a single operator or if all other participant operators agree and follow the same steps.

First, you must recreate all parties of the old participant in the new participant, keeping the same display name, but resulting in different ids due to the new namespace key:

/* Disconnect the old participant from the old domain to avoid new parties or contracts being created
 * Make sure domain and the old participant are quiet before exporting the parties or the ACS
 */
participantOld.domains.disconnect(oldDomainAlias)

// Determine all parties hosted on the old participants, except for admin parties
val partiesOld =
  participantOld.ledger_api.parties
    .list()
    .filter(party =>
      party.party.toProtoPrimitive != participantOld.uid.toProtoPrimitive && party.isLocal
    )

/* Allocate new parties on the new participant with the same name and
 * display name as on the old participant, but in the namespace of the
 * new participant
 */
partyIdsOldToPartyIdsNew = partiesOld.map { partyDetailsOld =>
  val oldParty = partyDetailsOld.party
  val partyId =
    participantNew.ledger_api.parties
      .allocate(oldParty.uid.id.toProtoPrimitive, partyDetailsOld.displayName)
      .party
  oldParty -> partyId
}.toMap

Secondly, you should migrate your DARs to the new participant:

// Migrate DARs from old to new participant
utils.migrate_dars(participantOld, participantNew)

Finally, you need to transfer the active contracts of all the parties from the old participant to the new one and re-write the party ids mentioned in those contracts to match the new party ids. You can then connect to the new sync domain:

// Export the acs of the old participant
val acsOld = participantOld.ledger_api.acs.of_all(
  limit = PositiveInt.MaxValue,
  includeCreatedEventBlob = true,
)

// Connect new participant to the new sequencer
participantNew.domains.reconnect_all()

/* Create traffic on the new participant so that the repair commands will
 * pick an identity snapshot that is aware of all party allocations
 */
participantNew.health.ping(participantNew)

// Disconnect new participants from the domain to be able to import the ACS
participantNew.domains.disconnect_all()

// Pick a ledger create time according to the domain's clock.
val ledgerCreateTime = clock.now.toInstant

// Switch the ACS to be based on the new party ids.
val participantNewACS = utils.change_contracts_party_ids(
  partyIdsOldToPartyIdsNew,
  acsOld,
  newKmsDomainId,
  ledgerCreateTime,
  newProtocolVersion,
)

// Upload ACS to new participants
File.temporaryFile(prefix = s"migration-${participantNew.name}", suffix = "gz") {
  tempFile =>
    val outputStream = tempFile.newGzipOutputStream()
    ResourceUtil.withResource(outputStream)(s =>
      participantNewACS.foreach(_.writeDelimitedTo(s))
    )
    outputStream.flush()
    val partiesHostedOnParticipantNew = partyIdsOldToPartyIdsNew.values.toSet
    participantNew.repair.import_acs(tempFile.canonicalPath, partiesHostedOnParticipantNew)
}

// Kill/stop the old participant
external.stop(oldParticipantRunConfig)

// Connect the new participant to the new domain
participantNew.domains.reconnect_all()

The result is a new participant node, in a different namespace, with its keys stored and managed by a KMS connected to a sync domain that can communicate using the appropriate key schemes.

You need to follow the same steps if you want to migrate a node back to using a non-KMS provider.