diff --git a/canton/community/app-base/src/main/scala/com/digitalasset/canton/admin/api/client/commands/LedgerApiCommands.scala b/canton/community/app-base/src/main/scala/com/digitalasset/canton/admin/api/client/commands/LedgerApiCommands.scala index 42a1a2cd89f..e8e96beac15 100644 --- a/canton/community/app-base/src/main/scala/com/digitalasset/canton/admin/api/client/commands/LedgerApiCommands.scala +++ b/canton/community/app-base/src/main/scala/com/digitalasset/canton/admin/api/client/commands/LedgerApiCommands.scala @@ -43,32 +43,109 @@ import com.daml.ledger.api.v2.admin.user_management_service.{ User, UserManagementServiceGrpc, } +import com.daml.ledger.api.v2.checkpoint.Checkpoint +import com.daml.ledger.api.v2.command_completion_service.CommandCompletionServiceGrpc.CommandCompletionServiceStub +import com.daml.ledger.api.v2.command_completion_service.{ + CommandCompletionServiceGrpc, + CompletionStreamRequest, + CompletionStreamResponse, +} +import com.daml.ledger.api.v2.command_service.CommandServiceGrpc.CommandServiceStub +import com.daml.ledger.api.v2.command_service.{ + CommandServiceGrpc, + SubmitAndWaitForTransactionResponse, + SubmitAndWaitForTransactionTreeResponse, + SubmitAndWaitRequest, +} +import com.daml.ledger.api.v2.command_submission_service.CommandSubmissionServiceGrpc.CommandSubmissionServiceStub +import com.daml.ledger.api.v2.command_submission_service.{ + CommandSubmissionServiceGrpc, + SubmitReassignmentRequest, + SubmitReassignmentResponse, + SubmitRequest, + SubmitResponse, +} +import com.daml.ledger.api.v2.commands.{Command, Commands, DisclosedContract} +import com.daml.ledger.api.v2.completion.Completion +import com.daml.ledger.api.v2.event.CreatedEvent +import com.daml.ledger.api.v2.event_query_service.EventQueryServiceGrpc.EventQueryServiceStub +import com.daml.ledger.api.v2.event_query_service.{ + EventQueryServiceGrpc, + GetEventsByContractIdRequest, + GetEventsByContractIdResponse, +} import com.daml.ledger.api.v2.participant_offset.ParticipantOffset +import com.daml.ledger.api.v2.reassignment.{AssignedEvent, Reassignment, UnassignedEvent} +import com.daml.ledger.api.v2.reassignment_command.{ + AssignCommand, + ReassignmentCommand, + UnassignCommand, +} +import com.daml.ledger.api.v2.state_service.StateServiceGrpc.StateServiceStub +import com.daml.ledger.api.v2.state_service.{ + GetActiveContractsRequest, + GetActiveContractsResponse, + GetConnectedDomainsRequest, + GetConnectedDomainsResponse, + GetLedgerEndRequest, + GetLedgerEndResponse, + StateServiceGrpc, +} import com.daml.ledger.api.v2.testing.time_service.TimeServiceGrpc.TimeServiceStub -import com.daml.ledger.api.v2.testing.time_service.{GetTimeRequest, SetTimeRequest, TimeServiceGrpc} -import com.digitalasset.canton.LfPartyId +import com.daml.ledger.api.v2.testing.time_service.{ + GetTimeRequest, + GetTimeResponse, + SetTimeRequest, + TimeServiceGrpc, +} +import com.daml.ledger.api.v2.transaction.{Transaction, TransactionTree} +import com.daml.ledger.api.v2.transaction_filter.{ + Filters, + InclusiveFilters, + TemplateFilter, + TransactionFilter, +} +import com.daml.ledger.api.v2.update_service.UpdateServiceGrpc.UpdateServiceStub +import com.daml.ledger.api.v2.update_service.{ + GetTransactionByIdRequest, + GetTransactionTreeResponse, + GetUpdateTreesResponse, + GetUpdatesRequest, + GetUpdatesResponse, + UpdateServiceGrpc, +} import com.digitalasset.canton.admin.api.client.commands.GrpcAdminCommand.{ DefaultUnboundedTimeout, + ServerEnforcedTimeout, TimeoutType, } import com.digitalasset.canton.admin.api.client.data.{ LedgerApiUser, LedgerMeteringReport, ListLedgerApiUsersResult, + TemplateId, UserRights, } import com.digitalasset.canton.config.RequireTypes.PositiveInt import com.digitalasset.canton.data.CantonTimestamp -import com.digitalasset.canton.ledger.api.domain import com.digitalasset.canton.ledger.api.domain.{IdentityProviderId, JwksUrl} +import com.digitalasset.canton.ledger.api.{DeduplicationPeriod, domain} import com.digitalasset.canton.ledger.client.services.admin.IdentityProviderConfigClient +import com.digitalasset.canton.logging.ErrorLoggingContext +import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} +import com.digitalasset.canton.networking.grpc.ForwardingStreamObserver +import com.digitalasset.canton.protocol.LfContractId import com.digitalasset.canton.serialization.ProtoConverter -import com.digitalasset.canton.topology.PartyId +import com.digitalasset.canton.topology.{DomainId, PartyId} import com.digitalasset.canton.util.BinaryFileUtil +import com.digitalasset.canton.{LfPackageId, LfPartyId, config} import com.google.protobuf.empty.Empty import com.google.protobuf.field_mask.FieldMask import io.grpc.* +import io.grpc.stub.StreamObserver +import java.time.Instant +import java.util.UUID import java.util.concurrent.ScheduledExecutorService import scala.concurrent.duration.FiniteDuration import scala.concurrent.{ExecutionContext, Future} @@ -800,6 +877,736 @@ object LedgerApiCommands { } } + trait SubscribeBase[Req, Resp, Res] extends GrpcAdminCommand[Req, AutoCloseable, AutoCloseable] { + // The subscription should never be cut short because of a gRPC timeout + override def timeoutType: TimeoutType = ServerEnforcedTimeout + + def observer: StreamObserver[Res] + + def doRequest( + service: this.Svc, + request: Req, + rawObserver: StreamObserver[Resp], + ): Unit + + def extractResults(response: Resp): IterableOnce[Res] + + implicit def loggingContext: ErrorLoggingContext + + override def submitRequest( + service: this.Svc, + request: Req, + ): Future[AutoCloseable] = { + val rawObserver = new ForwardingStreamObserver[Resp, Res](observer, extractResults) + val context = Context.current().withCancellation() + context.run(() => doRequest(service, request, rawObserver)) + Future.successful(context) + } + + override def handleResponse(response: AutoCloseable): Either[String, AutoCloseable] = Right( + response + ) + } + + object UpdateService { + + sealed trait UpdateTreeWrapper { + def updateId: String + } + sealed trait UpdateWrapper { + def updateId: String + def createEvent: Option[CreatedEvent] = + this match { + case UpdateService.TransactionWrapper(t) => t.events.headOption.map(_.getCreated) + case u: UpdateService.AssignedWrapper => u.assignedEvent.createdEvent + case _: UpdateService.UnassignedWrapper => None + } + } + object UpdateWrapper { + def isUnassigedWrapper(wrapper: UpdateWrapper): Boolean = wrapper match { + case UnassignedWrapper(_, _) => true + case _ => false + } + } + final case class TransactionTreeWrapper(transactionTree: TransactionTree) + extends UpdateTreeWrapper { + override def updateId: String = transactionTree.updateId + } + final case class TransactionWrapper(transaction: Transaction) extends UpdateWrapper { + override def updateId: String = transaction.updateId + } + sealed trait ReassignmentWrapper extends UpdateTreeWrapper with UpdateWrapper { + def reassignment: Reassignment + def unassignId: String = reassignment.getUnassignedEvent.unassignId + def offset: ParticipantOffset = ParticipantOffset( + ParticipantOffset.Value.Absolute(reassignment.offset) + ) + } + object ReassignmentWrapper { + def apply(reassignment: Reassignment): ReassignmentWrapper = { + val event = reassignment.event + event.assignedEvent + .map[ReassignmentWrapper](AssignedWrapper(reassignment, _)) + .orElse( + event.unassignedEvent.map[ReassignmentWrapper](UnassignedWrapper(reassignment, _)) + ) + .getOrElse( + throw new IllegalStateException( + s"Invalid reassignment event (only supported UnassignedEvent and AssignedEvent): ${reassignment.event}" + ) + ) + } + } + final case class AssignedWrapper(reassignment: Reassignment, assignedEvent: AssignedEvent) + extends ReassignmentWrapper { + override def updateId: String = reassignment.updateId + } + final case class UnassignedWrapper(reassignment: Reassignment, unassignedEvent: UnassignedEvent) + extends ReassignmentWrapper { + override def updateId: String = reassignment.updateId + } + + trait BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { + override type Svc = UpdateServiceStub + + override def createService(channel: ManagedChannel): UpdateServiceStub = + UpdateServiceGrpc.stub(channel) + } + + trait SubscribeUpdateBase[Resp, Res] + extends BaseCommand[GetUpdatesRequest, AutoCloseable, AutoCloseable] + with SubscribeBase[GetUpdatesRequest, Resp, Res] { + + def beginExclusive: ParticipantOffset + + def endInclusive: Option[ParticipantOffset] + + def filter: TransactionFilter + + def verbose: Boolean + + override def createRequest(): Either[String, GetUpdatesRequest] = Right { + GetUpdatesRequest( + beginExclusive = Some(beginExclusive), + endInclusive = endInclusive, + verbose = verbose, + filter = Some(filter), + ) + } + } + + final case class SubscribeTrees( + override val observer: StreamObserver[UpdateTreeWrapper], + override val beginExclusive: ParticipantOffset, + override val endInclusive: Option[ParticipantOffset], + override val filter: TransactionFilter, + override val verbose: Boolean, + )(override implicit val loggingContext: ErrorLoggingContext) + extends SubscribeUpdateBase[GetUpdateTreesResponse, UpdateTreeWrapper] { + override def doRequest( + service: UpdateServiceStub, + request: GetUpdatesRequest, + rawObserver: StreamObserver[GetUpdateTreesResponse], + ): Unit = + service.getUpdateTrees(request, rawObserver) + + override def extractResults( + response: GetUpdateTreesResponse + ): IterableOnce[UpdateTreeWrapper] = + response.update.transactionTree + .map[UpdateTreeWrapper](TransactionTreeWrapper) + .orElse(response.update.reassignment.map(ReassignmentWrapper(_))) + } + + final case class SubscribeFlat( + override val observer: StreamObserver[UpdateWrapper], + override val beginExclusive: ParticipantOffset, + override val endInclusive: Option[ParticipantOffset], + override val filter: TransactionFilter, + override val verbose: Boolean, + )(override implicit val loggingContext: ErrorLoggingContext) + extends SubscribeUpdateBase[GetUpdatesResponse, UpdateWrapper] { + override def doRequest( + service: UpdateServiceStub, + request: GetUpdatesRequest, + rawObserver: StreamObserver[GetUpdatesResponse], + ): Unit = + service.getUpdates(request, rawObserver) + + override def extractResults(response: GetUpdatesResponse): IterableOnce[UpdateWrapper] = + response.update.transaction + .map[UpdateWrapper](TransactionWrapper) + .orElse(response.update.reassignment.map(ReassignmentWrapper(_))) + } + + final case class GetTransactionById(parties: Set[LfPartyId], id: String)(implicit + ec: ExecutionContext + ) extends BaseCommand[GetTransactionByIdRequest, GetTransactionTreeResponse, Option[ + TransactionTree + ]] + with PrettyPrinting { + override def createRequest(): Either[String, GetTransactionByIdRequest] = Right { + GetTransactionByIdRequest( + updateId = id, + requestingParties = parties.toSeq, + ) + } + + override def submitRequest( + service: UpdateServiceStub, + request: GetTransactionByIdRequest, + ): Future[GetTransactionTreeResponse] = { + // The Ledger API will throw an error if it can't find a transaction by ID. + // However, as Canton is distributed, a transaction ID might show up later, so we don't treat this as + // an error and change it to a None + service.getTransactionTreeById(request).recover { + case e: StatusRuntimeException if e.getStatus.getCode == Status.Code.NOT_FOUND => + GetTransactionTreeResponse(None) + } + } + + override def handleResponse( + response: GetTransactionTreeResponse + ): Either[String, Option[TransactionTree]] = + Right(response.transaction) + + override def pretty: Pretty[GetTransactionById] = + prettyOfClass( + param("id", _.id.unquoted), + param("parties", _.parties), + ) + } + + } + + private[commands] trait SubmitCommand extends PrettyPrinting { + def actAs: Seq[LfPartyId] + def readAs: Seq[LfPartyId] + def commands: Seq[Command] + def workflowId: String + def commandId: String + def deduplicationPeriod: Option[DeduplicationPeriod] + def submissionId: String + def minLedgerTimeAbs: Option[Instant] + def disclosedContracts: Seq[DisclosedContract] + def domainId: Option[DomainId] + def applicationId: String + def packageIdSelectionPreference: Seq[LfPackageId] + + protected def mkCommand: Commands = Commands( + workflowId = workflowId, + applicationId = applicationId, + commandId = if (commandId.isEmpty) UUID.randomUUID().toString else commandId, + actAs = actAs, + readAs = readAs, + commands = commands, + deduplicationPeriod = deduplicationPeriod.fold( + Commands.DeduplicationPeriod.Empty: Commands.DeduplicationPeriod + ) { + case DeduplicationPeriod.DeduplicationDuration(duration) => + Commands.DeduplicationPeriod.DeduplicationDuration( + ProtoConverter.DurationConverter.toProtoPrimitive(duration) + ) + case DeduplicationPeriod.DeduplicationOffset(offset) => + Commands.DeduplicationPeriod.DeduplicationOffset( + offset.toHexString + ) + }, + minLedgerTimeAbs = minLedgerTimeAbs.map(ProtoConverter.InstantConverter.toProtoPrimitive), + submissionId = submissionId, + disclosedContracts = disclosedContracts, + domainId = domainId.map(_.toProtoPrimitive).getOrElse(""), + packageIdSelectionPreference = packageIdSelectionPreference.map(_.toString), + ) + + override def pretty: Pretty[this.type] = + prettyOfClass( + param("actAs", _.actAs), + param("readAs", _.readAs), + param("commandId", _.commandId.singleQuoted), + param("workflowId", _.workflowId.singleQuoted), + param("submissionId", _.submissionId.singleQuoted), + param("deduplicationPeriod", _.deduplicationPeriod), + paramIfDefined("minLedgerTimeAbs", _.minLedgerTimeAbs), + paramWithoutValue("commands"), + ) + } + + object CommandSubmissionService { + trait BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { + override type Svc = CommandSubmissionServiceStub + override def createService(channel: ManagedChannel): CommandSubmissionServiceStub = + CommandSubmissionServiceGrpc.stub(channel) + } + + final case class Submit( + override val actAs: Seq[LfPartyId], + override val readAs: Seq[LfPartyId], + override val commands: Seq[Command], + override val workflowId: String, + override val commandId: String, + override val deduplicationPeriod: Option[DeduplicationPeriod], + override val submissionId: String, + override val minLedgerTimeAbs: Option[Instant], + override val disclosedContracts: Seq[DisclosedContract], + override val domainId: Option[DomainId], + override val applicationId: String, + override val packageIdSelectionPreference: Seq[LfPackageId], + ) extends SubmitCommand + with BaseCommand[SubmitRequest, SubmitResponse, Unit] { + override def createRequest(): Either[String, SubmitRequest] = Right( + SubmitRequest(commands = Some(mkCommand)) + ) + + override def submitRequest( + service: CommandSubmissionServiceStub, + request: SubmitRequest, + ): Future[SubmitResponse] = { + service.submit(request) + } + + override def handleResponse(response: SubmitResponse): Either[String, Unit] = Right(()) + } + + final case class SubmitAssignCommand( + workflowId: String, + applicationId: String, + commandId: String, + submitter: LfPartyId, + submissionId: String, + unassignId: String, + source: DomainId, + target: DomainId, + ) extends BaseCommand[SubmitReassignmentRequest, SubmitReassignmentResponse, Unit] { + override def createRequest(): Either[String, SubmitReassignmentRequest] = Right( + SubmitReassignmentRequest( + Some( + ReassignmentCommand( + workflowId = workflowId, + applicationId = applicationId, + commandId = commandId, + submitter = submitter.toString, + command = ReassignmentCommand.Command.AssignCommand( + AssignCommand( + unassignId = unassignId, + source = source.toProtoPrimitive, + target = target.toProtoPrimitive, + ) + ), + submissionId = submissionId, + ) + ) + ) + ) + + override def submitRequest( + service: CommandSubmissionServiceStub, + request: SubmitReassignmentRequest, + ): Future[SubmitReassignmentResponse] = { + service.submitReassignment(request) + } + + override def handleResponse(response: SubmitReassignmentResponse): Either[String, Unit] = + Right(()) + } + + final case class SubmitUnassignCommand( + workflowId: String, + applicationId: String, + commandId: String, + submitter: LfPartyId, + submissionId: String, + contractId: LfContractId, + source: DomainId, + target: DomainId, + ) extends BaseCommand[SubmitReassignmentRequest, SubmitReassignmentResponse, Unit] { + override def createRequest(): Either[String, SubmitReassignmentRequest] = Right( + SubmitReassignmentRequest( + Some( + ReassignmentCommand( + workflowId = workflowId, + applicationId = applicationId, + commandId = commandId, + submitter = submitter.toString, + command = ReassignmentCommand.Command.UnassignCommand( + UnassignCommand( + contractId = contractId.coid.toString, + source = source.toProtoPrimitive, + target = target.toProtoPrimitive, + ) + ), + submissionId = submissionId, + ) + ) + ) + ) + + override def submitRequest( + service: CommandSubmissionServiceStub, + request: SubmitReassignmentRequest, + ): Future[SubmitReassignmentResponse] = { + service.submitReassignment(request) + } + + override def handleResponse(response: SubmitReassignmentResponse): Either[String, Unit] = + Right(()) + } + } + + object CommandService { + trait BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { + override type Svc = CommandServiceStub + override def createService(channel: ManagedChannel): CommandServiceStub = + CommandServiceGrpc.stub(channel) + } + + final case class SubmitAndWaitTransactionTree( + override val actAs: Seq[LfPartyId], + override val readAs: Seq[LfPartyId], + override val commands: Seq[Command], + override val workflowId: String, + override val commandId: String, + override val deduplicationPeriod: Option[DeduplicationPeriod], + override val submissionId: String, + override val minLedgerTimeAbs: Option[Instant], + override val disclosedContracts: Seq[DisclosedContract], + override val domainId: Option[DomainId], + override val applicationId: String, + override val packageIdSelectionPreference: Seq[LfPackageId], + ) extends SubmitCommand + with BaseCommand[ + SubmitAndWaitRequest, + SubmitAndWaitForTransactionTreeResponse, + TransactionTree, + ] { + + override def createRequest(): Either[String, SubmitAndWaitRequest] = + Right(SubmitAndWaitRequest(commands = Some(mkCommand))) + + override def submitRequest( + service: CommandServiceStub, + request: SubmitAndWaitRequest, + ): Future[SubmitAndWaitForTransactionTreeResponse] = + service.submitAndWaitForTransactionTree(request) + + override def handleResponse( + response: SubmitAndWaitForTransactionTreeResponse + ): Either[String, TransactionTree] = + response.transaction.toRight("Received response without any transaction tree") + + override def timeoutType: TimeoutType = DefaultUnboundedTimeout + + } + + final case class SubmitAndWaitTransaction( + override val actAs: Seq[LfPartyId], + override val readAs: Seq[LfPartyId], + override val commands: Seq[Command], + override val workflowId: String, + override val commandId: String, + override val deduplicationPeriod: Option[DeduplicationPeriod], + override val submissionId: String, + override val minLedgerTimeAbs: Option[Instant], + override val disclosedContracts: Seq[DisclosedContract], + override val domainId: Option[DomainId], + override val applicationId: String, + override val packageIdSelectionPreference: Seq[LfPackageId], + ) extends SubmitCommand + with BaseCommand[SubmitAndWaitRequest, SubmitAndWaitForTransactionResponse, Transaction] { + + override def createRequest(): Either[String, SubmitAndWaitRequest] = + Right(SubmitAndWaitRequest(commands = Some(mkCommand))) + + override def submitRequest( + service: CommandServiceStub, + request: SubmitAndWaitRequest, + ): Future[SubmitAndWaitForTransactionResponse] = + service.submitAndWaitForTransaction(request) + + override def handleResponse( + response: SubmitAndWaitForTransactionResponse + ): Either[String, Transaction] = + response.transaction.toRight("Received response without any transaction") + + override def timeoutType: TimeoutType = DefaultUnboundedTimeout + + } + } + + object StateService { + abstract class BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { + override type Svc = StateServiceStub + + override def createService(channel: ManagedChannel): StateServiceStub = + StateServiceGrpc.stub(channel) + } + + final case class LedgerEnd() + extends BaseCommand[GetLedgerEndRequest, GetLedgerEndResponse, ParticipantOffset] { + + override def createRequest(): Either[String, GetLedgerEndRequest] = + Right(GetLedgerEndRequest()) + + override def submitRequest( + service: StateServiceStub, + request: GetLedgerEndRequest, + ): Future[GetLedgerEndResponse] = + service.getLedgerEnd(request) + + override def handleResponse( + response: GetLedgerEndResponse + ): Either[String, ParticipantOffset] = + response.offset.toRight("Empty LedgerEndResponse received without offset") + } + + final case class GetConnectedDomains(partyId: LfPartyId) + extends BaseCommand[ + GetConnectedDomainsRequest, + GetConnectedDomainsResponse, + GetConnectedDomainsResponse, + ] { + + override def createRequest(): Either[String, GetConnectedDomainsRequest] = + Right(GetConnectedDomainsRequest(partyId.toString)) + + override def submitRequest( + service: StateServiceStub, + request: GetConnectedDomainsRequest, + ): Future[GetConnectedDomainsResponse] = + service.getConnectedDomains(request) + + override def handleResponse( + response: GetConnectedDomainsResponse + ): Either[String, GetConnectedDomainsResponse] = + Right(response) + } + + final case class GetActiveContracts( + observer: StreamObserver[GetActiveContractsResponse], + parties: Set[LfPartyId], + limit: PositiveInt, + templateFilter: Seq[TemplateId] = Seq.empty, + activeAtOffset: String = "", + verbose: Boolean = true, + timeout: FiniteDuration, + includeCreatedEventBlob: Boolean = false, + )(override implicit val loggingContext: ErrorLoggingContext) + extends BaseCommand[GetActiveContractsRequest, AutoCloseable, AutoCloseable] + with SubscribeBase[ + GetActiveContractsRequest, + GetActiveContractsResponse, + GetActiveContractsResponse, + ] { + + override def createRequest(): Either[String, GetActiveContractsRequest] = { + val filter = + if (templateFilter.nonEmpty) { + Filters( + Some( + InclusiveFilters(templateFilters = + templateFilter.map(tId => + TemplateFilter(Some(tId.toIdentifier), includeCreatedEventBlob) + ) + ) + ) + ) + } else Filters.defaultInstance + Right( + GetActiveContractsRequest( + filter = Some(TransactionFilter(parties.map((_, filter)).toMap)), + verbose = verbose, + activeAtOffset = activeAtOffset, + ) + ) + } + + override def doRequest( + service: StateServiceStub, + request: GetActiveContractsRequest, + rawObserver: StreamObserver[GetActiveContractsResponse], + ): Unit = + service.getActiveContracts(request, rawObserver) + + override def extractResults( + response: GetActiveContractsResponse + ): IterableOnce[GetActiveContractsResponse] = List(response) + + // fetching ACS might take long if we fetch a lot of data + override def timeoutType: TimeoutType = DefaultUnboundedTimeout + } + } + + final case class CompletionWrapper( + completion: Completion, + checkpoint: Checkpoint, + domainId: DomainId, + ) + + object CommandCompletionService { + abstract class BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { + override type Svc = CommandCompletionServiceStub + + override def createService(channel: ManagedChannel): CommandCompletionServiceStub = + CommandCompletionServiceGrpc.stub(channel) + } + + final case class CompletionRequest( + partyId: LfPartyId, + beginOffset: ParticipantOffset, + expectedCompletions: Int, + timeout: java.time.Duration, + applicationId: String, + )(filter: CompletionWrapper => Boolean, scheduler: ScheduledExecutorService) + extends BaseCommand[ + CompletionStreamRequest, + Seq[CompletionWrapper], + Seq[CompletionWrapper], + ] { + + override def createRequest(): Either[String, CompletionStreamRequest] = + Right( + CompletionStreamRequest( + applicationId = applicationId, + parties = Seq(partyId), + beginExclusive = Some(beginOffset), + ) + ) + + override def submitRequest( + service: CommandCompletionServiceStub, + request: CompletionStreamRequest, + ): Future[Seq[CompletionWrapper]] = { + import scala.jdk.DurationConverters.* + GrpcAdminCommand + .streamedResponse[CompletionStreamRequest, CompletionStreamResponse, CompletionWrapper]( + service.completionStream, + response => + List( + CompletionWrapper( + completion = response.completion.getOrElse( + throw new IllegalStateException("Completion should be present.") + ), + checkpoint = response.checkpoint.getOrElse( + throw new IllegalStateException("Checkpoint should be present.") + ), + domainId = DomainId.tryFromString(response.domainId), + ) + ).filter(filter), + request, + expectedCompletions, + timeout.toScala, + scheduler, + ) + } + + override def handleResponse( + response: Seq[CompletionWrapper] + ): Either[String, Seq[CompletionWrapper]] = + Right(response) + + override def timeoutType: TimeoutType = ServerEnforcedTimeout + } + + final case class CompletionCheckpointRequest( + partyId: LfPartyId, + beginExclusive: ParticipantOffset, + expectedCompletions: Int, + timeout: config.NonNegativeDuration, + applicationId: String, + )(filter: Completion => Boolean, scheduler: ScheduledExecutorService) + extends BaseCommand[CompletionStreamRequest, Seq[(Completion, Option[Checkpoint])], Seq[ + (Completion, Option[Checkpoint]) + ]] { + + override def createRequest(): Either[String, CompletionStreamRequest] = + Right( + CompletionStreamRequest( + applicationId = applicationId, + parties = Seq(partyId), + beginExclusive = Some(beginExclusive), + ) + ) + + override def submitRequest( + service: CommandCompletionServiceStub, + request: CompletionStreamRequest, + ): Future[Seq[(Completion, Option[Checkpoint])]] = { + def extract(response: CompletionStreamResponse): Seq[(Completion, Option[Checkpoint])] = { + val checkpoint = response.checkpoint + + response.completion.filter(filter).map(_ -> checkpoint).toList + } + + GrpcAdminCommand.streamedResponse[ + CompletionStreamRequest, + CompletionStreamResponse, + (Completion, Option[Checkpoint]), + ]( + service.completionStream, + extract, + request, + expectedCompletions, + timeout.asFiniteApproximation, + scheduler, + ) + } + + override def handleResponse( + response: Seq[(Completion, Option[Checkpoint])] + ): Either[String, Seq[(Completion, Option[Checkpoint])]] = + Right(response) + + override def timeoutType: TimeoutType = ServerEnforcedTimeout + } + + final case class Subscribe( + observer: StreamObserver[CompletionWrapper], + parties: Seq[String], + offset: Option[ParticipantOffset], + applicationId: String, + )(implicit loggingContext: ErrorLoggingContext) + extends BaseCommand[CompletionStreamRequest, AutoCloseable, AutoCloseable] { + // The subscription should never be cut short because of a gRPC timeout + override def timeoutType: TimeoutType = ServerEnforcedTimeout + + override def createRequest(): Either[String, CompletionStreamRequest] = Right { + CompletionStreamRequest( + applicationId = applicationId, + parties = parties, + beginExclusive = offset, + ) + } + + override def submitRequest( + service: CommandCompletionServiceStub, + request: CompletionStreamRequest, + ): Future[AutoCloseable] = { + val rawObserver = new ForwardingStreamObserver[CompletionStreamResponse, CompletionWrapper]( + observer, + response => + List( + CompletionWrapper( + completion = response.completion.getOrElse( + throw new IllegalStateException("Completion should be present.") + ), + checkpoint = response.checkpoint.getOrElse( + throw new IllegalStateException("Checkpoint should be present.") + ), + domainId = DomainId.tryFromString(response.domainId), + ) + ), + ) + val context = Context.current().withCancellation() + context.run(() => service.completionStream(request, rawObserver)) + Future.successful(context) + } + + override def handleResponse(response: AutoCloseable): Either[String, AutoCloseable] = Right( + response + ) + } + } + object Time { abstract class BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { override type Svc = TimeServiceStub @@ -808,23 +1615,19 @@ object LedgerApiCommands { TimeServiceGrpc.stub(channel) } - final case class Get(timeout: FiniteDuration)(scheduler: ScheduledExecutorService)(implicit - executionContext: ExecutionContext - ) extends BaseCommand[ + final object Get + extends BaseCommand[ GetTimeRequest, - Either[String, CantonTimestamp], + GetTimeResponse, CantonTimestamp, ] { override def submitRequest( service: TimeServiceStub, request: GetTimeRequest, - ): Future[Either[String, CantonTimestamp]] = - service.getTime(request).map { - _.currentTime - .toRight("Empty timestamp received from ledger Api server") - .flatMap(CantonTimestamp.fromProtoTimestamp(_).leftMap(_.message)) - } + ): Future[GetTimeResponse] = { + service.getTime(request) + } /** Create the request from configured options */ @@ -833,8 +1636,12 @@ object LedgerApiCommands { /** Handle the response the service has provided */ override def handleResponse( - response: Either[String, CantonTimestamp] - ): Either[String, CantonTimestamp] = response + response: GetTimeResponse + ): Either[String, CantonTimestamp] = + for { + prototTimestamp <- response.currentTime.map(Right(_)).getOrElse(Left("currentTime empty")) + result <- CantonTimestamp.fromProtoTimestamp(prototTimestamp).left.map(_.message) + } yield result } final case class Set(currentTime: CantonTimestamp, newTime: CantonTimestamp) @@ -857,9 +1664,43 @@ object LedgerApiCommands { /** Handle the response the service has provided */ - override def handleResponse(response: Empty): Either[String, Unit] = Either.unit + override def handleResponse(response: Empty): Either[String, Unit] = Right(()) } } + + object QueryService { + + abstract class BaseCommand[Req, Res] extends GrpcAdminCommand[Req, Res, Res] { + override type Svc = EventQueryServiceStub + + override def createService(channel: ManagedChannel): EventQueryServiceStub = + EventQueryServiceGrpc.stub(channel) + + override def handleResponse(response: Res): Either[String, Res] = Right(response) + } + + final case class GetEventsByContractId( + contractId: String, + requestingParties: Seq[String], + ) extends BaseCommand[ + GetEventsByContractIdRequest, + GetEventsByContractIdResponse, + ] { + + override def createRequest(): Either[String, GetEventsByContractIdRequest] = Right( + GetEventsByContractIdRequest( + contractId = contractId, + requestingParties = requestingParties, + ) + ) + + override def submitRequest( + service: EventQueryServiceStub, + request: GetEventsByContractIdRequest, + ): Future[GetEventsByContractIdResponse] = service.getEventsByContractId(request) + + } + } } diff --git a/canton/community/app-base/src/main/scala/com/digitalasset/canton/admin/api/client/commands/LedgerApiV2Commands.scala b/canton/community/app-base/src/main/scala/com/digitalasset/canton/admin/api/client/commands/LedgerApiV2Commands.scala deleted file mode 100644 index ff9af800835..00000000000 --- a/canton/community/app-base/src/main/scala/com/digitalasset/canton/admin/api/client/commands/LedgerApiV2Commands.scala +++ /dev/null @@ -1,932 +0,0 @@ -// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.digitalasset.canton.admin.api.client.commands - -import com.daml.ledger.api.v2.checkpoint.Checkpoint -import com.daml.ledger.api.v2.command_completion_service.CommandCompletionServiceGrpc.CommandCompletionServiceStub -import com.daml.ledger.api.v2.command_completion_service.{ - CommandCompletionServiceGrpc, - CompletionStreamRequest, - CompletionStreamResponse, -} -import com.daml.ledger.api.v2.command_service.CommandServiceGrpc.CommandServiceStub -import com.daml.ledger.api.v2.command_service.{ - CommandServiceGrpc, - SubmitAndWaitForTransactionResponse, - SubmitAndWaitForTransactionTreeResponse, - SubmitAndWaitRequest, -} -import com.daml.ledger.api.v2.command_submission_service.CommandSubmissionServiceGrpc.CommandSubmissionServiceStub -import com.daml.ledger.api.v2.command_submission_service.{ - CommandSubmissionServiceGrpc, - SubmitReassignmentRequest, - SubmitReassignmentResponse, - SubmitRequest, - SubmitResponse, -} -import com.daml.ledger.api.v2.commands.{Command, Commands, DisclosedContract} -import com.daml.ledger.api.v2.completion.Completion -import com.daml.ledger.api.v2.event.CreatedEvent -import com.daml.ledger.api.v2.event_query_service.EventQueryServiceGrpc.EventQueryServiceStub -import com.daml.ledger.api.v2.event_query_service.{ - EventQueryServiceGrpc, - GetEventsByContractIdRequest, - GetEventsByContractIdResponse, -} -import com.daml.ledger.api.v2.participant_offset.ParticipantOffset -import com.daml.ledger.api.v2.reassignment.{AssignedEvent, Reassignment, UnassignedEvent} -import com.daml.ledger.api.v2.reassignment_command.{ - AssignCommand, - ReassignmentCommand, - UnassignCommand, -} -import com.daml.ledger.api.v2.state_service.StateServiceGrpc.StateServiceStub -import com.daml.ledger.api.v2.state_service.{ - GetActiveContractsRequest, - GetActiveContractsResponse, - GetConnectedDomainsRequest, - GetConnectedDomainsResponse, - GetLedgerEndRequest, - GetLedgerEndResponse, - StateServiceGrpc, -} -import com.daml.ledger.api.v2.testing.time_service.TimeServiceGrpc.TimeServiceStub -import com.daml.ledger.api.v2.testing.time_service.{ - GetTimeRequest, - GetTimeResponse, - SetTimeRequest, - TimeServiceGrpc, -} -import com.daml.ledger.api.v2.transaction.{Transaction, TransactionTree} -import com.daml.ledger.api.v2.transaction_filter.{ - Filters, - InclusiveFilters, - TemplateFilter, - TransactionFilter, -} -import com.daml.ledger.api.v2.update_service.UpdateServiceGrpc.UpdateServiceStub -import com.daml.ledger.api.v2.update_service.{ - GetTransactionByIdRequest, - GetTransactionTreeResponse, - GetUpdateTreesResponse, - GetUpdatesRequest, - GetUpdatesResponse, - UpdateServiceGrpc, -} -import com.digitalasset.canton.admin.api.client.commands.GrpcAdminCommand.{ - DefaultUnboundedTimeout, - ServerEnforcedTimeout, - TimeoutType, -} -import com.digitalasset.canton.admin.api.client.data.TemplateId -import com.digitalasset.canton.config.RequireTypes.PositiveInt -import com.digitalasset.canton.data.CantonTimestamp -import com.digitalasset.canton.ledger.api.DeduplicationPeriod -import com.digitalasset.canton.logging.ErrorLoggingContext -import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} -import com.digitalasset.canton.networking.grpc.ForwardingStreamObserver -import com.digitalasset.canton.protocol.LfContractId -import com.digitalasset.canton.serialization.ProtoConverter -import com.digitalasset.canton.topology.DomainId -import com.digitalasset.canton.{LfPackageId, LfPartyId, config} -import com.google.protobuf.empty.Empty -import io.grpc.* -import io.grpc.stub.StreamObserver - -import java.time.Instant -import java.util.UUID -import java.util.concurrent.ScheduledExecutorService -import scala.concurrent.duration.FiniteDuration -import scala.concurrent.{ExecutionContext, Future} - -// TODO(#15280) delete LedgerApiCommands, and rename this to LedgerApiCommands -object LedgerApiV2Commands { - - trait SubscribeBase[Req, Resp, Res] extends GrpcAdminCommand[Req, AutoCloseable, AutoCloseable] { - // The subscription should never be cut short because of a gRPC timeout - override def timeoutType: TimeoutType = ServerEnforcedTimeout - - def observer: StreamObserver[Res] - - def doRequest( - service: this.Svc, - request: Req, - rawObserver: StreamObserver[Resp], - ): Unit - - def extractResults(response: Resp): IterableOnce[Res] - - implicit def loggingContext: ErrorLoggingContext - - override def submitRequest( - service: this.Svc, - request: Req, - ): Future[AutoCloseable] = { - val rawObserver = new ForwardingStreamObserver[Resp, Res](observer, extractResults) - val context = Context.current().withCancellation() - context.run(() => doRequest(service, request, rawObserver)) - Future.successful(context) - } - - override def handleResponse(response: AutoCloseable): Either[String, AutoCloseable] = Right( - response - ) - } - - object UpdateService { - - sealed trait UpdateTreeWrapper { - def updateId: String - } - sealed trait UpdateWrapper { - def updateId: String - def createEvent: Option[CreatedEvent] = - this match { - case UpdateService.TransactionWrapper(t) => t.events.headOption.map(_.getCreated) - case u: UpdateService.AssignedWrapper => u.assignedEvent.createdEvent - case _: UpdateService.UnassignedWrapper => None - } - } - object UpdateWrapper { - def isUnassigedWrapper(wrapper: UpdateWrapper): Boolean = wrapper match { - case UnassignedWrapper(_, _) => true - case _ => false - } - } - final case class TransactionTreeWrapper(transactionTree: TransactionTree) - extends UpdateTreeWrapper { - override def updateId: String = transactionTree.updateId - } - final case class TransactionWrapper(transaction: Transaction) extends UpdateWrapper { - override def updateId: String = transaction.updateId - } - sealed trait ReassignmentWrapper extends UpdateTreeWrapper with UpdateWrapper { - def reassignment: Reassignment - def unassignId: String = reassignment.getUnassignedEvent.unassignId - def offset: ParticipantOffset = ParticipantOffset( - ParticipantOffset.Value.Absolute(reassignment.offset) - ) - } - object ReassignmentWrapper { - def apply(reassignment: Reassignment): ReassignmentWrapper = { - val event = reassignment.event - event.assignedEvent - .map[ReassignmentWrapper](AssignedWrapper(reassignment, _)) - .orElse( - event.unassignedEvent.map[ReassignmentWrapper](UnassignedWrapper(reassignment, _)) - ) - .getOrElse( - throw new IllegalStateException( - s"Invalid reassignment event (only supported UnassignedEvent and AssignedEvent): ${reassignment.event}" - ) - ) - } - } - final case class AssignedWrapper(reassignment: Reassignment, assignedEvent: AssignedEvent) - extends ReassignmentWrapper { - override def updateId: String = reassignment.updateId - } - final case class UnassignedWrapper(reassignment: Reassignment, unassignedEvent: UnassignedEvent) - extends ReassignmentWrapper { - override def updateId: String = reassignment.updateId - } - - trait BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { - override type Svc = UpdateServiceStub - - override def createService(channel: ManagedChannel): UpdateServiceStub = - UpdateServiceGrpc.stub(channel) - } - - trait SubscribeUpdateBase[Resp, Res] - extends BaseCommand[GetUpdatesRequest, AutoCloseable, AutoCloseable] - with SubscribeBase[GetUpdatesRequest, Resp, Res] { - - def beginExclusive: ParticipantOffset - - def endInclusive: Option[ParticipantOffset] - - def filter: TransactionFilter - - def verbose: Boolean - - override def createRequest(): Either[String, GetUpdatesRequest] = Right { - GetUpdatesRequest( - beginExclusive = Some(beginExclusive), - endInclusive = endInclusive, - verbose = verbose, - filter = Some(filter), - ) - } - } - - final case class SubscribeTrees( - override val observer: StreamObserver[UpdateTreeWrapper], - override val beginExclusive: ParticipantOffset, - override val endInclusive: Option[ParticipantOffset], - override val filter: TransactionFilter, - override val verbose: Boolean, - )(override implicit val loggingContext: ErrorLoggingContext) - extends SubscribeUpdateBase[GetUpdateTreesResponse, UpdateTreeWrapper] { - override def doRequest( - service: UpdateServiceStub, - request: GetUpdatesRequest, - rawObserver: StreamObserver[GetUpdateTreesResponse], - ): Unit = - service.getUpdateTrees(request, rawObserver) - - override def extractResults( - response: GetUpdateTreesResponse - ): IterableOnce[UpdateTreeWrapper] = - response.update.transactionTree - .map[UpdateTreeWrapper](TransactionTreeWrapper) - .orElse(response.update.reassignment.map(ReassignmentWrapper(_))) - } - - final case class SubscribeFlat( - override val observer: StreamObserver[UpdateWrapper], - override val beginExclusive: ParticipantOffset, - override val endInclusive: Option[ParticipantOffset], - override val filter: TransactionFilter, - override val verbose: Boolean, - )(override implicit val loggingContext: ErrorLoggingContext) - extends SubscribeUpdateBase[GetUpdatesResponse, UpdateWrapper] { - override def doRequest( - service: UpdateServiceStub, - request: GetUpdatesRequest, - rawObserver: StreamObserver[GetUpdatesResponse], - ): Unit = - service.getUpdates(request, rawObserver) - - override def extractResults(response: GetUpdatesResponse): IterableOnce[UpdateWrapper] = - response.update.transaction - .map[UpdateWrapper](TransactionWrapper) - .orElse(response.update.reassignment.map(ReassignmentWrapper(_))) - } - - final case class GetTransactionById(parties: Set[LfPartyId], id: String)(implicit - ec: ExecutionContext - ) extends BaseCommand[GetTransactionByIdRequest, GetTransactionTreeResponse, Option[ - TransactionTree - ]] - with PrettyPrinting { - override def createRequest(): Either[String, GetTransactionByIdRequest] = Right { - GetTransactionByIdRequest( - updateId = id, - requestingParties = parties.toSeq, - ) - } - - override def submitRequest( - service: UpdateServiceStub, - request: GetTransactionByIdRequest, - ): Future[GetTransactionTreeResponse] = { - // The Ledger API will throw an error if it can't find a transaction by ID. - // However, as Canton is distributed, a transaction ID might show up later, so we don't treat this as - // an error and change it to a None - service.getTransactionTreeById(request).recover { - case e: StatusRuntimeException if e.getStatus.getCode == Status.Code.NOT_FOUND => - GetTransactionTreeResponse(None) - } - } - - override def handleResponse( - response: GetTransactionTreeResponse - ): Either[String, Option[TransactionTree]] = - Right(response.transaction) - - override def pretty: Pretty[GetTransactionById] = - prettyOfClass( - param("id", _.id.unquoted), - param("parties", _.parties), - ) - } - - } - - private[commands] trait SubmitCommand extends PrettyPrinting { - def actAs: Seq[LfPartyId] - def readAs: Seq[LfPartyId] - def commands: Seq[Command] - def workflowId: String - def commandId: String - def deduplicationPeriod: Option[DeduplicationPeriod] - def submissionId: String - def minLedgerTimeAbs: Option[Instant] - def disclosedContracts: Seq[DisclosedContract] - def domainId: Option[DomainId] - def applicationId: String - def packageIdSelectionPreference: Seq[LfPackageId] - - protected def mkCommand: Commands = Commands( - workflowId = workflowId, - applicationId = applicationId, - commandId = if (commandId.isEmpty) UUID.randomUUID().toString else commandId, - actAs = actAs, - readAs = readAs, - commands = commands, - deduplicationPeriod = deduplicationPeriod.fold( - Commands.DeduplicationPeriod.Empty: Commands.DeduplicationPeriod - ) { - case DeduplicationPeriod.DeduplicationDuration(duration) => - Commands.DeduplicationPeriod.DeduplicationDuration( - ProtoConverter.DurationConverter.toProtoPrimitive(duration) - ) - case DeduplicationPeriod.DeduplicationOffset(offset) => - Commands.DeduplicationPeriod.DeduplicationOffset( - offset.toHexString - ) - }, - minLedgerTimeAbs = minLedgerTimeAbs.map(ProtoConverter.InstantConverter.toProtoPrimitive), - submissionId = submissionId, - disclosedContracts = disclosedContracts, - domainId = domainId.map(_.toProtoPrimitive).getOrElse(""), - packageIdSelectionPreference = packageIdSelectionPreference.map(_.toString), - ) - - override def pretty: Pretty[this.type] = - prettyOfClass( - param("actAs", _.actAs), - param("readAs", _.readAs), - param("commandId", _.commandId.singleQuoted), - param("workflowId", _.workflowId.singleQuoted), - param("submissionId", _.submissionId.singleQuoted), - param("deduplicationPeriod", _.deduplicationPeriod), - paramIfDefined("minLedgerTimeAbs", _.minLedgerTimeAbs), - paramWithoutValue("commands"), - ) - } - - object CommandSubmissionService { - trait BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { - override type Svc = CommandSubmissionServiceStub - override def createService(channel: ManagedChannel): CommandSubmissionServiceStub = - CommandSubmissionServiceGrpc.stub(channel) - } - - final case class Submit( - override val actAs: Seq[LfPartyId], - override val readAs: Seq[LfPartyId], - override val commands: Seq[Command], - override val workflowId: String, - override val commandId: String, - override val deduplicationPeriod: Option[DeduplicationPeriod], - override val submissionId: String, - override val minLedgerTimeAbs: Option[Instant], - override val disclosedContracts: Seq[DisclosedContract], - override val domainId: Option[DomainId], - override val applicationId: String, - override val packageIdSelectionPreference: Seq[LfPackageId], - ) extends SubmitCommand - with BaseCommand[SubmitRequest, SubmitResponse, Unit] { - override def createRequest(): Either[String, SubmitRequest] = Right( - SubmitRequest(commands = Some(mkCommand)) - ) - - override def submitRequest( - service: CommandSubmissionServiceStub, - request: SubmitRequest, - ): Future[SubmitResponse] = { - service.submit(request) - } - - override def handleResponse(response: SubmitResponse): Either[String, Unit] = Right(()) - } - - final case class SubmitAssignCommand( - workflowId: String, - applicationId: String, - commandId: String, - submitter: LfPartyId, - submissionId: String, - unassignId: String, - source: DomainId, - target: DomainId, - ) extends BaseCommand[SubmitReassignmentRequest, SubmitReassignmentResponse, Unit] { - override def createRequest(): Either[String, SubmitReassignmentRequest] = Right( - SubmitReassignmentRequest( - Some( - ReassignmentCommand( - workflowId = workflowId, - applicationId = applicationId, - commandId = commandId, - submitter = submitter.toString, - command = ReassignmentCommand.Command.AssignCommand( - AssignCommand( - unassignId = unassignId, - source = source.toProtoPrimitive, - target = target.toProtoPrimitive, - ) - ), - submissionId = submissionId, - ) - ) - ) - ) - - override def submitRequest( - service: CommandSubmissionServiceStub, - request: SubmitReassignmentRequest, - ): Future[SubmitReassignmentResponse] = { - service.submitReassignment(request) - } - - override def handleResponse(response: SubmitReassignmentResponse): Either[String, Unit] = - Right(()) - } - - final case class SubmitUnassignCommand( - workflowId: String, - applicationId: String, - commandId: String, - submitter: LfPartyId, - submissionId: String, - contractId: LfContractId, - source: DomainId, - target: DomainId, - ) extends BaseCommand[SubmitReassignmentRequest, SubmitReassignmentResponse, Unit] { - override def createRequest(): Either[String, SubmitReassignmentRequest] = Right( - SubmitReassignmentRequest( - Some( - ReassignmentCommand( - workflowId = workflowId, - applicationId = applicationId, - commandId = commandId, - submitter = submitter.toString, - command = ReassignmentCommand.Command.UnassignCommand( - UnassignCommand( - contractId = contractId.coid.toString, - source = source.toProtoPrimitive, - target = target.toProtoPrimitive, - ) - ), - submissionId = submissionId, - ) - ) - ) - ) - - override def submitRequest( - service: CommandSubmissionServiceStub, - request: SubmitReassignmentRequest, - ): Future[SubmitReassignmentResponse] = { - service.submitReassignment(request) - } - - override def handleResponse(response: SubmitReassignmentResponse): Either[String, Unit] = - Right(()) - } - } - - object CommandService { - trait BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { - override type Svc = CommandServiceStub - override def createService(channel: ManagedChannel): CommandServiceStub = - CommandServiceGrpc.stub(channel) - } - - final case class SubmitAndWaitTransactionTree( - override val actAs: Seq[LfPartyId], - override val readAs: Seq[LfPartyId], - override val commands: Seq[Command], - override val workflowId: String, - override val commandId: String, - override val deduplicationPeriod: Option[DeduplicationPeriod], - override val submissionId: String, - override val minLedgerTimeAbs: Option[Instant], - override val disclosedContracts: Seq[DisclosedContract], - override val domainId: Option[DomainId], - override val applicationId: String, - override val packageIdSelectionPreference: Seq[LfPackageId], - ) extends SubmitCommand - with BaseCommand[ - SubmitAndWaitRequest, - SubmitAndWaitForTransactionTreeResponse, - TransactionTree, - ] { - - override def createRequest(): Either[String, SubmitAndWaitRequest] = - Right(SubmitAndWaitRequest(commands = Some(mkCommand))) - - override def submitRequest( - service: CommandServiceStub, - request: SubmitAndWaitRequest, - ): Future[SubmitAndWaitForTransactionTreeResponse] = - service.submitAndWaitForTransactionTree(request) - - override def handleResponse( - response: SubmitAndWaitForTransactionTreeResponse - ): Either[String, TransactionTree] = - response.transaction.toRight("Received response without any transaction tree") - - override def timeoutType: TimeoutType = DefaultUnboundedTimeout - - } - - final case class SubmitAndWaitTransaction( - override val actAs: Seq[LfPartyId], - override val readAs: Seq[LfPartyId], - override val commands: Seq[Command], - override val workflowId: String, - override val commandId: String, - override val deduplicationPeriod: Option[DeduplicationPeriod], - override val submissionId: String, - override val minLedgerTimeAbs: Option[Instant], - override val disclosedContracts: Seq[DisclosedContract], - override val domainId: Option[DomainId], - override val applicationId: String, - override val packageIdSelectionPreference: Seq[LfPackageId], - ) extends SubmitCommand - with BaseCommand[SubmitAndWaitRequest, SubmitAndWaitForTransactionResponse, Transaction] { - - override def createRequest(): Either[String, SubmitAndWaitRequest] = - Right(SubmitAndWaitRequest(commands = Some(mkCommand))) - - override def submitRequest( - service: CommandServiceStub, - request: SubmitAndWaitRequest, - ): Future[SubmitAndWaitForTransactionResponse] = - service.submitAndWaitForTransaction(request) - - override def handleResponse( - response: SubmitAndWaitForTransactionResponse - ): Either[String, Transaction] = - response.transaction.toRight("Received response without any transaction") - - override def timeoutType: TimeoutType = DefaultUnboundedTimeout - - } - } - - object StateService { - abstract class BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { - override type Svc = StateServiceStub - - override def createService(channel: ManagedChannel): StateServiceStub = - StateServiceGrpc.stub(channel) - } - - final case class LedgerEnd() - extends BaseCommand[GetLedgerEndRequest, GetLedgerEndResponse, ParticipantOffset] { - - override def createRequest(): Either[String, GetLedgerEndRequest] = - Right(GetLedgerEndRequest()) - - override def submitRequest( - service: StateServiceStub, - request: GetLedgerEndRequest, - ): Future[GetLedgerEndResponse] = - service.getLedgerEnd(request) - - override def handleResponse( - response: GetLedgerEndResponse - ): Either[String, ParticipantOffset] = - response.offset.toRight("Empty LedgerEndResponse received without offset") - } - - final case class GetConnectedDomains(partyId: LfPartyId) - extends BaseCommand[ - GetConnectedDomainsRequest, - GetConnectedDomainsResponse, - GetConnectedDomainsResponse, - ] { - - override def createRequest(): Either[String, GetConnectedDomainsRequest] = - Right(GetConnectedDomainsRequest(partyId.toString)) - - override def submitRequest( - service: StateServiceStub, - request: GetConnectedDomainsRequest, - ): Future[GetConnectedDomainsResponse] = - service.getConnectedDomains(request) - - override def handleResponse( - response: GetConnectedDomainsResponse - ): Either[String, GetConnectedDomainsResponse] = - Right(response) - } - - final case class GetActiveContracts( - observer: StreamObserver[GetActiveContractsResponse], - parties: Set[LfPartyId], - limit: PositiveInt, - templateFilter: Seq[TemplateId] = Seq.empty, - activeAtOffset: String = "", - verbose: Boolean = true, - timeout: FiniteDuration, - includeCreatedEventBlob: Boolean = false, - )(override implicit val loggingContext: ErrorLoggingContext) - extends BaseCommand[GetActiveContractsRequest, AutoCloseable, AutoCloseable] - with SubscribeBase[ - GetActiveContractsRequest, - GetActiveContractsResponse, - GetActiveContractsResponse, - ] { - - override def createRequest(): Either[String, GetActiveContractsRequest] = { - val filter = - if (templateFilter.nonEmpty) { - Filters( - Some( - InclusiveFilters(templateFilters = - templateFilter.map(tId => - TemplateFilter(Some(tId.toIdentifier), includeCreatedEventBlob) - ) - ) - ) - ) - } else Filters.defaultInstance - Right( - GetActiveContractsRequest( - filter = Some(TransactionFilter(parties.map((_, filter)).toMap)), - verbose = verbose, - activeAtOffset = activeAtOffset, - ) - ) - } - - override def doRequest( - service: StateServiceStub, - request: GetActiveContractsRequest, - rawObserver: StreamObserver[GetActiveContractsResponse], - ): Unit = - service.getActiveContracts(request, rawObserver) - - override def extractResults( - response: GetActiveContractsResponse - ): IterableOnce[GetActiveContractsResponse] = List(response) - - // fetching ACS might take long if we fetch a lot of data - override def timeoutType: TimeoutType = DefaultUnboundedTimeout - } - } - - final case class CompletionWrapper( - completion: Completion, - checkpoint: Checkpoint, - domainId: DomainId, - ) - - object CommandCompletionService { - abstract class BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { - override type Svc = CommandCompletionServiceStub - - override def createService(channel: ManagedChannel): CommandCompletionServiceStub = - CommandCompletionServiceGrpc.stub(channel) - } - - final case class CompletionRequest( - partyId: LfPartyId, - beginOffset: ParticipantOffset, - expectedCompletions: Int, - timeout: java.time.Duration, - applicationId: String, - )(filter: CompletionWrapper => Boolean, scheduler: ScheduledExecutorService) - extends BaseCommand[ - CompletionStreamRequest, - Seq[CompletionWrapper], - Seq[CompletionWrapper], - ] { - - override def createRequest(): Either[String, CompletionStreamRequest] = - Right( - CompletionStreamRequest( - applicationId = applicationId, - parties = Seq(partyId), - beginExclusive = Some(beginOffset), - ) - ) - - override def submitRequest( - service: CommandCompletionServiceStub, - request: CompletionStreamRequest, - ): Future[Seq[CompletionWrapper]] = { - import scala.jdk.DurationConverters.* - GrpcAdminCommand - .streamedResponse[CompletionStreamRequest, CompletionStreamResponse, CompletionWrapper]( - service.completionStream, - response => - List( - CompletionWrapper( - completion = response.completion.getOrElse( - throw new IllegalStateException("Completion should be present.") - ), - checkpoint = response.checkpoint.getOrElse( - throw new IllegalStateException("Checkpoint should be present.") - ), - domainId = DomainId.tryFromString(response.domainId), - ) - ).filter(filter), - request, - expectedCompletions, - timeout.toScala, - scheduler, - ) - } - - override def handleResponse( - response: Seq[CompletionWrapper] - ): Either[String, Seq[CompletionWrapper]] = - Right(response) - - override def timeoutType: TimeoutType = ServerEnforcedTimeout - } - - final case class CompletionCheckpointRequest( - partyId: LfPartyId, - beginExclusive: ParticipantOffset, - expectedCompletions: Int, - timeout: config.NonNegativeDuration, - applicationId: String, - )(filter: Completion => Boolean, scheduler: ScheduledExecutorService) - extends BaseCommand[CompletionStreamRequest, Seq[(Completion, Option[Checkpoint])], Seq[ - (Completion, Option[Checkpoint]) - ]] { - - override def createRequest(): Either[String, CompletionStreamRequest] = - Right( - CompletionStreamRequest( - applicationId = applicationId, - parties = Seq(partyId), - beginExclusive = Some(beginExclusive), - ) - ) - - override def submitRequest( - service: CommandCompletionServiceStub, - request: CompletionStreamRequest, - ): Future[Seq[(Completion, Option[Checkpoint])]] = { - def extract(response: CompletionStreamResponse): Seq[(Completion, Option[Checkpoint])] = { - val checkpoint = response.checkpoint - - response.completion.filter(filter).map(_ -> checkpoint).toList - } - - GrpcAdminCommand.streamedResponse[ - CompletionStreamRequest, - CompletionStreamResponse, - (Completion, Option[Checkpoint]), - ]( - service.completionStream, - extract, - request, - expectedCompletions, - timeout.asFiniteApproximation, - scheduler, - ) - } - - override def handleResponse( - response: Seq[(Completion, Option[Checkpoint])] - ): Either[String, Seq[(Completion, Option[Checkpoint])]] = - Right(response) - - override def timeoutType: TimeoutType = ServerEnforcedTimeout - } - - final case class Subscribe( - observer: StreamObserver[CompletionWrapper], - parties: Seq[String], - offset: Option[ParticipantOffset], - applicationId: String, - )(implicit loggingContext: ErrorLoggingContext) - extends BaseCommand[CompletionStreamRequest, AutoCloseable, AutoCloseable] { - // The subscription should never be cut short because of a gRPC timeout - override def timeoutType: TimeoutType = ServerEnforcedTimeout - - override def createRequest(): Either[String, CompletionStreamRequest] = Right { - CompletionStreamRequest( - applicationId = applicationId, - parties = parties, - beginExclusive = offset, - ) - } - - override def submitRequest( - service: CommandCompletionServiceStub, - request: CompletionStreamRequest, - ): Future[AutoCloseable] = { - val rawObserver = new ForwardingStreamObserver[CompletionStreamResponse, CompletionWrapper]( - observer, - response => - List( - CompletionWrapper( - completion = response.completion.getOrElse( - throw new IllegalStateException("Completion should be present.") - ), - checkpoint = response.checkpoint.getOrElse( - throw new IllegalStateException("Checkpoint should be present.") - ), - domainId = DomainId.tryFromString(response.domainId), - ) - ), - ) - val context = Context.current().withCancellation() - context.run(() => service.completionStream(request, rawObserver)) - Future.successful(context) - } - - override def handleResponse(response: AutoCloseable): Either[String, AutoCloseable] = Right( - response - ) - } - } - - object Time { - abstract class BaseCommand[Req, Resp, Res] extends GrpcAdminCommand[Req, Resp, Res] { - override type Svc = TimeServiceStub - - override def createService(channel: ManagedChannel): TimeServiceStub = - TimeServiceGrpc.stub(channel) - } - - final object Get - extends BaseCommand[ - GetTimeRequest, - GetTimeResponse, - CantonTimestamp, - ] { - - override def submitRequest( - service: TimeServiceStub, - request: GetTimeRequest, - ): Future[GetTimeResponse] = { - service.getTime(request) - } - - /** Create the request from configured options - */ - override def createRequest(): Either[String, GetTimeRequest] = Right(GetTimeRequest()) - - /** Handle the response the service has provided - */ - override def handleResponse( - response: GetTimeResponse - ): Either[String, CantonTimestamp] = - for { - prototTimestamp <- response.currentTime.map(Right(_)).getOrElse(Left("currentTime empty")) - result <- CantonTimestamp.fromProtoTimestamp(prototTimestamp).left.map(_.message) - } yield result - } - - final case class Set(currentTime: CantonTimestamp, newTime: CantonTimestamp) - extends BaseCommand[ - SetTimeRequest, - Empty, - Unit, - ] { - - override def submitRequest(service: TimeServiceStub, request: SetTimeRequest): Future[Empty] = - service.setTime(request) - - override def createRequest(): Either[String, SetTimeRequest] = - Right( - SetTimeRequest( - currentTime = Some(currentTime.toProtoTimestamp), - newTime = Some(newTime.toProtoTimestamp), - ) - ) - - /** Handle the response the service has provided - */ - override def handleResponse(response: Empty): Either[String, Unit] = Right(()) - - } - - } - - object QueryService { - - abstract class BaseCommand[Req, Res] extends GrpcAdminCommand[Req, Res, Res] { - override type Svc = EventQueryServiceStub - - override def createService(channel: ManagedChannel): EventQueryServiceStub = - EventQueryServiceGrpc.stub(channel) - - override def handleResponse(response: Res): Either[String, Res] = Right(response) - } - - final case class GetEventsByContractId( - contractId: String, - requestingParties: Seq[String], - ) extends BaseCommand[ - GetEventsByContractIdRequest, - GetEventsByContractIdResponse, - ] { - - override def createRequest(): Either[String, GetEventsByContractIdRequest] = Right( - GetEventsByContractIdRequest( - contractId = contractId, - requestingParties = requestingParties, - ) - ) - - override def submitRequest( - service: EventQueryServiceStub, - request: GetEventsByContractIdRequest, - ): Future[GetEventsByContractIdResponse] = service.getEventsByContractId(request) - - } - } -} diff --git a/canton/community/app-base/src/main/scala/com/digitalasset/canton/console/commands/LedgerApiAdministration.scala b/canton/community/app-base/src/main/scala/com/digitalasset/canton/console/commands/LedgerApiAdministration.scala index f26a045ed4f..d745796c1e2 100644 --- a/canton/community/app-base/src/main/scala/com/digitalasset/canton/console/commands/LedgerApiAdministration.scala +++ b/canton/community/app-base/src/main/scala/com/digitalasset/canton/console/commands/LedgerApiAdministration.scala @@ -43,16 +43,15 @@ import com.daml.lf.data.Ref import com.daml.metrics.api.MetricHandle.{Histogram, Meter} import com.daml.metrics.api.{MetricName, MetricsContext} import com.daml.scalautil.Statement.discard +import com.digitalasset.canton.admin.api.client.commands.LedgerApiCommands.CompletionWrapper +import com.digitalasset.canton.admin.api.client.commands.LedgerApiCommands.UpdateService.* import com.digitalasset.canton.admin.api.client.commands.LedgerApiTypeWrappers.{ WrappedContractEntry, WrappedIncompleteAssigned, WrappedIncompleteUnassigned, } -import com.digitalasset.canton.admin.api.client.commands.LedgerApiV2Commands.CompletionWrapper -import com.digitalasset.canton.admin.api.client.commands.LedgerApiV2Commands.UpdateService.* import com.digitalasset.canton.admin.api.client.commands.{ LedgerApiCommands, - LedgerApiV2Commands, ParticipantAdminCommands, } import com.digitalasset.canton.admin.api.client.data.* @@ -220,7 +219,7 @@ trait BaseLedgerApiAdministration extends NoTracing { check(FeatureFlag.Testing)( consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.UpdateService.SubscribeTrees( + LedgerApiCommands.UpdateService.SubscribeTrees( observer, beginOffset, endOffset, @@ -311,7 +310,7 @@ trait BaseLedgerApiAdministration extends NoTracing { check(FeatureFlag.Testing)( consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.UpdateService.SubscribeFlat( + LedgerApiCommands.UpdateService.SubscribeFlat( observer, beginOffset, endOffset, @@ -404,7 +403,7 @@ trait BaseLedgerApiAdministration extends NoTracing { def by_id(parties: Set[PartyId], id: String): Option[TransactionTreeProto] = check(FeatureFlag.Testing)(consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.UpdateService.GetTransactionById(parties.map(_.toLf), id)( + LedgerApiCommands.UpdateService.GetTransactionById(parties.map(_.toLf), id)( consoleEnvironment.environment.executionContext ) ) @@ -442,7 +441,6 @@ trait BaseLedgerApiAdministration extends NoTracing { domainId: Option[DomainId] = None, workflowId: String = "", commandId: String = "", - // TODO(#15280) This feature wont work after V1 is removed. Also after witness blinding is implemented, the underlying algorith will be broken. Idea: drop this feature and wait explicitly with some additional tooling. optTimeout: Option[config.NonNegativeDuration] = Some(timeouts.ledgerCommand), deduplicationPeriod: Option[DeduplicationPeriod] = None, submissionId: String = "", @@ -454,7 +452,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): TransactionTreeProto = { val tx = consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandService.SubmitAndWaitTransactionTree( + LedgerApiCommands.CommandService.SubmitAndWaitTransactionTree( actAs.map(_.toLf), readAs.map(_.toLf), commands, @@ -493,7 +491,6 @@ trait BaseLedgerApiAdministration extends NoTracing { domainId: Option[DomainId] = None, workflowId: String = "", commandId: String = "", - // TODO(#15280) This feature wont work after V1 is removed. Also after witness blinding is implemented, the underlying algorith will be broken. Idea: drop this feature and wait explicitly with some additional tooling. optTimeout: Option[config.NonNegativeDuration] = Some(timeouts.ledgerCommand), deduplicationPeriod: Option[DeduplicationPeriod] = None, submissionId: String = "", @@ -505,7 +502,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): TransactionV2 = { val tx = consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandService.SubmitAndWaitTransaction( + LedgerApiCommands.CommandService.SubmitAndWaitTransaction( actAs.map(_.toLf), readAs.map(_.toLf), commands, @@ -545,7 +542,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): Unit = check(FeatureFlag.Testing) { consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandSubmissionService.Submit( + LedgerApiCommands.CommandSubmissionService.Submit( actAs.map(_.toLf), readAs.map(_.toLf), commands, @@ -739,7 +736,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): Unit = check(FeatureFlag.Testing) { consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandSubmissionService.SubmitAssignCommand( + LedgerApiCommands.CommandSubmissionService.SubmitAssignCommand( workflowId = workflowId, applicationId = applicationId, commandId = commandId, @@ -770,7 +767,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): Unit = check(FeatureFlag.Testing) { consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandSubmissionService.SubmitUnassignCommand( + LedgerApiCommands.CommandSubmissionService.SubmitUnassignCommand( workflowId = workflowId, applicationId = applicationId, commandId = commandId, @@ -793,7 +790,7 @@ trait BaseLedgerApiAdministration extends NoTracing { def end(): ParticipantOffset = check(FeatureFlag.Testing)(consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.StateService.LedgerEnd() + LedgerApiCommands.StateService.LedgerEnd() ) }) @@ -801,7 +798,7 @@ trait BaseLedgerApiAdministration extends NoTracing { def connected_domains(partyId: PartyId): GetConnectedDomainsResponse = check(FeatureFlag.Testing)(consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.StateService.GetConnectedDomains(partyId.toLf) + LedgerApiCommands.StateService.GetConnectedDomains(partyId.toLf) ) }) @@ -837,7 +834,7 @@ trait BaseLedgerApiAdministration extends NoTracing { mkResult( consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.StateService.GetActiveContracts( + LedgerApiCommands.StateService.GetActiveContracts( observer, Set(party.toLf), limit, @@ -1005,7 +1002,7 @@ trait BaseLedgerApiAdministration extends NoTracing { mkResult( consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.StateService.GetActiveContracts( + LedgerApiCommands.StateService.GetActiveContracts( observer, localParties.toSet, limit, @@ -1237,7 +1234,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): Seq[CompletionWrapper] = check(FeatureFlag.Testing)(consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandCompletionService.CompletionRequest( + LedgerApiCommands.CommandCompletionService.CompletionRequest( partyId.toLf, beginOffset, atLeastNumCompletions, @@ -1265,7 +1262,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): Seq[(Completion, Option[Checkpoint])] = check(FeatureFlag.Testing)(consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandCompletionService.CompletionCheckpointRequest( + LedgerApiCommands.CommandCompletionService.CompletionCheckpointRequest( partyId.toLf, beginExclusive, atLeastNumCompletions, @@ -1295,7 +1292,7 @@ trait BaseLedgerApiAdministration extends NoTracing { check(FeatureFlag.Testing)( consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandCompletionService.Subscribe( + LedgerApiCommands.CommandCompletionService.Subscribe( observer, parties.map(_.toLf), Some(beginOffset), @@ -1700,7 +1697,7 @@ trait BaseLedgerApiAdministration extends NoTracing { def get(): CantonTimestamp = check(FeatureFlag.Testing)(consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.Time.Get + LedgerApiCommands.Time.Get ) }) @@ -1710,7 +1707,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ) def set(currentTime: CantonTimestamp, nextTime: CantonTimestamp): Unit = check(FeatureFlag.Testing)(consoleEnvironment.run { - ledgerApiCommand(LedgerApiV2Commands.Time.Set(currentTime, nextTime)) + ledgerApiCommand(LedgerApiCommands.Time.Set(currentTime, nextTime)) }) } @@ -1727,7 +1724,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): GetEventsByContractIdResponse = check(FeatureFlag.Testing)(consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.QueryService + LedgerApiCommands.QueryService .GetEventsByContractId(contractId, requestingParties.map(_.toLf)) ) }) @@ -1760,7 +1757,6 @@ trait BaseLedgerApiAdministration extends NoTracing { domainId: Option[DomainId] = None, workflowId: String = "", commandId: String = "", - // TODO(#15280) This feature wont work after V1 is removed. Also after witness blinding is implemented, the underlying algorith will be broken. Idea: drop this feature and wait explicitly with some additional tooling. optTimeout: Option[config.NonNegativeDuration] = Some(timeouts.ledgerCommand), deduplicationPeriod: Option[DeduplicationPeriod] = None, submissionId: String = "", @@ -1772,7 +1768,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): TransactionTree = check(FeatureFlag.Testing) { val tx = consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandService.SubmitAndWaitTransactionTree( + LedgerApiCommands.CommandService.SubmitAndWaitTransactionTree( actAs.map(_.toLf), readAs.map(_.toLf), commands.map(c => Command.fromJavaProto(c.toProtoCommand)), @@ -1814,7 +1810,6 @@ trait BaseLedgerApiAdministration extends NoTracing { domainId: Option[DomainId] = None, workflowId: String = "", commandId: String = "", - // TODO(#15280) This feature wont work after V1 is removed. Also after witness blinding is implemented, the underlying algorith will be broken. Idea: drop this feature and wait explicitly with some additional tooling. optTimeout: Option[config.NonNegativeDuration] = Some(timeouts.ledgerCommand), deduplicationPeriod: Option[DeduplicationPeriod] = None, submissionId: String = "", @@ -1826,7 +1821,7 @@ trait BaseLedgerApiAdministration extends NoTracing { ): Transaction = check(FeatureFlag.Testing) { val tx = consoleEnvironment.run { ledgerApiCommand( - LedgerApiV2Commands.CommandService.SubmitAndWaitTransaction( + LedgerApiCommands.CommandService.SubmitAndWaitTransaction( actAs.map(_.toLf), readAs.map(_.toLf), commands.map(c => Command.fromJavaProto(c.toProtoCommand)), diff --git a/canton/community/app-base/src/main/scala/com/digitalasset/canton/console/commands/ParticipantAdministration.scala b/canton/community/app-base/src/main/scala/com/digitalasset/canton/console/commands/ParticipantAdministration.scala index 3dcec3d4886..0dd37f32695 100644 --- a/canton/community/app-base/src/main/scala/com/digitalasset/canton/console/commands/ParticipantAdministration.scala +++ b/canton/community/app-base/src/main/scala/com/digitalasset/canton/console/commands/ParticipantAdministration.scala @@ -547,7 +547,7 @@ class ParticipantPruningAdministrationGroup( def find_safe_offset(beforeOrAt: Instant = Instant.now()): Option[ParticipantOffset] = { check(FeatureFlag.Preview) { val ledgerEnd = consoleEnvironment.run( - ledgerApiCommand(LedgerApiV2Commands.StateService.LedgerEnd()) + ledgerApiCommand(LedgerApiCommands.StateService.LedgerEnd()) ) consoleEnvironment .run( diff --git a/canton/community/base/src/main/scala/com/digitalasset/canton/logging/pretty/PrettyInstances.scala b/canton/community/base/src/main/scala/com/digitalasset/canton/logging/pretty/PrettyInstances.scala index b353a00eab1..8532ada5c29 100644 --- a/canton/community/base/src/main/scala/com/digitalasset/canton/logging/pretty/PrettyInstances.scala +++ b/canton/community/base/src/main/scala/com/digitalasset/canton/logging/pretty/PrettyInstances.scala @@ -219,6 +219,9 @@ trait PrettyInstances { implicit def prettyLfIdentifier: Pretty[com.daml.lf.data.Ref.Identifier] = prettyOfString(id => show"${id.packageId}:${id.qualifiedName}") + implicit def prettyLfPackageName: Pretty[com.daml.lf.data.Ref.PackageName] = + prettyOfString(packageName => show"${packageName.toString}") + implicit def prettyLfContractId: Pretty[LfContractId] = prettyOfString { case LfContractId.V1(discriminator, suffix) // Shorten only Canton contract ids diff --git a/canton/community/base/src/main/scala/com/digitalasset/canton/serialization/ProtoConverter.scala b/canton/community/base/src/main/scala/com/digitalasset/canton/serialization/ProtoConverter.scala index bb2b3a7a797..2a512a94c34 100644 --- a/canton/community/base/src/main/scala/com/digitalasset/canton/serialization/ProtoConverter.scala +++ b/canton/community/base/src/main/scala/com/digitalasset/canton/serialization/ProtoConverter.scala @@ -22,6 +22,7 @@ import com.digitalasset.canton.{ LedgerParticipantId, LedgerSubmissionId, LedgerTransactionId, + LfPackageName, LfPartyId, LfWorkflowId, ProtoDeserializationError, @@ -153,6 +154,9 @@ object ProtoConverter { def parseTemplateId(id: String): ParsingResult[LfTemplateId] = parseString(id)(LfTemplateId.fromString) + def parseLfPackageName(packageName: String): ParsingResult[LfPackageName] = + parseString(packageName)(LfPackageName.fromString) + private def parseString[T](from: String)(to: String => Either[String, T]): ParsingResult[T] = to(from).leftMap(StringConversionError) object InstantConverter extends ProtoConverter[Instant, Timestamp, ProtoDeserializationError] { diff --git a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/ArchivedEvent.java b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/ArchivedEvent.java index 513f8e2eb80..f7f8f969ccf 100644 --- a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/ArchivedEvent.java +++ b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/ArchivedEvent.java @@ -16,16 +16,20 @@ public final class ArchivedEvent implements Event { private final Identifier templateId; + private final String packageName; + private final String contractId; public ArchivedEvent( @NonNull List<@NonNull String> witnessParties, @NonNull String eventId, @NonNull Identifier templateId, + @NonNull String packageName, @NonNull String contractId) { this.witnessParties = witnessParties; this.eventId = eventId; this.templateId = templateId; + this.packageName = packageName; this.contractId = contractId; } @@ -47,6 +51,12 @@ public final class ArchivedEvent implements Event { return templateId; } + @NonNull + @Override + public String getPackageName() { + return packageName; + } + @NonNull @Override public String getContractId() { @@ -61,13 +71,13 @@ public final class ArchivedEvent implements Event { return Objects.equals(witnessParties, that.witnessParties) && Objects.equals(eventId, that.eventId) && Objects.equals(templateId, that.templateId) + && Objects.equals(packageName, that.packageName) && Objects.equals(contractId, that.contractId); } @Override public int hashCode() { - - return Objects.hash(witnessParties, eventId, templateId, contractId); + return Objects.hash(witnessParties, eventId, templateId, packageName, contractId); } @Override @@ -78,6 +88,8 @@ public final class ArchivedEvent implements Event { + ", eventId='" + eventId + '\'' + + ", packageName=" + + packageName + ", templateId=" + templateId + ", contractId='" @@ -91,6 +103,7 @@ public final class ArchivedEvent implements Event { .setContractId(getContractId()) .setEventId(getEventId()) .setTemplateId(getTemplateId().toProto()) + .setPackageName(getPackageName()) .addAllWitnessParties(this.getWitnessParties()) .build(); } @@ -100,6 +113,7 @@ public final class ArchivedEvent implements Event { archivedEvent.getWitnessPartiesList(), archivedEvent.getEventId(), Identifier.fromProto(archivedEvent.getTemplateId()), + archivedEvent.getPackageName(), archivedEvent.getContractId()); } } diff --git a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/CreatedEvent.java b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/CreatedEvent.java index 06bcca13ba5..9a48e6b9494 100644 --- a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/CreatedEvent.java +++ b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/CreatedEvent.java @@ -92,6 +92,7 @@ public final class CreatedEvent implements Event, TreeEvent { } @NonNull + @Override public String getPackageName() { return packageName; } diff --git a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/Event.java b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/Event.java index fc39dd335c4..ee5e1e93406 100644 --- a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/Event.java +++ b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/Event.java @@ -25,6 +25,9 @@ public interface Event { @NonNull Identifier getTemplateId(); + @NonNull + String getPackageName(); + @NonNull String getContractId(); diff --git a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/ExercisedEvent.java b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/ExercisedEvent.java index d0c92f50a3c..5059ebba5b8 100644 --- a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/ExercisedEvent.java +++ b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/ExercisedEvent.java @@ -17,6 +17,8 @@ public final class ExercisedEvent implements TreeEvent { private final Identifier templateId; + private final String packageName; + private final Optional interfaceId; private final String contractId; @@ -37,6 +39,7 @@ public final class ExercisedEvent implements TreeEvent { @NonNull List<@NonNull String> witnessParties, @NonNull String eventId, @NonNull Identifier templateId, + @NonNull String packageName, @NonNull Optional interfaceId, @NonNull String contractId, @NonNull String choice, @@ -48,6 +51,7 @@ public final class ExercisedEvent implements TreeEvent { this.witnessParties = witnessParties; this.eventId = eventId; this.templateId = templateId; + this.packageName = packageName; this.interfaceId = interfaceId; this.contractId = contractId; this.choice = choice; @@ -76,6 +80,12 @@ public final class ExercisedEvent implements TreeEvent { return templateId; } + @NonNull + @Override + public String getPackageName() { + return packageName; + } + @NonNull public Optional getInterfaceId() { return interfaceId; @@ -124,6 +134,7 @@ public final class ExercisedEvent implements TreeEvent { && Objects.equals(witnessParties, that.witnessParties) && Objects.equals(eventId, that.eventId) && Objects.equals(templateId, that.templateId) + && Objects.equals(packageName, that.packageName) && Objects.equals(interfaceId, that.interfaceId) && Objects.equals(contractId, that.contractId) && Objects.equals(choice, that.choice) @@ -140,6 +151,7 @@ public final class ExercisedEvent implements TreeEvent { witnessParties, eventId, templateId, + packageName, interfaceId, contractId, choice, @@ -160,6 +172,8 @@ public final class ExercisedEvent implements TreeEvent { + '\'' + ", templateId=" + templateId + + ", packageName=" + + packageName + ", interfaceId=" + interfaceId + ", contractId='" @@ -189,6 +203,7 @@ public final class ExercisedEvent implements TreeEvent { builder.setConsuming(isConsuming()); builder.setContractId(getContractId()); builder.setTemplateId(getTemplateId().toProto()); + builder.setPackageName(getPackageName()); interfaceId.ifPresent(i -> builder.setInterfaceId(i.toProto())); builder.addAllActingParties(getActingParties()); builder.addAllWitnessParties(getWitnessParties()); @@ -202,6 +217,7 @@ public final class ExercisedEvent implements TreeEvent { exercisedEvent.getWitnessPartiesList(), exercisedEvent.getEventId(), Identifier.fromProto(exercisedEvent.getTemplateId()), + exercisedEvent.getPackageName(), exercisedEvent.hasInterfaceId() ? Optional.of(Identifier.fromProto(exercisedEvent.getInterfaceId())) : Optional.empty(), diff --git a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/TreeEvent.java b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/TreeEvent.java index 695d2888231..d44da4fbf62 100644 --- a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/TreeEvent.java +++ b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/TreeEvent.java @@ -25,6 +25,9 @@ public interface TreeEvent { @NonNull Identifier getTemplateId(); + @NonNull + String getPackageName(); + @NonNull String getContractId(); diff --git a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/UnassignedEvent.java b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/UnassignedEvent.java index f12dcd7d32b..ad443f5265b 100644 --- a/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/UnassignedEvent.java +++ b/canton/community/bindings-java/src/main/java/com/daml/ledger/javaapi/data/UnassignedEvent.java @@ -17,6 +17,8 @@ public final class UnassignedEvent { private final @NonNull Identifier templateId; + private final @NonNull String packageName; + private final @NonNull String source; private final @NonNull String target; @@ -33,6 +35,7 @@ public final class UnassignedEvent { @NonNull String unassignId, @NonNull String contractId, @NonNull Identifier templateId, + @NonNull String packageName, @NonNull String source, @NonNull String target, @NonNull String submitter, @@ -42,6 +45,7 @@ public final class UnassignedEvent { this.unassignId = unassignId; this.contractId = contractId; this.templateId = templateId; + this.packageName = packageName; this.source = source; this.target = target; this.submitter = submitter; @@ -65,6 +69,11 @@ public final class UnassignedEvent { return templateId; } + @NonNull + public String getPackageName() { + return packageName; + } + @NonNull public String getSource() { return source; @@ -100,6 +109,7 @@ public final class UnassignedEvent { UnassignedEvent that = (UnassignedEvent) o; return Objects.equals(unassignId, that.unassignId) && Objects.equals(contractId, that.contractId) + && Objects.equals(packageName, that.packageName) && Objects.equals(templateId, that.templateId) && Objects.equals(source, that.source) && Objects.equals(target, that.target) @@ -115,6 +125,7 @@ public final class UnassignedEvent { unassignId, contractId, templateId, + packageName, source, target, submitter, @@ -132,6 +143,8 @@ public final class UnassignedEvent { + ", contractId='" + contractId + '\'' + + ", packageName=" + + packageName + ", templateId=" + templateId + ", source=" @@ -154,6 +167,7 @@ public final class UnassignedEvent { .setUnassignId(this.unassignId) .setContractId(this.contractId) .setTemplateId(this.getTemplateId().toProto()) + .setPackageName(this.packageName) .setSource(this.source) .setTarget(this.target) .setSubmitter(this.submitter) @@ -168,6 +182,7 @@ public final class UnassignedEvent { unassignedEvent.getUnassignId(), unassignedEvent.getContractId(), Identifier.fromProto(unassignedEvent.getTemplateId()), + unassignedEvent.getPackageName(), unassignedEvent.getSource(), unassignedEvent.getTarget(), unassignedEvent.getSubmitter(), diff --git a/canton/community/common/src/main/daml/CantonExamples/daml.yaml b/canton/community/common/src/main/daml/CantonExamples/daml.yaml index 6d596936c91..f04e25d493d 100644 --- a/canton/community/common/src/main/daml/CantonExamples/daml.yaml +++ b/canton/community/common/src/main/daml/CantonExamples/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 build-options: - --target=2.1 name: CantonExamples diff --git a/canton/community/common/src/main/resources/db/migration/canton/h2/stable/V2__lapi_3.0.sha256 b/canton/community/common/src/main/resources/db/migration/canton/h2/stable/V2__lapi_3.0.sha256 index b46026fb5f5..4b9f0b40a01 100644 --- a/canton/community/common/src/main/resources/db/migration/canton/h2/stable/V2__lapi_3.0.sha256 +++ b/canton/community/common/src/main/resources/db/migration/canton/h2/stable/V2__lapi_3.0.sha256 @@ -1 +1 @@ -68d8c1f10eca85f31673553157b156875530adf878640f8bd12ecf50bf801868 +0c54aa37e1982cbec6a13f7712ad18993e970f3b2f08e68ea0a8a60ebf4f1176 diff --git a/canton/community/common/src/main/resources/db/migration/canton/h2/stable/V2__lapi_3.0.sql b/canton/community/common/src/main/resources/db/migration/canton/h2/stable/V2__lapi_3.0.sql index 9a7c7f007b0..bd37945637f 100644 --- a/canton/community/common/src/main/resources/db/migration/canton/h2/stable/V2__lapi_3.0.sql +++ b/canton/community/common/src/main/resources/db/migration/canton/h2/stable/V2__lapi_3.0.sql @@ -235,6 +235,7 @@ CREATE TABLE lapi_events_consuming_exercise ( -- * shared event information contract_id VARCHAR(4000) NOT NULL, template_id INTEGER NOT NULL, + package_name INTEGER NOT NULL, flat_event_witnesses INTEGER ARRAY NOT NULL DEFAULT ARRAY[], -- stakeholders tree_event_witnesses INTEGER ARRAY NOT NULL DEFAULT ARRAY[], -- informees @@ -294,6 +295,7 @@ CREATE TABLE lapi_events_non_consuming_exercise ( -- * shared event information contract_id VARCHAR(4000) NOT NULL, template_id INTEGER NOT NULL, + package_name INTEGER NOT NULL, flat_event_witnesses INTEGER ARRAY NOT NULL DEFAULT ARRAY[], -- stakeholders tree_event_witnesses INTEGER ARRAY NOT NULL DEFAULT ARRAY[], -- informees @@ -350,6 +352,7 @@ CREATE TABLE lapi_events_unassign ( -- * shared event information contract_id VARCHAR(4000) NOT NULL, template_id INTEGER NOT NULL, + package_name INTEGER NOT NULL, flat_event_witnesses INTEGER ARRAY NOT NULL DEFAULT ARRAY[], -- stakeholders -- * common reassignment diff --git a/canton/community/common/src/main/resources/db/migration/canton/postgres/stable/V2__lapi_3.0.sha256 b/canton/community/common/src/main/resources/db/migration/canton/postgres/stable/V2__lapi_3.0.sha256 index ab739caa495..868c474883c 100644 --- a/canton/community/common/src/main/resources/db/migration/canton/postgres/stable/V2__lapi_3.0.sha256 +++ b/canton/community/common/src/main/resources/db/migration/canton/postgres/stable/V2__lapi_3.0.sha256 @@ -1 +1 @@ -f04f9f9a39bb4bf098d2b872a0a42636e2967957d9f616edd20b0e91c1ce91f6 +4db04c53914e522326c6570ec12e1b40200855c305989038a9c0272d18bfc1d2 diff --git a/canton/community/common/src/main/resources/db/migration/canton/postgres/stable/V2__lapi_3.0.sql b/canton/community/common/src/main/resources/db/migration/canton/postgres/stable/V2__lapi_3.0.sql index aada4892b0d..a0c9fb33292 100644 --- a/canton/community/common/src/main/resources/db/migration/canton/postgres/stable/V2__lapi_3.0.sql +++ b/canton/community/common/src/main/resources/db/migration/canton/postgres/stable/V2__lapi_3.0.sql @@ -276,6 +276,7 @@ CREATE TABLE lapi_events_consuming_exercise ( -- * shared event information contract_id text not null, template_id integer not null, + package_name integer not null, flat_event_witnesses integer[] default '{}'::integer[] not null, -- stakeholders tree_event_witnesses integer[] default '{}'::integer[] not null, -- informees @@ -395,6 +396,7 @@ CREATE TABLE lapi_events_non_consuming_exercise ( -- * shared event information contract_id text not null, template_id integer not null, + package_name integer not null, flat_event_witnesses integer[] default '{}'::integer[] not null, -- stakeholders tree_event_witnesses integer[] default '{}'::integer[] not null, -- informees @@ -446,6 +448,7 @@ CREATE TABLE lapi_events_unassign ( -- * shared event information contract_id text not null, template_id integer not null, + package_name integer not null, flat_event_witnesses integer[] default '{}'::integer[] not null, -- stakeholders -- * common reassignment diff --git a/canton/community/demo/src/main/daml/ai-analysis/daml.yaml b/canton/community/demo/src/main/daml/ai-analysis/daml.yaml index 27b0a146d1a..c7b8f9d21a3 100644 --- a/canton/community/demo/src/main/daml/ai-analysis/daml.yaml +++ b/canton/community/demo/src/main/daml/ai-analysis/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 build-options: - --target=2.1 name: ai-analysis diff --git a/canton/community/demo/src/main/daml/bank/daml.yaml b/canton/community/demo/src/main/daml/bank/daml.yaml index bdbe0f96008..651b24508f4 100644 --- a/canton/community/demo/src/main/daml/bank/daml.yaml +++ b/canton/community/demo/src/main/daml/bank/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 build-options: - --target=2.1 name: bank diff --git a/canton/community/demo/src/main/daml/doctor/daml.yaml b/canton/community/demo/src/main/daml/doctor/daml.yaml index 3459cffcb5f..47f21986dec 100644 --- a/canton/community/demo/src/main/daml/doctor/daml.yaml +++ b/canton/community/demo/src/main/daml/doctor/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 build-options: - --target=2.1 name: doctor diff --git a/canton/community/demo/src/main/daml/health-insurance/daml.yaml b/canton/community/demo/src/main/daml/health-insurance/daml.yaml index 6211142d067..02a05ced7fa 100644 --- a/canton/community/demo/src/main/daml/health-insurance/daml.yaml +++ b/canton/community/demo/src/main/daml/health-insurance/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 build-options: - --target=2.1 name: health-insurance diff --git a/canton/community/demo/src/main/daml/medical-records/daml.yaml b/canton/community/demo/src/main/daml/medical-records/daml.yaml index f7e8fdc6973..3d4ff59378b 100644 --- a/canton/community/demo/src/main/daml/medical-records/daml.yaml +++ b/canton/community/demo/src/main/daml/medical-records/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 build-options: - --target=2.1 name: medical-records diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/block/BlockSequencerStateManager.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/block/BlockSequencerStateManager.scala index f69a82cb356..172ffb2f04c 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/block/BlockSequencerStateManager.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/block/BlockSequencerStateManager.scala @@ -24,6 +24,7 @@ import com.digitalasset.canton.domain.sequencing.integrations.state.statemanager import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencer import com.digitalasset.canton.domain.sequencing.sequencer.errors.CreateSubscriptionError +import com.digitalasset.canton.domain.sequencing.sequencer.traffic.SequencerRateLimitManager import com.digitalasset.canton.error.SequencerBaseError import com.digitalasset.canton.lifecycle.{ AsyncCloseable, @@ -113,6 +114,7 @@ class BlockSequencerStateManager( override val maybeLowerTopologyTimestampBound: Option[CantonTimestamp], override protected val timeouts: ProcessingTimeout, protected val loggerFactory: NamedLoggerFactory, + rateLimitManager: SequencerRateLimitManager, )(implicit executionContext: ExecutionContext, closeContext: CloseContext) extends BlockSequencerStateManagerBase with NamedLogging { @@ -466,6 +468,18 @@ class BlockSequencerStateManager( _ <- store.finalizeBlockUpdate(newBlock) } yield { updateHeadState(priorHead, newHead) + // Use lastTs here under the following assumptions: + // 1. lastTs represents the timestamp of the last sequenced "send" event of the last block successfully processed + // Specifically, it is the last of the timestamps in the block passed to the rate limiter in the B.U.G for consumed and traffic updates methods. + // After setting safeForPruning to this timestamp, we will not be able to request balances from the balance manager prior to this timestamp. + // 2. This does not impose restrictions on the use of lastSequencerEventTimestamp when calling the rate limiter. + // Meaning it should be possible to use an old lastSequencerEventTimestamp when calling the rate limiter, even if it is older than lastTs here. + // If this changes, we we will need to use lastSequencerEventTimestamp here instead. + // 3. TODO(i15837): Under some HA failover scenarios, this may not be sufficient. Mainly because finalizeBlockUpdate above does not + // use synchronous commits for DB replicas. This has for consequence that theoretically a block could be finalized but not appear + // in the DB replica, while the pruning will be visible in the replica. This would lead the BUG to requesting balances for that block when + // reprocessing it, which would fail because the balances have been pruned. This needs to be considered when implementing HA for the BlockSequencer. + rateLimitManager.safeForPruning(newHead.block.lastTs) newState } } @@ -678,6 +692,7 @@ object BlockSequencerStateManager { enableInvariantCheck: Boolean, timeouts: ProcessingTimeout, loggerFactory: NamedLoggerFactory, + rateLimitManager: SequencerRateLimitManager, )(implicit executionContext: ExecutionContext, traceContext: TraceContext, @@ -698,6 +713,7 @@ object BlockSequencerStateManager { maybeLowerTopologyTimestampBound = maybeLowerTopologyTimestampBound, timeouts = timeouts, loggerFactory = loggerFactory, + rateLimitManager = rateLimitManager, ) } diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/SequencerNodeX.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/SequencerNodeX.scala index 24f4e74289e..48195895832 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/SequencerNodeX.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/SequencerNodeX.scala @@ -53,7 +53,7 @@ import com.digitalasset.canton.topology.transaction.{ } import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.traffic.TopUpEvent -import com.digitalasset.canton.util.SingleUseCell +import com.digitalasset.canton.util.{FutureUtil, SingleUseCell} import com.digitalasset.canton.version.ProtocolVersion import io.grpc.ServerServiceDefinition import org.apache.pekko.actor.ActorSystem @@ -169,6 +169,12 @@ class SequencerNodeBootstrapX( loggerFactory, ) + // Start auto pruning of traffic balances + FutureUtil.doNotAwaitUnlessShutdown( + balanceManager.startAutoPruning, + "Auto pruning of traffic balances", + ) + // add initialization service adminServerRegistry.addServiceU( SequencerInitializationServiceGrpc.bindService( diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/SequencerRuntimeForSeparateNode.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/SequencerRuntimeForSeparateNode.scala index 1473169fd35..353f21026e9 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/SequencerRuntimeForSeparateNode.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/SequencerRuntimeForSeparateNode.scala @@ -274,6 +274,7 @@ class SequencerRuntimeForSeparateNode( override def onClosed(): Unit = { Lifecycle.close( + Lifecycle.toCloseableOption(sequencer.rateLimitManager), timeTracker, syncCrypto, topologyClient, diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/DbSequencerStateManagerStore.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/DbSequencerStateManagerStore.scala index a136c34e832..678b0533a4e 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/DbSequencerStateManagerStore.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/DbSequencerStateManagerStore.scala @@ -487,18 +487,20 @@ class DbSequencerStateManagerStore( override def prune(requestedTimestamp: CantonTimestamp)(implicit traceContext: TraceContext ): Future[PruningResult] = for { - numberOfEventsBefore <- numberOfEvents() + numberOfEventsToBeDeleted <- numberOfEventsToBeDeletedByPruneAt(requestedTimestamp) _ <- pruneEvents(requestedTimestamp) min <- minCounters - numberOfEventsAfter <- numberOfEvents() - numberOfDeletions = numberOfEventsBefore - numberOfEventsAfter - } yield PruningResult(numberOfDeletions, min) + } yield PruningResult(numberOfEventsToBeDeleted, min) - override protected[state] def numberOfEvents()(implicit + override protected[state] def numberOfEventsToBeDeletedByPruneAt( + requestedTimestamp: CantonTimestamp + )(implicit traceContext: TraceContext - ): Future[Long] = - storage.query( - sql"select count(*) from seq_state_manager_events".as[Long].head, + ): Future[Long] = storage + .query( + sql"select count(*) from seq_state_manager_events where ts < $requestedTimestamp" + .as[Long] + .head, functionFullName, ) diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/InMemorySequencerStateManagerStore.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/InMemorySequencerStateManagerStore.scala index cac561ba340..2f29709d6c0 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/InMemorySequencerStateManagerStore.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/InMemorySequencerStateManagerStore.scala @@ -344,10 +344,13 @@ class InMemorySequencerStateManagerStore( result.get } - override protected[state] def numberOfEvents()(implicit + override protected[state] def numberOfEventsToBeDeletedByPruneAt( + requestedTimestamp: CantonTimestamp + )(implicit traceContext: TraceContext - ): Future[Long] = Future.successful(state.get().indices.map(_._2.events.size).sum.toLong) - + ): Future[Long] = Future.successful( + state.get().indices.map(_._2.events.count(_.timestamp < requestedTimestamp)).sum.toLong + ) override def fetchLowerBound()(implicit traceContext: TraceContext ): Future[Option[CantonTimestamp]] = Future.successful(state.get().pruningLowerBound) diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/SequencerStateManagerStore.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/SequencerStateManagerStore.scala index 559308f9efe..e015f29e5de 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/SequencerStateManagerStore.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/integrations/state/SequencerStateManagerStore.scala @@ -140,9 +140,10 @@ trait SequencerStateManagerStore { ): Future[Option[CantonTimestamp]] @VisibleForTesting - protected[state] def numberOfEvents()(implicit - traceContext: TraceContext + protected[state] def numberOfEventsToBeDeletedByPruneAt(requestedTimestamp: CantonTimestamp)( + implicit traceContext: TraceContext ): Future[Long] + } object SequencerStateManagerStore { diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/sequencer/block/BlockSequencerFactory.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/sequencer/block/BlockSequencerFactory.scala index e2fd6141e64..20b8a6844cc 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/sequencer/block/BlockSequencerFactory.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/sequencer/block/BlockSequencerFactory.scala @@ -148,6 +148,7 @@ abstract class BlockSequencerFactory( nodeParameters.enableAdditionalConsistencyChecks, nodeParameters.processingTimeouts, domainLoggerFactory, + rateLimitManager, ) } diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/sequencer/traffic/SequencerRateLimitManager.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/sequencer/traffic/SequencerRateLimitManager.scala index 06237419611..c47eefe8d50 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/sequencer/traffic/SequencerRateLimitManager.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/sequencer/traffic/SequencerRateLimitManager.scala @@ -26,7 +26,7 @@ import scala.concurrent.ExecutionContext /** Holds the traffic control state and control rate limiting logic of members of a sequencer */ -trait SequencerRateLimitManager { +trait SequencerRateLimitManager extends AutoCloseable { /** Create a traffic state for a new member at the given timestamp. * Its base traffic remainder will be equal to the max burst window configured at that point in time. @@ -102,6 +102,12 @@ trait SequencerRateLimitManager { /** Optional subscriber to the traffic control processor, only used for the new top up implementation */ def balanceUpdateSubscriber: Option[SequencerTrafficControlSubscriber] + + /** Marks the provided timestamp as safe for pruning. + * This has for consequence that requesting balances strictly below this timestamp may lead to an UnknownBalance error, + * as the balance will be eligible for pruning. + */ + def safeForPruning(timestamp: CantonTimestamp)(implicit traceContext: TraceContext): Unit } sealed trait SequencerRateLimitError diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/BalanceUpdateClientImpl.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/BalanceUpdateClientImpl.scala index 468fea05637..6830fee5772 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/BalanceUpdateClientImpl.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/BalanceUpdateClientImpl.scala @@ -49,4 +49,9 @@ class BalanceUpdateClientImpl( override lazy val balanceUpdateSubscription: Option[SequencerTrafficControlSubscriber] = Some( manager.subscription ) + + override def safeForPruning(timestamp: CantonTimestamp)(implicit + traceContext: TraceContext + ): Unit = + manager.setSafeToPruneBeforeExclusive(timestamp) } diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/EnterpriseSequencerRateLimitManager.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/EnterpriseSequencerRateLimitManager.scala index c340791971b..c82f2cd86de 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/EnterpriseSequencerRateLimitManager.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/EnterpriseSequencerRateLimitManager.scala @@ -31,12 +31,14 @@ import com.digitalasset.canton.sequencing.protocol.{ import com.digitalasset.canton.topology.Member import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.traffic.EventCostCalculator +import com.google.common.annotations.VisibleForTesting import scala.collection.concurrent.TrieMap import scala.concurrent.ExecutionContext class EnterpriseSequencerRateLimitManager( - balanceUpdateClient: BalanceUpdateClient, + @VisibleForTesting + private[canton] val balanceUpdateClient: BalanceUpdateClient, override protected val loggerFactory: NamedLoggerFactory, futureSupervisor: FutureSupervisor, override val timeouts: ProcessingTimeout, @@ -229,6 +231,11 @@ class EnterpriseSequencerRateLimitManager( } override def balanceUpdateSubscriber: Option[SequencerTrafficControlSubscriber] = balanceUpdateClient.balanceUpdateSubscription + + override def safeForPruning(timestamp: CantonTimestamp)(implicit + traceContext: TraceContext + ): Unit = + balanceUpdateClient.safeForPruning(timestamp) } object EnterpriseSequencerRateLimitManager { @@ -264,5 +271,6 @@ object EnterpriseSequencerRateLimitManager { ): FutureUnlessShutdown[Option[TrafficBalance]] def balanceUpdateSubscription: Option[SequencerTrafficControlSubscriber] = None + def safeForPruning(timestamp: CantonTimestamp)(implicit traceContext: TraceContext): Unit = {} } } diff --git a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/TrafficBalanceManager.scala b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/TrafficBalanceManager.scala index 50dd8f3d7e0..c03747a7ad7 100644 --- a/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/TrafficBalanceManager.scala +++ b/canton/community/domain/src/main/scala/com/digitalasset/canton/domain/sequencing/traffic/TrafficBalanceManager.scala @@ -445,7 +445,10 @@ class TrafficBalanceManager( def startAutoPruning(implicit traceContext: TraceContext ): FutureUnlessShutdown[Unit] = { - lazy val newPromise = mkPromise[Unit]("auto pruning started", futureSupervisor) + // This future will only complete when auto pruning is stopped manually or the node goes down + // so use the noop supervisor to avoid logging continuously that the future is not done + lazy val newPromise = + mkPromise[Unit]("auto pruning started", FutureSupervisor.Noop) autoPruningPromise.getAndUpdate({ case None => Some(newPromise) case existing => existing diff --git a/canton/community/domain/src/test/scala/com/digitalasset/canton/domain/sequencing/integrations/state/SequencerStateManagerStoreTest.scala b/canton/community/domain/src/test/scala/com/digitalasset/canton/domain/sequencing/integrations/state/SequencerStateManagerStoreTest.scala index 803b50342f4..964435ab226 100644 --- a/canton/community/domain/src/test/scala/com/digitalasset/canton/domain/sequencing/integrations/state/SequencerStateManagerStoreTest.scala +++ b/canton/community/domain/src/test/scala/com/digitalasset/canton/domain/sequencing/integrations/state/SequencerStateManagerStoreTest.scala @@ -559,18 +559,20 @@ trait SequencerStateManagerStoreTest _ <- store.acknowledge(bob, ts(6)) statusBefore <- store.status() pruningTimestamp = statusBefore.safePruningTimestampFor(now) - eventCountBefore <- store.numberOfEvents() + eventsToBeDeleted <- store.numberOfEventsToBeDeletedByPruneAt(pruningTimestamp) result <- { logger.debug(s"Pruning sequencer state manager store from $pruningTimestamp") store.prune(pruningTimestamp) } - eventCountAfter <- store.numberOfEvents() + eventsToBeDeletedAfterPruning <- store.numberOfEventsToBeDeletedByPruneAt( + pruningTimestamp + ) statusAfter <- store.status() lowerBound <- store.fetchLowerBound() } yield { result.eventsPruned shouldBe 3L - val eventsRemoved = eventCountBefore - eventCountAfter - eventsRemoved shouldBe 3L + eventsToBeDeleted shouldBe 3L + eventsToBeDeletedAfterPruning shouldBe 0L statusBefore.lowerBound shouldBe <(statusAfter.lowerBound) lowerBound.value shouldBe ts(6) // to prevent reads from before this point result.newMinimumCountersSupported shouldBe Map( diff --git a/canton/community/ledger-api/src/main/protobuf/com/daml/ledger/api/v2/event.proto b/canton/community/ledger-api/src/main/protobuf/com/daml/ledger/api/v2/event.proto index ccfd3eb5c2b..3df015c6b93 100644 --- a/canton/community/ledger-api/src/main/protobuf/com/daml/ledger/api/v2/event.proto +++ b/canton/community/ledger-api/src/main/protobuf/com/daml/ledger/api/v2/event.proto @@ -151,6 +151,10 @@ message ArchivedEvent { // in ``value.proto``). // Required repeated string witness_parties = 4; + + // The package name of the contract. + // Required + string package_name = 5; } // Records that a choice has been exercised on a target contract. @@ -215,4 +219,8 @@ message ExercisedEvent { // The result of exercising the choice. // Required Value exercise_result = 11; + + // The package name of the contract. + // Required + string package_name = 12; } diff --git a/canton/community/ledger-api/src/main/protobuf/com/daml/ledger/api/v2/reassignment.proto b/canton/community/ledger-api/src/main/protobuf/com/daml/ledger/api/v2/reassignment.proto index 781faa61922..05d761851fb 100644 --- a/canton/community/ledger-api/src/main/protobuf/com/daml/ledger/api/v2/reassignment.proto +++ b/canton/community/ledger-api/src/main/protobuf/com/daml/ledger/api/v2/reassignment.proto @@ -105,6 +105,10 @@ message UnassignedEvent { // The parties that are notified of this event. // Required repeated string witness_parties = 9; + + // The package name of the contract. + // Required + string package_name = 10; } // Records that a contract has been assigned, and it can be used on the target domain. diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/ledger/localstore/PersistentUserManagementStore.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/ledger/localstore/PersistentUserManagementStore.scala index 77b12e25a3e..ae4691f223c 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/ledger/localstore/PersistentUserManagementStore.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/ledger/localstore/PersistentUserManagementStore.scala @@ -77,7 +77,7 @@ class PersistentUserManagementStore( resourceVersion = 0, createdAt = now, ) - val internalId = backend.createUser(user = dbUser)(connection) + val internalId = retryOnceMore(backend.createUser(user = dbUser)(connection)) user.metadata.annotations.foreach { case (key, value) => backend.addUserAnnotation( internalId = internalId, @@ -309,16 +309,23 @@ class PersistentUserManagementStore( dbMetric: metrics.userManagement.type => DatabaseMetrics )( thunk: Connection => Result[T] - )(implicit loggingContext: LoggingContextWithTrace): Future[Result[T]] = - dbDispatcher - .executeSql(dbMetric(metrics.userManagement))(thunk) + )(implicit loggingContext: LoggingContextWithTrace): Future[Result[T]] = { + def execute(): Future[Result[T]] = + dbDispatcher.executeSql(dbMetric(metrics.userManagement))(thunk) + implicit val ec: ExecutionContext = directEc + execute() + .recoverWith { case RetryOnceMoreException(cause) => + logger.debug("Retrying transaction to handle potential race", cause) + execute() + } .recover[Result[T]] { case TooManyUserRightsRuntimeException(userId) => Left(TooManyUserRights(userId)) case ConcurrentUserUpdateDetectedRuntimeException(userId) => Left(UserManagementStore.ConcurrentUserUpdate(userId)) case MaxAnnotationsSizeExceededException(userId) => Left(UserManagementStore.MaxAnnotationsSizeExceeded(userId)) - }(directEc) + } + } private def toDomainUser( dbUser: UserManagementStorageBackend.DbUserWithId, @@ -385,6 +392,13 @@ class PersistentUserManagementStore( val now = timeProvider.getCurrentTime (now.getEpochSecond * 1000 * 1000) + (now.getNano / 1000) } + + private def retryOnceMore[T](body: => T): T = + try { + body + } catch { + case t: Throwable => throw RetryOnceMoreException(t) + } } object PersistentUserManagementStore { @@ -425,3 +439,5 @@ object PersistentUserManagementStore { loggerFactory = loggerFactory, )(executionContext, LoggingContextWithTrace(loggerFactory)) } + +final case class RetryOnceMoreException(underlying: Throwable) extends RuntimeException diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/ledger/participant/state/v2/Reassignment.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/ledger/participant/state/v2/Reassignment.scala index b59733de910..f24f125134d 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/ledger/participant/state/v2/Reassignment.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/ledger/participant/state/v2/Reassignment.scala @@ -20,6 +20,7 @@ object Reassignment { * * @param contractId Contract ID of the underlying contract. * @param templateId Template ID of the underlying contract. + * @param packageName Package name of the underlying contract's template. * @param stakeholders Stakeholders of the underlying contract. * @param assignmentExclusivity Before this time (measured on the target domain), only the submitter * of the unassignment can initiate the assignment. Defined for @@ -28,6 +29,7 @@ object Reassignment { final case class Unassign( contractId: Value.ContractId, templateId: Ref.Identifier, + packageName: Ref.PackageName, stakeholders: List[Ref.Party], assignmentExclusivity: Option[Timestamp], ) extends Reassignment { diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/index/InMemoryStateUpdater.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/index/InMemoryStateUpdater.scala index d0f9806c80c..7d54abbcda0 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/index/InMemoryStateUpdater.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/index/InMemoryStateUpdater.scala @@ -335,6 +335,7 @@ private[platform] object InMemoryStateUpdater { contractId = exercise.targetCoid, ledgerEffectiveTime = txAccepted.transactionMeta.ledgerEffectiveTime, templateId = exercise.templateId, + packageName = exercise.packageName, commandId = txAccepted.completionInfoO.map(_.commandId).getOrElse(""), workflowId = txAccepted.transactionMeta.workflowId.getOrElse(""), contractKey = @@ -387,7 +388,7 @@ private[platform] object InMemoryStateUpdater { offset = offset, events = events.toVector, completionDetails = completionDetails, - domainId = Some(txAccepted.domainId.toProtoPrimitive), // TODO(i15280) + domainId = Some(txAccepted.domainId.toProtoPrimitive), recordTime = txAccepted.recordTime, ) } diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/DbDto.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/DbDto.scala index 258bc6c55f2..19f0167f282 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/DbDto.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/DbDto.scala @@ -55,6 +55,7 @@ object DbDto { event_id: String, contract_id: String, template_id: String, + package_name: String, flat_event_witnesses: Set[String], tree_event_witnesses: Set[String], create_key_value: Option[Array[Byte]], @@ -109,6 +110,7 @@ object DbDto { submitter: Option[String], contract_id: String, template_id: String, + package_name: String, flat_event_witnesses: Set[String], event_sequential_id: Long, source_domain_id: String, diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/DbDtoToStringsForInterning.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/DbDtoToStringsForInterning.scala index 93888dab3b8..a8d1be892c8 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/DbDtoToStringsForInterning.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/DbDtoToStringsForInterning.scala @@ -36,6 +36,8 @@ object DbDtoToStringsForInterning { dbDto match { case dbDto: DbDto.EventCreate => Iterator(dbDto.package_name) case dbDto: DbDto.EventAssign => Iterator(dbDto.package_name) + case dbDto: DbDto.EventExercise => Iterator(dbDto.package_name) + case dbDto: DbDto.EventUnassign => Iterator(dbDto.package_name) case _ => Iterator.empty } diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/StorageBackend.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/StorageBackend.scala index 5655721f438..d925fe7fbfc 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/StorageBackend.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/StorageBackend.scala @@ -403,6 +403,7 @@ object EventStorageBackend { reassignmentCounter: Long, contractId: String, templateId: Identifier, + packageName: PackageName, witnessParties: Set[String], assignmentExclusivity: Option[Timestamp], traceContext: Option[Array[Byte]], diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/UpdateToDbDto.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/UpdateToDbDto.scala index bec3a2c34db..940d65f4524 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/UpdateToDbDto.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/UpdateToDbDto.scala @@ -283,6 +283,7 @@ object UpdateToDbDto { event_id = EventId(u.transactionId, nodeId).toLedgerString, contract_id = exercise.targetCoid.coid, template_id = templateId, + package_name = exercise.packageName, flat_event_witnesses = flatWitnesses, tree_event_witnesses = informees, create_key_value = createKeyValue @@ -364,6 +365,7 @@ object UpdateToDbDto { submitter = u.reassignmentInfo.submitter, contract_id = unassign.contractId.coid, template_id = templateId, + package_name = unassign.packageName, flat_event_witnesses = flatEventWitnesses.toSet, event_sequential_id = 0L, // this is filled later source_domain_id = u.reassignmentInfo.sourceDomain.unwrap.toProtoPrimitive, diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/common/EventStorageBackendTemplate.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/common/EventStorageBackendTemplate.scala index fe037bebdad..da0e33b660d 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/common/EventStorageBackendTemplate.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/common/EventStorageBackendTemplate.scala @@ -75,7 +75,7 @@ object EventStorageBackendTemplate { "event_id", "contract_id", "template_id", - "NULL as package_name", + "package_name", "NULL as create_argument", "NULL as create_argument_compression", "NULL as create_signatories", @@ -98,7 +98,7 @@ object EventStorageBackendTemplate { baseColumnsForFlatTransactionsExercise.mkString(", ") private type SharedRow = - Offset ~ String ~ Int ~ Long ~ String ~ String ~ Timestamp ~ Int ~ Option[String] ~ + Offset ~ String ~ Int ~ Long ~ String ~ String ~ Timestamp ~ Int ~ Int ~ Option[String] ~ Option[String] ~ Array[Int] ~ Option[Array[Int]] ~ Int ~ Option[Array[Byte]] ~ Timestamp private val sharedRow: RowParser[SharedRow] = @@ -110,6 +110,7 @@ object EventStorageBackendTemplate { str("contract_id") ~ timestampFromMicros("ledger_effective_time") ~ int("template_id") ~ + int("package_name") ~ str("command_id").? ~ str("workflow_id").? ~ array[Int]("event_witnesses") ~ @@ -121,7 +122,7 @@ object EventStorageBackendTemplate { private type CreatedEventRow = SharedRow ~ Array[Byte] ~ Option[Int] ~ Array[Int] ~ Array[Int] ~ Option[Array[Byte]] ~ Option[Hash] ~ Option[Int] ~ Option[Array[Int]] ~ - Option[Array[Byte]] ~ Int + Option[Array[Byte]] private val createdEventRow: RowParser[CreatedEventRow] = sharedRow ~ @@ -133,8 +134,7 @@ object EventStorageBackendTemplate { hashFromHexString("create_key_hash").? ~ int("create_key_value_compression").? ~ array[Int]("create_key_maintainers").? ~ - byteArray("driver_metadata").? ~ - int("package_name") + byteArray("driver_metadata").? private type ExercisedEventRow = SharedRow ~ Boolean ~ String ~ Array[Byte] ~ Option[Int] ~ Option[Array[Byte]] ~ Option[Int] ~ @@ -163,8 +163,8 @@ object EventStorageBackendTemplate { ): RowParser[EventStorageBackend.Entry[Raw.FlatEvent.Created]] = createdEventRow map { case eventOffset ~ transactionId ~ nodeIndex ~ eventSequentialId ~ eventId ~ contractId ~ ledgerEffectiveTime ~ - templateId ~ commandId ~ workflowId ~ eventWitnesses ~ submitters ~ internedDomainId ~ traceContext ~ recordTime ~ createArgument ~ createArgumentCompression ~ - createSignatories ~ createObservers ~ createKeyValue ~ createKeyHash ~ createKeyValueCompression ~ createKeyMaintainers ~ driverMetadata ~ packageName => + templateId ~ packageName ~ commandId ~ workflowId ~ eventWitnesses ~ submitters ~ internedDomainId ~ traceContext ~ recordTime ~ createArgument ~ createArgumentCompression ~ + createSignatories ~ createObservers ~ createKeyValue ~ createKeyHash ~ createKeyValueCompression ~ createKeyMaintainers ~ driverMetadata => // ArraySeq.unsafeWrapArray is safe here // since we get the Array from parsing and don't let it escape anywhere. EventStorageBackend.Entry( @@ -219,7 +219,7 @@ object EventStorageBackendTemplate { ): RowParser[EventStorageBackend.Entry[Raw.FlatEvent.Archived]] = archivedEventRow map { case eventOffset ~ transactionId ~ nodeIndex ~ eventSequentialId ~ eventId ~ contractId ~ ledgerEffectiveTime ~ - templateId ~ commandId ~ workflowId ~ eventWitnesses ~ submitters ~ internedDomainId ~ traceContext ~ recordTime => + templateId ~ packageName ~ commandId ~ workflowId ~ eventWitnesses ~ submitters ~ internedDomainId ~ traceContext ~ recordTime => // ArraySeq.unsafeWrapArray is safe here // since we get the Array from parsing and don't let it escape anywhere. EventStorageBackend.Entry( @@ -238,6 +238,7 @@ object EventStorageBackendTemplate { eventId = eventId, contractId = contractId, templateId = stringInterning.templateId.externalize(templateId), + packageName = stringInterning.packageName.externalize(packageName), eventWitnesses = ArraySeq.unsafeWrapArray( eventWitnesses.view .filter(allQueryingParties) @@ -266,9 +267,9 @@ object EventStorageBackendTemplate { ): RowParser[EventStorageBackend.Entry[Raw.TreeEvent.Created]] = createdEventRow map { case eventOffset ~ transactionId ~ nodeIndex ~ eventSequentialId ~ eventId ~ contractId ~ ledgerEffectiveTime ~ - templateId ~ commandId ~ workflowId ~ eventWitnesses ~ submitters ~ internedDomainId ~ traceContext ~ recordTime ~ + templateId ~ packageName ~ commandId ~ workflowId ~ eventWitnesses ~ submitters ~ internedDomainId ~ traceContext ~ recordTime ~ createArgument ~ createArgumentCompression ~ createSignatories ~ createObservers ~ - createKeyValue ~ createKeyHash ~ createKeyValueCompression ~ createKeyMaintainers ~ driverMetadata ~ packageName => + createKeyValue ~ createKeyHash ~ createKeyValueCompression ~ createKeyMaintainers ~ driverMetadata => // ArraySeq.unsafeWrapArray is safe here // since we get the Array from parsing and don't let it escape anywhere. EventStorageBackend.Entry( @@ -322,7 +323,7 @@ object EventStorageBackendTemplate { stringInterning: StringInterning, ): RowParser[EventStorageBackend.Entry[Raw.TreeEvent.Exercised]] = exercisedEventRow map { - case eventOffset ~ transactionId ~ nodeIndex ~ eventSequentialId ~ eventId ~ contractId ~ ledgerEffectiveTime ~ templateId ~ commandId ~ workflowId ~ eventWitnesses ~ submitters ~ internedDomainId ~ traceContext ~ recordTime ~ exerciseConsuming ~ qualifiedChoiceName ~ exerciseArgument ~ exerciseArgumentCompression ~ exerciseResult ~ exerciseResultCompression ~ exerciseActors ~ exerciseChildEventIds => + case eventOffset ~ transactionId ~ nodeIndex ~ eventSequentialId ~ eventId ~ contractId ~ ledgerEffectiveTime ~ templateId ~ packageName ~ commandId ~ workflowId ~ eventWitnesses ~ submitters ~ internedDomainId ~ traceContext ~ recordTime ~ exerciseConsuming ~ qualifiedChoiceName ~ exerciseArgument ~ exerciseArgumentCompression ~ exerciseResult ~ exerciseResultCompression ~ exerciseActors ~ exerciseChildEventIds => val Ref.QualifiedChoiceName(interfaceId, choiceName) = Ref.QualifiedChoiceName.assertFromString(qualifiedChoiceName) // ArraySeq.unsafeWrapArray is safe here @@ -343,6 +344,7 @@ object EventStorageBackendTemplate { eventId = eventId, contractId = contractId, templateId = stringInterning.templateId.externalize(templateId), + packageName = stringInterning.packageName.externalize(packageName), interfaceId = interfaceId, exerciseConsuming = exerciseConsuming, exerciseChoice = choiceName, @@ -418,7 +420,7 @@ object EventStorageBackendTemplate { "contract_id", "ledger_effective_time", "template_id", - "NULL as package_name", + "package_name", "workflow_id", "NULL as create_argument", "NULL as create_argument_compression", @@ -554,6 +556,7 @@ object EventStorageBackendTemplate { str("update_id") ~ str("contract_id") ~ int("template_id") ~ + int("package_name") ~ array[Int]("flat_event_witnesses") ~ timestampFromMicros("assignment_exclusivity").? ~ byteArray("trace_context").? ~ @@ -575,6 +578,7 @@ object EventStorageBackendTemplate { updateId ~ contractId ~ templateId ~ + packageName ~ flatEventWitnesses ~ assignmentExclusivity ~ traceContext ~ @@ -591,6 +595,7 @@ object EventStorageBackendTemplate { updateId = updateId, contractId = contractId, templateId = stringInterning.templateId.externalize(templateId), + packageName = stringInterning.packageName.externalize(packageName), witnessParties = flatEventWitnesses.view .filter(allQueryingParties) .map(stringInterning.party.unsafe.externalize) diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/common/Schema.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/common/Schema.scala index 4054b770eab..3fd9b8d673c 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/common/Schema.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/backend/common/Schema.scala @@ -163,6 +163,9 @@ private[backend] object AppendOnlySchema { "template_id" -> fieldStrategy.int(stringInterning => dbDto => stringInterning.templateId.unsafe.internalize(dbDto.template_id) ), + "package_name" -> fieldStrategy.int(stringInterning => + dbDto => stringInterning.packageName.unsafe.internalize(dbDto.package_name) + ), "flat_event_witnesses" -> fieldStrategy.intArray(stringInterning => _.flat_event_witnesses.map(stringInterning.party.unsafe.internalize) ), @@ -200,6 +203,9 @@ private[backend] object AppendOnlySchema { "template_id" -> fieldStrategy.int(stringInterning => dbDto => stringInterning.templateId.unsafe.internalize(dbDto.template_id) ), + "package_name" -> fieldStrategy.int(stringInterning => + dbDto => stringInterning.packageName.unsafe.internalize(dbDto.package_name) + ), "flat_event_witnesses" -> fieldStrategy.intArray(stringInterning => _.flat_event_witnesses.map(stringInterning.party.unsafe.internalize) ), diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/JdbcLedgerDao.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/JdbcLedgerDao.scala index 4d34aaaaa14..092dec597c3 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/JdbcLedgerDao.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/JdbcLedgerDao.scala @@ -268,7 +268,7 @@ private class JdbcLedgerDao( recordTime = recordTime, completionInfo = info, reasonTemplate = reason, - domainId = DomainId.tryFromString("invalid::deadbeef"), // TODO(i15280) + domainId = DomainId.tryFromString("invalid::deadbeef"), ) ) ), @@ -663,7 +663,7 @@ private class JdbcLedgerDao( blindingInfoO = blindingInfoO, hostedWitnesses = hostedWitnesses, contractMetadata = Map.empty, - domainId = DomainId.tryFromString("invalid::deadbeef"), // TODO(i15280) + domainId = DomainId.tryFromString("invalid::deadbeef"), ) ) ), diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/Raw.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/Raw.scala index ac76a9f86d8..97dce653c78 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/Raw.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/Raw.scala @@ -11,6 +11,7 @@ import com.daml.ledger.api.v2.event.{ } import com.daml.ledger.api.v2.transaction.TreeEvent as PbTreeEvent import com.daml.lf.crypto.Hash +import com.daml.lf.data.Ref import com.daml.lf.data.Ref.PackageName import com.daml.lf.data.Time.Timestamp import com.digitalasset.canton.ledger.api.util.{LfEngineToApi, TimestampConversion} @@ -205,6 +206,7 @@ object Raw { eventId: String, contractId: String, templateId: Identifier, + packageName: Ref.PackageName, eventWitnesses: ArraySeq[String], ): Raw.FlatEvent.Archived = new Raw.FlatEvent.Archived( @@ -212,6 +214,7 @@ object Raw { eventId = eventId, contractId = contractId, templateId = Some(LfEngineToApi.toApiIdentifier(templateId)), + packageName = packageName, witnessParties = eventWitnesses, ) ) @@ -314,6 +317,7 @@ object Raw { eventId: String, contractId: String, templateId: Identifier, + packageName: PackageName, interfaceId: Option[Identifier], exerciseConsuming: Boolean, exerciseChoice: String, @@ -330,6 +334,7 @@ object Raw { eventId = eventId, contractId = contractId, templateId = Some(LfEngineToApi.toApiIdentifier(templateId)), + packageName = packageName, interfaceId = interfaceId.map(LfEngineToApi.toApiIdentifier), choice = exerciseChoice, choiceArgument = null, diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/TransactionLogUpdatesConversions.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/TransactionLogUpdatesConversions.scala index cbfb2d0511a..575b435c1b5 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/TransactionLogUpdatesConversions.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/TransactionLogUpdatesConversions.scala @@ -240,6 +240,7 @@ private[events] object TransactionLogUpdatesConversions { eventId = exercisedEvent.eventId.toLedgerString, contractId = exercisedEvent.contractId.coid, templateId = Some(LfEngineToApi.toApiIdentifier(exercisedEvent.templateId)), + packageName = exercisedEvent.packageName, witnessParties = requestingParties.iterator.filter(exercisedEvent.flatEventWitnesses).toSeq, ) @@ -456,6 +457,7 @@ private[events] object TransactionLogUpdatesConversions { eventId = exercisedEvent.eventId.toLedgerString, contractId = exercisedEvent.contractId.coid, templateId = Some(LfEngineToApi.toApiIdentifier(exercisedEvent.templateId)), + packageName = exercisedEvent.packageName, interfaceId = exercisedEvent.interfaceId.map(LfEngineToApi.toApiIdentifier), choice = exercisedEvent.choice, choiceArgument = Some(choiceArgument), @@ -595,6 +597,7 @@ private[events] object TransactionLogUpdatesConversions { reassignmentCounter = info.reassignmentCounter, contractId = unassign.contractId.coid, templateId = Some(LfEngineToApi.toApiIdentifier(unassign.templateId)), + packageName = unassign.packageName, assignmentExclusivity = unassign.assignmentExclusivity.map(TimestampConversion.fromLf), witnessParties = reassignmentAccepted.reassignmentInfo.hostedStakeholders diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/TransactionsReader.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/TransactionsReader.scala index 345b1485f34..147a2827784 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/TransactionsReader.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/dao/events/TransactionsReader.scala @@ -322,6 +322,7 @@ private[dao] object TransactionsReader { unassignId = rawUnassignEvent.unassignId, contractId = rawUnassignEvent.contractId, templateId = Some(LfEngineToApi.toApiIdentifier(rawUnassignEvent.templateId)), + packageName = rawUnassignEvent.packageName, source = rawUnassignEvent.sourceDomainId, target = rawUnassignEvent.targetDomainId, submitter = rawUnassignEvent.submitter.getOrElse(""), diff --git a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/interfaces/TransactionLogUpdate.scala b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/interfaces/TransactionLogUpdate.scala index 3291797db3a..cc3dae34cee 100644 --- a/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/interfaces/TransactionLogUpdate.scala +++ b/canton/community/ledger/ledger-api-core/src/main/scala/com/digitalasset/canton/platform/store/interfaces/TransactionLogUpdate.scala @@ -137,6 +137,7 @@ object TransactionLogUpdate { contractId: ContractId, ledgerEffectiveTime: Timestamp, templateId: Identifier, + packageName: PackageName, interfaceId: Option[Identifier], commandId: String, workflowId: String, diff --git a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/ledger/localstore/UserStoreTests.scala b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/ledger/localstore/UserStoreTests.scala index 904b9015402..f8946fee54d 100644 --- a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/ledger/localstore/UserStoreTests.scala +++ b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/ledger/localstore/UserStoreTests.scala @@ -22,6 +22,7 @@ import com.digitalasset.canton.ledger.localstore.api.{ import com.digitalasset.canton.logging.LoggingContextWithTrace import org.scalatest.freespec.AsyncFreeSpec +import scala.concurrent.Future import scala.language.implicitConversions /** Common tests for implementations of [[UserManagementStore]] @@ -150,6 +151,19 @@ trait UserStoreTests extends UserStoreSpecBase { self: AsyncFreeSpec => } } + "disallow re-creating an existing user concurrently" in { + testIt { tested => + val user = newUser("user1") + for { + res <- Future.sequence( + Seq(tested.createUser(user, Set.empty), tested.createUser(user, Set.empty)) + ) + } yield { + res should contain(Left(UserExists(user.id))) + } + } + } + "deny permission re-creating an existing user within another IDP" in { testIt { tested => val user = newUser("user1") diff --git a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/index/InMemoryStateUpdaterSpec.scala b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/index/InMemoryStateUpdaterSpec.scala index 25cba3c623c..a17d4e1ff12 100644 --- a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/index/InMemoryStateUpdaterSpec.scala +++ b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/index/InMemoryStateUpdaterSpec.scala @@ -305,6 +305,7 @@ object InMemoryStateUpdaterSpec { Reassignment.Unassign( contractId = someCreateNode.coid, templateId = templateId2, + packageName = packageName, stakeholders = List(party2), assignmentExclusivity = Some(Timestamp.assertFromLong(123456L)), ) @@ -614,6 +615,7 @@ object InMemoryStateUpdaterSpec { reassignment = Reassignment.Unassign( contractId = someCreateNode.coid, templateId = templateId2, + packageName = packageName, stakeholders = List(party2), assignmentExclusivity = Some(Timestamp.assertFromLong(123456L)), ), diff --git a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/indexer/parallel/ParallelIndexerSubscriptionSpec.scala b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/indexer/parallel/ParallelIndexerSubscriptionSpec.scala index 23343744aa4..07496063bed 100644 --- a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/indexer/parallel/ParallelIndexerSubscriptionSpec.scala +++ b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/indexer/parallel/ParallelIndexerSubscriptionSpec.scala @@ -108,6 +108,7 @@ class ParallelIndexerSubscriptionSpec extends AnyFlatSpec with Matchers with Nam event_id = "", contract_id = "1", template_id = "", + package_name = "", flat_event_witnesses = Set.empty, tree_event_witnesses = Set.empty, create_key_value = None, @@ -162,6 +163,7 @@ class ParallelIndexerSubscriptionSpec extends AnyFlatSpec with Matchers with Nam submitter = None, contract_id = "", template_id = "", + package_name = "", flat_event_witnesses = Set.empty, event_sequential_id = 0, source_domain_id = "", diff --git a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/DbDtoToStringsForInterningSpec.scala b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/DbDtoToStringsForInterningSpec.scala index f5a1c2fc30b..977c6cf45f7 100644 --- a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/DbDtoToStringsForInterningSpec.scala +++ b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/DbDtoToStringsForInterningSpec.scala @@ -71,7 +71,9 @@ class DbDtoToStringsForInterningSpec extends AnyFlatSpec with Matchers { ).sorted iterators.packageNames.toList.sorted shouldBe List( "25.1", + "50.1", "87.1", + "94.1", ).sorted } @@ -135,6 +137,7 @@ class DbDtoToStringsForInterningSpec extends AnyFlatSpec with Matchers { event_id = "48", contract_id = "49", template_id = "50", + package_name = "50.1", flat_event_witnesses = Set("51", "52", "53"), tree_event_witnesses = Set("54", "55", "56"), exercise_argument = Array.empty, @@ -229,6 +232,7 @@ class DbDtoToStringsForInterningSpec extends AnyFlatSpec with Matchers { submitter = Option("s2"), contract_id = "", template_id = "94", + package_name = "94.1", flat_event_witnesses = Set("95", "96"), event_sequential_id = 0, source_domain_id = "domain7", diff --git a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestValues.scala b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestValues.scala index ed2fde41234..23a130a84ce 100644 --- a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestValues.scala +++ b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestValues.scala @@ -208,6 +208,7 @@ private[store] object StorageBackendTestValues { event_id = EventId(transactionId, NodeId(0)).toLedgerString, contract_id = contractId.coid, template_id = someTemplateId.toString, + package_name = somePackageName, flat_event_witnesses = if (consuming) Set(signatory) else Set.empty, tree_event_witnesses = Set(signatory, actor), create_key_value = None, @@ -289,6 +290,7 @@ private[store] object StorageBackendTestValues { submitter = Option(someParty), contract_id = contractId.coid, template_id = someTemplateId.toString, + package_name = somePackageName, flat_event_witnesses = Set(signatory, observer), event_sequential_id = eventSequentialId, source_domain_id = sourceDomainId, diff --git a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestsPruning.scala b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestsPruning.scala index 4175bfd0553..5e715e1d4b3 100644 --- a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestsPruning.scala +++ b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestsPruning.scala @@ -173,13 +173,13 @@ private[backend] trait StorageBackendTestsPruning def assertAllDataPresent(): Assertion = assertIndexDbDataSql( consuming = Vector(EventConsuming(6), EventConsuming(9)), consumingFilterStakeholder = - Vector(FilterConsumingStakeholder(6, 3), FilterConsumingStakeholder(9, 3)), + Vector(FilterConsumingStakeholder(6, 4), FilterConsumingStakeholder(9, 4)), consumingFilterNonStakeholder = Vector(FilterConsumingNonStakeholder(6, 1), FilterConsumingNonStakeholder(9, 1)), nonConsuming = Vector(EventNonConsuming(5), EventNonConsuming(8)), - nonConsumingFilter = Vector(FilterNonConsuming(5, 3), FilterNonConsuming(8, 3)), + nonConsumingFilter = Vector(FilterNonConsuming(5, 4), FilterNonConsuming(8, 4)), unassign = Vector(EventUnassign(7), EventUnassign(10)), - unassignFilter = Vector(FilterUnassign(7, 3), FilterUnassign(10, 3)), + unassignFilter = Vector(FilterUnassign(7, 4), FilterUnassign(10, 4)), ) assertAllDataPresent() @@ -190,18 +190,18 @@ private[backend] trait StorageBackendTestsPruning pruneEventsSql(offset(5), pruneAllDivulgedContracts = true) assertIndexDbDataSql( consuming = Vector(EventConsuming(9)), - consumingFilterStakeholder = Vector(FilterConsumingStakeholder(9, 3)), + consumingFilterStakeholder = Vector(FilterConsumingStakeholder(9, 4)), consumingFilterNonStakeholder = Vector(FilterConsumingNonStakeholder(9, 1)), nonConsuming = Vector(EventNonConsuming(8)), - nonConsumingFilter = Vector(FilterNonConsuming(8, 3)), + nonConsumingFilter = Vector(FilterNonConsuming(8, 4)), unassign = Vector(EventUnassign(10)), - unassignFilter = Vector(FilterUnassign(10, 3)), + unassignFilter = Vector(FilterUnassign(10, 4)), ) // Prune at the ledger end, but setting the unassign incomplete pruneEventsSql(endOffset, pruneAllDivulgedContracts = true, Vector(offset(8))) assertIndexDbDataSql( unassign = Vector(EventUnassign(10)), - unassignFilter = Vector(FilterUnassign(10, 3)), + unassignFilter = Vector(FilterUnassign(10, 4)), ) // Prune at the ledger end pruneEventsSql(endOffset, pruneAllDivulgedContracts = true) @@ -580,10 +580,10 @@ private[backend] trait StorageBackendTestsPruning def assertAllDataPresent(txMeta: Vector[TxMeta]): Assertion = assertIndexDbDataSql( assign = Vector(EventAssign(1)), - assignFilter = Vector(FilterAssign(1, 3)), + assignFilter = Vector(FilterAssign(1, 4)), consuming = Vector(EventConsuming(2)), consumingFilterStakeholder = Vector( - FilterConsumingStakeholder(2, 3), + FilterConsumingStakeholder(2, 4), FilterConsumingStakeholder(2, 7), ), txMeta = txMeta, @@ -646,11 +646,9 @@ private[backend] trait StorageBackendTestsPruning def assertAllDataPresent(txMeta: Vector[TxMeta]): Assertion = assertIndexDbDataSql( assign = Vector(EventAssign(1)), - assignFilter = Vector(FilterAssign(1, 3)), + assignFilter = Vector(FilterAssign(1, 4)), unassign = Vector(EventUnassign(2)), - unassignFilter = Vector( - FilterUnassign(2, 3) - ), + unassignFilter = Vector(FilterUnassign(2, 4)), txMeta = txMeta, ) @@ -789,14 +787,14 @@ private[backend] trait StorageBackendTestsPruning def assertAllDataPresent(txMeta: Seq[TxMeta]): Assertion = assertIndexDbDataSql( assign = Vector(EventAssign(4)), - assignFilter = Vector(FilterAssign(4, 3)), + assignFilter = Vector(FilterAssign(4, 4)), consuming = Vector(EventConsuming(1), EventConsuming(5), EventConsuming(6), EventConsuming(9)), consumingFilterStakeholder = Vector( - FilterConsumingStakeholder(1, 3), - FilterConsumingStakeholder(5, 3), - FilterConsumingStakeholder(6, 3), - FilterConsumingStakeholder(9, 3), + FilterConsumingStakeholder(1, 4), + FilterConsumingStakeholder(5, 4), + FilterConsumingStakeholder(6, 4), + FilterConsumingStakeholder(9, 4), ), unassign = Vector( EventUnassign(2), @@ -806,11 +804,11 @@ private[backend] trait StorageBackendTestsPruning EventUnassign(10), ), unassignFilter = Vector( - FilterUnassign(2, 3), - FilterUnassign(3, 3), - FilterUnassign(7, 3), - FilterUnassign(8, 3), - FilterUnassign(10, 3), + FilterUnassign(2, 4), + FilterUnassign(3, 4), + FilterUnassign(7, 4), + FilterUnassign(8, 4), + FilterUnassign(10, 4), ), txMeta = txMeta, ) @@ -848,12 +846,12 @@ private[backend] trait StorageBackendTestsPruning pruneEventsSql(offset(5), pruneAllDivulgedContracts = true) assertIndexDbDataSql( assign = Vector(EventAssign(4)), - assignFilter = Vector(FilterAssign(4, 3)), + assignFilter = Vector(FilterAssign(4, 4)), consuming = Vector(EventConsuming(5), EventConsuming(6), EventConsuming(9)), consumingFilterStakeholder = Vector( - FilterConsumingStakeholder(5, 3), - FilterConsumingStakeholder(6, 3), - FilterConsumingStakeholder(9, 3), + FilterConsumingStakeholder(5, 4), + FilterConsumingStakeholder(6, 4), + FilterConsumingStakeholder(9, 4), ), unassign = Vector( EventUnassign(7), @@ -861,9 +859,9 @@ private[backend] trait StorageBackendTestsPruning EventUnassign(10), ), unassignFilter = Vector( - FilterUnassign(7, 3), - FilterUnassign(8, 3), - FilterUnassign(10, 3), + FilterUnassign(7, 4), + FilterUnassign(8, 4), + FilterUnassign(10, 4), ), txMeta = Vector( TxMeta("00000006"), @@ -878,16 +876,16 @@ private[backend] trait StorageBackendTestsPruning pruneEventsSql(offset(9), pruneAllDivulgedContracts = true) assertIndexDbDataSql( assign = Vector(EventAssign(4)), - assignFilter = Vector(FilterAssign(4, 3)), + assignFilter = Vector(FilterAssign(4, 4)), consuming = Vector(EventConsuming(9)), consumingFilterStakeholder = Vector( - FilterConsumingStakeholder(9, 3) + FilterConsumingStakeholder(9, 4) ), unassign = Vector( EventUnassign(10) ), unassignFilter = Vector( - FilterUnassign(10, 3) + FilterUnassign(10, 4) ), txMeta = Vector( TxMeta("00000010"), @@ -903,16 +901,16 @@ private[backend] trait StorageBackendTestsPruning ) assertIndexDbDataSql( assign = Vector(EventAssign(4)), - assignFilter = Vector(FilterAssign(4, 3)), + assignFilter = Vector(FilterAssign(4, 4)), consuming = Vector(EventConsuming(9)), consumingFilterStakeholder = Vector( - FilterConsumingStakeholder(9, 3) + FilterConsumingStakeholder(9, 4) ), unassign = Vector( EventUnassign(10) ), unassignFilter = Vector( - FilterUnassign(10, 3) + FilterUnassign(10, 4) ), txMeta = Vector.empty, ) @@ -925,12 +923,12 @@ private[backend] trait StorageBackendTestsPruning ) assertIndexDbDataSql( assign = Vector(EventAssign(4)), - assignFilter = Vector(FilterAssign(4, 3)), + assignFilter = Vector(FilterAssign(4, 4)), unassign = Vector( EventUnassign(10) ), unassignFilter = Vector( - FilterUnassign(10, 3) + FilterUnassign(10, 4) ), txMeta = Vector.empty, ) diff --git a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestsReassignmentEvents.scala b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestsReassignmentEvents.scala index 0dfd45818b5..00da2e9fb08 100644 --- a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestsReassignmentEvents.scala +++ b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/StorageBackendTestsReassignmentEvents.scala @@ -312,6 +312,7 @@ private[backend] trait StorageBackendTestsReassignmentEvents reassignmentCounter = 1000L, contractId = hashCid("#1").coid, templateId = someTemplateId, + packageName = somePackageName, updateId = offset(1).toHexString, witnessParties = Set("signatory"), assignmentExclusivity = Some(Time.Timestamp.assertFromLong(11111)), @@ -329,6 +330,7 @@ private[backend] trait StorageBackendTestsReassignmentEvents reassignmentCounter = 1000L, contractId = hashCid("#2").coid, templateId = someTemplateId, + packageName = somePackageName, updateId = offset(2).toHexString, witnessParties = Set("signatory"), assignmentExclusivity = Some(Time.Timestamp.assertFromLong(11111)), diff --git a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/UpdateToDbDtoSpec.scala b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/UpdateToDbDtoSpec.scala index 10f8a19d437..37fe16b515b 100644 --- a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/UpdateToDbDtoSpec.scala +++ b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/backend/UpdateToDbDtoSpec.scala @@ -406,6 +406,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { event_id = EventId(transactionId, exerciseNodeId).toLedgerString, contract_id = exerciseNode.targetCoid.coid, template_id = exerciseNode.templateId.toString, + package_name = exerciseNode.packageName, flat_event_witnesses = Set("signatory", "observer"), // stakeholders tree_event_witnesses = Set("signatory", "observer"), // informees create_key_value = None, @@ -511,6 +512,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { event_id = EventId(transactionId, exerciseNodeId).toLedgerString, contract_id = exerciseNode.targetCoid.coid, template_id = exerciseNode.templateId.toString, + package_name = exerciseNode.packageName, flat_event_witnesses = Set.empty, // stakeholders tree_event_witnesses = Set("signatory"), // informees create_key_value = None, @@ -636,6 +638,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { event_id = EventId(transactionId, exerciseNodeAId).toLedgerString, contract_id = exerciseNodeA.targetCoid.coid, template_id = exerciseNodeA.templateId.toString, + package_name = exerciseNodeA.packageName, flat_event_witnesses = Set.empty, // stakeholders tree_event_witnesses = Set("signatory"), // informees create_key_value = None, @@ -672,6 +675,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { event_id = EventId(transactionId, exerciseNodeBId).toLedgerString, contract_id = exerciseNodeB.targetCoid.coid, template_id = exerciseNodeB.templateId.toString, + package_name = exerciseNodeB.packageName, flat_event_witnesses = Set.empty, // stakeholders tree_event_witnesses = Set("signatory"), // informees create_key_value = None, @@ -705,6 +709,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { event_id = EventId(transactionId, exerciseNodeCId).toLedgerString, contract_id = exerciseNodeC.targetCoid.coid, template_id = exerciseNodeC.templateId.toString, + package_name = exerciseNodeC.packageName, flat_event_witnesses = Set.empty, // stakeholders tree_event_witnesses = Set("signatory"), // informees create_key_value = None, @@ -881,6 +886,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { event_id = EventId(transactionId, exerciseNodeId).toLedgerString, contract_id = exerciseNode.targetCoid.coid, template_id = exerciseNode.templateId.toString, + package_name = exerciseNode.packageName, flat_event_witnesses = Set("signatory", "observer"), tree_event_witnesses = Set("signatory", "observer", "divulgee"), create_key_value = None, @@ -1025,6 +1031,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { event_id = EventId(transactionId, exerciseNodeId).toLedgerString, contract_id = exerciseNode.targetCoid.coid, template_id = exerciseNode.templateId.toString, + package_name = exerciseNode.packageName, flat_event_witnesses = Set("signatory", "observer"), tree_event_witnesses = Set("signatory", "observer", "divulgee"), create_key_value = None, @@ -1572,6 +1579,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { reassignment = Reassignment.Unassign( contractId = contractId, templateId = createNode.templateId, + packageName = createNode.packageName, stakeholders = List("signatory12", "observer23", "asdasdasd").map(Ref.Party.assertFromString), assignmentExclusivity = Some(Time.Timestamp.assertFromLong(123456)), @@ -1588,6 +1596,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { submitter = someParty, contract_id = createNode.coid.coid, template_id = createNode.templateId.toString, + package_name = createNode.packageName, flat_event_witnesses = Set("signatory12", "observer23", "asdasdasd"), event_sequential_id = 0, source_domain_id = "x::domain1", diff --git a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/dao/SequentialWriteDaoSpec.scala b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/dao/SequentialWriteDaoSpec.scala index 612a8875d66..31895672bf4 100644 --- a/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/dao/SequentialWriteDaoSpec.scala +++ b/canton/community/ledger/ledger-api-core/src/test/scala/com/digitalasset/canton/platform/store/dao/SequentialWriteDaoSpec.scala @@ -251,6 +251,7 @@ object SequentialWriteDaoSpec { event_id = "", contract_id = "1", template_id = "", + package_name = "2", flat_event_witnesses = Set.empty, tree_event_witnesses = Set.empty, create_key_value = None, diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/carbonv1/daml.yaml b/canton/community/ledger/ledger-common-dars/src/main/daml/carbonv1/daml.yaml index a354dc6e844..5c3670a3a22 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/carbonv1/daml.yaml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/carbonv1/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 name: carbonv1-tests source: . version: 3.0.0 diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/carbonv2/daml.yaml b/canton/community/ledger/ledger-common-dars/src/main/daml/carbonv2/daml.yaml index 88ee1470a55..eaeab9c9303 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/carbonv2/daml.yaml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/carbonv2/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 name: carbonv2-tests data-dependencies: - ../../../../scala-2.13/resource_managed/main/carbonv1-tests-3.0.0.dar diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/experimental/daml.yaml b/canton/community/ledger/ledger-common-dars/src/main/daml/experimental/daml.yaml index 7d78f0e8142..1969ed6e82c 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/experimental/daml.yaml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/experimental/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 name: experimental-tests source: . version: 3.0.0 diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/model/Test.daml b/canton/community/ledger/ledger-common-dars/src/main/daml/model/Test.daml index 81ea20cb7d2..7776f4d04e9 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/model/Test.daml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/model/Test.daml @@ -22,6 +22,10 @@ template Dummy controller operator do assert False + nonconsuming choice DummyNonConsuming: () + controller operator + do return () + nonconsuming choice Clone: ContractId Dummy controller operator do create Dummy with operator diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/model/daml.yaml b/canton/community/ledger/ledger-common-dars/src/main/daml/model/daml.yaml index 419539dfaf3..54e188eba29 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/model/daml.yaml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/model/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 name: model-tests source: . version: 3.0.0 diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/package_management/daml.yaml b/canton/community/ledger/ledger-common-dars/src/main/daml/package_management/daml.yaml index 29c7a25788d..64796887c23 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/package_management/daml.yaml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/package_management/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 name: package-management-tests source: . version: 3.0.0 diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/semantic/daml.yaml b/canton/community/ledger/ledger-common-dars/src/main/daml/semantic/daml.yaml index cc530ca902e..7a4bb91d939 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/semantic/daml.yaml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/semantic/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 name: semantic-tests source: . version: 3.0.0 diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/1.0.0/daml.yaml b/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/1.0.0/daml.yaml index 6c6c83a0af3..44b5eade352 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/1.0.0/daml.yaml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/1.0.0/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 name: upgrade-tests source: . version: 1.0.0 diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/2.0.0/daml.yaml b/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/2.0.0/daml.yaml index 5713d18ebbd..c2378d4236c 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/2.0.0/daml.yaml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/2.0.0/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 name: upgrade-tests source: . version: 2.0.0 diff --git a/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/3.0.0/daml.yaml b/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/3.0.0/daml.yaml index f21d8012fd9..d2cf0c25d23 100644 --- a/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/3.0.0/daml.yaml +++ b/canton/community/ledger/ledger-common-dars/src/main/daml/upgrade/3.0.0/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 name: upgrade-tests source: . version: 3.0.0 diff --git a/canton/community/ledger/ledger-json-api/pqs-schema/project/build.properties b/canton/community/ledger/ledger-json-api/pqs-schema/project/build.properties new file mode 100644 index 00000000000..e8a1e246e8a --- /dev/null +++ b/canton/community/ledger/ledger-json-api/pqs-schema/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.9.7 diff --git a/canton/community/ledger/ledger-json-api/src/test/daml/v2_1/daml.yaml b/canton/community/ledger/ledger-json-api/src/test/daml/v2_1/daml.yaml index ffd9297d76b..40b5cab4388 100644 --- a/canton/community/ledger/ledger-json-api/src/test/daml/v2_1/daml.yaml +++ b/canton/community/ledger/ledger-json-api/src/test/daml/v2_1/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 build-options: - --target=2.1 name: JsonEncodingTest diff --git a/canton/community/ledger/ledger-json-api/src/test/daml/v2_dev/daml.yaml b/canton/community/ledger/ledger-json-api/src/test/daml/v2_dev/daml.yaml index a3f9d24f185..277805d2753 100644 --- a/canton/community/ledger/ledger-json-api/src/test/daml/v2_dev/daml.yaml +++ b/canton/community/ledger/ledger-json-api/src/test/daml/v2_dev/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 build-options: - --target=2.dev name: JsonEncodingTestDev diff --git a/canton/community/participant/src/main/daml/daml.yaml b/canton/community/participant/src/main/daml/daml.yaml index b26fdb8b0cf..992ec87fbd6 100644 --- a/canton/community/participant/src/main/daml/daml.yaml +++ b/canton/community/participant/src/main/daml/daml.yaml @@ -1,4 +1,4 @@ -sdk-version: 3.0.0-snapshot.20240306.12855.0.v7bee7ef3 +sdk-version: 3.0.0-snapshot.20240307.12859.0.v66d83e43 build-options: - --target=2.1 name: AdminWorkflows diff --git a/canton/community/participant/src/main/protobuf/com/digitalasset/canton/participant/protocol/v30/ledger_sync_event.proto b/canton/community/participant/src/main/protobuf/com/digitalasset/canton/participant/protocol/v30/ledger_sync_event.proto index 0928e2e068f..edafc8b6384 100644 --- a/canton/community/participant/src/main/protobuf/com/digitalasset/canton/participant/protocol/v30/ledger_sync_event.proto +++ b/canton/community/participant/src/main/protobuf/com/digitalasset/canton/participant/protocol/v30/ledger_sync_event.proto @@ -175,6 +175,7 @@ message TransferredOut { bool is_transferring_participant = 12; repeated string hosted_stakeholders = 13; int64 transfer_counter = 14; + string package_name = 15; } message TransferredIn { diff --git a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/admin/repair/ChangeAssignation.scala b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/admin/repair/ChangeAssignation.scala index d088b31b8c0..8d9b1fd145c 100644 --- a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/admin/repair/ChangeAssignation.scala +++ b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/admin/repair/ChangeAssignation.scala @@ -345,6 +345,7 @@ private final class ChangeAssignation( submitter = None, contractId = contract.payload.contract.contractId, templateId = Option(contract.payload.contract.contractInstance.unversioned.template), + packageName = contract.payload.contract.contractInstance.unversioned.packageName, contractStakeholders = contract.payload.contract.metadata.stakeholders, transferId = transferId, targetDomain = targetDomainId, diff --git a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/metrics/ParticipantMetrics.scala b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/metrics/ParticipantMetrics.scala index bbcc232ca65..309995c4536 100644 --- a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/metrics/ParticipantMetrics.scala +++ b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/metrics/ParticipantMetrics.scala @@ -132,8 +132,8 @@ class SyncDomainMetrics( @MetricDoc.Tag( summary = "Size of conflict detection task queue", - description = """The task scheduler will schedule tasks to run at a given timestamp. This metric - |exposes the number of tasks that are waiting in the task queue for the right time to pass. + description = """This metric measures the size of the queue for conflict detection between + |concurrent transactions. |A huge number does not necessarily indicate a bottleneck; |it could also mean that a huge number of tasks have not yet arrived at their execution time.""", qualification = Debug, @@ -147,11 +147,10 @@ class SyncDomainMetrics( object transactionProcessing extends TransactionProcessingMetrics(prefix, factory) @MetricDoc.Tag( - summary = "Size of conflict detection task queue", - description = """The task scheduler will schedule tasks to run at a given timestamp. This metric - |exposes the number of tasks that are waiting in the task queue for the right time to pass. - |A huge number does not necessarily indicate a bottleneck; - |it could also mean that a huge number of tasks have not yet arrived at their execution time.""", + summary = "Number of requests being validated on the domain.", + description = """Number of requests that are currently being validated on the domain. + |This also covers requests submitted by other participants. + |""", qualification = Debug, ) val numDirtyRequests: Counter = factory.counter(prefix :+ "dirty-requests") diff --git a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/protocol/RequestJournal.scala b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/protocol/RequestJournal.scala index 5b78b8994e2..6f599c30680 100644 --- a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/protocol/RequestJournal.scala +++ b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/protocol/RequestJournal.scala @@ -4,7 +4,6 @@ package com.digitalasset.canton.participant.protocol import cats.data.OptionT -import com.daml.scalautil.Statement.discard import com.digitalasset.canton.concurrent.{FutureSupervisor, SupervisedPromise} import com.digitalasset.canton.data.{ CantonTimestamp, @@ -156,16 +155,12 @@ class RequestJournal( } private def incrementNumDirtyRequests(): Unit = { - discard { - numDirtyRequests.incrementAndGet() - } + numDirtyRequests.incrementAndGet().discard metrics.numDirtyRequests.inc() } private def decrementNumDirtyRequests(): Unit = { - discard { - numDirtyRequests.decrementAndGet() - } + numDirtyRequests.decrementAndGet().discard metrics.numDirtyRequests.dec() } diff --git a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/protocol/transfer/TransferOutProcessingSteps.scala b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/protocol/transfer/TransferOutProcessingSteps.scala index b662c36e50e..ee5edd11ecb 100644 --- a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/protocol/transfer/TransferOutProcessingSteps.scala +++ b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/protocol/transfer/TransferOutProcessingSteps.scala @@ -48,6 +48,7 @@ import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.util.EitherTUtil.{condUnitET, ifThenET} import com.digitalasset.canton.version.Transfer.{SourceProtocolVersion, TargetProtocolVersion} import com.digitalasset.canton.{ + LfPackageName, LfPartyId, RequestCounter, SequencerCounter, @@ -454,6 +455,7 @@ class TransferOutProcessingSteps( WithContractHash.fromContract(contract, fullTree.contractId), fullTree.transferCounter, contract.rawContractInstance.contractInstance.unversioned.template, + contract.rawContractInstance.contractInstance.unversioned.packageName, transferringParticipant, fullTree.submitterMetadata, transferId, @@ -544,6 +546,7 @@ class TransferOutProcessingSteps( WithContractHash(contractId, contractHash), transferCounter, templateId, + packageName, transferringParticipant, submitterMetadata, transferId, @@ -600,6 +603,7 @@ class TransferOutProcessingSteps( transferOutEvent <- createTransferredOut( contractId, templateId, + packageName, stakeholders, submitterMetadata, transferId, @@ -631,6 +635,7 @@ class TransferOutProcessingSteps( private def createTransferredOut( contractId: LfContractId, templateId: LfTemplateId, + packageName: LfPackageName, contractStakeholders: Set[LfPartyId], submitterMetadata: TransferSubmitterMetadata, transferId: TransferId, @@ -663,6 +668,7 @@ class TransferOutProcessingSteps( submitter = Option(submitterMetadata.submitter), contractId = contractId, templateId = Some(templateId), + packageName = packageName, contractStakeholders = contractStakeholders, transferId = transferId, targetDomain = targetDomain, @@ -768,6 +774,7 @@ object TransferOutProcessingSteps { contractIdAndHash: WithContractHash[LfContractId], transferCounter: TransferCounter, templateId: LfTemplateId, + packageName: LfPackageName, transferringParticipant: Boolean, submitterMetadata: TransferSubmitterMetadata, transferId: TransferId, diff --git a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/store/SerializableLedgerSyncEvent.scala b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/store/SerializableLedgerSyncEvent.scala index 7bec335cfc9..a148abc2bf9 100644 --- a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/store/SerializableLedgerSyncEvent.scala +++ b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/store/SerializableLedgerSyncEvent.scala @@ -1010,6 +1010,7 @@ private[store] final case class SerializableTransferredOut( submitter, contractId, templateId, + packageName, contractStakeholders, transferId, target, @@ -1026,6 +1027,7 @@ private[store] final case class SerializableTransferredOut( recordTime = SerializableLfTimestamp(transferId.transferOutTimestamp.underlying).toProtoV30, contractId = contractId.toProtoPrimitive, templateId = templateId.map(_.toString).getOrElse(""), + packageName = packageName, contractStakeholders = contractStakeholders.toSeq, sourceDomain = transferId.sourceDomain.toProtoPrimitive, targetDomain = target.toProtoPrimitive, @@ -1057,6 +1059,7 @@ private[store] object SerializableTransferredOut { isTransferringParticipant, hostedStakeholdersP, transferCounterP, + packageNameP, ) = transferOutP for { @@ -1074,6 +1077,7 @@ private[store] object SerializableTransferredOut { ) workflowId <- ProtoConverter.parseLFWorkflowIdO(workflowIdP) templateId <- ProtoConverter.parseTemplateIdO(templateIdP) + packageName <- ProtoConverter.parseLfPackageName(packageNameP) hostedStakeholders <- hostedStakeholdersP.traverse(ProtoConverter.parseLfPartyId) } yield LedgerSyncEvent.TransferredOut( updateId = updateId, @@ -1081,6 +1085,7 @@ private[store] object SerializableTransferredOut { submitter = submitter, contractId = contractId, templateId = templateId, + packageName = packageName, contractStakeholders = contractStakeholders.toSet, transferId = TransferId(SourceDomainId(rawSourceDomainId), CantonTimestamp(recordTime)), targetDomain = TargetDomainId(rawTargetDomainId), diff --git a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/sync/LedgerSyncEvent.scala b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/sync/LedgerSyncEvent.scala index 1d2522fb738..5b94ca90c0b 100644 --- a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/sync/LedgerSyncEvent.scala +++ b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/sync/LedgerSyncEvent.scala @@ -39,6 +39,7 @@ import com.digitalasset.canton.{ LedgerParticipantId, LedgerSubmissionId, LedgerTransactionId, + LfPackageName, LfPartyId, LfTimestamp, LfWorkflowId, @@ -435,6 +436,7 @@ object LedgerSyncEvent { submitter: Option[LfPartyId], contractId: LfContractId, templateId: Option[LfTemplateId], + packageName: LfPackageName, contractStakeholders: Set[LfPartyId], transferId: TransferId, targetDomain: TargetDomainId, @@ -461,6 +463,7 @@ object LedgerSyncEvent { param("transferId", _.transferId), param("contractId", _.contractId), paramIfDefined("templateId", _.templateId), + param("packageName", _.packageName), param("target", _.targetDomain), paramIfDefined("transferInExclusivity", _.transferInExclusivity), paramIfDefined("workflowId", _.workflowId), @@ -488,6 +491,7 @@ object LedgerSyncEvent { s"templateId should not be empty in transfer-id: $transferId" ) ), + packageName = packageName, stakeholders = contractStakeholders.toList, assignmentExclusivity = transferInExclusivity, ), diff --git a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/traffic/TrafficStateController.scala b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/traffic/TrafficStateController.scala index 98465509829..1712f2d130a 100644 --- a/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/traffic/TrafficStateController.scala +++ b/canton/community/participant/src/main/scala/com/digitalasset/canton/participant/traffic/TrafficStateController.scala @@ -31,7 +31,6 @@ class TrafficStateController( implicit tc: TraceContext ): Unit = { metrics.trafficControl.topologyTransaction.updateValue(newBalance.value) - metrics.trafficControl.extraTrafficAvailable.updateValue(newBalance.value) val newState = currentTrafficState.updateAndGet { case Some(old) if old.timestamp <= timestamp => old.state @@ -45,6 +44,9 @@ class TrafficStateController( } case other => other } + newState + .map(_.state.extraTrafficRemainder.value) + .foreach(metrics.trafficControl.extraTrafficAvailable.updateValue) logger.debug(s"Updating traffic state after balance update to $newState") } diff --git a/canton/community/participant/src/test/scala/com/digitalasset/canton/participant/protocol/transfer/TransferOutProcessingStepsTest.scala b/canton/community/participant/src/test/scala/com/digitalasset/canton/participant/protocol/transfer/TransferOutProcessingStepsTest.scala index ad03933c003..ee13370a18f 100644 --- a/canton/community/participant/src/test/scala/com/digitalasset/canton/participant/protocol/transfer/TransferOutProcessingStepsTest.scala +++ b/canton/community/participant/src/test/scala/com/digitalasset/canton/participant/protocol/transfer/TransferOutProcessingStepsTest.scala @@ -60,6 +60,7 @@ import com.digitalasset.canton.{ LedgerApplicationId, LedgerCommandId, LfPackageId, + LfPackageName, LfPartyId, RequestCounter, SequencerCounter, @@ -106,6 +107,8 @@ final class TransferOutProcessingStepsTest private val templateId = LfTemplateId.assertFromString("transferoutprocessingstepstestpackage:template:id") + private val packageName = + LfPackageName.assertFromString("transferoutprocessingstepstestpackagename") private val initialTransferCounter: TransferCounterO = Some(TransferCounter.Genesis) @@ -831,6 +834,7 @@ final class TransferOutProcessingStepsTest WithContractHash(contractId, contractHash), TransferCounter.Genesis, templateId = templateId, + packageName = packageName, transferringParticipant = false, submitterMetadata = submitterMetadata(submitter), transferId, diff --git a/language-support/java/bindings-rxjava/src/test/scala/com/daml/ledger/rxjava/grpc/helpers/TransactionGenerator.scala b/language-support/java/bindings-rxjava/src/test/scala/com/daml/ledger/rxjava/grpc/helpers/TransactionGenerator.scala index a2019e78af7..0b1db79908f 100644 --- a/language-support/java/bindings-rxjava/src/test/scala/com/daml/ledger/rxjava/grpc/helpers/TransactionGenerator.scala +++ b/language-support/java/bindings-rxjava/src/test/scala/com/daml/ledger/rxjava/grpc/helpers/TransactionGenerator.scala @@ -258,18 +258,28 @@ object TransactionGenerator { val archivedEventGen: Gen[(Archived, data.ArchivedEvent)] = for { eventId <- nonEmptyId + pkgName <- nonEmptyId contractId <- nonEmptyId (scalaTemplateId, javaTemplateId) <- identifierGen parties <- Gen.listOf(nonEmptyId) } yield ( - Archived(ArchivedEvent(eventId, contractId, Some(scalaTemplateId), parties)), - new data.ArchivedEvent(parties.asJava, eventId, javaTemplateId, contractId), + Archived( + ArchivedEvent( + eventId = eventId, + contractId = contractId, + templateId = Some(scalaTemplateId), + witnessParties = parties, + packageName = pkgName, + ) + ), + new data.ArchivedEvent(parties.asJava, eventId, javaTemplateId, pkgName, contractId), ) val exercisedEventGen: Gen[(Exercised, data.ExercisedEvent)] = for { eventId <- nonEmptyId contractId <- nonEmptyId (scalaTemplateId, javaTemplateId) <- identifierGen + pkgName <- nonEmptyId mbInterfaceId <- Gen.option(identifierGen) scalaInterfaceId = mbInterfaceId.map(_._1) javaInterfaceId = mbInterfaceId.map(_._2) @@ -283,23 +293,25 @@ object TransactionGenerator { } yield ( Exercised( ExercisedEvent( - eventId, - contractId, - Some(scalaTemplateId), - scalaInterfaceId, - choice, - Some(scalaChoiceArgument), - actingParties, - consuming, - witnessParties, - Nil, - Some(scalaExerciseResult), + eventId = eventId, + contractId = contractId, + templateId = Some(scalaTemplateId), + interfaceId = scalaInterfaceId, + choice = choice, + choiceArgument = Some(scalaChoiceArgument), + actingParties = actingParties, + consuming = consuming, + witnessParties = witnessParties, + childEventIds = Nil, + exerciseResult = Some(scalaExerciseResult), + packageName = pkgName, ) ), new data.ExercisedEvent( witnessParties.asJava, eventId, javaTemplateId, + pkgName, javaInterfaceId.toJava, contractId, choice,