Participant Node Migration with the Same Namespace

One way of migrating a non-KMS participant to a new KMS-enabled one is to create a new participant connected to a KMS-compatible sync domain (e.g. running JCE with KMS-supported encryption and signing keys) and then transfer the data from the old node to the new one. In this example, you transfer the parties, contracts, and DARs, along with the node’s identity. Thus, both the old and new participants share the same namespace. This has the advantage of being feasible in a multi-operator setting even when the other operators do not agree to this migration, because it is transparent to them and their shared contracts.

Warning

You must save the root namespace key of the old participant somewhere offline so that it can be used, in the future, to migrate the node to a new protocol version. This is necessary because you need to sign topology transactions with the root namespace key everytime you migrate a node to a new protocol version that is not supported by the old domain.

First, delegate the namespace of the old participant to the new participant:

val namespaceOld = participantOld.uid.namespace.fingerprint
val namespaceNew = participantNew.uid.namespace.fingerprint

val rootNamespaceDelegationOld = participantOld.topology.namespace_delegations
  .list(filterNamespace = namespaceOld.toProtoPrimitive)
  .head
  .context
  .serialized

val namespaceKeyNew = participantNew.keys.public.download(namespaceNew)
participantOld.keys.public.upload(namespaceKeyNew, Some("pNew-namespace-key"))

// Delegate namespace of old participant to new participant
val delegation = participantOld.topology.namespace_delegations.authorize(
  ops = TopologyChangeOp.Add,
  namespace = namespaceOld,
  authorizedKey = namespaceNew,
)

participantNew.topology.load_transaction(rootNamespaceDelegationOld)
participantNew.topology.load_transaction(delegation)

Secondly, you must recreate all parties of the old participant in the new participant:

/* 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)

// Disconnect from new KMS-compatible domain to prepare migration of parties and contracts
participantNew.domains.disconnect(newDomainAlias)

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

parties.foreach { party =>
  participantNew.topology.party_to_participant_mappings
    .authorize(ops = TopologyChangeOp.Add, party = party, participant = participantNew.id)
}

Thirdly, 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 connect to the new sync domain:

val parties = participantOld.ledger_api.parties
  .list()
  .filter(party =>
    party.party.toProtoPrimitive != participantOld.uid.toProtoPrimitive && party.isLocal
  )
  .map(_.party)

// There should be no conflicts between the protocol version of the old domain and the new domain
val protocolVersion = newKmsDomainManager.config.init.domainParameters.initialProtocolVersion

File.usingTemporaryFile("participantOld-acs", suffix = ".txt") { acsFile =>
  val acsFileName = acsFile.toString

  // Export from old participant
  participantOld.repair.export_acs(
    parties = parties.toSet,
    outputFile = acsFileName,
    contractDomainRenames = Map(oldDomainId -> (newKmsDomainId, protocolVersion)),
    partiesOffboarding = false,
  )

  // Import to new participant
  participantNew.repair.import_acs(acsFileName, parties.toSet)
}

// Kill/stop the old participant
participantOld.stop()

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

The result is a new participant node, that retains the old 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.