Skip to content
Merged
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
3 changes: 3 additions & 0 deletions UNRELEASED.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ schedule, i.e. if you add an entry effective at or after the first
header, prepend the new date header that corresponds to the
Wednesday after your change.

## Until 2025-05-07 (Exclusive)
- The VettedPackage validFrom and validUntil fields have been renamed to validFromInclusive and validFromExclusive.

## Until 2025-04-30 (Exclusive)
- JSON API - fixed openapi documentation for maps: (`eventsById`,`filtersByParty`).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,23 @@ import "scalapb/scalapb.proto";
* The PartyManagementService allows modifying party hosting on participants.
*/
service PartyManagementService {
// Initiate adding a party already hosted on one or more non-local participants to the
// local participant in the specified synchronizer.
// Performs some checks synchronously and then starts the replication asynchronously.
// Initiate adding a party already hosted on one or more source participants to this
// target participant in the specified synchronizer.
// Performs some checks synchronously and then starts the party addition asynchronously.
//
// Depends on the party authorizing the PartyToParticipant topology proposal at the
// specified topology_serial prior or concurrently to this endpoint. On the other hand,
// this target participant authorizes the topology proposal as part of this endpoint in
// a specific, intermediate step, and therefore the target participant signature must not
// already be in place prior to the call.
rpc AddPartyAsync(AddPartyAsyncRequest) returns (AddPartyAsyncResponse);

// Status endpoint that given an add_party_request_id returns status information about progress,
// completion, or errors of a previous call to AddPartyAsync on the source or target
// participant.
//
// Note that the status reflects the state as perceived by the local participant and does not
// imply the state of remote participants. The status on the target participant is more
// Note that the status reflects the state as perceived by this participant and does not
// imply the state of other participants. The status on the target participant is more
// authoritative as the target participant drives the process of adding the party. For example
// when the target participant status indicates "completed", the party has been added
// successfully.
Expand All @@ -44,14 +50,13 @@ message AddPartyAsyncRequest {
// The synchronizer in which to replicate the party
// Required
string synchronizer_id = 2;
// Optionally, the source participant already hosting the party
// Required if the party is already hosted on multiple participants.
// A source participant already hosting the party
// Required
string source_participant_uid = 3;
// Optionally, the topology serial number of this request (auto-determined if omitted)
// NOTE: omitting the serial MAY end up overwriting previous mappings processed concurrently.
// To avoid such cases, first read the PartyToParticipant state using the TopologyManagerReadService
// and update the mappings accordingly, incrementing the serial by one and setting it explicitly.
uint32 serial = 4;
// The topology serial number of the PartyToParticipant topology transaction used to
// add the party to this target participant.
// Required
uint32 topology_serial = 4;
}

message AddPartyAsyncResponse {
Expand All @@ -76,45 +81,39 @@ message GetAddPartyStatusResponse {
string synchronizer_id = 2;
string source_participant_uid = 3;
string target_participant_uid = 4;
uint32 topology_serial = 5;

message Status {
// The add-party request has been submitted by the target participant, or accepted by the
// source participant.
message ProposalProcessed {
optional uint32 topology_serial = 1;
}
message ProposalProcessed {}
// The add-party request has been observed as agreed to by all participants.
message AgreementAccepted {
string sequencer_uid = 1;
optional uint32 topology_serial = 2;
}
// The PartyToParticipant topology transaction has been authorized by all party and
// participant signers.
message TopologyAuthorized {
string sequencer_uid = 1;
uint32 topology_serial = 2;
// The timestamp at which the ACS snapshot for replication is taken.
google.protobuf.Timestamp timestamp = 3;
google.protobuf.Timestamp timestamp = 2;
}
// The local participant has connected to the sequencer channel for ACS replication.
// This participant has connected to the sequencer channel for ACS replication.
message ConnectionEstablished {
string sequencer_uid = 1;
uint32 topology_serial = 2;
google.protobuf.Timestamp timestamp = 3;
google.protobuf.Timestamp timestamp = 2;
}
// The local participant is ready for ACS replication or has started replicating the ACS.
// This participant is ready for ACS replication or has started replicating the ACS.
message ReplicatingAcs {
string sequencer_uid = 1;
uint32 topology_serial = 2;
google.protobuf.Timestamp timestamp = 3;
uint32 contracts_replicated = 4;
google.protobuf.Timestamp timestamp = 2;
uint32 contracts_replicated = 3;
}
// The local participant has completed its part of the ACS replication.
// This participant has completed its part of the ACS replication.
message Completed {
string sequencer_uid = 1;
uint32 topology_serial = 2;
google.protobuf.Timestamp timestamp = 3;
uint32 contracts_replicated = 4;
google.protobuf.Timestamp timestamp = 2;
uint32 contracts_replicated = 3;
}
// The add-party request has failed after the specified last successful status.
message Error {
Expand All @@ -140,7 +139,7 @@ message GetAddPartyStatusResponse {
}
}

Status status = 5;
Status status = 6;
}

message ExportAcsTargetSynchronizer {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,8 +469,8 @@ object ParticipantAdminCommands {
final case class AddPartyAsync(
party: PartyId,
synchronizerId: SynchronizerId,
sourceParticipant: Option[ParticipantId],
serial: Option[PositiveInt],
sourceParticipant: ParticipantId,
serial: PositiveInt,
) extends GrpcAdminCommand[
v30.AddPartyAsyncRequest,
v30.AddPartyAsyncResponse,
Expand All @@ -486,8 +486,8 @@ object ParticipantAdminCommands {
v30.AddPartyAsyncRequest(
partyId = party.toProtoPrimitive,
synchronizerId = synchronizerId.toProtoPrimitive,
sourceParticipantUid = sourceParticipant.fold("")(_.uid.toProtoPrimitive),
serial = serial.fold(0)(_.value),
sourceParticipantUid = sourceParticipant.uid.toProtoPrimitive,
topologySerial = serial.value,
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.admin.grpc.{BaseQuery, TopologyStoreId}
import com.digitalasset.canton.topology.admin.v30
import com.digitalasset.canton.topology.admin.v30.*
import com.digitalasset.canton.topology.admin.v30.AuthorizeRequest.Type.{
Proposal,
TransactionHashBytes,
}
import com.digitalasset.canton.topology.admin.v30.AuthorizeRequest.Type.{Proposal, TransactionHash}
import com.digitalasset.canton.topology.admin.v30.IdentityInitializationServiceGrpc.IdentityInitializationServiceStub
import com.digitalasset.canton.topology.admin.v30.TopologyAggregationServiceGrpc.TopologyAggregationServiceStub
import com.digitalasset.canton.topology.admin.v30.TopologyManagerReadServiceGrpc.TopologyManagerReadServiceStub
Expand Down Expand Up @@ -915,7 +912,7 @@ object TopologyAdminCommands {
}

final case class Authorize[M <: TopologyMapping: ClassTag](
transactionHash: ByteString,
transactionHash: String,
mustFullyAuthorize: Boolean,
signedBy: Seq[Fingerprint],
store: TopologyStoreId,
Expand All @@ -928,7 +925,7 @@ object TopologyAdminCommands {

override protected def createRequest(): Either[String, AuthorizeRequest] = Right(
AuthorizeRequest(
TransactionHashBytes(transactionHash),
TransactionHash(transactionHash),
mustFullyAuthorize = mustFullyAuthorize,
forceChanges = Seq.empty,
signedBy = signedBy.map(_.toProtoPrimitive),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

package com.digitalasset.canton.admin.api.client.data

import cats.syntax.traverse.*
import com.digitalasset.canton.ProtoDeserializationError
import com.digitalasset.canton.admin.participant.v30
import com.digitalasset.canton.config.RequireTypes.{NonNegativeInt, PositiveInt}
Expand All @@ -24,6 +23,7 @@ final case class AddPartyStatus(
synchronizerId: SynchronizerId,
sourceParticipantId: ParticipantId,
targetParticipantId: ParticipantId,
topologySerial: PositiveInt,
status: AddPartyStatus.Status,
)

Expand All @@ -47,13 +47,18 @@ object AddPartyStatus {
"target_participant_uid",
)
.map(ParticipantId(_))
topologySerial <- ProtoConverter.parsePositiveInt(
"topology_serial",
proto.topologySerial,
)
statusP <- ProtoConverter.required("status", proto.status).map(_.status)
status <- parseStatus(statusP)
} yield AddPartyStatus(
partyId,
synchronizerId,
sourceParticipantId,
targetParticipantId,
topologySerial,
status,
)

Expand All @@ -62,64 +67,41 @@ object AddPartyStatus {
): ParsingResult[Status] =
statusP match {
case v30.GetAddPartyStatusResponse.Status.Status.ProposalProcessed(status) =>
for {
topologySerialO <- status.topologySerial.traverse(
ProtoConverter.parsePositiveInt("topology_serial", _)
)
} yield ProposalProcessed(topologySerialO)
Right(ProposalProcessed)
case v30.GetAddPartyStatusResponse.Status.Status.AgreementAccepted(status) =>
for {
sequencerId <- UniqueIdentifier
.fromProtoPrimitive(status.sequencerUid, "sequencer_id")
.map(SequencerId(_))
topologySerialO <- status.topologySerial.traverse(
ProtoConverter.parsePositiveInt("topology_serial", _)
)
} yield AgreementAccepted(sequencerId, topologySerialO)
} yield AgreementAccepted(sequencerId)
case v30.GetAddPartyStatusResponse.Status.Status.TopologyAuthorized(status) =>
for {
commonFields <- parseCommonFields(
status.sequencerUid,
status.topologySerial,
status.timestamp,
)
(sequencerId, topologySerial, timestamp) = commonFields
} yield TopologyAuthorized(sequencerId, topologySerial, timestamp)
commonFields <- parseCommonFields(status.sequencerUid, status.timestamp)
(sequencerId, timestamp) = commonFields
} yield TopologyAuthorized(sequencerId, timestamp)
case v30.GetAddPartyStatusResponse.Status.Status.ConnectionEstablished(status) =>
for {
commonFields <- parseCommonFields(
status.sequencerUid,
status.topologySerial,
status.timestamp,
)
(sequencerId, topologySerial, timestamp) = commonFields
} yield ConnectionEstablished(sequencerId, topologySerial, timestamp)
commonFields <- parseCommonFields(status.sequencerUid, status.timestamp)
(sequencerId, timestamp) = commonFields
} yield ConnectionEstablished(sequencerId, timestamp)
case v30.GetAddPartyStatusResponse.Status.Status.ReplicatingAcs(status) =>
for {
commonFields <- parseCommonFields(
status.sequencerUid,
status.topologySerial,
status.timestamp,
)
(sequencerId, topologySerial, timestamp) = commonFields
commonFields <- parseCommonFields(status.sequencerUid, status.timestamp)
(sequencerId, timestamp) = commonFields
contractsReplicated <- ProtoConverter.parseNonNegativeInt(
"contracts_replicated",
status.contractsReplicated,
)
} yield ReplicatingAcs(sequencerId, topologySerial, timestamp, contractsReplicated)
} yield ReplicatingAcs(sequencerId, timestamp, contractsReplicated)
case v30.GetAddPartyStatusResponse.Status.Status.Completed(status) =>
for {
commonFields <- parseCommonFields(
status.sequencerUid,
status.topologySerial,
status.timestamp,
)
(sequencerId, topologySerial, timestamp) = commonFields
commonFields <- parseCommonFields(status.sequencerUid, status.timestamp)
(sequencerId, timestamp) = commonFields
contractsReplicated <- ProtoConverter.parseNonNegativeInt(
"contracts_replicated",
status.contractsReplicated,
)
} yield Completed(sequencerId, topologySerial, timestamp, contractsReplicated)
} yield Completed(sequencerId, timestamp, contractsReplicated)
case v30.GetAddPartyStatusResponse.Status.Status.Error(status) =>
for {
statusPriorToErrorP <- ProtoConverter.required(
Expand Down Expand Up @@ -147,40 +129,33 @@ object AddPartyStatus {

private def parseCommonFields(
sequencerUidP: String,
topologySerialP: Int,
timestampPO: Option[protobuf.timestamp.Timestamp],
): ParsingResult[(SequencerId, PositiveInt, CantonTimestamp)] = for {
): ParsingResult[(SequencerId, CantonTimestamp)] = for {
sequencerId <- UniqueIdentifier
.fromProtoPrimitive(sequencerUidP, "sequencer_id")
.map(SequencerId(_))
topologySerial <- ProtoConverter.parsePositiveInt("topology_serial", topologySerialP)
timestampP <- ProtoConverter.required("timestamp", timestampPO)
timestamp <- CantonTimestamp.fromProtoTimestamp(timestampP)
} yield (sequencerId, topologySerial, timestamp)
} yield (sequencerId, timestamp)

sealed trait Status
final case class ProposalProcessed(topologySerial: Option[PositiveInt]) extends Status
final case class AgreementAccepted(sequencerId: SequencerId, topologySerial: Option[PositiveInt])
extends Status
final case object ProposalProcessed extends Status
final case class AgreementAccepted(sequencerId: SequencerId) extends Status
final case class TopologyAuthorized(
sequencerId: SequencerId,
topologySerial: PositiveInt,
timestamp: CantonTimestamp,
) extends Status
final case class ConnectionEstablished(
sequencerId: SequencerId,
topologySerial: PositiveInt,
timestamp: CantonTimestamp,
) extends Status
final case class ReplicatingAcs(
sequencerId: SequencerId,
topologySerial: PositiveInt,
timestamp: CantonTimestamp,
contractsReplicated: NonNegativeInt,
) extends Status
final case class Completed(
sequencerId: SequencerId,
topologySerial: PositiveInt,
timestamp: CantonTimestamp,
contractsReplicated: NonNegativeInt,
) extends Status
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1951,8 +1951,7 @@ object CantonConfig {
.foldLeft(c) { case (subConfig, (key, obj)) =>
subConfig.withValue(key, goVal(key, obj))
}
go(config)
.resolve()
go(config.resolve()) // Resolve config _before_ redacting confidential fields
.root()
.get("canton")
.render(CantonConfig.defaultConfigRenderer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,10 @@ trait ConsoleMacros extends NamedLogging with NoTracing {
)
)

val LfContractId.V1(discriminator, _) = contract.contractId
val LfContractId.V1(discriminator, _) = contract.contractId match {
case cid: LfContractId.V1 => cid
case _ => sys.error("ContractId V2 are not supported")
}
val pureCrypto = participant.underlying
.map(_.cryptoPureApi)
.getOrElse(sys.error("where is my crypto?"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,11 @@ trait LocalInstanceReference extends InstanceReference with NoTracing {
protected def startInstance(): Either[StartupError, Unit] =
nodes.startAndWait(name)
protected def stopInstance(): Either[ShutdownError, Unit] = nodes.stopAndWait(name)

/** We use [[com.digitalasset.canton.crypto.Crypto]] because it supports key administration
* console commands that do not require the node to be connected to a synchronizer. It is
* intended to be used exclusively by the admin API with a trusted operator.
*/
protected[canton] def crypto: Crypto

protected def runCommandIfRunning[Result](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,8 @@ class ParticipantPartiesAdministrationGroup(
def add_party_async(
party: PartyId,
synchronizerId: SynchronizerId,
sourceParticipant: Option[ParticipantId],
serial: Option[PositiveInt],
sourceParticipant: ParticipantId,
serial: PositiveInt,
): String = check(FeatureFlag.Preview) {
consoleEnvironment.run {
reference.adminCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,7 @@ class TopologyAdministrationGroup(
consoleEnvironment.run {
adminCommand(
TopologyAdminCommands.Write.Authorize(
txHash.hash.getCryptographicEvidence,
txHash.hash.toHexString,
mustFullyAuthorize = mustBeFullyAuthorized,
signedBy = signedBy,
store = store,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def authorize_external_party_hosting(
# Authorize the hosting
topology_write_client.Authorize(
topology_manager_write_service_pb2.AuthorizeRequest(
transaction_hash_bytes=party_to_participant_proposal.context.transaction_hash,
transaction_hash=party_to_participant_proposal.context.transaction_hash.hex(),
must_fully_authorize=False,
store=common_pb2.StoreId(
synchronizer=common_pb2.StoreId.Synchronizer(
Expand Down
Loading