|
| 1 | +import com.google.protobuf.ByteString |
| 2 | + |
| 3 | +import java.io._ |
| 4 | +import java.net.{HttpURLConnection, URI} |
| 5 | +import java.nio.file.{Files, Path, StandardCopyOption} |
| 6 | +import scala.concurrent.blocking |
| 7 | +import better.files.File |
| 8 | +import com.digitalasset.canton.console.{ |
| 9 | + InstanceReference, |
| 10 | + LocalInstanceReference, |
| 11 | + LocalMediatorReference, |
| 12 | + LocalSequencerReference, |
| 13 | + MediatorReference, |
| 14 | + ParticipantReference, |
| 15 | + SequencerReference, |
| 16 | +} |
| 17 | +import com.digitalasset.canton.version.ProtocolVersion._ |
| 18 | +import com.digitalasset.canton.topology.{SynchronizerId, UniqueIdentifier} |
| 19 | +import com.digitalasset.canton.console.commands.ConsoleCommandGroup |
| 20 | +import com.digitalasset.canton.util.BinaryFileUtil |
| 21 | +import com.digitalasset.canton.sequencing.{SequencerConnections, SubmissionRequestAmplification} |
| 22 | + |
| 23 | + |
| 24 | +val cantonDir = "canton" |
| 25 | +val synchronizerDir = s"$cantonDir/synchronizer-bootstrap" |
| 26 | + |
| 27 | +logger.info(s"WALLET-KERNEL-BOOTSTRAP") |
| 28 | + |
| 29 | +val keyName = "participant1NameSpaceKey" |
| 30 | + |
| 31 | +participant1.keys.secret.upload_from(s"$cantonDir/participant1.key", Some(keyName)) |
| 32 | + |
| 33 | +val key = participant1.keys.secret.list(filterName = keyName).headOption.get |
| 34 | + |
| 35 | + |
| 36 | +val namespaceKey = key.publicKey match { case s: SigningPublicKey => s } |
| 37 | + |
| 38 | + |
| 39 | +val namespace = Namespace(namespaceKey.id) |
| 40 | + |
| 41 | +participant1.topology.init_id_from_uid(UniqueIdentifier.tryCreate(participant1.name, namespace)) |
| 42 | + |
| 43 | +participant1.topology.namespace_delegations.propose_delegation(namespace, namespaceKey, CanSignAllMappings) |
| 44 | + |
| 45 | +participant1.health.wait_for_ready_for_node_topology() |
| 46 | + |
| 47 | +val sequencerAuthKey = participant1.keys.secret.generate_signing_key(s"participant1-${SigningKeyUsage.SequencerAuthentication.identifier}", SigningKeyUsage.SequencerAuthenticationOnly) |
| 48 | + |
| 49 | +val signingKey = participant1.keys.secret.generate_signing_key(s"participant1-${SigningKeyUsage.Protocol.identifier}", SigningKeyUsage.ProtocolOnly) |
| 50 | +val encryptionKey = participant1.keys.secret.generate_encryption_key("participant1-encryption") |
| 51 | + |
| 52 | +participant1.topology.owner_to_key_mappings.propose( |
| 53 | + OwnerToKeyMapping( |
| 54 | + participant1.id.member, |
| 55 | + com.daml.nonempty.NonEmpty(Seq, sequencerAuthKey, signingKey, encryptionKey), |
| 56 | + ), |
| 57 | + signedBy = Seq(namespaceKey.fingerprint, sequencerAuthKey.fingerprint, signingKey.fingerprint), |
| 58 | +) |
| 59 | +participant1.health.wait_for_initialized() |
| 60 | + |
| 61 | +//import data |
| 62 | +logger.info("Importing sequencer/mediator data ") |
| 63 | + |
| 64 | +val synchronizerId = SynchronizerId.tryFromString(better.files.File(s"$synchronizerDir/synchronizer-id").contentAsString) |
| 65 | +logger.info(s"synchronizer id is $synchronizerId") |
| 66 | + |
| 67 | +val testedProtocolVersion = ProtocolVersion.v33 |
| 68 | + |
| 69 | + |
| 70 | +val newStaticSynchronizerParameters = |
| 71 | + StaticSynchronizerParameters.defaultsWithoutKMS(protocolVersion = testedProtocolVersion) |
| 72 | + |
| 73 | +migrateNode( |
| 74 | + migratedNode = sequencer1, |
| 75 | + newStaticSynchronizerParameters = newStaticSynchronizerParameters, |
| 76 | + synchronizerId = synchronizerId, |
| 77 | + newSequencers = Seq(sequencer1), |
| 78 | + dars = Seq(), |
| 79 | + exportDirectory = better.files.File(synchronizerDir), |
| 80 | +) |
| 81 | + |
| 82 | +migrateNode( |
| 83 | + migratedNode = mediator1, |
| 84 | + newStaticSynchronizerParameters = newStaticSynchronizerParameters, |
| 85 | + synchronizerId = synchronizerId, |
| 86 | + newSequencers = Seq(sequencer1), |
| 87 | + dars = Seq(), |
| 88 | + exportDirectory = better.files.File(synchronizerDir), |
| 89 | +) |
| 90 | + |
| 91 | + |
| 92 | +// start all local instances defined in the configuration file |
| 93 | + |
| 94 | +nodes.local.start() |
| 95 | + |
| 96 | +bootstrap.synchronizer( |
| 97 | + synchronizerName = "wallet", |
| 98 | + sequencers = Seq(sequencer1), |
| 99 | + mediators = Seq(mediator1), |
| 100 | + synchronizerOwners = Seq(sequencer1), |
| 101 | + synchronizerThreshold = PositiveInt.one, |
| 102 | + staticSynchronizerParameters = StaticSynchronizerParameters.defaultsWithoutKMS(ProtocolVersion.forSynchronizer), |
| 103 | +) |
| 104 | + |
| 105 | + |
| 106 | + |
| 107 | +// Connect participant1 to wallet using the connect macro. |
| 108 | +// The connect macro will inspect the synchronizer configuration to find the correct URL and Port. |
| 109 | +// The macro is convenient for local testing, but obviously doesn't work in a distributed setup. |
| 110 | +participant1.synchronizers.connect_local(sequencer1, alias = "wallet") |
| 111 | + |
| 112 | +utils.retry_until_true { |
| 113 | + participant1.synchronizers.active("wallet") |
| 114 | +} |
| 115 | + |
| 116 | + |
| 117 | +logger.info(s"WALLET-KERNEL-BOOTSTRAP: Creating operator user and party") |
| 118 | +val operatorParty = participant1.ledger_api.parties.allocate("operator").party |
| 119 | + |
| 120 | +participant1.ledger_api.users.create(id = "operator", actAs = Set(operatorParty), readAs = Set(operatorParty), primaryParty = Some(operatorParty), participantAdmin = false, isDeactivated = false, annotations = Map("foo" -> "bar", "description" -> "This is a description")) |
| 121 | + |
| 122 | +logger.info(s"WALLET-KERNEL-BOOTSTRAP: created operator user and party") |
| 123 | + |
| 124 | + |
| 125 | + |
| 126 | +def initializeSequencer( |
| 127 | + migrated: SequencerReference, |
| 128 | + genesisState: ByteString, |
| 129 | + staticSynchronizerParameters: StaticSynchronizerParameters, |
| 130 | + ): Unit = { |
| 131 | + migrated.health.wait_for_ready_for_initialization() |
| 132 | + migrated.setup.assign_from_genesis_state( |
| 133 | + genesisState, |
| 134 | + staticSynchronizerParameters, |
| 135 | + ) |
| 136 | + |
| 137 | + migrated.health.initialized() |
| 138 | + |
| 139 | +} |
| 140 | + |
| 141 | +def migrateNode( |
| 142 | + migratedNode: InstanceReference with ConsoleCommandGroup, |
| 143 | + newStaticSynchronizerParameters: StaticSynchronizerParameters, |
| 144 | + synchronizerId: SynchronizerId, |
| 145 | + newSequencers: Seq[SequencerReference], |
| 146 | + dars: Seq[String], |
| 147 | + sequencerTrustThreshold: PositiveInt = PositiveInt.one, |
| 148 | + exportDirectory: File, |
| 149 | + ): Unit = { |
| 150 | + val files = UpgradeDataFiles.from(migratedNode.name, exportDirectory) |
| 151 | + |
| 152 | + files.keys.foreach { case (keys, name) => |
| 153 | + migratedNode.keys.secret.upload(keys, name) |
| 154 | + } |
| 155 | + migratedNode.topology.init_id_from_uid(files.uid) |
| 156 | + migratedNode.health.wait_for_ready_for_node_topology() |
| 157 | + migratedNode.topology.transactions |
| 158 | + .import_topology_snapshot(files.authorizedStore, TopologyStoreId.Authorized) |
| 159 | + |
| 160 | + migratedNode match { |
| 161 | + case newSequencer: SequencerReference => |
| 162 | + initializeSequencer(newSequencer, files.genesisState, newStaticSynchronizerParameters) |
| 163 | + |
| 164 | + case newMediator: MediatorReference => |
| 165 | + newMediator.setup.assign( |
| 166 | + synchronizerId, |
| 167 | + SequencerConnections.tryMany( |
| 168 | + newSequencers |
| 169 | + .map(s => s.sequencerConnection.withAlias(SequencerAlias.tryCreate(s.name))), |
| 170 | + sequencerTrustThreshold, |
| 171 | + SubmissionRequestAmplification.NoAmplification, |
| 172 | + ), |
| 173 | + ) |
| 174 | + |
| 175 | + case newParticipant: ParticipantReference => |
| 176 | + val node = newParticipant |
| 177 | + // user-manual-entry-begin: WaitForParticipantInitialization |
| 178 | + node.health.wait_for_initialized() |
| 179 | + // user-manual-entry-end: WaitForParticipantInitialization |
| 180 | + dars.foreach(dar => newParticipant.dars.upload(dar)) |
| 181 | + |
| 182 | + case _ => |
| 183 | + throw new IllegalStateException( |
| 184 | + s"Unsupported migration from $files to $migratedNode" |
| 185 | + ) |
| 186 | + } |
| 187 | + } |
| 188 | + |
| 189 | +final case class UpgradeDataFiles( |
| 190 | + uidFile: File, |
| 191 | + keyFiles: Seq[File], |
| 192 | + authorizedStoreFile: File, |
| 193 | + acsSnapshotFile: File, |
| 194 | + genesisStateFile: File, |
| 195 | + ) { |
| 196 | + def uid: UniqueIdentifier = |
| 197 | + UniqueIdentifier.tryFromProtoPrimitive( |
| 198 | + uidFile.contentAsString |
| 199 | + ) |
| 200 | + |
| 201 | + def keys: Seq[(ByteString, Option[String])] = |
| 202 | + keyFiles.map { file => |
| 203 | + val key = BinaryFileUtil.tryReadByteStringFromFile(file.canonicalPath) |
| 204 | + val name = file.name.stripSuffix(".keys") |
| 205 | + key -> Option(name) |
| 206 | + } |
| 207 | + |
| 208 | + def authorizedStore: ByteString = |
| 209 | + BinaryFileUtil.tryReadByteStringFromFile(authorizedStoreFile.canonicalPath) |
| 210 | + |
| 211 | + def genesisState: ByteString = |
| 212 | + BinaryFileUtil.tryReadByteStringFromFile(genesisStateFile.canonicalPath) |
| 213 | + } |
| 214 | + |
| 215 | + object UpgradeDataFiles { |
| 216 | + def from(nodeName: String, baseDirectory: File): UpgradeDataFiles = { |
| 217 | + val keys = |
| 218 | + baseDirectory.list |
| 219 | + .filter(file => file.name.startsWith(nodeName) && file.name.endsWith(".keys")) |
| 220 | + .toList |
| 221 | + UpgradeDataFiles( |
| 222 | + uidFile = baseDirectory / s"$nodeName-uid", |
| 223 | + keyFiles = keys, |
| 224 | + authorizedStoreFile = baseDirectory / s"$nodeName-authorized-store", |
| 225 | + acsSnapshotFile = baseDirectory / s"$nodeName-acs-snapshot", |
| 226 | + genesisStateFile = baseDirectory / s"$nodeName-genesis-state", |
| 227 | + ) |
| 228 | + } |
| 229 | + } |
0 commit comments