update canton to 20240604.13418.v5318c201 (#19327)

tell-slack: canton

Co-authored-by: Azure Pipelines Daml Build <support@digitalasset.com>
This commit is contained in:
azure-pipelines[bot] 2024-06-04 14:39:55 +02:00 committed by GitHub
parent 18e4e155fb
commit 79929ac266
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
80 changed files with 687 additions and 1630 deletions

View File

@ -4,14 +4,7 @@
package com.digitalasset.canton.console package com.digitalasset.canton.console
import com.digitalasset.canton.admin.api.client.commands.EnterpriseSequencerAdminCommands.LocatePruningTimestampCommand import com.digitalasset.canton.admin.api.client.commands.EnterpriseSequencerAdminCommands.LocatePruningTimestampCommand
import com.digitalasset.canton.admin.api.client.commands.{ import com.digitalasset.canton.admin.api.client.commands.*
EnterpriseSequencerAdminCommands,
EnterpriseSequencerBftAdminCommands,
GrpcAdminCommand,
PruningSchedulerCommands,
SequencerAdminCommands,
SequencerPublicCommands,
}
import com.digitalasset.canton.admin.api.client.data.StaticDomainParameters as ConsoleStaticDomainParameters import com.digitalasset.canton.admin.api.client.data.StaticDomainParameters as ConsoleStaticDomainParameters
import com.digitalasset.canton.config.RequireTypes.{ExistingFile, NonNegativeInt, Port, PositiveInt} import com.digitalasset.canton.config.RequireTypes.{ExistingFile, NonNegativeInt, Port, PositiveInt}
import com.digitalasset.canton.config.* import com.digitalasset.canton.config.*
@ -51,7 +44,6 @@ import com.digitalasset.canton.participant.{ParticipantNode, ParticipantNodeBoot
import com.digitalasset.canton.sequencer.admin.v30.SequencerPruningAdministrationServiceGrpc import com.digitalasset.canton.sequencer.admin.v30.SequencerPruningAdministrationServiceGrpc
import com.digitalasset.canton.sequencer.admin.v30.SequencerPruningAdministrationServiceGrpc.SequencerPruningAdministrationServiceStub import com.digitalasset.canton.sequencer.admin.v30.SequencerPruningAdministrationServiceGrpc.SequencerPruningAdministrationServiceStub
import com.digitalasset.canton.sequencing.{GrpcSequencerConnection, SequencerConnections} import com.digitalasset.canton.sequencing.{GrpcSequencerConnection, SequencerConnections}
import com.digitalasset.canton.time.EnrichedDurations.*
import com.digitalasset.canton.topology.* import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.store.TimeQuery import com.digitalasset.canton.topology.store.TimeQuery
import com.digitalasset.canton.tracing.NoTracing import com.digitalasset.canton.tracing.NoTracing
@ -1053,16 +1045,9 @@ abstract class SequencerReference(
|The command will fail if a client has not yet read and acknowledged some data up to the specified time.""" |The command will fail if a client has not yet read and acknowledged some data up to the specified time."""
) )
def prune_at(timestamp: CantonTimestamp): String = { def prune_at(timestamp: CantonTimestamp): String = {
val status = this.status() this.consoleEnvironment.run {
val unauthenticatedMembers =
status.unauthenticatedMembersToDisable(
this.consoleEnvironment.environment.config.parameters.retentionPeriodDefaults.unauthenticatedMembers.toInternal
)
unauthenticatedMembers.foreach(disable_member)
val msg = this.consoleEnvironment.run {
runner.adminCommand(EnterpriseSequencerAdminCommands.Prune(timestamp)) runner.adminCommand(EnterpriseSequencerAdminCommands.Prune(timestamp))
} }
s"$msg. Automatically disabled ${unauthenticatedMembers.size} unauthenticated member clients."
} }
@Help.Summary( @Help.Summary(
@ -1078,10 +1063,7 @@ abstract class SequencerReference(
if (dryRun) { if (dryRun) {
formatDisableDryRun(timestamp, clientsToDisable) formatDisableDryRun(timestamp, clientsToDisable)
} else { } else {
val authenticatedClientsToDisable = clientsToDisable.members.toSeq.filter(_.isAuthenticated) clientsToDisable.members.toSeq.foreach(disable_member)
// There's no need to explicitly disable unauthenticated members
// prune will take care of that implicitly
authenticatedClientsToDisable.foreach(disable_member)
// check we can now prune for the provided timestamp // check we can now prune for the provided timestamp
val statusAfterDisabling = status() val statusAfterDisabling = status()
@ -1094,7 +1076,7 @@ abstract class SequencerReference(
val pruneMsg = prune_at(timestamp) val pruneMsg = prune_at(timestamp)
if (clientsToDisable.members.nonEmpty) { if (clientsToDisable.members.nonEmpty) {
s"$pruneMsg\nDisabled the following authenticated members:${authenticatedClientsToDisable.map(_.toString).sorted.mkString("\n - ", "\n - ", "\n")}" s"$pruneMsg\nDisabled the following members:${clientsToDisable.members.toSeq.map(_.toString).sorted.mkString("\n - ", "\n - ", "\n")}"
} else { } else {
pruneMsg pruneMsg
} }

View File

@ -69,13 +69,6 @@ service SequencerService {
// still accept the request internally and therefore emit events later on. // still accept the request internally and therefore emit events later on.
rpc SendAsyncVersioned(SendAsyncVersionedRequest) returns (SendAsyncVersionedResponse); rpc SendAsyncVersioned(SendAsyncVersionedRequest) returns (SendAsyncVersionedResponse);
// Submit an unauthenticated request to the sequencer.
// The behavior is as for SendAsyncVersioned, except that the sender is not authenticated.
// (Further details, e.g. about allowed recipients, can be found in the implementation.)
//
// This method will be discontinued soon.
rpc SendAsyncUnauthenticatedVersioned(SendAsyncUnauthenticatedVersionedRequest) returns (SendAsyncUnauthenticatedVersionedResponse);
// Establishes a stream with the server to receive sequenced events from the domain after the given // Establishes a stream with the server to receive sequenced events from the domain after the given
// counter. The delivered events will have a sequential counter and monotonically increasing timestamp. // counter. The delivered events will have a sequential counter and monotonically increasing timestamp.
// //
@ -84,11 +77,6 @@ service SequencerService {
// event.topology_timestamp refers to a time before the sequencer has been onboarded. // event.topology_timestamp refers to a time before the sequencer has been onboarded.
rpc SubscribeVersioned(SubscriptionRequest) returns (stream VersionedSubscriptionResponse); rpc SubscribeVersioned(SubscriptionRequest) returns (stream VersionedSubscriptionResponse);
// Same as SubscribeVersioned except that it lacks authentication for the subscriber.
//
// This method will be discontinued soon.
rpc SubscribeUnauthenticatedVersioned(SubscriptionRequest) returns (stream VersionedSubscriptionResponse);
// Allows a member to acknowledge that they have read all events up to and including the provided timestamp, // Allows a member to acknowledge that they have read all events up to and including the provided timestamp,
// and that they will never re-read these events again. This information is currently only used for informational // and that they will never re-read these events again. This information is currently only used for informational
// purposes and to provide a watermark for which it is safe to prune earlier events from the sequencer data stores. // purposes and to provide a watermark for which it is safe to prune earlier events from the sequencer data stores.
@ -108,41 +96,6 @@ message SendAsyncVersionedRequest {
bytes signed_submission_request = 1; bytes signed_submission_request = 1;
} }
message SendAsyncUnauthenticatedVersionedRequest {
// Contains a versioned SubmissionRequest
bytes submission_request = 1;
}
message SendAsyncUnauthenticatedVersionedResponse {
Error error = 1; // Defined iff the response is an error.
message Error {
oneof reason {
// The sequencer couldn't read the request (typically indicates a serialization and/or versioning bug).
string request_invalid = 1;
// The sequencer could read the request but refused to handle it (the request may violate a max size constraint).
string request_refused = 2;
// The sequencer is overloaded and does not have capacity to handle this request.
string overloaded = 3;
// The specified sender is not registered so the sequencer cannot guarantee publishing a Deliver event if the request can be sequenced.
string sender_unknown = 4;
// The sequencer is shutting down so is declining to process new requests
string shutting_down = 5;
// The sequencer is unavailable and can't currently process requests
string unavailable = 6;
// There are one or more recipients that are not registered so the sequencer cannot guarantee publishing a Deliver event if the request can be sequenced.
// This message was added in protocol version 1.1, therefore it must not be used by a sequencer operating on Canton 1.0 protocol version.
string unknown_recipients = 7;
}
}
}
// Changes compared to SendAsyncResponse: added `Internal` and `Generic`. Note: `Generic` is not used yet, it is introduced for upgradability purposes. // Changes compared to SendAsyncResponse: added `Internal` and `Generic`. Note: `Generic` is not used yet, it is introduced for upgradability purposes.
message SendAsyncVersionedResponse { message SendAsyncVersionedResponse {
Error error = 1; // Defined iff the response is an error. Error error = 1; // Defined iff the response is an error.

View File

@ -23,7 +23,7 @@ object FatalError {
case None => logger.error(message) case None => logger.error(message)
} }
sys.exit(1) sys.exit(117)
} }
def exitOnFatalError(error: CantonError, logger: TracedLogger)(implicit def exitOnFatalError(error: CantonError, logger: TracedLogger)(implicit

View File

@ -0,0 +1,147 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.canton.sequencing
import cats.data.EitherT
import com.digitalasset.canton.concurrent.FutureSupervisor
import com.digitalasset.canton.config.RequireTypes.PositiveInt
import com.digitalasset.canton.discard.Implicits.DiscardOps
import com.digitalasset.canton.lifecycle.UnlessShutdown.{AbortedDueToShutdown, Outcome}
import com.digitalasset.canton.lifecycle.{
FutureUnlessShutdown,
PromiseUnlessShutdown,
UnlessShutdown,
}
import com.digitalasset.canton.logging.{ErrorLoggingContext, TracedLogger}
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.Thereafter.syntax.ThereafterOps
import com.digitalasset.canton.util.{ErrorUtil, FutureUtil}
import java.util.concurrent.atomic.AtomicInteger
import scala.collection.concurrent.TrieMap
import scala.concurrent.{ExecutionContext, blocking}
import scala.util.{Failure, Success, Try}
/** Utility class to make BFT-style operations.
*/
object BftSender {
/** Returned when the request fails to reach the required threshold
* @param successes The operators that successfully performed the request, grouped by hash of the result
* @param failures The operators that failed to perform the request
* @tparam K type of the value hash
* @tparam I type of the identity of operators
* @tparam E error type of the operation performed
*/
final case class FailedToReachThreshold[K, I, E](
successes: Map[K, Set[I]],
failures: Map[I, Either[Throwable, E]],
)
/** Make a request to multiple operators and aggregate the responses such as the final result will be successful
* only if "threshold" responses were identical.
* As soon as the threshold is reached, this method returns. It will also return with an error as soon as it is guaranteed that
* it cannot possibly gather sufficiently identical requests to meet the threshold.
* @param description description of the request
* @param threshold minimum value of identical results that need to be received for the request to be successful (inclusive)
* @param operators operators to use for the request. The request will be performed via every operator.
* @param performRequest request to be performed.
* @param resultHashKey function to provide a hash from a result. This is what determine whether 2 responses are identical.
* @tparam I key of the operator, typically and ID
* @tparam E Error type of performRequest
* @tparam O operator type: object with which the performRequest function will be called
* @tparam A type of the result
* @tparam K type of the result hash
* @return The result of performRequest if sufficiently many responses were identical from the operators.
*/
def makeRequest[I, E, O, A, K](
description: String,
futureSupervisor: FutureSupervisor,
logger: TracedLogger,
operators: Map[I, O],
threshold: PositiveInt,
performRequest: O => EitherT[FutureUnlessShutdown, E, A],
resultHashKey: A => K,
)(implicit
traceContext: TraceContext,
executionContext: ExecutionContext,
): EitherT[FutureUnlessShutdown, FailedToReachThreshold[K, I, E], A] = {
implicit val elc: ErrorLoggingContext = ErrorLoggingContext.fromTracedLogger(logger)
// Keeps track of successful responses in a hashmap so we can easily count how many identical ones we get
val successfulResults = TrieMap.empty[K, Set[I]]
// Separately keep track of the total number of responses received for fast comparison
val responsesCount = new AtomicInteger(0)
// We don't technically need the failures, but keep them around so we can log and return them if we never reach the threshold
val failedResults = TrieMap.empty[I, Either[Throwable, E]]
// Promise that provide the result for this method
val promise = new PromiseUnlessShutdown[Either[FailedToReachThreshold[K, I, E], A]](
description,
futureSupervisor,
)
// Provides an object on which to synchronize to avoid concurrency issues when checking results
val lock = new Object
def addResult(operatorId: I, result: Try[UnlessShutdown[Either[E, A]]]): Unit = blocking {
lock.synchronized {
if (promise.isCompleted) {
logger.debug(
s"Ignoring response $result from $operatorId for $description since the threshold has already been reached or the request has failed."
)
} else {
val responsesReceived = responsesCount.incrementAndGet()
// If there's not enough missing responses left to get to the threshold, we can fail immediately
def checkIfStillPossibleToReachThreshold(): Unit = {
val missingResponses = operators.size - responsesReceived
val bestChanceOfReachingThreshold =
successfulResults.values.map(_.size).foldLeft(0)(_.max(_))
if (bestChanceOfReachingThreshold + missingResponses < threshold.value) {
logger.info(
s"Cannot reach threshold for $description. Threshold = ${threshold.value}, failed results: $failedResults, successful results: $successfulResults"
)
promise.outcome(
Left(FailedToReachThreshold[K, I, E](successfulResults.toMap, failedResults.toMap))
)
}
}
// Checks that the operator has not provided a result yet (successful or not)
ErrorUtil.requireState(
!(successfulResults.values.toList.flatten ++ failedResults.keySet).contains(operatorId),
s"Operator $operatorId has already provided a result. Please report this as a bug",
)
result match {
case Success(Outcome(Right(value))) =>
val updated = successfulResults.updateWith(resultHashKey(value)) {
case Some(operators) => Some(operators ++ Set(operatorId))
case None => Some(Set(operatorId))
}
// If we've reached the threshold we can stop
if (updated.map(_.size).getOrElse(0) >= threshold.value) promise.outcome(Right(value))
case Success(Outcome(Left(error))) =>
failedResults.put(operatorId, Right(error)).discard
case Failure(ex) =>
failedResults.put(operatorId, Left(ex)).discard
case Success(AbortedDueToShutdown) =>
promise.shutdown()
}
if (!promise.isCompleted) checkIfStillPossibleToReachThreshold()
}
}
}
operators.foreach { case (operatorId, operator) =>
FutureUtil.doNotAwaitUnlessShutdown(
performRequest(operator).value
.thereafter(addResult(operatorId, _))
.map(_ => ()),
s"$description failed for $operatorId",
)
}
EitherT(promise.futureUS)
}
}

View File

@ -44,7 +44,6 @@ object MemberAuthentication extends MemberAuthentication {
def apply(member: Member): Either[AuthenticationError, MemberAuthentication] = member match { def apply(member: Member): Either[AuthenticationError, MemberAuthentication] = member match {
case _: ParticipantId | _: MediatorId => Right(this) case _: ParticipantId | _: MediatorId => Right(this)
case _: SequencerId => Left(AuthenticationNotSupportedForMember(member)) case _: SequencerId => Left(AuthenticationNotSupportedForMember(member))
case _: UnauthenticatedMemberId => Left(AuthenticationNotSupportedForMember(member))
} }
sealed abstract class AuthenticationError(val reason: String, val code: String) sealed abstract class AuthenticationError(val reason: String, val code: String)

View File

@ -15,7 +15,7 @@ import com.digitalasset.canton.sequencing.authentication.{
AuthenticationTokenManagerConfig, AuthenticationTokenManagerConfig,
} }
import com.digitalasset.canton.time.Clock import com.digitalasset.canton.time.Clock
import com.digitalasset.canton.topology.{AuthenticatedMember, DomainId, UnauthenticatedMemberId} import com.digitalasset.canton.topology.{DomainId, Member}
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import com.google.common.annotations.VisibleForTesting import com.google.common.annotations.VisibleForTesting
import io.grpc.ForwardingClientCall.SimpleForwardingClientCall import io.grpc.ForwardingClientCall.SimpleForwardingClientCall
@ -33,7 +33,7 @@ import scala.util.control.NonFatal
*/ */
private[grpc] class SequencerClientTokenAuthentication( private[grpc] class SequencerClientTokenAuthentication(
domainId: DomainId, domainId: DomainId,
member: AuthenticatedMember, member: Member,
tokenManagerPerEndpoint: NonEmpty[Map[Endpoint, AuthenticationTokenManager]], tokenManagerPerEndpoint: NonEmpty[Map[Endpoint, AuthenticationTokenManager]],
protected val loggerFactory: NamedLoggerFactory, protected val loggerFactory: NamedLoggerFactory,
)(implicit executionContext: ExecutionContext) )(implicit executionContext: ExecutionContext)
@ -160,7 +160,7 @@ private[grpc] class SequencerClientTokenAuthentication(
object SequencerClientTokenAuthentication { object SequencerClientTokenAuthentication {
def apply( def apply(
domainId: DomainId, domainId: DomainId,
authenticatedMember: AuthenticatedMember, member: Member,
obtainTokenPerEndpoint: NonEmpty[ obtainTokenPerEndpoint: NonEmpty[
Map[ Map[
Endpoint, Endpoint,
@ -186,7 +186,7 @@ object SequencerClientTokenAuthentication {
} }
new SequencerClientTokenAuthentication( new SequencerClientTokenAuthentication(
domainId, domainId,
authenticatedMember, member,
tokenManagerPerEndpoint, tokenManagerPerEndpoint,
loggerFactory, loggerFactory,
) )
@ -194,32 +194,6 @@ object SequencerClientTokenAuthentication {
} }
class SequencerClientNoAuthentication(domainId: DomainId, member: UnauthenticatedMemberId)
extends SequencerClientAuthentication {
private val metadata: Metadata = {
val metadata = new Metadata()
metadata.put(Constant.MEMBER_ID_METADATA_KEY, member.toProtoPrimitive)
metadata.put(Constant.DOMAIN_ID_METADATA_KEY, domainId.toProtoPrimitive)
metadata
}
override def apply[S <: AbstractStub[S]](client: S): S =
client.withCallCredentials(callCredentials)
@VisibleForTesting
private[grpc] val callCredentials: CallCredentials = new CallCredentials {
override def applyRequestMetadata(
requestInfo: CallCredentials.RequestInfo,
appExecutor: Executor,
applier: CallCredentials.MetadataApplier,
): Unit = applier.apply(metadata)
override def thisUsesUnstableApi(): Unit = {
// yes, we know - cheers grpc
}
}
}
trait SequencerClientAuthentication { trait SequencerClientAuthentication {
/** Apply the sequencer authentication components to a grpc client stub */ /** Apply the sequencer authentication components to a grpc client stub */

View File

@ -291,7 +291,6 @@ object SequencerSubscriptionFactoryPekko {
def fromTransport[E]( def fromTransport[E](
sequencerID: SequencerId, sequencerID: SequencerId,
transport: SequencerClientTransportPekko.Aux[E], transport: SequencerClientTransportPekko.Aux[E],
requiresAuthentication: Boolean,
member: Member, member: Member,
protocolVersion: ProtocolVersion, protocolVersion: ProtocolVersion,
): SequencerSubscriptionFactoryPekko[E] = ): SequencerSubscriptionFactoryPekko[E] =
@ -302,8 +301,7 @@ object SequencerSubscriptionFactoryPekko {
traceContext: TraceContext traceContext: TraceContext
): SequencerSubscriptionPekko[E] = { ): SequencerSubscriptionPekko[E] = {
val request = SubscriptionRequest(member, startingCounter, protocolVersion) val request = SubscriptionRequest(member, startingCounter, protocolVersion)
if (requiresAuthentication) transport.subscribe(request) transport.subscribe(request)
else transport.subscribeUnauthenticated(request)
} }
override val retryPolicy: SubscriptionErrorRetryPolicyPekko[E] = override val retryPolicy: SubscriptionErrorRetryPolicyPekko[E] =

View File

@ -315,14 +315,13 @@ object ResilientSequencerSubscription extends SequencerSubscriptionErrorGroup {
warnDelay: FiniteDuration, warnDelay: FiniteDuration,
maxRetryDelay: FiniteDuration, maxRetryDelay: FiniteDuration,
timeouts: ProcessingTimeout, timeouts: ProcessingTimeout,
requiresAuthentication: Boolean,
loggerFactory: NamedLoggerFactory, loggerFactory: NamedLoggerFactory,
)(implicit executionContext: ExecutionContext): ResilientSequencerSubscription[E] = { )(implicit executionContext: ExecutionContext): ResilientSequencerSubscription[E] = {
new ResilientSequencerSubscription[E]( new ResilientSequencerSubscription[E](
sequencerId, sequencerId,
startingFrom, startingFrom,
handler, handler,
createSubscription(member, getTransport, requiresAuthentication, protocolVersion), createSubscription(member, getTransport, protocolVersion),
SubscriptionRetryDelayRule( SubscriptionRetryDelayRule(
initialDelay, initialDelay,
warnDelay, warnDelay,
@ -337,7 +336,6 @@ object ResilientSequencerSubscription extends SequencerSubscriptionErrorGroup {
private def createSubscription[E]( private def createSubscription[E](
member: Member, member: Member,
getTransport: => UnlessShutdown[SequencerClientTransport], getTransport: => UnlessShutdown[SequencerClientTransport],
requiresAuthentication: Boolean,
protocolVersion: ProtocolVersion, protocolVersion: ProtocolVersion,
): SequencerSubscriptionFactory[E] = ): SequencerSubscriptionFactory[E] =
new SequencerSubscriptionFactory[E] { new SequencerSubscriptionFactory[E] {
@ -348,8 +346,7 @@ object ResilientSequencerSubscription extends SequencerSubscriptionErrorGroup {
getTransport getTransport
.map { transport => .map { transport =>
val subscription = val subscription =
if (requiresAuthentication) transport.subscribe(request, handler)(traceContext) transport.subscribe(request, handler)(traceContext)
else transport.subscribeUnauthenticated(request, handler)(traceContext)
(subscription, transport.subscriptionRetryPolicy) (subscription, transport.subscriptionRetryPolicy)
} }
} }

View File

@ -50,6 +50,7 @@ import com.digitalasset.canton.util.ErrorUtil
import com.digitalasset.canton.util.PekkoUtil.WithKillSwitch import com.digitalasset.canton.util.PekkoUtil.WithKillSwitch
import com.digitalasset.canton.util.PekkoUtil.syntax.* import com.digitalasset.canton.util.PekkoUtil.syntax.*
import com.digitalasset.canton.version.ProtocolVersion import com.digitalasset.canton.version.ProtocolVersion
import com.google.common.annotations.VisibleForTesting
import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.{ExecutionContext, Future}
@ -409,9 +410,7 @@ trait SequencedEventValidatorFactory {
* The [[com.digitalasset.canton.sequencing.client.SequencerSubscription]] requests this event again. * The [[com.digitalasset.canton.sequencing.client.SequencerSubscription]] requests this event again.
* @param unauthenticated Whether the subscription is unauthenticated * @param unauthenticated Whether the subscription is unauthenticated
*/ */
def create( def create()(implicit loggingContext: NamedLoggingContext): SequencedEventValidator
unauthenticated: Boolean
)(implicit loggingContext: NamedLoggingContext): SequencedEventValidator
} }
object SequencedEventValidatorFactory { object SequencedEventValidatorFactory {
@ -426,19 +425,13 @@ object SequencedEventValidatorFactory {
domainId: DomainId, domainId: DomainId,
warn: Boolean = true, warn: Boolean = true,
): SequencedEventValidatorFactory = new SequencedEventValidatorFactory { ): SequencedEventValidatorFactory = new SequencedEventValidatorFactory {
override def create( override def create()(implicit loggingContext: NamedLoggingContext): SequencedEventValidator =
unauthenticated: Boolean
)(implicit loggingContext: NamedLoggingContext): SequencedEventValidator =
SequencedEventValidator.noValidation(domainId, warn) SequencedEventValidator.noValidation(domainId, warn)
} }
} }
/** Validate whether a received event is valid for processing. /** Validate whether a received event is valid for processing. */
*
* @param unauthenticated if true, then the connection is unauthenticated. in such cases, we have to skip some validations.
*/
class SequencedEventValidatorImpl( class SequencedEventValidatorImpl(
unauthenticated: Boolean,
domainId: DomainId, domainId: DomainId,
protocolVersion: ProtocolVersion, protocolVersion: ProtocolVersion,
syncCryptoApi: SyncCryptoClient[SyncCryptoApi], syncCryptoApi: SyncCryptoClient[SyncCryptoApi],
@ -576,21 +569,15 @@ class SequencedEventValidatorImpl(
Either.cond(receivedDomainId == domainId, (), BadDomainId(domainId, receivedDomainId)) Either.cond(receivedDomainId == domainId, (), BadDomainId(domainId, receivedDomainId))
} }
private def verifySignature( @VisibleForTesting
protected def verifySignature(
priorEventO: Option[PossiblyIgnoredSerializedEvent], priorEventO: Option[PossiblyIgnoredSerializedEvent],
event: OrdinarySerializedEvent, event: OrdinarySerializedEvent,
sequencerId: SequencerId, sequencerId: SequencerId,
protocolVersion: ProtocolVersion, protocolVersion: ProtocolVersion,
): EitherT[FutureUnlessShutdown, SequencedEventValidationError[Nothing], Unit] = { ): EitherT[FutureUnlessShutdown, SequencedEventValidationError[Nothing], Unit] = {
implicit val traceContext: TraceContext = event.traceContext implicit val traceContext: TraceContext = event.traceContext
if (unauthenticated) { if (event.counter == SequencerCounter.Genesis) {
// TODO(i4933) once we have topology data on the sequencer api, we might fetch the domain keys
// and use the domain keys to validate anything here if we are unauthenticated
logger.debug(
s"Skipping sequenced event validation for counter ${event.counter} and timestamp ${event.timestamp} in unauthenticated subscription from $sequencerId"
)
EitherT.fromEither[FutureUnlessShutdown](checkNoTimestampOfSigningKey(event))
} else if (event.counter == SequencerCounter.Genesis) {
// TODO(#4933) This is a fresh subscription. Either fetch the domain keys via a future sequencer API and validate the signature // TODO(#4933) This is a fresh subscription. Either fetch the domain keys via a future sequencer API and validate the signature
// or wait until the topology processor has processed the topology information in the first message and then validate the signature. // or wait until the topology processor has processed the topology information in the first message and then validate the signature.
logger.info( logger.info(

View File

@ -8,7 +8,6 @@ import cats.data.EitherT
import cats.implicits.catsSyntaxOptionId import cats.implicits.catsSyntaxOptionId
import cats.syntax.alternative.* import cats.syntax.alternative.*
import cats.syntax.either.* import cats.syntax.either.*
import cats.syntax.foldable.*
import cats.syntax.functor.* import cats.syntax.functor.*
import cats.syntax.parallel.* import cats.syntax.parallel.*
import com.daml.metrics.Timed import com.daml.metrics.Timed
@ -101,31 +100,6 @@ trait SequencerClient extends SequencerClientSend with FlagCloseable {
*/ */
def trafficStateController: TrafficStateController def trafficStateController: TrafficStateController
/** Sends a request to sequence a deliver event to the sequencer.
* This method merely dispatches to one of the other methods (`sendAsync` or `sendAsyncUnauthenticated`)
* depending if member is Authenticated or Unauthenticated.
*/
def sendAsyncUnauthenticatedOrNot(
batch: Batch[DefaultOpenEnvelope],
topologyTimestamp: Option[CantonTimestamp] = None,
maxSequencingTime: CantonTimestamp = generateMaxSequencingTime,
messageId: MessageId = generateMessageId,
aggregationRule: Option[AggregationRule] = None,
callback: SendCallback = SendCallback.empty,
amplify: Boolean = false,
)(implicit traceContext: TraceContext): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit]
/** Does the same as [[sendAsync]], except that this method is supposed to be used
* only by unauthenticated members for very specific operations that do not require authentication
* such as requesting that a participant's topology data gets accepted by the topology manager
*/
def sendAsyncUnauthenticated(
batch: Batch[DefaultOpenEnvelope],
maxSequencingTime: CantonTimestamp = generateMaxSequencingTime,
messageId: MessageId = generateMessageId,
callback: SendCallback = SendCallback.empty,
)(implicit traceContext: TraceContext): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit]
/** Create a subscription for sequenced events for this member, /** Create a subscription for sequenced events for this member,
* starting after the prehead in the `sequencerCounterTrackerStore`. * starting after the prehead in the `sequencerCounterTrackerStore`.
* *
@ -170,15 +144,6 @@ trait SequencerClient extends SequencerClientSend with FlagCloseable {
fetchCleanTimestamp: PeriodicAcknowledgements.FetchCleanTimestamp, fetchCleanTimestamp: PeriodicAcknowledgements.FetchCleanTimestamp,
)(implicit traceContext: TraceContext): Future[Unit] )(implicit traceContext: TraceContext): Future[Unit]
/** Does the same as [[subscribeAfter]], except that this method is supposed to be used
* only by unauthenticated members
*/
def subscribeAfterUnauthenticated(
priorTimestamp: CantonTimestamp,
eventHandler: PossiblyIgnoredApplicationHandler[ClosedEnvelope],
timeTracker: DomainTimeTracker,
)(implicit traceContext: TraceContext): Future[Unit]
/** Acknowledge that we have successfully processed all events up to and including the given timestamp. /** Acknowledge that we have successfully processed all events up to and including the given timestamp.
* The client should then never subscribe for events from before this point. * The client should then never subscribe for events from before this point.
*/ */
@ -250,37 +215,6 @@ abstract class SequencerClientImpl(
private lazy val printer = private lazy val printer =
new CantonPrettyPrinter(loggingConfig.api.maxStringLength, loggingConfig.api.maxMessageLines) new CantonPrettyPrinter(loggingConfig.api.maxStringLength, loggingConfig.api.maxMessageLines)
override def sendAsyncUnauthenticatedOrNot(
batch: Batch[DefaultOpenEnvelope],
topologyTimestamp: Option[CantonTimestamp],
maxSequencingTime: CantonTimestamp,
messageId: MessageId,
aggregationRule: Option[AggregationRule],
callback: SendCallback,
amplify: Boolean,
)(implicit
traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] = {
member match {
case _: AuthenticatedMember =>
sendAsync(
batch = batch,
topologyTimestamp = topologyTimestamp,
maxSequencingTime = maxSequencingTime,
messageId = messageId,
aggregationRule = aggregationRule,
callback = callback,
)
case _: UnauthenticatedMemberId =>
sendAsyncUnauthenticated(
batch = batch,
maxSequencingTime = maxSequencingTime,
messageId = messageId,
callback = callback,
)
}
}
override def sendAsync( override def sendAsync(
batch: Batch[DefaultOpenEnvelope], batch: Batch[DefaultOpenEnvelope],
topologyTimestamp: Option[CantonTimestamp], topologyTimestamp: Option[CantonTimestamp],
@ -293,29 +227,9 @@ abstract class SequencerClientImpl(
traceContext: TraceContext traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] = ): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] =
for { for {
_ <- EitherT.cond[FutureUnlessShutdown](
member.isAuthenticated,
(),
SendAsyncClientError.RequestInvalid(
"Only authenticated members can use the authenticated send operation"
): SendAsyncClientError,
)
// TODO(#12950): Validate that group addresses map to at least one member // TODO(#12950): Validate that group addresses map to at least one member
_ <- EitherT.cond[FutureUnlessShutdown](
topologyTimestamp.isEmpty || batch.envelopes.forall(
_.recipients.allRecipients.forall {
case MemberRecipient(m) => m.isAuthenticated
case _ => true
}
),
(),
SendAsyncClientError.RequestInvalid(
"Requests addressed to unauthenticated members must not specify a topology timestamp"
): SendAsyncClientError,
)
result <- sendAsyncInternal( result <- sendAsyncInternal(
batch, batch,
requiresAuthentication = true,
topologyTimestamp, topologyTimestamp,
maxSequencingTime, maxSequencingTime,
messageId, messageId,
@ -325,37 +239,6 @@ abstract class SequencerClientImpl(
) )
} yield result } yield result
/** Does the same as [[sendAsync]], except that this method is supposed to be used
* only by unauthenticated members for very specific operations that do not require authentication
* such as requesting that a participant's topology data gets accepted by the topology manager
*/
override def sendAsyncUnauthenticated(
batch: Batch[DefaultOpenEnvelope],
maxSequencingTime: CantonTimestamp = generateMaxSequencingTime,
messageId: MessageId = generateMessageId,
callback: SendCallback = SendCallback.empty,
)(implicit
traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] =
if (member.isAuthenticated)
EitherT.leftT(
SendAsyncClientError.RequestInvalid(
"Only unauthenticated members can use the unauthenticated send operation"
)
)
else
sendAsyncInternal(
batch,
requiresAuthentication = false,
// Requests involving unauthenticated members must not specify a topology timestamp
topologyTimestamp = None,
maxSequencingTime = maxSequencingTime,
messageId = messageId,
aggregationRule = None,
callback = callback,
amplify = false,
)
private def checkRequestSize( private def checkRequestSize(
request: SubmissionRequest, request: SubmissionRequest,
maxRequestSize: MaxRequestSize, maxRequestSize: MaxRequestSize,
@ -375,7 +258,6 @@ abstract class SequencerClientImpl(
private def sendAsyncInternal( private def sendAsyncInternal(
batch: Batch[DefaultOpenEnvelope], batch: Batch[DefaultOpenEnvelope],
requiresAuthentication: Boolean,
topologyTimestamp: Option[CantonTimestamp], topologyTimestamp: Option[CantonTimestamp],
maxSequencingTime: CantonTimestamp, maxSequencingTime: CantonTimestamp,
messageId: MessageId, messageId: MessageId,
@ -523,7 +405,6 @@ abstract class SequencerClientImpl(
_ <- performSend( _ <- performSend(
messageId, messageId,
request, request,
requiresAuthentication,
amplify, amplify,
() => peekAtSendResult(), () => peekAtSendResult(),
syncCryptoApi, syncCryptoApi,
@ -537,7 +418,6 @@ abstract class SequencerClientImpl(
private def performSend( private def performSend(
messageId: MessageId, messageId: MessageId,
request: SubmissionRequest, request: SubmissionRequest,
requiresAuthentication: Boolean,
amplify: Boolean, amplify: Boolean,
peekAtSendResult: () => Option[UnlessShutdown[SendResult]], peekAtSendResult: () => Option[UnlessShutdown[SendResult]],
topologySnapshot: SyncCryptoApi, topologySnapshot: SyncCryptoApi,
@ -546,45 +426,41 @@ abstract class SequencerClientImpl(
): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] = { ): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] = {
EitherTUtil EitherTUtil
.timed(metrics.submissions.sends) { .timed(metrics.submissions.sends) {
val timeout = timeouts.network.duration val (sequencerId, transport, patienceO) =
if (requiresAuthentication) { sequencersTransportState.nextAmplifiedTransport(Seq.empty)
val (sequencerId, transport, patienceO) = // Do not add an aggregation rule for amplifiable requests if amplification has not been configured
sequencersTransportState.nextAmplifiedTransport(Seq.empty) val amplifiableRequest =
// Do not add an aggregation rule for amplifiable requests if amplification has not been configured if (amplify && request.aggregationRule.isEmpty && patienceO.isDefined) {
val amplifiableRequest = val aggregationRule =
if (amplify && request.aggregationRule.isEmpty && patienceO.isDefined) { AggregationRule(NonEmpty(Seq, member), PositiveInt.one, protocolVersion)
val aggregationRule = logger.debug(
AggregationRule(NonEmpty(Seq, member), PositiveInt.one, protocolVersion) s"Adding aggregation rule $aggregationRule to submission request with message ID $messageId"
logger.debug(
s"Adding aggregation rule $aggregationRule to submission request with message ID $messageId"
)
request.copy(aggregationRule = aggregationRule.some)
} else request
for {
signedContent <- requestSigner
.signRequest(
amplifiableRequest,
HashPurpose.SubmissionRequestSignature,
Some(topologySnapshot),
)
.leftMap { err =>
val message = s"Error signing submission request $err"
logger.error(message)
SendAsyncClientError.RequestRefused(SendAsyncError.RequestRefused(message))
}
_ <- amplifiedSend(
signedContent,
sequencerId,
transport,
if (amplify) patienceO else None,
peekAtSendResult,
) )
} yield () request.copy(aggregationRule = aggregationRule.some)
} else } else request
sequencersTransportState.transport
.sendAsyncUnauthenticatedVersioned(request, timeout) for {
signedContent <- requestSigner
.signRequest(
amplifiableRequest,
HashPurpose.SubmissionRequestSignature,
Some(topologySnapshot),
)
.leftMap { err =>
val message = s"Error signing submission request $err"
logger.error(message)
SendAsyncClientError.RequestRefused(SendAsyncError.RequestRefused(message))
}
_ <- amplifiedSend(
signedContent,
sequencerId,
transport,
if (amplify) patienceO else None,
peekAtSendResult,
)
} yield ()
} }
.leftSemiflatMap { err => .leftSemiflatMap { err =>
// increment appropriate error metrics // increment appropriate error metrics
@ -796,27 +672,6 @@ abstract class SequencerClientImpl(
eventHandler, eventHandler,
timeTracker, timeTracker,
fetchCleanTimestamp, fetchCleanTimestamp,
requiresAuthentication = true,
)
/** Does the same as [[subscribeAfter]], except that this method is supposed to be used
* only by unauthenticated members
*
* The method does not verify the signature of the server.
*/
def subscribeAfterUnauthenticated(
priorTimestamp: CantonTimestamp,
eventHandler: PossiblyIgnoredApplicationHandler[ClosedEnvelope],
timeTracker: DomainTimeTracker,
)(implicit traceContext: TraceContext): Future[Unit] =
subscribeAfterInternal(
priorTimestamp,
// We do not track cleanliness for unauthenticated subscriptions
cleanPreheadTsO = None,
eventHandler,
timeTracker,
PeriodicAcknowledgements.noAcknowledgements,
requiresAuthentication = false,
) )
protected def subscribeAfterInternal( protected def subscribeAfterInternal(
@ -825,7 +680,6 @@ abstract class SequencerClientImpl(
nonThrottledEventHandler: PossiblyIgnoredApplicationHandler[ClosedEnvelope], nonThrottledEventHandler: PossiblyIgnoredApplicationHandler[ClosedEnvelope],
timeTracker: DomainTimeTracker, timeTracker: DomainTimeTracker,
fetchCleanTimestamp: PeriodicAcknowledgements.FetchCleanTimestamp, fetchCleanTimestamp: PeriodicAcknowledgements.FetchCleanTimestamp,
requiresAuthentication: Boolean,
)(implicit traceContext: TraceContext): Future[Unit] )(implicit traceContext: TraceContext): Future[Unit]
/** Acknowledge that we have successfully processed all events up to and including the given timestamp. /** Acknowledge that we have successfully processed all events up to and including the given timestamp.
@ -967,7 +821,6 @@ class RichSequencerClientImpl(
nonThrottledEventHandler: PossiblyIgnoredApplicationHandler[ClosedEnvelope], nonThrottledEventHandler: PossiblyIgnoredApplicationHandler[ClosedEnvelope],
timeTracker: DomainTimeTracker, timeTracker: DomainTimeTracker,
fetchCleanTimestamp: PeriodicAcknowledgements.FetchCleanTimestamp, fetchCleanTimestamp: PeriodicAcknowledgements.FetchCleanTimestamp,
requiresAuthentication: Boolean,
)(implicit traceContext: TraceContext): Future[Unit] = { )(implicit traceContext: TraceContext): Future[Unit] = {
val throttledEventHandler = ThrottlingApplicationEventHandler.throttle( val throttledEventHandler = ThrottlingApplicationEventHandler.throttle(
config.maximumInFlightEventBatches, config.maximumInFlightEventBatches,
@ -1054,7 +907,6 @@ class RichSequencerClientImpl(
sequencerAlias, sequencerAlias,
sequencerTransport.sequencerId, sequencerTransport.sequencerId,
preSubscriptionEvent, preSubscriptionEvent,
requiresAuthentication,
eventHandler, eventHandler,
).discard ).discard
} }
@ -1062,21 +914,19 @@ class RichSequencerClientImpl(
// periodically acknowledge that we've successfully processed up to the clean counter // periodically acknowledge that we've successfully processed up to the clean counter
// We only need to it setup once; the sequencer client will direct the acknowledgements to the // We only need to it setup once; the sequencer client will direct the acknowledgements to the
// right transport. // right transport.
if (requiresAuthentication) { // unauthenticated members don't need to ack periodicAcknowledgementsRef.set(
periodicAcknowledgementsRef.set( PeriodicAcknowledgements
PeriodicAcknowledgements .create(
.create( config.acknowledgementInterval.underlying,
config.acknowledgementInterval.underlying, deferredSubscriptionHealth.getState.isOk,
deferredSubscriptionHealth.getState.isOk, RichSequencerClientImpl.this,
RichSequencerClientImpl.this, fetchCleanTimestamp,
fetchCleanTimestamp, clock,
clock, timeouts,
timeouts, loggerFactory,
loggerFactory, )
) .some
.some )
)
}
} }
} }
@ -1095,14 +945,13 @@ class RichSequencerClientImpl(
sequencerAlias: SequencerAlias, sequencerAlias: SequencerAlias,
sequencerId: SequencerId, sequencerId: SequencerId,
preSubscriptionEvent: Option[PossiblyIgnoredSerializedEvent], preSubscriptionEvent: Option[PossiblyIgnoredSerializedEvent],
requiresAuthentication: Boolean,
eventHandler: OrdinaryApplicationHandler[ClosedEnvelope], eventHandler: OrdinaryApplicationHandler[ClosedEnvelope],
)(implicit )(implicit
traceContext: TraceContext traceContext: TraceContext
): ResilientSequencerSubscription[SequencerClientSubscriptionError] = { ): ResilientSequencerSubscription[SequencerClientSubscriptionError] = {
// previously seen counter takes precedence over the lower bound // previously seen counter takes precedence over the lower bound
val nextCounter = preSubscriptionEvent.fold(initialCounterLowerBound)(_.counter) val nextCounter = preSubscriptionEvent.fold(initialCounterLowerBound)(_.counter)
val eventValidator = eventValidatorFactory.create(unauthenticated = !requiresAuthentication) val eventValidator = eventValidatorFactory.create()
logger.info( logger.info(
s"Starting subscription for alias=$sequencerAlias, id=$sequencerId at timestamp ${preSubscriptionEvent s"Starting subscription for alias=$sequencerAlias, id=$sequencerId at timestamp ${preSubscriptionEvent
.map(_.timestamp)}; next counter $nextCounter" .map(_.timestamp)}; next counter $nextCounter"
@ -1145,7 +994,6 @@ class RichSequencerClientImpl(
config.warnDisconnectDelay.underlying, config.warnDisconnectDelay.underlying,
config.maxConnectionRetryDelay.underlying, config.maxConnectionRetryDelay.underlying,
timeouts, timeouts,
requiresAuthentication,
loggerFactory, loggerFactory,
) )
@ -1598,7 +1446,6 @@ class SequencerClientImplPekko[E: Pretty](
nonThrottledEventHandler: PossiblyIgnoredApplicationHandler[ClosedEnvelope], nonThrottledEventHandler: PossiblyIgnoredApplicationHandler[ClosedEnvelope],
timeTracker: DomainTimeTracker, timeTracker: DomainTimeTracker,
fetchCleanTimestamp: FetchCleanTimestamp, fetchCleanTimestamp: FetchCleanTimestamp,
requiresAuthentication: Boolean,
)(implicit traceContext: TraceContext): Future[Unit] = { )(implicit traceContext: TraceContext): Future[Unit] = {
val throttledEventHandler = ThrottlingApplicationEventHandler.throttle( val throttledEventHandler = ThrottlingApplicationEventHandler.throttle(
config.maximumInFlightEventBatches, config.maximumInFlightEventBatches,
@ -1671,7 +1518,7 @@ class SequencerClientImplPekko[E: Pretty](
} }
logger.debug(subscriptionStartLogMessage) logger.debug(subscriptionStartLogMessage)
val eventValidator = eventValidatorFactory.create(unauthenticated = !requiresAuthentication) val eventValidator = eventValidatorFactory.create()
val aggregator = new SequencerAggregatorPekko( val aggregator = new SequencerAggregatorPekko(
domainId, domainId,
eventValidator, eventValidator,
@ -1707,7 +1554,6 @@ class SequencerClientImplPekko[E: Pretty](
SequencerSubscriptionFactoryPekko.fromTransport( SequencerSubscriptionFactoryPekko.fromTransport(
transportContainer.sequencerId, transportContainer.sequencerId,
transportContainer.clientTransport, transportContainer.clientTransport,
requiresAuthentication,
member, member,
protocolVersion, protocolVersion,
) )
@ -1827,21 +1673,19 @@ class SequencerClientImplPekko[E: Pretty](
// periodically acknowledge that we've successfully processed up to the clean counter // periodically acknowledge that we've successfully processed up to the clean counter
// We only need to it setup once; the sequencer client will direct the acknowledgements to the // We only need to it setup once; the sequencer client will direct the acknowledgements to the
// right transport. // right transport.
if (requiresAuthentication) { // unauthenticated members don't need to ack periodicAcknowledgementsRef.set(
periodicAcknowledgementsRef.set( PeriodicAcknowledgements
PeriodicAcknowledgements .create(
.create( config.acknowledgementInterval.underlying,
config.acknowledgementInterval.underlying, health.getState.isOk,
health.getState.isOk, this,
this, fetchCleanTimestamp,
fetchCleanTimestamp, clock,
clock, timeouts,
timeouts, loggerFactory,
loggerFactory, )
) .some
.some )
)
}
replayCompleted.futureUS replayCompleted.futureUS
} }

View File

@ -141,14 +141,13 @@ object SequencerClientFactory {
) )
// pluggable send approach to support transitioning to the new async sends // pluggable send approach to support transitioning to the new async sends
validatorFactory = new SequencedEventValidatorFactory { validatorFactory = new SequencedEventValidatorFactory {
override def create( override def create()(implicit
unauthenticated: Boolean loggingContext: NamedLoggingContext
)(implicit loggingContext: NamedLoggingContext): SequencedEventValidator = ): SequencedEventValidator =
if (config.skipSequencedEventValidation) { if (config.skipSequencedEventValidation) {
SequencedEventValidator.noValidation(domainId) SequencedEventValidator.noValidation(domainId)
} else { } else {
new SequencedEventValidatorImpl( new SequencedEventValidatorImpl(
unauthenticated,
domainId, domainId,
domainParameters.protocolVersion, domainParameters.protocolVersion,
syncCryptoApi, syncCryptoApi,

View File

@ -11,21 +11,13 @@ import com.digitalasset.canton.lifecycle.Lifecycle.CloseableChannel
import com.digitalasset.canton.lifecycle.{FlagCloseable, Lifecycle} import com.digitalasset.canton.lifecycle.{FlagCloseable, Lifecycle}
import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging}
import com.digitalasset.canton.networking.Endpoint import com.digitalasset.canton.networking.Endpoint
import com.digitalasset.canton.sequencing.authentication.grpc.{ import com.digitalasset.canton.sequencing.authentication.grpc.SequencerClientTokenAuthentication
SequencerClientNoAuthentication,
SequencerClientTokenAuthentication,
}
import com.digitalasset.canton.sequencing.authentication.{ import com.digitalasset.canton.sequencing.authentication.{
AuthenticationTokenManagerConfig, AuthenticationTokenManagerConfig,
AuthenticationTokenProvider, AuthenticationTokenProvider,
} }
import com.digitalasset.canton.time.Clock import com.digitalasset.canton.time.Clock
import com.digitalasset.canton.topology.{ import com.digitalasset.canton.topology.{DomainId, Member}
AuthenticatedMember,
DomainId,
Member,
UnauthenticatedMemberId,
}
import com.digitalasset.canton.tracing.{TraceContext, TraceContextGrpc} import com.digitalasset.canton.tracing.{TraceContext, TraceContextGrpc}
import com.digitalasset.canton.version.ProtocolVersion import com.digitalasset.canton.version.ProtocolVersion
import io.grpc.ManagedChannel import io.grpc.ManagedChannel
@ -68,20 +60,15 @@ class GrpcSequencerClientAuth(
tokenProvider.generateToken(authenticationClient) tokenProvider.generateToken(authenticationClient)
} }
} }
val clientAuthentication = member match { val clientAuthentication = SequencerClientTokenAuthentication(
case unauthenticatedMember: UnauthenticatedMemberId => domainId,
new SequencerClientNoAuthentication(domainId, unauthenticatedMember) member,
case authenticatedMember: AuthenticatedMember => obtainTokenPerEndpoint,
SequencerClientTokenAuthentication( tokenProvider.isClosing,
domainId, tokenManagerConfig,
authenticatedMember, clock,
obtainTokenPerEndpoint, loggerFactory,
tokenProvider.isClosing, )
tokenManagerConfig,
clock,
loggerFactory,
)
}
clientAuthentication(client) clientAuthentication(client)
} }

View File

@ -124,29 +124,16 @@ private[transports] abstract class GrpcSequencerClientTransportCommon(
"send-async-versioned", "send-async-versioned",
request.content.messageId, request.content.messageId,
timeout, timeout,
SendAsyncUnauthenticatedVersionedResponse.fromSendAsyncVersionedResponseProto, SendAsyncVersionedResponse.fromProtoV30,
) )
} }
override def sendAsyncUnauthenticatedVersioned(request: SubmissionRequest, timeout: Duration)(
implicit traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] = sendInternal(
stub =>
stub.sendAsyncUnauthenticatedVersioned(
v30.SendAsyncUnauthenticatedVersionedRequest(submissionRequest = request.toByteString)
),
"send-async-unauthenticated-versioned",
request.messageId,
timeout,
SendAsyncUnauthenticatedVersionedResponse.fromSendAsyncUnauthenticatedVersionedResponseProto,
)
private def sendInternal[Resp]( private def sendInternal[Resp](
send: SequencerServiceStub => Future[Resp], send: SequencerServiceStub => Future[Resp],
endpoint: String, endpoint: String,
messageId: MessageId, messageId: MessageId,
timeout: Duration, timeout: Duration,
fromResponseProto: Resp => ParsingResult[SendAsyncUnauthenticatedVersionedResponse], fromResponseProto: Resp => ParsingResult[SendAsyncVersionedResponse],
)(implicit )(implicit
traceContext: TraceContext traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] = { ): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] = {
@ -171,7 +158,7 @@ private[transports] abstract class GrpcSequencerClientTransportCommon(
private def fromResponse[Proto]( private def fromResponse[Proto](
p: Proto, p: Proto,
deserializer: Proto => ParsingResult[SendAsyncUnauthenticatedVersionedResponse], deserializer: Proto => ParsingResult[SendAsyncVersionedResponse],
): Either[SendAsyncClientResponseError, Unit] = { ): Either[SendAsyncClientResponseError, Unit] = {
for { for {
response <- deserializer(p) response <- deserializer(p)
@ -333,13 +320,6 @@ class GrpcSequencerClientTransport(
override def subscribe[E]( override def subscribe[E](
subscriptionRequest: SubscriptionRequest, subscriptionRequest: SubscriptionRequest,
handler: SerializedEventHandler[E], handler: SerializedEventHandler[E],
)(implicit traceContext: TraceContext): SequencerSubscription[E] =
subscribeInternal(subscriptionRequest, handler, requiresAuthentication = true)
private def subscribeInternal[E](
subscriptionRequest: SubscriptionRequest,
handler: SerializedEventHandler[E],
requiresAuthentication: Boolean,
)(implicit traceContext: TraceContext): SequencerSubscription[E] = { )(implicit traceContext: TraceContext): SequencerSubscription[E] = {
// we intentionally don't use `Context.current()` as we don't want to inherit the // we intentionally don't use `Context.current()` as we don't want to inherit the
// cancellation scope from upstream requests // cancellation scope from upstream requests
@ -355,29 +335,16 @@ class GrpcSequencerClientTransport(
context.run(() => context.run(() =>
TraceContextGrpc.withGrpcContext(traceContext) { TraceContextGrpc.withGrpcContext(traceContext) {
if (requiresAuthentication) { sequencerServiceClient.subscribeVersioned(
sequencerServiceClient.subscribeVersioned( subscriptionRequest.toProtoV30,
subscriptionRequest.toProtoV30, subscription.observer,
subscription.observer, )
)
} else {
sequencerServiceClient.subscribeUnauthenticatedVersioned(
subscriptionRequest.toProtoV30,
subscription.observer,
)
}
} }
) )
subscription subscription
} }
override def subscribeUnauthenticated[E](
request: SubscriptionRequest,
handler: SerializedEventHandler[E],
)(implicit traceContext: TraceContext): SequencerSubscription[E] =
subscribeInternal(request, handler, requiresAuthentication = false)
override def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy = override def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy =
new GrpcSubscriptionErrorRetryPolicy(loggerFactory) new GrpcSubscriptionErrorRetryPolicy(loggerFactory)
} }

View File

@ -62,20 +62,9 @@ class GrpcSequencerClientTransportPekko(
override type SubscriptionError = GrpcSequencerSubscriptionError override type SubscriptionError = GrpcSequencerSubscriptionError
override def subscribe(request: SubscriptionRequest)(implicit override def subscribe(subscriptionRequest: SubscriptionRequest)(implicit
traceContext: TraceContext traceContext: TraceContext
): SequencerSubscriptionPekko[SubscriptionError] = ): SequencerSubscriptionPekko[SubscriptionError] = {
subscribeInternal(request, requiresAuthentication = true)
override def subscribeUnauthenticated(request: SubscriptionRequest)(implicit
traceContext: TraceContext
): SequencerSubscriptionPekko[SubscriptionError] =
subscribeInternal(request, requiresAuthentication = false)
private def subscribeInternal(
subscriptionRequest: SubscriptionRequest,
requiresAuthentication: Boolean,
)(implicit traceContext: TraceContext): SequencerSubscriptionPekko[SubscriptionError] = {
val subscriptionRequestP = subscriptionRequest.toProtoV30 val subscriptionRequestP = subscriptionRequest.toProtoV30
@ -133,9 +122,7 @@ class GrpcSequencerClientTransportPekko(
) )
} }
val subscriber = val subscriber = sequencerServiceClient.subscribeVersioned _
if (requiresAuthentication) sequencerServiceClient.subscribeVersioned _
else sequencerServiceClient.subscribeUnauthenticatedVersioned _
mkSubscription(subscriber)(SubscriptionResponse.fromVersionedProtoV30(protocolVersion)(_)(_)) mkSubscription(subscriber)(SubscriptionResponse.fromVersionedProtoV30(protocolVersion)(_)(_))
} }

View File

@ -32,13 +32,6 @@ trait SequencerClientTransportCommon extends FlagCloseable with SupportsHandshak
traceContext: TraceContext traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] ): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit]
def sendAsyncUnauthenticatedVersioned(
request: SubmissionRequest,
timeout: Duration,
)(implicit
traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit]
/** Acknowledge that we have successfully processed all events up to and including the given timestamp. /** Acknowledge that we have successfully processed all events up to and including the given timestamp.
* The client should then never subscribe for events from before this point. * The client should then never subscribe for events from before this point.
* *
@ -67,10 +60,6 @@ trait SequencerClientTransport extends SequencerClientTransportCommon {
traceContext: TraceContext traceContext: TraceContext
): SequencerSubscription[E] ): SequencerSubscription[E]
def subscribeUnauthenticated[E](request: SubscriptionRequest, handler: SerializedEventHandler[E])(
implicit traceContext: TraceContext
): SequencerSubscription[E]
/** The transport can decide which errors will cause the sequencer client to not try to reestablish a subscription */ /** The transport can decide which errors will cause the sequencer client to not try to reestablish a subscription */
def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy
} }

View File

@ -22,10 +22,6 @@ trait SequencerClientTransportPekko extends SequencerClientTransportCommon {
traceContext: TraceContext traceContext: TraceContext
): SequencerSubscriptionPekko[SubscriptionError] ): SequencerSubscriptionPekko[SubscriptionError]
def subscribeUnauthenticated(request: SubscriptionRequest)(implicit
traceContext: TraceContext
): SequencerSubscriptionPekko[SubscriptionError]
/** The transport can decide which errors will cause the sequencer client to not try to reestablish a subscription */ /** The transport can decide which errors will cause the sequencer client to not try to reestablish a subscription */
def subscriptionRetryPolicyPekko: SubscriptionErrorRetryPolicyPekko[SubscriptionError] def subscriptionRetryPolicyPekko: SubscriptionErrorRetryPolicyPekko[SubscriptionError]
} }

View File

@ -63,11 +63,6 @@ class ReplayingEventsSequencerClientTransport(
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] = ): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] =
EitherT.rightT(()) EitherT.rightT(())
/** Does nothing */
override def sendAsyncUnauthenticatedVersioned(request: SubmissionRequest, timeout: Duration)(
implicit traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] = EitherT.rightT(())
/** Does nothing */ /** Does nothing */
override def acknowledgeSigned(request: SignedContent[AcknowledgeRequest])(implicit override def acknowledgeSigned(request: SignedContent[AcknowledgeRequest])(implicit
traceContext: TraceContext traceContext: TraceContext
@ -113,11 +108,6 @@ class ReplayingEventsSequencerClientTransport(
new ReplayingSequencerSubscription(timeouts, loggerFactory) new ReplayingSequencerSubscription(timeouts, loggerFactory)
} }
override def subscribeUnauthenticated[E](
request: SubscriptionRequest,
handler: SerializedEventHandler[E],
)(implicit traceContext: TraceContext): SequencerSubscription[E] = subscribe(request, handler)
/** Will never request a retry. */ /** Will never request a retry. */
override def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy = override def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy =
SubscriptionErrorRetryPolicy.never SubscriptionErrorRetryPolicy.never
@ -147,10 +137,6 @@ class ReplayingEventsSequencerClientTransport(
new UnsupportedOperationException("subscribe(SubmissionRequest) is not yet implemented") new UnsupportedOperationException("subscribe(SubmissionRequest) is not yet implemented")
) )
override def subscribeUnauthenticated(request: SubscriptionRequest)(implicit
traceContext: TraceContext
): SequencerSubscriptionPekko[Nothing] = subscribe(request)
override def subscriptionRetryPolicyPekko: SubscriptionErrorRetryPolicyPekko[Nothing] = override def subscriptionRetryPolicyPekko: SubscriptionErrorRetryPolicyPekko[Nothing] =
SubscriptionErrorRetryPolicyPekko.never SubscriptionErrorRetryPolicyPekko.never
} }

View File

@ -384,14 +384,6 @@ abstract class ReplayingSendsSequencerClientTransportCommon(
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] = ): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] =
EitherT.rightT(()) EitherT.rightT(())
/** We're replaying sends so shouldn't allow the app to send any new ones */
override def sendAsyncUnauthenticatedVersioned(
request: SubmissionRequest,
timeout: Duration,
)(implicit
traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] = EitherT.rightT(())
override def acknowledgeSigned(request: SignedContent[AcknowledgeRequest])(implicit override def acknowledgeSigned(request: SignedContent[AcknowledgeRequest])(implicit
traceContext: TraceContext traceContext: TraceContext
): EitherT[FutureUnlessShutdown, String, Boolean] = ): EitherT[FutureUnlessShutdown, String, Boolean] =
@ -451,11 +443,6 @@ class ReplayingSendsSequencerClientTransportImpl(
): Unit = closeReasonPromise.trySuccess(reason).discard[Boolean] ): Unit = closeReasonPromise.trySuccess(reason).discard[Boolean]
} }
override def subscribeUnauthenticated[E](
request: SubscriptionRequest,
handler: SerializedEventHandler[E],
)(implicit traceContext: TraceContext): SequencerSubscription[E] = subscribe(request, handler)
override def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy = override def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy =
SubscriptionErrorRetryPolicy.never SubscriptionErrorRetryPolicy.never
@ -471,11 +458,6 @@ class ReplayingSendsSequencerClientTransportImpl(
traceContext: TraceContext traceContext: TraceContext
): SequencerSubscriptionPekko[SubscriptionError] = underlyingTransport.subscribe(request) ): SequencerSubscriptionPekko[SubscriptionError] = underlyingTransport.subscribe(request)
override def subscribeUnauthenticated(request: SubscriptionRequest)(implicit
traceContext: TraceContext
): SequencerSubscriptionPekko[SubscriptionError] =
underlyingTransport.subscribeUnauthenticated(request)
override def subscriptionRetryPolicyPekko: SubscriptionErrorRetryPolicyPekko[SubscriptionError] = override def subscriptionRetryPolicyPekko: SubscriptionErrorRetryPolicyPekko[SubscriptionError] =
SubscriptionErrorRetryPolicyPekko.never SubscriptionErrorRetryPolicyPekko.never
} }

View File

@ -17,15 +17,10 @@ sealed trait SendAsyncError extends PrettyPrinting {
val message: String val message: String
protected def toResponseReasonProto: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason
private[protocol] def toSendAsyncUnauthenticatedVersionedResponseProto private[protocol] def toResponseProtoV30: v30.SendAsyncVersionedResponse.Error =
: v30.SendAsyncUnauthenticatedVersionedResponse.Error = v30.SendAsyncVersionedResponse.Error(toProtoV30)
v30.SendAsyncUnauthenticatedVersionedResponse.Error(toResponseReasonProto)
private[protocol] def toSendAsyncVersionedResponseProto: v30.SendAsyncVersionedResponse.Error =
v30.SendAsyncVersionedResponse.Error(toSignedResponseReasonProto)
override def pretty: Pretty[SendAsyncError] = prettyOfClass(unnamedParam(_.message.unquoted)) override def pretty: Pretty[SendAsyncError] = prettyOfClass(unnamedParam(_.message.unquoted))
@ -37,59 +32,41 @@ object SendAsyncError {
/** The request could not be deserialized to be processed */ /** The request could not be deserialized to be processed */
final case class RequestInvalid(message: String) extends SendAsyncError { final case class RequestInvalid(message: String) extends SendAsyncError {
protected def toResponseReasonProto protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason =
: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason =
v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.RequestInvalid(message)
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason =
v30.SendAsyncVersionedResponse.Error.Reason.RequestInvalid(message) v30.SendAsyncVersionedResponse.Error.Reason.RequestInvalid(message)
override def category: ErrorCategory = ErrorCategory.InvalidIndependentOfSystemState override def category: ErrorCategory = ErrorCategory.InvalidIndependentOfSystemState
} }
/** The request server could read the request but refused to accept it */ /** The request server could read the request but refused to accept it */
final case class RequestRefused(message: String) extends SendAsyncError { final case class RequestRefused(message: String) extends SendAsyncError {
protected def toResponseReasonProto protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason =
: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason =
v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.RequestRefused(message)
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason =
v30.SendAsyncVersionedResponse.Error.Reason.RequestRefused(message) v30.SendAsyncVersionedResponse.Error.Reason.RequestRefused(message)
override def category: ErrorCategory = ErrorCategory.InvalidGivenCurrentSystemStateOther override def category: ErrorCategory = ErrorCategory.InvalidGivenCurrentSystemStateOther
} }
/** The Sequencer is overloaded and declined to handle the request */ /** The Sequencer is overloaded and declined to handle the request */
final case class Overloaded(message: String) extends SendAsyncError { final case class Overloaded(message: String) extends SendAsyncError {
protected def toResponseReasonProto protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason =
: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason =
v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.Overloaded(message)
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason =
v30.SendAsyncVersionedResponse.Error.Reason.Overloaded(message) v30.SendAsyncVersionedResponse.Error.Reason.Overloaded(message)
override def category: ErrorCategory = ErrorCategory.ContentionOnSharedResources override def category: ErrorCategory = ErrorCategory.ContentionOnSharedResources
} }
/** The sequencer is unable to process requests (if the service is running it could mean the sequencer is going through a crash recovery process) */ /** The sequencer is unable to process requests (if the service is running it could mean the sequencer is going through a crash recovery process) */
final case class Unavailable(message: String) extends SendAsyncError { final case class Unavailable(message: String) extends SendAsyncError {
protected def toResponseReasonProto protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason =
: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason =
v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.Unavailable(message)
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason =
v30.SendAsyncVersionedResponse.Error.Reason.Unavailable(message) v30.SendAsyncVersionedResponse.Error.Reason.Unavailable(message)
override def category: ErrorCategory = ErrorCategory.TransientServerFailure override def category: ErrorCategory = ErrorCategory.TransientServerFailure
} }
/** The Sequencer was unable to handle the send as the sender was unknown so could not asynchronously deliver them a deliver event or error */ /** The Sequencer was unable to handle the send as the sender was unknown so could not asynchronously deliver them a deliver event or error */
final case class SenderUnknown(message: String) extends SendAsyncError { final case class SenderUnknown(message: String) extends SendAsyncError {
protected def toResponseReasonProto protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason =
: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason =
v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.SenderUnknown(message)
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason =
v30.SendAsyncVersionedResponse.Error.Reason.SenderUnknown(message) v30.SendAsyncVersionedResponse.Error.Reason.SenderUnknown(message)
override def category: ErrorCategory = ErrorCategory.InvalidGivenCurrentSystemStateOther override def category: ErrorCategory = ErrorCategory.InvalidGivenCurrentSystemStateOther
} }
final case class UnknownRecipients(message: String) extends SendAsyncError { final case class UnknownRecipients(message: String) extends SendAsyncError {
protected def toResponseReasonProto protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason =
: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason =
v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.UnknownRecipients(message)
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason =
v30.SendAsyncVersionedResponse.Error.Reason.UnknownRecipients(message) v30.SendAsyncVersionedResponse.Error.Reason.UnknownRecipients(message)
override def category: ErrorCategory = ErrorCategory.InvalidGivenCurrentSystemStateOther override def category: ErrorCategory = ErrorCategory.InvalidGivenCurrentSystemStateOther
} }
@ -103,70 +80,33 @@ object SendAsyncError {
/** The sequencer declined to process new requests as it is shutting down */ /** The sequencer declined to process new requests as it is shutting down */
final case class ShuttingDown(message: String = "Sequencer shutting down") final case class ShuttingDown(message: String = "Sequencer shutting down")
extends SendAsyncError { extends SendAsyncError {
protected def toResponseReasonProto protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason =
: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason =
v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.ShuttingDown(message)
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason =
v30.SendAsyncVersionedResponse.Error.Reason.ShuttingDown(message) v30.SendAsyncVersionedResponse.Error.Reason.ShuttingDown(message)
override def category: ErrorCategory = ErrorCategory.TransientServerFailure override def category: ErrorCategory = ErrorCategory.TransientServerFailure
} }
final case class Internal(message: String) extends SendAsyncError { final case class Internal(message: String) extends SendAsyncError {
protected def toResponseReasonProto
: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason =
throw new IllegalStateException(
"Message `Internal` introduced with protocol version 4 should not be included in `v30.SendAsyncUnauthenticatedVersionedResponse`"
)
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason = protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason =
v30.SendAsyncVersionedResponse.Error.Reason.Internal(message) v30.SendAsyncVersionedResponse.Error.Reason.Internal(message)
override def category: ErrorCategory = ErrorCategory.TransientServerFailure override def category: ErrorCategory = ErrorCategory.TransientServerFailure
} }
final case class Generic(message: String) extends SendAsyncError { final case class Generic(message: String) extends SendAsyncError {
protected def toResponseReasonProto protected def toProtoV30: v30.SendAsyncVersionedResponse.Error.Reason =
: v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason =
throw new IllegalStateException(
"Message `Generic` introduced with protocol version 4 should not be included in `v30.SendAsyncUnauthenticatedVersionedResponse`"
)
protected def toSignedResponseReasonProto: v30.SendAsyncVersionedResponse.Error.Reason =
v30.SendAsyncVersionedResponse.Error.Reason.Generic(message) v30.SendAsyncVersionedResponse.Error.Reason.Generic(message)
override def category: ErrorCategory = ErrorCategory.TransientServerFailure override def category: ErrorCategory = ErrorCategory.TransientServerFailure
} }
private[protocol] def fromErrorProto(
error: v30.SendAsyncUnauthenticatedVersionedResponse.Error
): ParsingResult[SendAsyncError] =
error.reason match {
case v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.Empty =>
ProtoDeserializationError
.FieldNotSet("SendAsyncUnauthenticatedVersionedResponse.error.reason")
.asLeft
case v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.RequestInvalid(message) =>
RequestInvalid(message).asRight
case v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.RequestRefused(message) =>
RequestRefused(message).asRight
case v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.Overloaded(message) =>
Overloaded(message).asRight
case v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.Unavailable(message) =>
Unavailable(message).asRight
case v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.SenderUnknown(message) =>
SenderUnknown(message).asRight
case v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.UnknownRecipients(message) =>
UnknownRecipients(message).asRight
case v30.SendAsyncUnauthenticatedVersionedResponse.Error.Reason.ShuttingDown(message) =>
ShuttingDown(message).asRight
}
private[protocol] def fromSignedErrorProto( private[protocol] def fromSignedErrorProto(
error: v30.SendAsyncVersionedResponse.Error error: v30.SendAsyncVersionedResponse.Error
): ParsingResult[SendAsyncError] = ): ParsingResult[SendAsyncError] =
error.reason match { error.reason match {
case v30.SendAsyncVersionedResponse.Error.Reason.Empty => case v30.SendAsyncVersionedResponse.Error.Reason.Empty =>
ProtoDeserializationError ProtoDeserializationError
.FieldNotSet("SendAsyncUnauthenticatedVersionedResponse.error.reason") .FieldNotSet("SendAsyncVersionedResponse.error.reason")
.asLeft .asLeft
case v30.SendAsyncVersionedResponse.Error.Reason.RequestInvalid(message) => case v30.SendAsyncVersionedResponse.Error.Reason.RequestInvalid(message) =>
RequestInvalid(message).asRight RequestInvalid(message).asRight
@ -188,29 +128,17 @@ object SendAsyncError {
} }
} }
final case class SendAsyncUnauthenticatedVersionedResponse(error: Option[SendAsyncError]) { final case class SendAsyncVersionedResponse(error: Option[SendAsyncError]) {
def toSendAsyncUnauthenticatedVersionedResponseProto
: v30.SendAsyncUnauthenticatedVersionedResponse =
v30.SendAsyncUnauthenticatedVersionedResponse(
error.map(_.toSendAsyncUnauthenticatedVersionedResponseProto)
)
def toSendAsyncVersionedResponseProto: v30.SendAsyncVersionedResponse = def toProtoV30: v30.SendAsyncVersionedResponse =
v30.SendAsyncVersionedResponse(error.map(_.toSendAsyncVersionedResponseProto)) v30.SendAsyncVersionedResponse(error.map(_.toResponseProtoV30))
} }
object SendAsyncUnauthenticatedVersionedResponse { object SendAsyncVersionedResponse {
def fromSendAsyncUnauthenticatedVersionedResponseProto( def fromProtoV30(
responseP: v30.SendAsyncUnauthenticatedVersionedResponse
): ParsingResult[SendAsyncUnauthenticatedVersionedResponse] =
for {
error <- responseP.error.traverse(SendAsyncError.fromErrorProto)
} yield SendAsyncUnauthenticatedVersionedResponse(error)
def fromSendAsyncVersionedResponseProto(
responseP: v30.SendAsyncVersionedResponse responseP: v30.SendAsyncVersionedResponse
): ParsingResult[SendAsyncUnauthenticatedVersionedResponse] = ): ParsingResult[SendAsyncVersionedResponse] =
for { for {
error <- responseP.error.traverse(SendAsyncError.fromSignedErrorProto) error <- responseP.error.traverse(SendAsyncError.fromSignedErrorProto)
} yield SendAsyncUnauthenticatedVersionedResponse(error) } yield SendAsyncVersionedResponse(error)
} }

View File

@ -130,7 +130,7 @@ object TimeProof {
)(implicit )(implicit
traceContext: TraceContext traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] = ): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] =
client.sendAsyncUnauthenticatedOrNot( client.sendAsync(
// we intentionally ask for an empty event to be sequenced to observe the time. // we intentionally ask for an empty event to be sequenced to observe the time.
// this means we can safely share this event without mentioning other recipients. // this means we can safely share this event without mentioning other recipients.
batch = Batch.empty(protocolVersion), batch = Batch.empty(protocolVersion),

View File

@ -9,12 +9,10 @@ import com.daml.nonempty.NonEmpty
import com.digitalasset.canton.ProtoDeserializationError.ValueConversionError import com.digitalasset.canton.ProtoDeserializationError.ValueConversionError
import com.digitalasset.canton.config.CantonRequireTypes.{String255, String3, String300} import com.digitalasset.canton.config.CantonRequireTypes.{String255, String3, String300}
import com.digitalasset.canton.config.RequireTypes.{NonNegativeInt, PositiveInt} import com.digitalasset.canton.config.RequireTypes.{NonNegativeInt, PositiveInt}
import com.digitalasset.canton.crypto.RandomOps
import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting}
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.store.db.DbDeserializationException import com.digitalasset.canton.store.db.DbDeserializationException
import com.digitalasset.canton.topology.MediatorGroup.MediatorGroupIndex import com.digitalasset.canton.topology.MediatorGroup.MediatorGroupIndex
import com.digitalasset.canton.util.HexString
import com.digitalasset.canton.{LedgerParticipantId, LfPartyId, ProtoDeserializationError} import com.digitalasset.canton.{LedgerParticipantId, LfPartyId, ProtoDeserializationError}
import com.google.common.annotations.VisibleForTesting import com.google.common.annotations.VisibleForTesting
import io.circe.Encoder import io.circe.Encoder
@ -53,7 +51,6 @@ object MemberCode {
case MediatorId.Code.threeLetterId => Right(MediatorId.Code) case MediatorId.Code.threeLetterId => Right(MediatorId.Code)
case ParticipantId.Code.threeLetterId => Right(ParticipantId.Code) case ParticipantId.Code.threeLetterId => Right(ParticipantId.Code)
case SequencerId.Code.threeLetterId => Right(SequencerId.Code) case SequencerId.Code.threeLetterId => Right(SequencerId.Code)
case UnauthenticatedMemberId.Code.threeLetterId => Right(UnauthenticatedMemberId.Code)
case _ => Left(s"Unknown three letter type $code") case _ => Left(s"Unknown three letter type $code")
} }
@ -76,8 +73,6 @@ sealed trait Member extends Identity with Product with Serializable {
def description: String def description: String
def isAuthenticated: Boolean
override def toProtoPrimitive: String = toLengthLimitedString.unwrap override def toProtoPrimitive: String = toLengthLimitedString.unwrap
def toLengthLimitedString: String300 = def toLengthLimitedString: String300 =
@ -102,7 +97,6 @@ object Member {
case MediatorId.Code => Right(MediatorId(uid)) case MediatorId.Code => Right(MediatorId(uid))
case ParticipantId.Code => Right(ParticipantId(uid)) case ParticipantId.Code => Right(ParticipantId(uid))
case SequencerId.Code => Right(SequencerId(uid)) case SequencerId.Code => Right(SequencerId(uid))
case UnauthenticatedMemberId.Code => Right(UnauthenticatedMemberId(uid))
} }
} }
@ -151,35 +145,6 @@ object Member {
} }
sealed trait AuthenticatedMember extends Member {
override def code: AuthenticatedMemberCode
override def isAuthenticated: Boolean = true
}
sealed trait AuthenticatedMemberCode extends MemberCode
final case class UnauthenticatedMemberId(uid: UniqueIdentifier) extends Member {
override def code: MemberCode = UnauthenticatedMemberId.Code
override val description: String = "unauthenticated member"
override def isAuthenticated: Boolean = false
}
object UnauthenticatedMemberId {
object Code extends MemberCode {
val threeLetterId: String3 = String3.tryCreate("UNM")
}
private val RandomIdentifierNumberOfBytes = 20
def tryCreate(namespace: Namespace)(randomOps: RandomOps): UnauthenticatedMemberId =
UnauthenticatedMemberId(
UniqueIdentifier.tryCreate(
HexString.toHexString(randomOps.generateRandomByteString(RandomIdentifierNumberOfBytes)),
namespace.fingerprint.unwrap,
)
)
}
final case class DomainId(uid: UniqueIdentifier) extends Identity { final case class DomainId(uid: UniqueIdentifier) extends Identity {
def unwrap: UniqueIdentifier = uid def unwrap: UniqueIdentifier = uid
def toLengthLimitedString: String255 = uid.toLengthLimitedString def toLengthLimitedString: String255 = uid.toLengthLimitedString
@ -218,11 +183,9 @@ object DomainId {
} }
/** A participant identifier */ /** A participant identifier */
final case class ParticipantId(uid: UniqueIdentifier) final case class ParticipantId(uid: UniqueIdentifier) extends Member with NodeIdentity {
extends AuthenticatedMember
with NodeIdentity {
override def code: AuthenticatedMemberCode = ParticipantId.Code override def code: MemberCode = ParticipantId.Code
override val description: String = "participant" override val description: String = "participant"
@ -233,7 +196,7 @@ final case class ParticipantId(uid: UniqueIdentifier)
} }
object ParticipantId { object ParticipantId {
object Code extends AuthenticatedMemberCode { object Code extends MemberCode {
val threeLetterId: String3 = String3.tryCreate("PAR") val threeLetterId: String3 = String3.tryCreate("PAR")
} }
def apply(identifier: String, namespace: Namespace): ParticipantId = def apply(identifier: String, namespace: Namespace): ParticipantId =
@ -314,8 +277,6 @@ object PartyId {
} }
sealed trait DomainMember extends AuthenticatedMember
/** @param index uniquely identifies the group, just like [[MediatorId]] for single mediators. /** @param index uniquely identifies the group, just like [[MediatorId]] for single mediators.
* @param active the active mediators belonging to the group * @param active the active mediators belonging to the group
* @param passive the passive mediators belonging to the group * @param passive the passive mediators belonging to the group
@ -337,14 +298,14 @@ object MediatorGroup {
val MediatorGroupIndex = NonNegativeInt val MediatorGroupIndex = NonNegativeInt
} }
final case class MediatorId(uid: UniqueIdentifier) extends DomainMember with NodeIdentity { final case class MediatorId(uid: UniqueIdentifier) extends Member with NodeIdentity {
override def code: AuthenticatedMemberCode = MediatorId.Code override def code: MemberCode = MediatorId.Code
override val description: String = "mediator" override val description: String = "mediator"
override def member: Member = this override def member: Member = this
} }
object MediatorId { object MediatorId {
object Code extends AuthenticatedMemberCode { object Code extends MemberCode {
val threeLetterId = String3.tryCreate("MED") val threeLetterId = String3.tryCreate("MED")
} }
@ -371,15 +332,15 @@ final case class SequencerGroup(
threshold: PositiveInt, threshold: PositiveInt,
) )
final case class SequencerId(uid: UniqueIdentifier) extends DomainMember with NodeIdentity { final case class SequencerId(uid: UniqueIdentifier) extends Member with NodeIdentity {
override def code: AuthenticatedMemberCode = SequencerId.Code override def code: MemberCode = SequencerId.Code
override val description: String = "sequencer" override val description: String = "sequencer"
override def member: Member = this override def member: Member = this
} }
object SequencerId { object SequencerId {
object Code extends AuthenticatedMemberCode { object Code extends MemberCode {
val threeLetterId = String3.tryCreate("SEQ") val threeLetterId = String3.tryCreate("SEQ")
} }

View File

@ -410,7 +410,7 @@ class ValidatingTopologyMappingChecks(
toValidate.mapping.member match { toValidate.mapping.member match {
case participantId: ParticipantId => case participantId: ParticipantId =>
ensureParticipantDoesNotHostParties(effective, participantId) ensureParticipantDoesNotHostParties(effective, participantId)
case _: UnauthenticatedMemberId | _: AuthenticatedMember => EitherTUtil.unit case _ => EitherTUtil.unit
} }
} }

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --target=2.1 - --target=2.1
name: CantonExamples name: CantonExamples

View File

@ -133,7 +133,7 @@ class DomainTopologyService(
): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] = { ): EitherT[FutureUnlessShutdown, SendAsyncClientError, Unit] = {
logger.debug(s"Broadcasting topology transaction: ${request.broadcasts}") logger.debug(s"Broadcasting topology transaction: ${request.broadcasts}")
EitherTUtil.logOnErrorU( EitherTUtil.logOnErrorU(
sequencerClient.sendAsyncUnauthenticatedOrNot( sequencerClient.sendAsync(
Batch.of(protocolVersion, (request, Recipients.cc(TopologyBroadcastAddress.recipient))), Batch.of(protocolVersion, (request, Recipients.cc(TopologyBroadcastAddress.recipient))),
maxSequencingTime = maxSequencingTime =
clock.now.add(topologyConfig.topologyTransactionRegistrationTimeout.toInternal.duration), clock.now.add(topologyConfig.topologyTransactionRegistrationTimeout.toInternal.duration),

View File

@ -3,12 +3,13 @@
package com.digitalasset.canton.sequencing package com.digitalasset.canton.sequencing
import cats.data.EitherT
import com.daml.nonempty.NonEmpty import com.daml.nonempty.NonEmpty
import com.digitalasset.canton.config.RequireTypes.PositiveInt import com.digitalasset.canton.config.RequireTypes.PositiveInt
import com.digitalasset.canton.crypto.provider.symbolic.SymbolicCrypto import com.digitalasset.canton.crypto.provider.symbolic.SymbolicCrypto
import com.digitalasset.canton.crypto.{Fingerprint, Signature} import com.digitalasset.canton.crypto.{Fingerprint, Signature}
import com.digitalasset.canton.health.{AtomicHealthComponent, ComponentHealthState} import com.digitalasset.canton.health.{AtomicHealthComponent, ComponentHealthState}
import com.digitalasset.canton.lifecycle.OnShutdownRunner import com.digitalasset.canton.lifecycle.{FutureUnlessShutdown, OnShutdownRunner}
import com.digitalasset.canton.logging.TracedLogger import com.digitalasset.canton.logging.TracedLogger
import com.digitalasset.canton.sequencing.SequencerAggregatorPekko.HasSequencerSubscriptionFactoryPekko import com.digitalasset.canton.sequencing.SequencerAggregatorPekko.HasSequencerSubscriptionFactoryPekko
import com.digitalasset.canton.sequencing.SequencerAggregatorPekkoTest.Config import com.digitalasset.canton.sequencing.SequencerAggregatorPekkoTest.Config
@ -18,15 +19,7 @@ import com.digitalasset.canton.sequencing.client.TestSequencerSubscriptionFactor
Failure, Failure,
} }
import com.digitalasset.canton.sequencing.client.TestSubscriptionError.UnretryableError import com.digitalasset.canton.sequencing.client.TestSubscriptionError.UnretryableError
import com.digitalasset.canton.sequencing.client.{ import com.digitalasset.canton.sequencing.client.*
ResilientSequencerSubscription,
SequencedEventTestFixture,
SequencedEventValidator,
SequencedEventValidatorImpl,
SequencerSubscriptionFactoryPekko,
TestSequencerSubscriptionFactoryPekko,
TestSubscriptionError,
}
import com.digitalasset.canton.topology.{DefaultTestIdentities, SequencerId} import com.digitalasset.canton.topology.{DefaultTestIdentities, SequencerId}
import com.digitalasset.canton.util.OrderedBucketMergeHub.{ import com.digitalasset.canton.util.OrderedBucketMergeHub.{
ActiveSourceTerminated, ActiveSourceTerminated,
@ -34,7 +27,8 @@ import com.digitalasset.canton.util.OrderedBucketMergeHub.{
DeadlockTrigger, DeadlockTrigger,
NewConfiguration, NewConfiguration,
} }
import com.digitalasset.canton.util.{OrderedBucketMergeConfig, ResourceUtil} import com.digitalasset.canton.util.{EitherTUtil, OrderedBucketMergeConfig, ResourceUtil}
import com.digitalasset.canton.version.ProtocolVersion
import com.digitalasset.canton.{ import com.digitalasset.canton.{
BaseTest, BaseTest,
HasExecutionContext, HasExecutionContext,
@ -324,14 +318,21 @@ class SequencerAggregatorPekkoTest
import fixture.* import fixture.*
val validator = new SequencedEventValidatorImpl( val validator = new SequencedEventValidatorImpl(
// Disable signature checking
unauthenticated = true,
defaultDomainId, defaultDomainId,
testedProtocolVersion, testedProtocolVersion,
subscriberCryptoApi, subscriberCryptoApi,
loggerFactory, loggerFactory,
timeouts, timeouts,
) ) {
override protected def verifySignature(
priorEventO: Option[PossiblyIgnoredSerializedEvent],
event: OrdinarySerializedEvent,
sequencerId: SequencerId,
protocolVersion: ProtocolVersion,
): EitherT[FutureUnlessShutdown, SequencedEventValidationError[Nothing], Unit] =
EitherTUtil.unitUS
}
val initialCounter = SequencerCounter(10) val initialCounter = SequencerCounter(10)
val aggregator = mkAggregatorPekko(validator) val aggregator = mkAggregatorPekko(validator)
val ((source, (doneF, health_)), sink) = Source val ((source, (doneF, health_)), sink) = Source
@ -371,11 +372,16 @@ class SequencerAggregatorPekkoTest
val events = mkEvents(initialCounter, 4) val events = mkEvents(initialCounter, 4)
val events1 = events.take(2) val events1 = events.take(2)
// alice reports events 10,11,12,13
factoryAlice.add(events.map(_.copy(signatures = NonEmpty(Set, signatureAlice)))*) factoryAlice.add(events.map(_.copy(signatures = NonEmpty(Set, signatureAlice)))*)
// bob reports events 10,11
factoryBob.add(events1.map(_.copy(signatures = NonEmpty(Set, signatureBob)))*) factoryBob.add(events1.map(_.copy(signatures = NonEmpty(Set, signatureBob)))*)
// events
val events2 = events.drop(1) val events2 = events.drop(1)
// bob reports events 12,13
factoryBob.add(events2.drop(1).map(_.copy(signatures = NonEmpty(Set, signatureBob)))*) factoryBob.add(events2.drop(1).map(_.copy(signatures = NonEmpty(Set, signatureBob)))*)
// carlos reports events 11,12
factoryCarlos.add( factoryCarlos.add(
events2.take(2).map(_.copy(signatures = NonEmpty(Set, signatureCarlos)))* events2.take(2).map(_.copy(signatures = NonEmpty(Set, signatureCarlos)))*
) )

View File

@ -0,0 +1,176 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.canton.sequencing.client
import cats.data.EitherT
import com.digitalasset.canton.config.RequireTypes.PositiveInt
import com.digitalasset.canton.lifecycle.UnlessShutdown.AbortedDueToShutdown
import com.digitalasset.canton.lifecycle.{FutureUnlessShutdown, PromiseUnlessShutdown}
import com.digitalasset.canton.logging.SuppressionRule
import com.digitalasset.canton.sequencing.BftSender
import com.digitalasset.canton.sequencing.BftSender.FailedToReachThreshold
import com.digitalasset.canton.{BaseTest, HasExecutionContext}
import org.scalatest.Outcome
import org.scalatest.wordspec.FixtureAnyWordSpec
import org.slf4j.event.Level
import scala.concurrent.duration.*
class BftSenderTest extends FixtureAnyWordSpec with BaseTest with HasExecutionContext {
class Env {
val promise1 = new PromiseUnlessShutdown[Either[String, Int]]("p1", futureSupervisor)
val promise2 = new PromiseUnlessShutdown[Either[String, Int]]("p2", futureSupervisor)
val promise3 = new PromiseUnlessShutdown[Either[String, Int]]("p3", futureSupervisor)
val transports: Map[String, MockTransport] = Map(
"sequencer1" -> new MockTransport(EitherT(promise1.futureUS)),
"sequencer2" -> new MockTransport(EitherT(promise2.futureUS)),
"sequencer3" -> new MockTransport(EitherT(promise3.futureUS)),
)
}
override type FixtureParam = Env
override def withFixture(test: OneArgTest): Outcome =
withFixture(test.toNoArgTest(new Env()))
final class MockTransport(result: EitherT[FutureUnlessShutdown, String, Int]) {
def performRequest: EitherT[FutureUnlessShutdown, String, Int] = result
}
private def checkNotCompleted[E, A](result: EitherT[FutureUnlessShutdown, E, A]) = {
always(1.second) {
result.value.isCompleted shouldBe false
}
}
private def mkRequest(threshold: PositiveInt)(implicit env: Env) = {
import env.*
BftSender.makeRequest[String, String, MockTransport, Int, Int](
"test",
futureSupervisor,
logger,
transports,
threshold,
_.performRequest,
identity,
)
}
"BftSender" should {
"gather requests from threshold-many transports" in { implicit env =>
import env.*
val threshold = PositiveInt.tryCreate(2)
val result = mkRequest(threshold)
checkNotCompleted(result)
promise1.outcome(Right(1))
checkNotCompleted(result)
promise2.outcome(Right(2))
checkNotCompleted(result)
promise3.outcome(Right(1))
result.valueOrFailShutdown("result").futureValue shouldBe 1
}
"return as soon as it has enough identical responses" in { implicit env =>
import env.*
val threshold = PositiveInt.tryCreate(2)
val result = mkRequest(threshold)
checkNotCompleted(result)
promise1.outcome(Right(1))
checkNotCompleted(result)
promise2.outcome(Right(1))
result.valueOrFailShutdown("result").futureValue shouldBe 1
}
"fail early if it can't get enough responses" in { env =>
import env.*
val threshold = PositiveInt.tryCreate(3)
val promise4 = new PromiseUnlessShutdown[Either[String, Int]]("p4", futureSupervisor)
val promise5 = new PromiseUnlessShutdown[Either[String, Int]]("p5", futureSupervisor)
val transports: Map[String, MockTransport] = Map(
"sequencer1" -> new MockTransport(EitherT(promise1.futureUS)),
"sequencer2" -> new MockTransport(EitherT(promise2.futureUS)),
"sequencer3" -> new MockTransport(EitherT(promise3.futureUS)),
"sequencer4" -> new MockTransport(EitherT(promise4.futureUS)),
"sequencer5" -> new MockTransport(EitherT(promise5.futureUS)),
)
loggerFactory.assertEventuallyLogsSeq(SuppressionRule.Level(Level.ERROR))(
{
val result = BftSender.makeRequest[String, String, MockTransport, Int, Int](
"test",
futureSupervisor,
logger,
transports,
threshold,
_.performRequest,
identity,
)
val exception = new RuntimeException("BOOM")
checkNotCompleted(result)
promise1.outcome(Right(1))
checkNotCompleted(result)
promise2.outcome(Right(2))
checkNotCompleted(result)
promise3.outcome(Left("failed"))
checkNotCompleted(result)
promise4.failure(exception)
result.value.failOnShutdown.futureValue shouldBe Left(
FailedToReachThreshold(
Map(1 -> Set("sequencer1"), 2 -> Set("sequencer2")),
Map[String, Either[Throwable, String]](
"sequencer3" -> Right("failed"),
"sequencer4" -> Left(exception),
),
)
)
},
logs => {
forExactly(1, logs) { m =>
m.toString should include(s"test failed for sequencer4")
}
},
)
}
"fail with shutdown if any response is a shutdown" in { implicit env =>
import env.*
val threshold = PositiveInt.tryCreate(3)
val result = mkRequest(threshold)
checkNotCompleted(result)
promise1.outcome(Right(1))
checkNotCompleted(result)
promise2.shutdown()
result.value.unwrap.futureValue shouldBe AbortedDueToShutdown
}
"subsequent results should not trigger errors" in { implicit env =>
import env.*
val threshold = PositiveInt.one
val result = mkRequest(threshold)
checkNotCompleted(result)
promise1.outcome(Right(1))
result.valueOrFailShutdown("result").futureValue shouldBe 1
promise2.outcome(Right(1))
promise3.outcome(Left("failed"))
}
}
}

View File

@ -135,7 +135,6 @@ class SequencedEventTestFixture(
syncCryptoApi: DomainSyncCryptoClient = subscriberCryptoApi syncCryptoApi: DomainSyncCryptoClient = subscriberCryptoApi
)(implicit executionContext: ExecutionContext): SequencedEventValidatorImpl = { )(implicit executionContext: ExecutionContext): SequencedEventValidatorImpl = {
new SequencedEventValidatorImpl( new SequencedEventValidatorImpl(
unauthenticated = false,
defaultDomainId, defaultDomainId,
testedProtocolVersion, testedProtocolVersion,
syncCryptoApi, syncCryptoApi,

View File

@ -1103,13 +1103,6 @@ class SequencerClientTest
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] = ): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] =
sendAsync(request.content).mapK(FutureUnlessShutdown.outcomeK) sendAsync(request.content).mapK(FutureUnlessShutdown.outcomeK)
override def sendAsyncUnauthenticatedVersioned(
request: SubmissionRequest,
timeout: Duration,
)(implicit
traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] = ???
override def subscribe[E](request: SubscriptionRequest, handler: SerializedEventHandler[E])( override def subscribe[E](request: SubscriptionRequest, handler: SerializedEventHandler[E])(
implicit traceContext: TraceContext implicit traceContext: TraceContext
): SequencerSubscription[E] = { ): SequencerSubscription[E] = {
@ -1134,11 +1127,6 @@ class SequencerClientTest
override protected def loggerFactory: NamedLoggerFactory = override protected def loggerFactory: NamedLoggerFactory =
SequencerClientTest.this.loggerFactory SequencerClientTest.this.loggerFactory
override def subscribeUnauthenticated[E](
request: SubscriptionRequest,
handler: SerializedEventHandler[E],
)(implicit traceContext: TraceContext): SequencerSubscription[E] = ???
override def downloadTopologyStateForInit(request: TopologyStateForInitRequest)(implicit override def downloadTopologyStateForInit(request: TopologyStateForInitRequest)(implicit
traceContext: TraceContext traceContext: TraceContext
): EitherT[Future, String, TopologyStateForInitResponse] = ??? ): EitherT[Future, String, TopologyStateForInitResponse] = ???
@ -1166,10 +1154,6 @@ class SequencerClientTest
) )
} }
override def subscribeUnauthenticated(request: SubscriptionRequest)(implicit
traceContext: TraceContext
): SequencerSubscriptionPekko[SubscriptionError] = subscribe(request)
override def subscriptionRetryPolicyPekko override def subscriptionRetryPolicyPekko
: SubscriptionErrorRetryPolicyPekko[SubscriptionError] = : SubscriptionErrorRetryPolicyPekko[SubscriptionError] =
SubscriptionErrorRetryPolicyPekko.never SubscriptionErrorRetryPolicyPekko.never
@ -1268,9 +1252,7 @@ class SequencerClientTest
private class ConstantSequencedEventValidatorFactory(eventValidator: SequencedEventValidator) private class ConstantSequencedEventValidatorFactory(eventValidator: SequencedEventValidator)
extends SequencedEventValidatorFactory { extends SequencedEventValidatorFactory {
override def create( override def create()(implicit loggingContext: NamedLoggingContext): SequencedEventValidator =
unauthenticated: Boolean
)(implicit loggingContext: NamedLoggingContext): SequencedEventValidator =
eventValidator eventValidator
} }

View File

@ -10,8 +10,6 @@ import org.scalacheck.Arbitrary
object GeneratorsTopology { object GeneratorsTopology {
import com.digitalasset.canton.config.GeneratorsConfig.* import com.digitalasset.canton.config.GeneratorsConfig.*
implicit val domainMemberArb: Arbitrary[DomainMember] = genArbitrary
implicit val authenticatedMemberArb: Arbitrary[AuthenticatedMember] = genArbitrary
implicit val fingerprintArb: Arbitrary[Fingerprint] = Arbitrary( implicit val fingerprintArb: Arbitrary[Fingerprint] = Arbitrary(
string68Arb.arbitrary.map(Fingerprint.tryCreate) string68Arb.arbitrary.map(Fingerprint.tryCreate)
) )

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --target=2.1 - --target=2.1
name: ai-analysis name: ai-analysis

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --target=2.1 - --target=2.1
name: bank name: bank

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --target=2.1 - --target=2.1
name: doctor name: doctor

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --target=2.1 - --target=2.1
name: health-insurance name: health-insurance

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --target=2.1 - --target=2.1
name: medical-records name: medical-records

View File

@ -6,11 +6,7 @@ package com.digitalasset.canton.domain.block
import cats.syntax.either.* import cats.syntax.either.*
import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.domain.block.RawLedgerBlock.RawBlockEvent import com.digitalasset.canton.domain.block.RawLedgerBlock.RawBlockEvent
import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.{ import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.SenderSigned
SenderSigned,
SignedOrderingRequest,
SignedOrderingRequestOps,
}
import com.digitalasset.canton.logging.HasLoggerName import com.digitalasset.canton.logging.HasLoggerName
import com.digitalasset.canton.sequencing.protocol.{ import com.digitalasset.canton.sequencing.protocol.{
AcknowledgeRequest, AcknowledgeRequest,
@ -18,11 +14,8 @@ import com.digitalasset.canton.sequencing.protocol.{
SignedContent, SignedContent,
SubmissionRequest, SubmissionRequest,
} }
import com.digitalasset.canton.serialization.HasCryptographicEvidence
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.serialization.{
BytestringWithCryptographicEvidence,
HasCryptographicEvidence,
}
import com.digitalasset.canton.tracing.Traced import com.digitalasset.canton.tracing.Traced
import com.digitalasset.canton.version.ProtocolVersion import com.digitalasset.canton.version.ProtocolVersion
import com.digitalasset.canton.{LfTimestamp, ProtoDeserializationError} import com.digitalasset.canton.{LfTimestamp, ProtoDeserializationError}
@ -38,13 +31,12 @@ object LedgerBlockEvent extends HasLoggerName {
final case class Send( final case class Send(
timestamp: CantonTimestamp, timestamp: CantonTimestamp,
signedOrderingRequest: SignedOrderingRequest, signedSubmissionRequest: SenderSigned[SubmissionRequest],
originalPayloadSize: Int = originalPayloadSize: Int =
0, // default is 0 for testing as this value is only used for metrics 0, // default is 0 for testing as this value is only used for metrics
) extends LedgerBlockEvent { ) extends LedgerBlockEvent
lazy val signedSubmissionRequest = signedOrderingRequest.signedSubmissionRequest
} final case class Acknowledgment(request: SenderSigned[AcknowledgeRequest])
final case class Acknowledgment(request: SignedContent[AcknowledgeRequest])
extends LedgerBlockEvent extends LedgerBlockEvent
def fromRawBlockEvent( def fromRawBlockEvent(
@ -53,7 +45,7 @@ object LedgerBlockEvent extends HasLoggerName {
blockEvent match { blockEvent match {
case RawBlockEvent.Send(request, microsecondsSinceEpoch) => case RawBlockEvent.Send(request, microsecondsSinceEpoch) =>
for { for {
deserializedRequest <- deserializeSignedOrderingRequest(protocolVersion)(request) deserializedRequest <- deserializeSignedRequest(protocolVersion)(request)
timestamp <- LfTimestamp timestamp <- LfTimestamp
.fromLong(microsecondsSinceEpoch) .fromLong(microsecondsSinceEpoch)
.leftMap(e => ProtoDeserializationError.TimestampConversionError(e)) .leftMap(e => ProtoDeserializationError.TimestampConversionError(e))
@ -64,32 +56,21 @@ object LedgerBlockEvent extends HasLoggerName {
) )
} }
def deserializeSignedOrderingRequest( def deserializeSignedRequest(
protocolVersion: ProtocolVersion protocolVersion: ProtocolVersion
)(submissionRequestBytes: ByteString): ParsingResult[SignedOrderingRequest] = { )(
submissionRequestBytes: ByteString
): ParsingResult[SenderSigned[SubmissionRequest]] = {
// TODO(i10428) Prevent zip bombing when decompressing the request // TODO(i10428) Prevent zip bombing when decompressing the request
for { for {
sequencerSignedContent <- SignedContent sequencerSignedContent <- SignedContent
.fromByteString(protocolVersion)(submissionRequestBytes) .fromByteString(protocolVersion)(submissionRequestBytes)
signedOrderingRequest <- sequencerSignedContent signedOrderingRequest <- deserializeSenderSignedSubmissionRequest(protocolVersion)(
.deserializeContent( sequencerSignedContent
deserializeOrderingRequestToValueClass(protocolVersion) )
.andThen(deserializeOrderingRequestSignedContent(protocolVersion))
)
} yield signedOrderingRequest } yield signedOrderingRequest
} }
private def deserializeOrderingRequestToValueClass(
protocolVersion: ProtocolVersion
): ByteString => ParsingResult[SignedContent[BytestringWithCryptographicEvidence]] =
SignedContent.fromByteString(protocolVersion)
private def deserializeOrderingRequestSignedContent(protocolVersion: ProtocolVersion)(
signedContentParsingResult: ParsingResult[SignedContent[BytestringWithCryptographicEvidence]]
): ParsingResult[SenderSigned[SubmissionRequest]] = for {
signedContent <- signedContentParsingResult
senderSignedRequest <- deserializeSenderSignedSubmissionRequest(protocolVersion)(signedContent)
} yield senderSignedRequest
private def deserializeSenderSignedSubmissionRequest[A <: HasCryptographicEvidence]( private def deserializeSenderSignedSubmissionRequest[A <: HasCryptographicEvidence](
protocolVersion: ProtocolVersion protocolVersion: ProtocolVersion

View File

@ -34,7 +34,7 @@ import com.digitalasset.canton.resource.IdempotentInsert.insertVerifyingConflict
import com.digitalasset.canton.resource.{DbStorage, DbStore} import com.digitalasset.canton.resource.{DbStorage, DbStore}
import com.digitalasset.canton.sequencing.OrdinarySerializedEvent import com.digitalasset.canton.sequencing.OrdinarySerializedEvent
import com.digitalasset.canton.sequencing.protocol.TrafficState import com.digitalasset.canton.sequencing.protocol.TrafficState
import com.digitalasset.canton.topology.{Member, UnauthenticatedMemberId} import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.version.ProtocolVersion import com.digitalasset.canton.version.ProtocolVersion
import com.digitalasset.canton.{SequencerCounter, resource} import com.digitalasset.canton.{SequencerCounter, resource}
@ -152,20 +152,14 @@ class DbSequencerBlockStore(
val addMember = sequencerStore.addMemberDBIO(_, _) val addMember = sequencerStore.addMemberDBIO(_, _)
val addAcks = sequencerStore.acknowledgeDBIO _ val addAcks = sequencerStore.acknowledgeDBIO _
val addEvents = sequencerStore.addEventsDBIO(trafficState)(_) val addEvents = sequencerStore.addEventsDBIO(trafficState)(_)
val (unauthenticated, disabledMembers) = membersDisabled.partitionMap {
case unauthenticated: UnauthenticatedMemberId => Left(unauthenticated)
case other => Right(other)
}
val disableMember = sequencerStore.disableMemberDBIO _ val disableMember = sequencerStore.disableMemberDBIO _
val unregisterUnauthenticatedMember = sequencerStore.unregisterUnauthenticatedMember _
val dbio = DBIO val dbio = DBIO
.seq( .seq(
newMembers.toSeq.map(addMember.tupled) ++ newMembers.toSeq.map(addMember.tupled) ++
acknowledgments.toSeq.map(addAcks.tupled) ++ acknowledgments.toSeq.map(addAcks.tupled) ++
unauthenticated.map(unregisterUnauthenticatedMember) ++
Seq(sequencerStore.addInFlightAggregationUpdatesDBIO(inFlightAggregationUpdates)) ++ Seq(sequencerStore.addInFlightAggregationUpdatesDBIO(inFlightAggregationUpdates)) ++
disabledMembers.map(disableMember): _* membersDisabled.map(disableMember): _*
) )
.transactionally .transactionally

View File

@ -31,7 +31,7 @@ import com.digitalasset.canton.domain.sequencing.sequencer.{
import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging}
import com.digitalasset.canton.sequencing.OrdinarySerializedEvent import com.digitalasset.canton.sequencing.OrdinarySerializedEvent
import com.digitalasset.canton.sequencing.protocol.TrafficState import com.digitalasset.canton.sequencing.protocol.TrafficState
import com.digitalasset.canton.topology.{Member, UnauthenticatedMemberId} import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import monocle.macros.syntax.lens.* import monocle.macros.syntax.lens.*
import org.apache.pekko.NotUsed import org.apache.pekko.NotUsed
@ -112,11 +112,6 @@ class InMemorySequencerBlockStore(
val addEvents = sequencerStore.addEvents(_, trafficState) val addEvents = sequencerStore.addEvents(_, trafficState)
val addAcks = sequencerStore.acknowledge(_, _) val addAcks = sequencerStore.acknowledge(_, _)
val disableMember = sequencerStore.disableMember(_) val disableMember = sequencerStore.disableMember(_)
val unregisterUnauthenticatedMember = sequencerStore.unregisterUnauthenticatedMember(_)
val (unauthenticated, disabledMembers) = membersDisabled.partitionMap {
case unauthenticated: UnauthenticatedMemberId => Left(unauthenticated)
case other => Right(other)
}
// Since these updates are being run sequentially from the state manager, there is no problem with this // Since these updates are being run sequentially from the state manager, there is no problem with this
// implementation not being atomic. // implementation not being atomic.
// Also because this is an in-mem implementation, there is no concern about crashing mid update since all state // Also because this is an in-mem implementation, there is no concern about crashing mid update since all state
@ -125,8 +120,7 @@ class InMemorySequencerBlockStore(
_ <- Future.traverse(newMembers.toSeq)(addMember.tupled) _ <- Future.traverse(newMembers.toSeq)(addMember.tupled)
_ <- Future.traverse(events)(addEvents) _ <- Future.traverse(events)(addEvents)
_ <- Future.traverse(acknowledgments.toSeq)(addAcks.tupled) _ <- Future.traverse(acknowledgments.toSeq)(addAcks.tupled)
_ <- Future.traverse(disabledMembers)(disableMember) _ <- Future.traverse(membersDisabled)(disableMember)
_ <- Future.traverse(unauthenticated)(unregisterUnauthenticatedMember)
_ <- sequencerStore.addInFlightAggregationUpdates(inFlightAggregationUpdates) _ <- sequencerStore.addInFlightAggregationUpdates(inFlightAggregationUpdates)
} yield () } yield ()
} }

View File

@ -364,11 +364,9 @@ private[update] final class BlockChunkProcessor(
sequencedSubmission sequencedSubmission
def recipientIsKnown(member: Member): Future[Option[Member]] = { def recipientIsKnown(member: Member): Future[Option[Member]] = {
if (!member.isAuthenticated) Future.successful(None) sequencingSnapshot.ipsSnapshot
else .isMemberKnown(member)
sequencingSnapshot.ipsSnapshot .map(Option.when(_)(member))
.isMemberKnown(member)
.map(Option.when(_)(member))
} }
val topologySnapshot = topologySnapshotO.getOrElse(sequencingSnapshot).ipsSnapshot val topologySnapshot = topologySnapshotO.getOrElse(sequencingSnapshot).ipsSnapshot
@ -394,10 +392,9 @@ private[update] final class BlockChunkProcessor(
) )
} yield { } yield {
val knownGroupMembers = groupToMembers.values.flatten val knownGroupMembers = groupToMembers.values.flatten
val allowUnauthenticatedSender = Option.when(!sender.isAuthenticated)(sender).toList
val allMembersInSubmission = val allMembersInSubmission =
Set.empty ++ knownGroupMembers ++ knownMemberRecipientsOrSender ++ allowUnauthenticatedSender Set.empty ++ knownGroupMembers ++ knownMemberRecipientsOrSender
(allMembersInSubmission -- state.ephemeral.registeredMembers) (allMembersInSubmission -- state.ephemeral.registeredMembers)
.map(_ -> sequencingTimestamp) .map(_ -> sequencingTimestamp)
.toSeq .toSeq
@ -474,7 +471,7 @@ private[update] final class BlockChunkProcessor(
value.foreach(_.withTraceContext { implicit traceContext => value.foreach(_.withTraceContext { implicit traceContext =>
{ {
case LedgerBlockEvent.Send(_, signedSubmissionRequest, payloadSize) => case LedgerBlockEvent.Send(_, signedSubmissionRequest, payloadSize) =>
signedSubmissionRequest.content.content.batch.allRecipients signedSubmissionRequest.content.batch.allRecipients
.foldLeft(RecipientStats()) { .foldLeft(RecipientStats()) {
case (acc, MemberRecipient(ParticipantId(_)) | ParticipantsOfParty(_)) => case (acc, MemberRecipient(ParticipantId(_)) | ParticipantsOfParty(_)) =>
acc.copy(participants = true) acc.copy(participants = true)
@ -482,15 +479,10 @@ private[update] final class BlockChunkProcessor(
acc.copy(mediators = true) acc.copy(mediators = true)
case (acc, MemberRecipient(SequencerId(_)) | SequencersOfDomain) => case (acc, MemberRecipient(SequencerId(_)) | SequencersOfDomain) =>
acc.copy(sequencers = true) acc.copy(sequencers = true)
case (
acc,
MemberRecipient(UnauthenticatedMemberId(_)),
) =>
acc // not used
case (acc, AllMembersOfDomain) => acc.copy(broadcast = true) case (acc, AllMembersOfDomain) => acc.copy(broadcast = true)
} }
.updateMetric( .updateMetric(
signedSubmissionRequest.content.content.sender, signedSubmissionRequest.content.sender,
payloadSize, payloadSize,
logger, logger,
metrics, metrics,

View File

@ -20,7 +20,6 @@ import com.digitalasset.canton.domain.block.data.{
} }
import com.digitalasset.canton.domain.block.{BlockEvents, LedgerBlockEvent, RawLedgerBlock} import com.digitalasset.canton.domain.block.{BlockEvents, LedgerBlockEvent, RawLedgerBlock}
import com.digitalasset.canton.domain.metrics.BlockMetrics import com.digitalasset.canton.domain.metrics.BlockMetrics
import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.SignedOrderingRequestOps
import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode
import com.digitalasset.canton.domain.sequencing.sequencer.errors.SequencerError.InvalidLedgerEvent import com.digitalasset.canton.domain.sequencing.sequencer.errors.SequencerError.InvalidLedgerEvent
import com.digitalasset.canton.domain.sequencing.sequencer.traffic.SequencerRateLimitManager import com.digitalasset.canton.domain.sequencing.sequencer.traffic.SequencerRateLimitManager
@ -171,9 +170,9 @@ class BlockUpdateGeneratorImpl(
private def isAddressingSequencers(event: LedgerBlockEvent): Boolean = private def isAddressingSequencers(event: LedgerBlockEvent): Boolean =
event match { event match {
case Send(_, signedOrderingRequest, _) => case Send(_, signedSubmissionRequest, _) =>
val allRecipients = val allRecipients =
signedOrderingRequest.signedSubmissionRequest.content.batch.allRecipients signedSubmissionRequest.content.batch.allRecipients
allRecipients.contains(AllMembersOfDomain) || allRecipients.contains(AllMembersOfDomain) ||
allRecipients.contains(SequencersOfDomain) allRecipients.contains(SequencersOfDomain)
case _ => false case _ => false

View File

@ -712,9 +712,7 @@ private[update] final class SubmissionRequestValidator(
// If we haven't seen any topology transactions yet, then we cannot verify signatures, so we skip it. // If we haven't seen any topology transactions yet, then we cannot verify signatures, so we skip it.
// In practice this should only happen for the first ever transaction, which contains the initial topology data. // In practice this should only happen for the first ever transaction, which contains the initial topology data.
val skipCheck = if (latestSequencerEventTimestamp.isEmpty) {
latestSequencerEventTimestamp.isEmpty || !submissionRequest.sender.isAuthenticated
if (skipCheck) {
EitherT.pure[FutureUnlessShutdown, SubmissionRequestOutcome](()) EitherT.pure[FutureUnlessShutdown, SubmissionRequestOutcome](())
} else { } else {
val alarm = for { val alarm = for {

View File

@ -30,7 +30,7 @@ import com.digitalasset.canton.sequencing.protocol.*
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.store.SequencedEventStore.OrdinarySequencedEvent import com.digitalasset.canton.store.SequencedEventStore.OrdinarySequencedEvent
import com.digitalasset.canton.store.db.DbDeserializationException import com.digitalasset.canton.store.db.DbDeserializationException
import com.digitalasset.canton.topology.{Member, UnauthenticatedMemberId} import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.{SerializableTraceContext, TraceContext} import com.digitalasset.canton.tracing.{SerializableTraceContext, TraceContext}
import com.digitalasset.canton.util.{ErrorUtil, RangeUtil} import com.digitalasset.canton.util.{ErrorUtil, RangeUtil}
import com.digitalasset.canton.version.* import com.digitalasset.canton.version.*
@ -450,15 +450,6 @@ class DbSequencerStateManagerStore(
_ <- sqlu"update seq_state_manager_members set enabled = ${false} where member = $member" _ <- sqlu"update seq_state_manager_members set enabled = ${false} where member = $member"
} yield () } yield ()
def unregisterUnauthenticatedMember(
member: UnauthenticatedMemberId
): DbAction.WriteOnly[Unit] = for {
_ <-
sqlu"delete from seq_state_manager_events where member = $member"
_ <-
sqlu"delete from seq_state_manager_members where member = $member"
} yield ()
override def isEnabled(member: Member)(implicit traceContext: TraceContext): Future[Boolean] = override def isEnabled(member: Member)(implicit traceContext: TraceContext): Future[Boolean] =
storage storage
.query( .query(

View File

@ -24,7 +24,7 @@ import com.digitalasset.canton.domain.sequencing.sequencer.{
import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging}
import com.digitalasset.canton.sequencing.OrdinarySerializedEvent import com.digitalasset.canton.sequencing.OrdinarySerializedEvent
import com.digitalasset.canton.sequencing.protocol.TrafficState import com.digitalasset.canton.sequencing.protocol.TrafficState
import com.digitalasset.canton.topology.{Member, UnauthenticatedMemberId} import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.ErrorUtil import com.digitalasset.canton.util.ErrorUtil
import org.apache.pekko.NotUsed import org.apache.pekko.NotUsed
@ -208,9 +208,6 @@ class InMemorySequencerStateManagerStore(
def disableMember(member: Member): State = def disableMember(member: Member): State =
copy(indices = indices + (member -> indices(member).disable())) copy(indices = indices + (member -> indices(member).disable()))
def unregisterUnauthenticatedMember(member: UnauthenticatedMemberId): State =
copy(indices = indices - member)
def addEvents( def addEvents(
events: Map[Member, OrdinarySerializedEvent], events: Map[Member, OrdinarySerializedEvent],
trafficSate: Map[Member, TrafficState], trafficSate: Map[Member, TrafficState],
@ -310,15 +307,6 @@ class InMemorySequencerStateManagerStore(
Future.unit Future.unit
} }
def unregisterUnauthenticatedMember(
member: UnauthenticatedMemberId
): Future[Unit] = {
state.getAndUpdate {
_.unregisterUnauthenticatedMember(member)
}
Future.unit
}
override def isEnabled(member: Member)(implicit traceContext: TraceContext): Future[Boolean] = override def isEnabled(member: Member)(implicit traceContext: TraceContext): Future[Boolean] =
Future.successful(state.get().indices.get(member).fold(false)(_.isEnabled)) Future.successful(state.get().indices.get(member).fold(false)(_.isEnabled))

View File

@ -19,10 +19,9 @@ import com.digitalasset.canton.sequencing.protocol.{
} }
import com.digitalasset.canton.time.EnrichedDurations.* import com.digitalasset.canton.time.EnrichedDurations.*
import com.digitalasset.canton.time.{Clock, PeriodicAction} import com.digitalasset.canton.time.{Clock, PeriodicAction}
import com.digitalasset.canton.topology.{DomainMember, Member, UnauthenticatedMemberId} import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.Spanning.SpanWrapper import com.digitalasset.canton.tracing.Spanning.SpanWrapper
import com.digitalasset.canton.tracing.{Spanning, TraceContext} import com.digitalasset.canton.tracing.{Spanning, TraceContext}
import com.digitalasset.canton.util.ErrorUtil
import com.digitalasset.canton.util.ShowUtil.* import com.digitalasset.canton.util.ShowUtil.*
import io.opentelemetry.api.trace.Tracer import io.opentelemetry.api.trace.Tracer
@ -69,11 +68,6 @@ abstract class BaseSequencer(
) )
.leftMap(e => SendAsyncError.RequestRefused(e)) .leftMap(e => SendAsyncError.RequestRefused(e))
.mapK(FutureUnlessShutdown.outcomeK) .mapK(FutureUnlessShutdown.outcomeK)
_ <- EitherT
.right(
autoRegisterUnauthenticatedSender(submission)
)
.mapK(FutureUnlessShutdown.outcomeK) // TODO(#18399): Propagate FUS
_ <- sendAsyncSignedInternal(signedSubmissionWithFixedTs) _ <- sendAsyncSignedInternal(signedSubmissionWithFixedTs)
} yield () } yield ()
} }
@ -102,11 +96,6 @@ abstract class BaseSequencer(
withSpan("Sequencer.sendAsync") { implicit traceContext => span => withSpan("Sequencer.sendAsync") { implicit traceContext => span =>
setSpanAttributes(span, submission) setSpanAttributes(span, submission)
for { for {
_ <- EitherT
.right(
autoRegisterUnauthenticatedSender(submission)
)
.mapK(FutureUnlessShutdown.outcomeK) // TODO(#18399): Propagate FUS
_ <- sendAsyncInternal(submission) _ <- sendAsyncInternal(submission)
} yield () } yield ()
} }
@ -116,19 +105,7 @@ abstract class BaseSequencer(
span.setAttribute("message_id", submission.messageId.unwrap) span.setAttribute("message_id", submission.messageId.unwrap)
} }
private def autoRegisterUnauthenticatedSender( protected def localSequencerMember: Member
submission: SubmissionRequest
)(implicit traceContext: TraceContext): Future[Unit] =
submission.sender match {
case member: UnauthenticatedMemberId =>
registerMember(member).valueOr(error =>
// this error should not happen, as currently registration errors are only for authenticated users
ErrorUtil.invalidState(s"Unexpected error: $error")
)
case _ => Future.unit
}
protected def localSequencerMember: DomainMember
protected def disableMemberInternal(member: Member)(implicit protected def disableMemberInternal(member: Member)(implicit
traceContext: TraceContext traceContext: TraceContext
): Future[Unit] ): Future[Unit]

View File

@ -25,25 +25,11 @@ import com.digitalasset.canton.metrics.MetricsHelper
import com.digitalasset.canton.resource.Storage import com.digitalasset.canton.resource.Storage
import com.digitalasset.canton.scheduler.PruningScheduler import com.digitalasset.canton.scheduler.PruningScheduler
import com.digitalasset.canton.sequencing.client.SequencerClient import com.digitalasset.canton.sequencing.client.SequencerClient
import com.digitalasset.canton.sequencing.protocol.{ import com.digitalasset.canton.sequencing.protocol.*
AcknowledgeRequest,
MemberRecipient,
SendAsyncError,
SignedContent,
SubmissionRequest,
TrafficState,
}
import com.digitalasset.canton.sequencing.traffic.TrafficControlErrors import com.digitalasset.canton.sequencing.traffic.TrafficControlErrors
import com.digitalasset.canton.time.EnrichedDurations.* import com.digitalasset.canton.time.EnrichedDurations.*
import com.digitalasset.canton.time.{Clock, NonNegativeFiniteDuration} import com.digitalasset.canton.time.{Clock, NonNegativeFiniteDuration}
import com.digitalasset.canton.topology.{ import com.digitalasset.canton.topology.{DomainId, Member, SequencerId}
AuthenticatedMember,
DomainId,
DomainMember,
Member,
SequencerId,
UnauthenticatedMemberId,
}
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.tracing.TraceContext.withNewTraceContext import com.digitalasset.canton.tracing.TraceContext.withNewTraceContext
import com.digitalasset.canton.util.FutureUtil.doNotAwait import com.digitalasset.canton.util.FutureUtil.doNotAwait
@ -242,37 +228,26 @@ class DatabaseSequencer(
override def registerMember(member: Member)(implicit override def registerMember(member: Member)(implicit
traceContext: TraceContext traceContext: TraceContext
): EitherT[Future, RegisterError, Unit] = { ): EitherT[Future, RegisterError, Unit] = {
if (!member.isAuthenticated) { for {
for { firstKnownAtO <- EitherT.right[RegisterError](
isRegistered <- EitherT.right[RegisterError](isRegistered(member)) cryptoApi.headSnapshot.ipsSnapshot.memberFirstKnownAt(member)
_ <- EitherTUtil.ifThenET[Future, RegisterError]( )
!isRegistered _ <- firstKnownAtO match {
) { case Some(firstKnownAt) =>
registerMemberInternal(member, clock.now) logger.debug(s"Registering member $member with timestamp $firstKnownAt")
} registerMemberInternal(member, firstKnownAt)
} yield ()
} else {
for {
firstKnownAtO <- EitherT.right[RegisterError](
cryptoApi.headSnapshot.ipsSnapshot.memberFirstKnownAt(member)
)
_ <- firstKnownAtO match {
case Some(firstKnownAt) =>
logger.debug(s"Registering member $member with timestamp $firstKnownAt")
registerMemberInternal(member, firstKnownAt)
case None => case None =>
val error: RegisterError = val error: RegisterError =
OperationError[RegisterMemberError]( OperationError[RegisterMemberError](
RegisterMemberError.UnexpectedError( RegisterMemberError.UnexpectedError(
member, member,
s"Member $member is not known in the topology", s"Member $member is not known in the topology",
)
) )
EitherT.leftT[Future, Unit](error) )
} EitherT.leftT[Future, Unit](error)
} yield () }
} } yield ()
} }
/** Package private to use access method in tests, see `TestDatabaseSequencerWrapper`. /** Package private to use access method in tests, see `TestDatabaseSequencerWrapper`.
@ -329,27 +304,20 @@ class DatabaseSequencer(
if (!unifiedSequencer) { if (!unifiedSequencer) {
reader.read(member, offset) reader.read(member, offset)
} else { } else {
if (!member.isAuthenticated) { for {
// allowing unauthenticated members to read events is the same as automatically registering an unauthenticated member isKnown <- EitherT.right[CreateSubscriptionError](
// and then proceeding with the subscription. cryptoApi.currentSnapshotApproximation.ipsSnapshot.isMemberKnown(member)
// optimization: if the member is unauthenticated, we don't need to fetch all members from the snapshot )
reader.read(member, offset) _ <- EitherTUtil.condUnitET[Future](
} else { isKnown,
for { CreateSubscriptionError.UnknownMember(member): CreateSubscriptionError,
isKnown <- EitherT.right[CreateSubscriptionError]( )
cryptoApi.currentSnapshotApproximation.ipsSnapshot.isMemberKnown(member) isRegistered <- EitherT.right(isRegistered(member))
) _ <- EitherTUtil.ifThenET[Future, CreateSubscriptionError](!isRegistered) {
_ <- EitherTUtil.condUnitET[Future]( registerMember(member).leftMap(CreateSubscriptionError.MemberRegisterError)
isKnown, }
CreateSubscriptionError.UnknownMember(member): CreateSubscriptionError, eventSource <- reader.read(member, offset)
) } yield eventSource
isRegistered <- EitherT.right(isRegistered(member))
_ <- EitherTUtil.ifThenET[Future, CreateSubscriptionError](!isRegistered) {
registerMember(member).leftMap(CreateSubscriptionError.MemberRegisterError)
}
eventSource <- reader.read(member, offset)
} yield eventSource
}
} }
} }
@ -375,19 +343,13 @@ class DatabaseSequencer(
member: Member member: Member
)(implicit traceContext: TraceContext): Future[Unit] = { )(implicit traceContext: TraceContext): Future[Unit] = {
withExpectedRegisteredMember(member, "Disable member") { memberId => withExpectedRegisteredMember(member, "Disable member") { memberId =>
member match { store.disableMember(memberId)
// Unauthenticated members being disabled get automatically unregistered
case unauthenticated: UnauthenticatedMemberId =>
store.unregisterUnauthenticatedMember(unauthenticated)
case _: AuthenticatedMember =>
store.disableMember(memberId)
}
} }
} }
// For the database sequencer, the SequencerId serves as the local sequencer identity/member // For the database sequencer, the SequencerId serves as the local sequencer identity/member
// until the database and block sequencers are unified. // until the database and block sequencers are unified.
override protected def localSequencerMember: DomainMember = SequencerId(domainId.uid) override protected def localSequencerMember: Member = SequencerId(domainId.uid)
/** helper for performing operations that are expected to be called with a registered member so will just throw if we /** helper for performing operations that are expected to be called with a registered member so will just throw if we
* find the member is unregistered. * find the member is unregistered.

View File

@ -73,16 +73,6 @@ class DirectSequencerClientTransport(
.sendAsyncSigned(request) .sendAsyncSigned(request)
.leftMap(SendAsyncClientError.RequestRefused) .leftMap(SendAsyncClientError.RequestRefused)
override def sendAsyncUnauthenticatedVersioned(
request: SubmissionRequest,
timeout: Duration,
)(implicit
traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncClientResponseError, Unit] =
ErrorUtil.internalError(
new UnsupportedOperationException("Direct client does not support unauthenticated sends")
)
override def acknowledgeSigned(request: SignedContent[AcknowledgeRequest])(implicit override def acknowledgeSigned(request: SignedContent[AcknowledgeRequest])(implicit
traceContext: TraceContext traceContext: TraceContext
): EitherT[FutureUnlessShutdown, String, Boolean] = ): EitherT[FutureUnlessShutdown, String, Boolean] =
@ -152,19 +142,6 @@ class DirectSequencerClientTransport(
} }
} }
override def subscribeUnauthenticated[E](
request: SubscriptionRequest,
handler: SerializedEventHandler[E],
)(implicit traceContext: TraceContext): SequencerSubscription[E] =
unsupportedUnauthenticatedSubscription
private def unsupportedUnauthenticatedSubscription(implicit traceContext: TraceContext): Nothing =
ErrorUtil.internalError(
new UnsupportedOperationException(
"Direct client does not support unauthenticated subscriptions"
)
)
override def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy = override def subscriptionRetryPolicy: SubscriptionErrorRetryPolicy =
// unlikely there will be any errors with this direct transport implementation // unlikely there will be any errors with this direct transport implementation
SubscriptionErrorRetryPolicy.never SubscriptionErrorRetryPolicy.never
@ -207,11 +184,6 @@ class DirectSequencerClientTransport(
SequencerSubscriptionPekko(source, health) SequencerSubscriptionPekko(source, health)
} }
override def subscribeUnauthenticated(request: SubscriptionRequest)(implicit
traceContext: TraceContext
): SequencerSubscriptionPekko[SubscriptionError] =
unsupportedUnauthenticatedSubscription
override def subscriptionRetryPolicyPekko: SubscriptionErrorRetryPolicyPekko[SubscriptionError] = override def subscriptionRetryPolicyPekko: SubscriptionErrorRetryPolicyPekko[SubscriptionError] =
// unlikely there will be any errors with this direct transport implementation // unlikely there will be any errors with this direct transport implementation
SubscriptionErrorRetryPolicyPekko.never SubscriptionErrorRetryPolicyPekko.never

View File

@ -6,6 +6,7 @@ package com.digitalasset.canton.domain.sequencing.sequencer
import cats.data.EitherT import cats.data.EitherT
import com.digitalasset.canton.SequencerCounter import com.digitalasset.canton.SequencerCounter
import com.digitalasset.canton.config.RequireTypes.{NonNegativeLong, PositiveInt} import com.digitalasset.canton.config.RequireTypes.{NonNegativeLong, PositiveInt}
import com.digitalasset.canton.crypto.{DomainSnapshotSyncCryptoApi, HashPurpose}
import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.RegisterError import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.RegisterError
import com.digitalasset.canton.domain.sequencing.sequencer.errors.{ import com.digitalasset.canton.domain.sequencing.sequencer.errors.{
@ -31,12 +32,13 @@ import com.digitalasset.canton.sequencing.traffic.TrafficControlErrors.TrafficCo
import com.digitalasset.canton.serialization.HasCryptographicEvidence import com.digitalasset.canton.serialization.HasCryptographicEvidence
import com.digitalasset.canton.topology.Member import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.version.ProtocolVersion
import io.grpc.ServerServiceDefinition import io.grpc.ServerServiceDefinition
import org.apache.pekko.Done import org.apache.pekko.Done
import org.apache.pekko.stream.KillSwitch import org.apache.pekko.stream.KillSwitch
import org.apache.pekko.stream.scaladsl.Source import org.apache.pekko.stream.scaladsl.Source
import scala.concurrent.Future import scala.concurrent.{ExecutionContext, Future}
/** Errors from pruning */ /** Errors from pruning */
sealed trait PruningError { sealed trait PruningError {
@ -283,9 +285,11 @@ object Sequencer extends HasLoggerName {
*/ */
type SequencerSigned[A <: HasCryptographicEvidence] = SignedContent[SenderSigned[A]] type SequencerSigned[A <: HasCryptographicEvidence] = SignedContent[SenderSigned[A]]
/** Ordering request signed by the sequencer. /** A signed ordering request. This is a double-signed message where the outer signature is the sequencer's,
* Outer signature is the signature of the sequencer that received the submission request. * and the inner signature is the sender's.
* Inner signature is the signature of the member from which the submission request originated. * The sequencer signature may be introduced and used by the implementation to ensure that the submission
* originates from the expected sequencer node. This may be necessary if the implementation is split across
* multiple processes.
* *
* ┌─────────────────┐ ┌────────────┐ * ┌─────────────────┐ ┌────────────┐
* SenderSigned Sequencer * SenderSigned Sequencer
@ -307,6 +311,31 @@ object Sequencer extends HasLoggerName {
*/ */
type SignedOrderingRequest = SequencerSigned[SubmissionRequest] type SignedOrderingRequest = SequencerSigned[SubmissionRequest]
/** Sign a submission request with the sequencer's private key.
* This utility may be used by a [[com.digitalasset.canton.domain.sequencing.sequencer.block.BlockOrderer]]
* implementation as described in [[SignedOrderingRequest]].
*/
def signOrderingRequest[A <: HasCryptographicEvidence](
content: SignedContent[SubmissionRequest],
cryptoApi: DomainSnapshotSyncCryptoApi,
protocolVersion: ProtocolVersion,
)(implicit
ec: ExecutionContext,
tc: TraceContext,
): EitherT[FutureUnlessShutdown, SendAsyncError.Internal, SignedOrderingRequest] =
for {
signed <- SignedContent
.create(
cryptoApi.pureCrypto,
cryptoApi,
content,
Some(cryptoApi.ipsSnapshot.timestamp),
HashPurpose.OrderingRequestSignature,
protocolVersion,
)
.leftMap(error => SendAsyncError.Internal(s"Could not sign ordering request: $error"))
} yield signed
implicit class SignedOrderingRequestOps(val value: SignedOrderingRequest) extends AnyVal { implicit class SignedOrderingRequestOps(val value: SignedOrderingRequest) extends AnyVal {
def signedSubmissionRequest: SignedContent[SubmissionRequest] = def signedSubmissionRequest: SignedContent[SubmissionRequest] =
value.content value.content

View File

@ -8,8 +8,7 @@ import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting}
import com.digitalasset.canton.sequencer.admin.v30 import com.digitalasset.canton.sequencer.admin.v30
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.time.NonNegativeFiniteDuration import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.topology.{Member, UnauthenticatedMemberId}
trait AbstractSequencerMemberStatus extends Product with Serializable { trait AbstractSequencerMemberStatus extends Product with Serializable {
def registeredAt: CantonTimestamp def registeredAt: CantonTimestamp
@ -166,17 +165,6 @@ final case class SequencerPruningStatus(
*/ */
lazy val safePruningTimestamp: CantonTimestamp = safePruningTimestampFor(now) lazy val safePruningTimestamp: CantonTimestamp = safePruningTimestampFor(now)
def unauthenticatedMembersToDisable(retentionPeriod: NonNegativeFiniteDuration): Set[Member] =
members.foldLeft(Set.empty[Member]) { (toDisable, memberStatus) =>
memberStatus.member match {
case _: UnauthenticatedMemberId if memberStatus.enabled =>
if (now.minus(retentionPeriod.unwrap) > memberStatus.safePruningTimestamp) {
toDisable + memberStatus.member
} else toDisable
case _ => toDisable
}
}
/** List clients that would need to be disabled to allow pruning at the given timestamp. /** List clients that would need to be disabled to allow pruning at the given timestamp.
*/ */
def clientsPreventingPruning(timestamp: CantonTimestamp): SequencerClients = def clientsPreventingPruning(timestamp: CantonTimestamp): SequencerClients =

View File

@ -246,8 +246,7 @@ class SequencerReader(
signingSnapshot <- OptionT signingSnapshot <- OptionT
.fromOption[FutureUnlessShutdown](topologySnapshotO) .fromOption[FutureUnlessShutdown](topologySnapshotO)
.getOrElseF { .getOrElseF {
val warnIfApproximate = val warnIfApproximate = event.counter > SequencerCounter.Genesis
(event.counter > SequencerCounter.Genesis) && member.isAuthenticated
SyncCryptoClient.getSnapshotForTimestampUS( SyncCryptoClient.getSnapshotForTimestampUS(
syncCryptoApi, syncCryptoApi,
event.timestamp, event.timestamp,

View File

@ -56,12 +56,6 @@ object SequencerValidations {
(), (),
"Sender is not eligible according to the aggregation rule", "Sender is not eligible according to the aggregation rule",
) )
unauthenticatedEligibleSenders = eligibleSenders.filterNot(_.isAuthenticated)
_ <- Either.cond(
unauthenticatedEligibleSenders.isEmpty,
(),
s"Eligible senders in aggregation rule must be authenticated, but found unauthenticated members $unauthenticatedEligibleSenders",
)
} yield () } yield ()
} }

View File

@ -5,12 +5,12 @@ package com.digitalasset.canton.domain.sequencing.sequencer.block
import cats.data.EitherT import cats.data.EitherT
import com.digitalasset.canton.domain.block.{RawLedgerBlock, SequencerDriverHealthStatus} import com.digitalasset.canton.domain.block.{RawLedgerBlock, SequencerDriverHealthStatus}
import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.SignedOrderingRequest import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.SenderSigned
import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode
import com.digitalasset.canton.sequencing.protocol.{ import com.digitalasset.canton.sequencing.protocol.{
AcknowledgeRequest, AcknowledgeRequest,
SendAsyncError, SendAsyncError,
SignedContent, SubmissionRequest,
} }
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import io.grpc.ServerServiceDefinition import io.grpc.ServerServiceDefinition
@ -63,12 +63,8 @@ trait BlockOrderer extends AutoCloseable {
/** Orders a submission. /** Orders a submission.
* If the sequencer node is honest, this normally results in a [[com.digitalasset.canton.domain.block.RawLedgerBlock.RawBlockEvent.Send]]. * If the sequencer node is honest, this normally results in a [[com.digitalasset.canton.domain.block.RawLedgerBlock.RawBlockEvent.Send]].
* In exceptional cases (crashes, high load, ...), a sequencer may drop submissions. * In exceptional cases (crashes, high load, ...), a sequencer may drop submissions.
* There's a double [[com.digitalasset.canton.sequencing.protocol.SignedContent]] wrapping because
* the outer signature is the sequencer's, and the inner signature is the sender's.
* The sequencer signature may be used by the implementation to ensure that the submission originates from the
* expected sequencer node. This may be necessary if the implementation is split across multiple processes.
*/ */
def send(signedSubmission: SignedOrderingRequest)(implicit def send(signedSubmissionRequest: SenderSigned[SubmissionRequest])(implicit
traceContext: TraceContext traceContext: TraceContext
): EitherT[Future, SendAsyncError, Unit] ): EitherT[Future, SendAsyncError, Unit]
@ -76,7 +72,7 @@ trait BlockOrderer extends AutoCloseable {
* If the sequencer node is honest, this normally results in a [[com.digitalasset.canton.domain.block.RawLedgerBlock.RawBlockEvent.Acknowledgment]]. * If the sequencer node is honest, this normally results in a [[com.digitalasset.canton.domain.block.RawLedgerBlock.RawBlockEvent.Acknowledgment]].
* In exceptional cases (crashes, high load, ...), a sequencer may drop acknowledgements. * In exceptional cases (crashes, high load, ...), a sequencer may drop acknowledgements.
*/ */
def acknowledge(signedAcknowledgeRequest: SignedContent[AcknowledgeRequest])(implicit def acknowledge(signedAcknowledgeRequest: SenderSigned[AcknowledgeRequest])(implicit
traceContext: TraceContext traceContext: TraceContext
): Future[Unit] ): Future[Unit]

View File

@ -9,17 +9,14 @@ import com.digitalasset.canton.SequencerCounter
import com.digitalasset.canton.concurrent.FutureSupervisor import com.digitalasset.canton.concurrent.FutureSupervisor
import com.digitalasset.canton.config.ProcessingTimeout import com.digitalasset.canton.config.ProcessingTimeout
import com.digitalasset.canton.config.RequireTypes.{NonNegativeLong, PositiveInt} import com.digitalasset.canton.config.RequireTypes.{NonNegativeLong, PositiveInt}
import com.digitalasset.canton.crypto.{DomainSyncCryptoClient, HashPurpose, Signature} import com.digitalasset.canton.crypto.{DomainSyncCryptoClient, Signature}
import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.domain.block.BlockSequencerStateManagerBase import com.digitalasset.canton.domain.block.BlockSequencerStateManagerBase
import com.digitalasset.canton.domain.block.data.SequencerBlockStore import com.digitalasset.canton.domain.block.data.SequencerBlockStore
import com.digitalasset.canton.domain.block.update.{BlockUpdateGeneratorImpl, LocalBlockUpdate} import com.digitalasset.canton.domain.block.update.{BlockUpdateGeneratorImpl, LocalBlockUpdate}
import com.digitalasset.canton.domain.metrics.SequencerMetrics import com.digitalasset.canton.domain.metrics.SequencerMetrics
import com.digitalasset.canton.domain.sequencing.sequencer.PruningError.UnsafePruningPoint import com.digitalasset.canton.domain.sequencing.sequencer.PruningError.UnsafePruningPoint
import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.{ import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.EventSource
EventSource,
SignedOrderingRequest,
}
import com.digitalasset.canton.domain.sequencing.sequencer.* import com.digitalasset.canton.domain.sequencing.sequencer.*
import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode
import com.digitalasset.canton.domain.sequencing.sequencer.errors.CreateSubscriptionError import com.digitalasset.canton.domain.sequencing.sequencer.errors.CreateSubscriptionError
@ -42,7 +39,6 @@ import com.digitalasset.canton.sequencing.traffic.{
TrafficControlErrors, TrafficControlErrors,
TrafficPurchasedSubmissionHandler, TrafficPurchasedSubmissionHandler,
} }
import com.digitalasset.canton.serialization.HasCryptographicEvidence
import com.digitalasset.canton.time.Clock import com.digitalasset.canton.time.Clock
import com.digitalasset.canton.topology.* import com.digitalasset.canton.topology.*
import com.digitalasset.canton.tracing.{TraceContext, Traced} import com.digitalasset.canton.tracing.{TraceContext, Traced}
@ -230,26 +226,6 @@ class BlockSequencer(
override def adminServices: Seq[ServerServiceDefinition] = blockOrderer.adminServices override def adminServices: Seq[ServerServiceDefinition] = blockOrderer.adminServices
private def signOrderingRequest[A <: HasCryptographicEvidence](
content: SignedContent[SubmissionRequest]
)(implicit
tc: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncError.Internal, SignedOrderingRequest] = {
val privateCrypto = cryptoApi.currentSnapshotApproximation
for {
signed <- SignedContent
.create(
cryptoApi.pureCrypto,
privateCrypto,
content,
Some(privateCrypto.ipsSnapshot.timestamp),
HashPurpose.OrderingRequestSignature,
protocolVersion,
)
.leftMap(error => SendAsyncError.Internal(s"Could not sign ordering request: $error"))
} yield signed
}
override protected def sendAsyncSignedInternal( override protected def sendAsyncSignedInternal(
signedSubmission: SignedContent[SubmissionRequest] signedSubmission: SignedContent[SubmissionRequest]
)(implicit traceContext: TraceContext): EitherT[FutureUnlessShutdown, SendAsyncError, Unit] = { )(implicit traceContext: TraceContext): EitherT[FutureUnlessShutdown, SendAsyncError, Unit] = {
@ -276,9 +252,7 @@ class BlockSequencer(
// TODO(#18399): currentApproximation vs headSnapshot? // TODO(#18399): currentApproximation vs headSnapshot?
cryptoApi.currentSnapshotApproximation.ipsSnapshot cryptoApi.currentSnapshotApproximation.ipsSnapshot
.allMembers() .allMembers()
.map(allMembers => .map(allMembers => (member: Member) => allMembers.contains(member))
(member: Member) => allMembers.contains(member) || !member.isAuthenticated
)
) )
.mapK(FutureUnlessShutdown.outcomeK) .mapK(FutureUnlessShutdown.outcomeK)
// TODO(#18399): Why we don't check group recipients here? // TODO(#18399): Why we don't check group recipients here?
@ -293,14 +267,13 @@ class BlockSequencer(
s"Invoking send operation on the ledger with the following protobuf message serialized to bytes ${prettyPrinter s"Invoking send operation on the ledger with the following protobuf message serialized to bytes ${prettyPrinter
.printAdHoc(submission.toProtoVersioned)}" .printAdHoc(submission.toProtoVersioned)}"
) )
signedOrderingRequest <- signOrderingRequest(signedSubmission)
_ <- _ <-
EitherT( EitherT(
futureSupervisor futureSupervisor
.supervised( .supervised(
s"Sending submission request with id ${submission.messageId} from $sender to ${batch.allRecipients}" s"Sending submission request with id ${submission.messageId} from $sender to ${batch.allRecipients}"
)( )(
blockOrderer.send(signedOrderingRequest).value blockOrderer.send(signedSubmission).value
) )
).mapK(FutureUnlessShutdown.outcomeK) ).mapK(FutureUnlessShutdown.outcomeK)
} yield () } yield ()
@ -313,22 +286,15 @@ class BlockSequencer(
if (unifiedSequencer) { if (unifiedSequencer) {
super.readInternal(member, offset) super.readInternal(member, offset)
} else { } else {
if (!member.isAuthenticated) { EitherT
// allowing unauthenticated members to read events is the same as automatically registering an unauthenticated member .right(cryptoApi.currentSnapshotApproximation.ipsSnapshot.isMemberKnown(member))
// and then proceeding with the subscription. .flatMap { isKnown =>
// optimization: if the member is unauthenticated, we don't need to fetch all members from the snapshot if (isKnown) {
EitherT.fromEither[Future](stateManager.readEventsForMember(member, offset)) EitherT.fromEither[Future](stateManager.readEventsForMember(member, offset))
} else { } else {
EitherT EitherT.leftT(CreateSubscriptionError.UnknownMember(member))
.right(cryptoApi.currentSnapshotApproximation.ipsSnapshot.isMemberKnown(member))
.flatMap { isKnown =>
if (isKnown) {
EitherT.fromEither[Future](stateManager.readEventsForMember(member, offset))
} else {
EitherT.leftT(CreateSubscriptionError.UnknownMember(member))
}
} }
} }
} }
} }
@ -338,8 +304,7 @@ class BlockSequencer(
if (unifiedSequencer) { if (unifiedSequencer) {
super.isRegistered(member) super.isRegistered(member)
} else { } else {
if (!member.isAuthenticated) Future.successful(true) cryptoApi.headSnapshot.ipsSnapshot.isMemberKnown(member)
else cryptoApi.headSnapshot.ipsSnapshot.isMemberKnown(member)
} }
} }
@ -374,7 +339,7 @@ class BlockSequencer(
} }
} }
override protected def localSequencerMember: DomainMember = sequencerId override protected def localSequencerMember: Member = sequencerId
override def acknowledge(member: Member, timestamp: CantonTimestamp)(implicit override def acknowledge(member: Member, timestamp: CantonTimestamp)(implicit
traceContext: TraceContext traceContext: TraceContext

View File

@ -9,7 +9,7 @@ import com.digitalasset.canton.domain.block.{
SequencerDriver, SequencerDriver,
SequencerDriverHealthStatus, SequencerDriverHealthStatus,
} }
import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.SignedOrderingRequest import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.SenderSigned
import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode
import com.digitalasset.canton.sequencing.protocol.* import com.digitalasset.canton.sequencing.protocol.*
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
@ -33,14 +33,14 @@ class DriverBlockOrderer(
driver.subscribe() driver.subscribe()
override def send( override def send(
signedSubmission: SignedOrderingRequest signedSubmissionRequest: SenderSigned[SubmissionRequest]
)(implicit traceContext: TraceContext): EitherT[Future, SendAsyncError, Unit] = )(implicit traceContext: TraceContext): EitherT[Future, SendAsyncError, Unit] =
// The driver API doesn't provide error reporting, so we don't attempt to translate the exception // The driver API doesn't provide error reporting, so we don't attempt to translate the exception
EitherT.right( EitherT.right(
driver.send(signedSubmission.toByteString) driver.send(signedSubmissionRequest.toByteString)
) )
override def acknowledge(signedAcknowledgeRequest: SignedContent[AcknowledgeRequest])(implicit override def acknowledge(signedAcknowledgeRequest: SenderSigned[AcknowledgeRequest])(implicit
traceContext: TraceContext traceContext: TraceContext
): Future[Unit] = ): Future[Unit] =
driver.acknowledge(signedAcknowledgeRequest.toByteString) driver.acknowledge(signedAcknowledgeRequest.toByteString)

View File

@ -6,7 +6,7 @@ package com.digitalasset.canton.domain.sequencing.sequencer.errors
import com.daml.error.{ErrorCategory, ErrorCode, Explanation, Resolution} import com.daml.error.{ErrorCategory, ErrorCode, Explanation, Resolution}
import com.digitalasset.canton.error.BaseCantonError import com.digitalasset.canton.error.BaseCantonError
import com.digitalasset.canton.error.CantonErrorGroups.SequencerErrorGroup import com.digitalasset.canton.error.CantonErrorGroups.SequencerErrorGroup
import com.digitalasset.canton.topology.DomainMember import com.digitalasset.canton.topology.Member
sealed trait SequencerAdministrationError extends BaseCantonError sealed trait SequencerAdministrationError extends BaseCantonError
@ -26,7 +26,7 @@ object SequencerAdministrationError extends SequencerErrorGroup {
"CANNOT_DISABLE_LOCAL_SEQUENCER_MEMBER", "CANNOT_DISABLE_LOCAL_SEQUENCER_MEMBER",
ErrorCategory.InvalidIndependentOfSystemState, ErrorCategory.InvalidIndependentOfSystemState,
) { ) {
final case class Error(sequencerMember: DomainMember) final case class Error(sequencerMember: Member)
extends BaseCantonError.Impl( extends BaseCantonError.Impl(
cause = s"Sequencer ${sequencerMember} cannot disable its local sequencer subscription" cause = s"Sequencer ${sequencerMember} cannot disable its local sequencer subscription"
) )

View File

@ -30,7 +30,7 @@ import com.digitalasset.canton.resource.DbStorage.Profile.{H2, Oracle, Postgres}
import com.digitalasset.canton.resource.DbStorage.* import com.digitalasset.canton.resource.DbStorage.*
import com.digitalasset.canton.sequencing.protocol.MessageId import com.digitalasset.canton.sequencing.protocol.MessageId
import com.digitalasset.canton.store.db.DbDeserializationException import com.digitalasset.canton.store.db.DbDeserializationException
import com.digitalasset.canton.topology.{Member, UnauthenticatedMemberId} import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.{SerializableTraceContext, TraceContext} import com.digitalasset.canton.tracing.{SerializableTraceContext, TraceContext}
import com.digitalasset.canton.util.EitherTUtil.condUnitET import com.digitalasset.canton.util.EitherTUtil.condUnitET
import com.digitalasset.canton.util.{EitherTUtil, ErrorUtil, retry} import com.digitalasset.canton.util.{EitherTUtil, ErrorUtil, retry}
@ -382,23 +382,6 @@ class DbSequencerStore(
"registerMember", "registerMember",
) )
def unregisterUnauthenticatedMember(member: UnauthenticatedMemberId)(implicit
traceContext: TraceContext
): Future[Unit] =
for {
memberRemoved <- storage.update(
{
sqlu"""
delete from sequencer_members where member = $member
"""
},
functionFullName,
)
_ = evictFromCache(member)
} yield logger.debug(
s"Removed at least $memberRemoved unauthenticated members"
)
protected override def lookupMemberInternal(member: Member)(implicit protected override def lookupMemberInternal(member: Member)(implicit
traceContext: TraceContext traceContext: TraceContext
): Future[Option[RegisteredMember]] = ): Future[Option[RegisteredMember]] =

View File

@ -17,7 +17,7 @@ import com.digitalasset.canton.domain.sequencing.sequencer.*
import com.digitalasset.canton.domain.sequencing.sequencer.store.InMemorySequencerStore.CheckpointDataAtCounter import com.digitalasset.canton.domain.sequencing.sequencer.store.InMemorySequencerStore.CheckpointDataAtCounter
import com.digitalasset.canton.lifecycle.CloseContext import com.digitalasset.canton.lifecycle.CloseContext
import com.digitalasset.canton.logging.NamedLoggerFactory import com.digitalasset.canton.logging.NamedLoggerFactory
import com.digitalasset.canton.topology.{Member, UnauthenticatedMemberId} import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.FutureInstances.* import com.digitalasset.canton.util.FutureInstances.*
import com.digitalasset.canton.util.{EitherTUtil, ErrorUtil, retry} import com.digitalasset.canton.util.{EitherTUtil, ErrorUtil, retry}
@ -79,18 +79,6 @@ class InMemorySequencerStore(
.memberId .memberId
} }
override def unregisterUnauthenticatedMember(member: UnauthenticatedMemberId)(implicit
traceContext: TraceContext
): Future[Unit] = {
disabledClientsRef
.getAndUpdate(disabledClients =>
disabledClients.copy(members = disabledClients.members - member)
)
evictFromCache(member)
Future.successful(members.remove(member)).void
}
protected override def lookupMemberInternal(member: Member)(implicit protected override def lookupMemberInternal(member: Member)(implicit
traceContext: TraceContext traceContext: TraceContext
): Future[Option[RegisteredMember]] = ): Future[Option[RegisteredMember]] =

View File

@ -3,7 +3,7 @@
package com.digitalasset.canton.domain.sequencing.sequencer.store package com.digitalasset.canton.domain.sequencing.sequencer.store
import com.digitalasset.canton.topology.{Member, UnauthenticatedMemberId} import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.{TraceContext, Traced} import com.digitalasset.canton.tracing.{TraceContext, Traced}
import com.github.blemale.scaffeine.{Cache, Scaffeine} import com.github.blemale.scaffeine.{Cache, Scaffeine}
@ -36,11 +36,4 @@ class SequencerMemberCache(populate: Traced[Member] => Future[Option[RegisteredM
cache.getIfPresent(member).fold(lookupFromStore)(result => Future.successful(Option(result))) cache.getIfPresent(member).fold(lookupFromStore)(result => Future.successful(Option(result)))
} }
/** Evicts an unauthenticated member from the cache. Used when unregistering unauthenticated members.
* @param member member to evict from the cache
*/
def evict(member: UnauthenticatedMemberId): Unit = {
cache.invalidate(member)
}
} }

View File

@ -14,14 +14,8 @@ import com.digitalasset.canton.config.ProcessingTimeout
import com.digitalasset.canton.config.RequireTypes.{NonNegativeInt, PositiveNumeric} import com.digitalasset.canton.config.RequireTypes.{NonNegativeInt, PositiveNumeric}
import com.digitalasset.canton.data.CantonTimestamp import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.domain.sequencing.sequencer.PruningError.UnsafePruningPoint import com.digitalasset.canton.domain.sequencing.sequencer.PruningError.UnsafePruningPoint
import com.digitalasset.canton.domain.sequencing.sequencer.*
import com.digitalasset.canton.domain.sequencing.sequencer.store.SequencerStore.SequencerPruningResult import com.digitalasset.canton.domain.sequencing.sequencer.store.SequencerStore.SequencerPruningResult
import com.digitalasset.canton.domain.sequencing.sequencer.{
CommitMode,
PruningError,
SequencerPruningStatus,
SequencerSnapshot,
WriteNotification,
}
import com.digitalasset.canton.lifecycle.CloseContext import com.digitalasset.canton.lifecycle.CloseContext
import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting} import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting}
import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging} import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging}
@ -29,7 +23,7 @@ import com.digitalasset.canton.resource.{DbStorage, MemoryStorage, Storage}
import com.digitalasset.canton.sequencing.protocol.{MessageId, SequencedEvent} import com.digitalasset.canton.sequencing.protocol.{MessageId, SequencedEvent}
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.time.NonNegativeFiniteDuration import com.digitalasset.canton.time.NonNegativeFiniteDuration
import com.digitalasset.canton.topology.{Member, UnauthenticatedMemberId} import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.{HasTraceContext, TraceContext, Traced} import com.digitalasset.canton.tracing.{HasTraceContext, TraceContext, Traced}
import com.digitalasset.canton.util.EitherTUtil.condUnitET import com.digitalasset.canton.util.EitherTUtil.condUnitET
import com.digitalasset.canton.util.FutureInstances.* import com.digitalasset.canton.util.FutureInstances.*
@ -462,19 +456,6 @@ trait SequencerStore extends NamedLogging with AutoCloseable {
traceContext: TraceContext traceContext: TraceContext
): Future[SequencerMemberId] ): Future[SequencerMemberId]
/** Unregister a disabled unauthenticated member.
* This should delete the member from the store.
*/
def unregisterUnauthenticatedMember(member: UnauthenticatedMemberId)(implicit
traceContext: TraceContext
): Future[Unit]
/** Evict unauthenticated member from the cache.
*/
final protected def evictFromCache(member: UnauthenticatedMemberId): Unit = {
memberCache.evict(member)
}
/** Lookup an existing member id for the given member. /** Lookup an existing member id for the given member.
* Will return a cached value if available. * Will return a cached value if available.
* Return [[scala.None]] if no id exists. * Return [[scala.None]] if no id exists.

View File

@ -7,6 +7,7 @@ import cats.data.EitherT
import cats.instances.future.* import cats.instances.future.*
import cats.syntax.either.* import cats.syntax.either.*
import cats.syntax.foldable.* import cats.syntax.foldable.*
import cats.syntax.functor.*
import com.daml.metrics.api.MetricsContext import com.daml.metrics.api.MetricsContext
import com.daml.nameof.NameOf.functionFullName import com.daml.nameof.NameOf.functionFullName
import com.digitalasset.canton.ProtoDeserializationError.ProtoDeserializationFailure import com.digitalasset.canton.ProtoDeserializationError.ProtoDeserializationFailure
@ -27,7 +28,6 @@ import com.digitalasset.canton.protocol.DomainParametersLookup.SequencerDomainPa
import com.digitalasset.canton.protocol.DynamicDomainParametersLookup import com.digitalasset.canton.protocol.DynamicDomainParametersLookup
import com.digitalasset.canton.sequencing.OrdinarySerializedEvent import com.digitalasset.canton.sequencing.OrdinarySerializedEvent
import com.digitalasset.canton.sequencing.protocol.* import com.digitalasset.canton.sequencing.protocol.*
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.time.Clock import com.digitalasset.canton.time.Clock
import com.digitalasset.canton.topology.* import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.store.{ import com.digitalasset.canton.topology.store.{
@ -140,85 +140,6 @@ object GrpcSequencerService {
protocolVersion, protocolVersion,
) )
/** Abstracts the steps that are different in processing the submission requests coming from the various sendAsync endpoints
* @tparam ProtoClass The scalapb generated class of the RPC request message
*/
private sealed trait SubmissionRequestProcessing[ProtoClass <: scalapb.GeneratedMessage] {
/** The Scala class to which the `ProtoClass` should deserialize to */
type ValueClass
/** Tries to parse the proto class to the value class, erroring if the request exceeds the given limit. */
def parse(
requestP: ProtoClass,
maxRequestSize: MaxRequestSize,
protocolVersion: ProtocolVersion,
): ParsingResult[ValueClass]
/** Extract the [[SubmissionRequest]] from the value class */
def unwrap(request: ValueClass): SubmissionRequest
/** Call the appropriate send method on the [[Sequencer]] */
def send(request: ValueClass, sequencer: Sequencer)(implicit
traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncError, Unit]
}
private object VersionedSignedSubmissionRequestProcessing
extends SubmissionRequestProcessing[v30.SendAsyncVersionedRequest] {
override type ValueClass = SignedContent[SubmissionRequest]
override def parse(
requestP: v30.SendAsyncVersionedRequest,
maxRequestSize: MaxRequestSize,
protocolVersion: ProtocolVersion,
): ParsingResult[SignedContent[SubmissionRequest]] = {
for {
signedContent <- SignedContent.fromByteString(protocolVersion)(
requestP.signedSubmissionRequest
)
signedSubmissionRequest <- signedContent.deserializeContent(
SubmissionRequest
.fromByteString(protocolVersion)(
MaxRequestSizeToDeserialize.Limit(maxRequestSize.value)
)
)
} yield signedSubmissionRequest
}
override def unwrap(request: SignedContent[SubmissionRequest]): SubmissionRequest =
request.content
override def send(request: SignedContent[SubmissionRequest], sequencer: Sequencer)(implicit
traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncError, Unit] = sequencer.sendAsyncSigned(request)
}
private object VersionedUnsignedSubmissionRequestProcessing
extends SubmissionRequestProcessing[v30.SendAsyncUnauthenticatedVersionedRequest] {
override type ValueClass = SubmissionRequest
override def parse(
requestP: v30.SendAsyncUnauthenticatedVersionedRequest,
maxRequestSize: MaxRequestSize,
protocolVersion: ProtocolVersion,
): ParsingResult[SubmissionRequest] =
SubmissionRequest.fromByteString(protocolVersion)(
MaxRequestSizeToDeserialize.Limit(maxRequestSize.value)
)(
requestP.submissionRequest
)
override def unwrap(request: SubmissionRequest): SubmissionRequest = request
override def send(request: SubmissionRequest, sequencer: Sequencer)(implicit
traceContext: TraceContext
): EitherT[FutureUnlessShutdown, SendAsyncError, Unit] =
sequencer.sendAsync(request)
}
private sealed trait WrappedAcknowledgeRequest extends Product with Serializable { private sealed trait WrappedAcknowledgeRequest extends Product with Serializable {
def unwrap: AcknowledgeRequest def unwrap: AcknowledgeRequest
} }
@ -269,26 +190,11 @@ class GrpcSequencerService(
override def sendAsyncVersioned( override def sendAsyncVersioned(
requestP: v30.SendAsyncVersionedRequest requestP: v30.SendAsyncVersionedRequest
): Future[v30.SendAsyncVersionedResponse] = ): Future[v30.SendAsyncVersionedResponse] =
validateAndSend( validateAndSend(requestP).map(_.toProtoV30)
requestP,
VersionedSignedSubmissionRequestProcessing,
isUsingAuthenticatedEndpoint = true,
).map(_.toSendAsyncVersionedResponseProto)
override def sendAsyncUnauthenticatedVersioned( private def validateAndSend(
requestP: v30.SendAsyncUnauthenticatedVersionedRequest proto: v30.SendAsyncVersionedRequest
): Future[v30.SendAsyncUnauthenticatedVersionedResponse] = ): Future[SendAsyncVersionedResponse] = {
validateAndSend(
requestP,
VersionedUnsignedSubmissionRequestProcessing,
isUsingAuthenticatedEndpoint = false,
).map(_.toSendAsyncUnauthenticatedVersionedResponseProto)
private def validateAndSend[ProtoClass <: scalapb.GeneratedMessage](
proto: ProtoClass,
processing: SubmissionRequestProcessing[ProtoClass],
isUsingAuthenticatedEndpoint: Boolean,
): Future[SendAsyncUnauthenticatedVersionedResponse] = {
implicit val traceContext: TraceContext = TraceContextGrpc.fromGrpcContext implicit val traceContext: TraceContext = TraceContextGrpc.fromGrpcContext
// This has to run at the beginning, because it reads from a thread-local. // This has to run at the beginning, because it reads from a thread-local.
@ -296,17 +202,24 @@ class GrpcSequencerService(
def parseAndValidate( def parseAndValidate(
maxRequestSize: MaxRequestSize maxRequestSize: MaxRequestSize
): Either[SendAsyncError, processing.ValueClass] = for { ): Either[SendAsyncError, SignedContent[SubmissionRequest]] = for {
request <- processing signedContent <- SignedContent
.parse(proto, maxRequestSize, protocolVersion) .fromByteString(protocolVersion)(proto.signedSubmissionRequest)
.leftMap(requestDeserializationError(_, maxRequestSize))
signedSubmissionRequest <- signedContent
.deserializeContent(
SubmissionRequest
.fromByteString(protocolVersion)(
MaxRequestSizeToDeserialize.Limit(maxRequestSize.value)
)
)
.leftMap(requestDeserializationError(_, maxRequestSize)) .leftMap(requestDeserializationError(_, maxRequestSize))
_ <- validateSubmissionRequest( _ <- validateSubmissionRequest(
proto.serializedSize, proto.serializedSize,
processing.unwrap(request), signedSubmissionRequest.content,
senderFromMetadata, senderFromMetadata,
) )
_ <- checkSenderPermission(processing.unwrap(request), isUsingAuthenticatedEndpoint) } yield signedSubmissionRequest
} yield request
lazy val sendET = for { lazy val sendET = for {
domainParameters <- EitherT domainParameters <- EitherT
@ -317,25 +230,25 @@ class GrpcSequencerService(
request <- EitherT.fromEither[FutureUnlessShutdown]( request <- EitherT.fromEither[FutureUnlessShutdown](
parseAndValidate(domainParameters.maxRequestSize) parseAndValidate(domainParameters.maxRequestSize)
) )
_ <- checkRate(processing.unwrap(request)).mapK(FutureUnlessShutdown.outcomeK) _ <- checkRate(request.content).mapK(FutureUnlessShutdown.outcomeK)
_ <- processing.send(request, sequencer) _ <- sequencer.sendAsyncSigned(request)
} yield () } yield ()
performUnlessClosingUSF(functionFullName)(sendET.value.map { res => performUnlessClosingUSF(functionFullName)(sendET.value.map { res =>
res.left.foreach { err => res.left.foreach { err =>
logger.info(s"Rejecting submission request by $senderFromMetadata with $err") logger.info(s"Rejecting submission request by $senderFromMetadata with $err")
} }
toSendAsyncUnauthenticatedVersionedResponse(res) toSendAsyncVersionedResponse(res)
}) })
.onShutdown( .onShutdown(
SendAsyncUnauthenticatedVersionedResponse(error = Some(SendAsyncError.ShuttingDown())) SendAsyncVersionedResponse(error = Some(SendAsyncError.ShuttingDown()))
) )
} }
private def toSendAsyncUnauthenticatedVersionedResponse( private def toSendAsyncVersionedResponse(
result: Either[SendAsyncError, Unit] result: Either[SendAsyncError, Unit]
): SendAsyncUnauthenticatedVersionedResponse = ): SendAsyncVersionedResponse =
SendAsyncUnauthenticatedVersionedResponse(result.swap.toOption) SendAsyncVersionedResponse(result.swap.toOption)
private def requestDeserializationError( private def requestDeserializationError(
error: ProtoDeserializationError, error: ProtoDeserializationError,
@ -354,29 +267,6 @@ class GrpcSequencerService(
SendAsyncError.RequestInvalid(message) SendAsyncError.RequestInvalid(message)
} }
private def checkSenderPermission(
submissionRequest: SubmissionRequest,
isUsingAuthenticatedEndpoint: Boolean,
)(implicit traceContext: TraceContext): Either[SendAsyncError, Unit] = {
val sender = submissionRequest.sender
for {
_ <- Either.cond(
sender.isAuthenticated == isUsingAuthenticatedEndpoint,
(),
refuse(submissionRequest.messageId.toProtoPrimitive, sender)(
s"Sender $sender needs to use ${if (isUsingAuthenticatedEndpoint) "unauthenticated"
else "authenticated"} send operation"
),
)
_ <- sender match {
case authMember: AuthenticatedMember =>
checkAuthenticatedSendPermission(submissionRequest, authMember)
case unauthMember: UnauthenticatedMemberId =>
checkUnauthenticatedSendPermission(submissionRequest, unauthMember)
}
} yield ()
}
private def validateSubmissionRequest( private def validateSubmissionRequest(
requestSize: Int, requestSize: Int,
request: SubmissionRequest, request: SubmissionRequest,
@ -424,14 +314,6 @@ class GrpcSequencerService(
SequencerValidations.checkToAtMostOneMediator(request), SequencerValidations.checkToAtMostOneMediator(request),
"Batch contains multiple mediators as recipients.", "Batch contains multiple mediators as recipients.",
) )
_ <- refuseUnless(sender)(
noTopologyTimestampIfUnauthenticated(
sender,
request.topologyTimestamp,
request.batch.envelopes,
),
"Requests sent from or to unauthenticated members must not specify the topology timestamp",
)
_ <- request.aggregationRule.traverse_(validateAggregationRule(sender, messageId, _)) _ <- request.aggregationRule.traverse_(validateAggregationRule(sender, messageId, _))
} yield { } yield {
metrics.publicApi.bytesProcessed.mark(requestSize.toLong)(MetricsContext.Empty) metrics.publicApi.bytesProcessed.mark(requestSize.toLong)(MetricsContext.Empty)
@ -442,22 +324,6 @@ class GrpcSequencerService(
} }
} }
/** Reject requests that involve unauthenticated members and specify the topology timestamp.
* This is because the unauthenticated member typically does not know the domain topology state
* and therefore cannot validate that the requested timestamp is within the topology timestamp tolerance.
*/
private def noTopologyTimestampIfUnauthenticated(
sender: Member,
topologyTimestampO: Option[CantonTimestamp],
envelopes: Seq[ClosedEnvelope],
): Boolean =
topologyTimestampO.isEmpty || (sender.isAuthenticated && envelopes.forall(
_.recipients.allRecipients.forall {
case MemberRecipient(m) => m.isAuthenticated
case _ => true
}
))
private def validateAggregationRule( private def validateAggregationRule(
sender: Member, sender: Member,
messageId: MessageId, messageId: MessageId,
@ -481,46 +347,6 @@ class GrpcSequencerService(
SendAsyncError.RequestRefused(message) SendAsyncError.RequestRefused(message)
} }
private def checkAuthenticatedSendPermission(
request: SubmissionRequest,
sender: AuthenticatedMember,
)(implicit traceContext: TraceContext): Either[SendAsyncError, Unit] = sender match {
case _ =>
val unauthRecipients = request.batch.envelopes
.toSet[ClosedEnvelope]
.flatMap(_.recipients.allRecipients)
.collect { case MemberRecipient(unauthMember: UnauthenticatedMemberId) =>
unauthMember
}
Either.cond(
unauthRecipients.isEmpty,
(),
refuse(request.messageId.toProtoPrimitive, sender)(
s"Member is trying to send message to unauthenticated ${unauthRecipients.mkString(" ,")}. Only domain manager can do that."
),
)
}
private def checkUnauthenticatedSendPermission(
request: SubmissionRequest,
unauthenticatedMember: UnauthenticatedMemberId,
)(implicit traceContext: TraceContext): Either[SendAsyncError, Unit] = {
// unauthenticated member can only send messages to IDM
val nonIdmRecipients = request.batch.envelopes
.flatMap(_.recipients.allRecipients)
.filter {
case TopologyBroadcastAddress.recipient => false
case _ => true
}
Either.cond(
nonIdmRecipients.isEmpty,
(),
refuse(request.messageId.toProtoPrimitive, unauthenticatedMember)(
s"Unauthenticated member is trying to send message to members other than the topology broadcast address ${TopologyBroadcastAddress.recipient}"
),
)
}
private def checkRate( private def checkRate(
request: SubmissionRequest request: SubmissionRequest
)(implicit )(implicit
@ -602,25 +428,12 @@ class GrpcSequencerService(
subscribeInternal[v30.VersionedSubscriptionResponse]( subscribeInternal[v30.VersionedSubscriptionResponse](
request, request,
responseObserver, responseObserver,
requiresAuthentication = true,
toVersionSubscriptionResponseV0,
)
override def subscribeUnauthenticatedVersioned(
request: v30.SubscriptionRequest,
responseObserver: StreamObserver[v30.VersionedSubscriptionResponse],
): Unit =
subscribeInternal[v30.VersionedSubscriptionResponse](
request,
responseObserver,
requiresAuthentication = false,
toVersionSubscriptionResponseV0, toVersionSubscriptionResponseV0,
) )
private def subscribeInternal[T]( private def subscribeInternal[T](
request: v30.SubscriptionRequest, request: v30.SubscriptionRequest,
responseObserver: StreamObserver[T], responseObserver: StreamObserver[T],
requiresAuthentication: Boolean,
toSubscriptionResponse: OrdinarySerializedEvent => T, toSubscriptionResponse: OrdinarySerializedEvent => T,
): Unit = { ): Unit = {
implicit val traceContext: TraceContext = TraceContextGrpc.fromGrpcContext implicit val traceContext: TraceContext = TraceContextGrpc.fromGrpcContext
@ -637,7 +450,7 @@ class GrpcSequencerService(
(), (),
Status.UNAVAILABLE.withDescription("Domain is being shutdown."), Status.UNAVAILABLE.withDescription("Domain is being shutdown."),
) )
_ <- checkSubscriptionMemberPermission(member, requiresAuthentication) _ <- checkSubscriptionMemberPermission(member)
authenticationTokenO = IdentityContextHelper.getCurrentStoredAuthenticationToken authenticationTokenO = IdentityContextHelper.getCurrentStoredAuthenticationToken
_ <- subscriptionPool _ <- subscriptionPool
.create( .create(
@ -659,27 +472,10 @@ class GrpcSequencerService(
} }
} }
private def checkSubscriptionMemberPermission(member: Member, requiresAuthentication: Boolean)( private def checkSubscriptionMemberPermission(member: Member)(implicit
implicit traceContext: TraceContext traceContext: TraceContext
): Either[Status, Unit] = ): Either[Status, Unit] =
(member, requiresAuthentication) match { checkAuthenticatedMemberPermission(member)
case (authMember: AuthenticatedMember, true) =>
checkAuthenticatedMemberPermission(authMember)
case (authMember: AuthenticatedMember, false) =>
Left(
Status.PERMISSION_DENIED.withDescription(
s"Member $authMember needs to use authenticated subscribe operation"
)
)
case (_: UnauthenticatedMemberId, false) =>
Right(())
case (unauthMember: UnauthenticatedMemberId, true) =>
Left(
Status.PERMISSION_DENIED.withDescription(
s"Member $unauthMember cannot use authenticated subscribe operation"
)
)
}
override def acknowledgeSigned( override def acknowledgeSigned(
request: v30.AcknowledgeSignedRequest request: v30.AcknowledgeSignedRequest

View File

@ -23,7 +23,7 @@ import com.digitalasset.canton.sequencing.authentication.{
AuthenticationTokenManagerConfig, AuthenticationTokenManagerConfig,
} }
import com.digitalasset.canton.time.SimClock import com.digitalasset.canton.time.SimClock
import com.digitalasset.canton.topology.* import com.digitalasset.canton.topology.{DomainId, ParticipantId, UniqueIdentifier}
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.{BaseTest, HasExecutionContext} import com.digitalasset.canton.{BaseTest, HasExecutionContext}
import io.grpc.inprocess.{InProcessChannelBuilder, InProcessServerBuilder} import io.grpc.inprocess.{InProcessChannelBuilder, InProcessServerBuilder}
@ -93,11 +93,6 @@ class SequencerAuthenticationServerInterceptorTest
val participantId = val participantId =
UniqueIdentifier.fromProtoPrimitive_("p1::default").map(new ParticipantId(_)).value UniqueIdentifier.fromProtoPrimitive_("p1::default").map(new ParticipantId(_)).value
val unauthenticatedMemberId =
UniqueIdentifier
.fromProtoPrimitive_("unm1::default")
.map(new UnauthenticatedMemberId(_))
.value
val neverExpire = CantonTimestamp.MaxValue val neverExpire = CantonTimestamp.MaxValue
val crypto = new SymbolicPureCrypto val crypto = new SymbolicPureCrypto
val token = AuthenticationTokenWithExpiry(AuthenticationToken.generate(crypto), neverExpire) val token = AuthenticationTokenWithExpiry(AuthenticationToken.generate(crypto), neverExpire)

View File

@ -26,13 +26,7 @@ import com.digitalasset.canton.topology.DefaultTestIdentities.{
participant2, participant2,
sequencerId, sequencerId,
} }
import com.digitalasset.canton.topology.{ import com.digitalasset.canton.topology.{Member, SequencerId, UniqueIdentifier}
DomainMember,
Member,
SequencerId,
UnauthenticatedMemberId,
UniqueIdentifier,
}
import com.digitalasset.canton.tracing.TraceContext import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.{BaseTest, SequencerCounter} import com.digitalasset.canton.{BaseTest, SequencerCounter}
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
@ -70,9 +64,6 @@ class BaseSequencerTest extends AsyncWordSpec with BaseTest {
private implicit val materializer: Materializer = mock[Materializer] // not used private implicit val materializer: Materializer = mock[Materializer] // not used
private val unauthenticatedMemberId =
UniqueIdentifier.fromProtoPrimitive_("unm1::default").map(new UnauthenticatedMemberId(_)).value
class StubSequencer(existingMembers: Set[Member]) class StubSequencer(existingMembers: Set[Member])
extends BaseSequencer( extends BaseSequencer(
loggerFactory, loggerFactory,
@ -156,7 +147,7 @@ class BaseSequencerTest extends AsyncWordSpec with BaseTest {
traceContext: TraceContext traceContext: TraceContext
): EitherT[Future, String, SequencerSnapshot] = ): EitherT[Future, String, SequencerSnapshot] =
??? ???
override protected val localSequencerMember: DomainMember = sequencerId override protected val localSequencerMember: Member = sequencerId
override protected def disableMemberInternal(member: Member)(implicit override protected def disableMemberInternal(member: Member)(implicit
traceContext: TraceContext traceContext: TraceContext
): Future[Unit] = Future.unit ): Future[Unit] = Future.unit
@ -201,16 +192,7 @@ class BaseSequencerTest extends AsyncWordSpec with BaseTest {
name should { name should {
"sends from an unauthenticated member should auto register this member" in { "sends should not auto register" in {
val sequencer = new StubSequencer(existingMembers = Set(participant1))
val request =
submission(from = unauthenticatedMemberId, to = Set(participant1, participant2))
for {
_ <- send(sequencer)(request).value.failOnShutdown
} yield sequencer.newlyRegisteredMembers should contain only unauthenticatedMemberId
}
"sends from anyone else should not auto register" in {
val sequencer = new StubSequencer(existingMembers = Set(participant1)) val sequencer = new StubSequencer(existingMembers = Set(participant1))
val request = submission(from = participant1, to = Set(participant1, participant2)) val request = submission(from = participant1, to = Set(participant1, participant2))
@ -221,17 +203,6 @@ class BaseSequencerTest extends AsyncWordSpec with BaseTest {
} }
} }
"read" should {
"read from an unauthenticated member should auto register this member" in {
val sequencer = new StubSequencer(existingMembers = Set(participant1))
for {
_ <- sequencer
.read(unauthenticatedMemberId, SequencerCounter(0))
.value
} yield sequencer.newlyRegisteredMembers should contain only unauthenticatedMemberId
}
}
"health" should { "health" should {
"onHealthChange should register listener and immediately call it with current status" in { "onHealthChange should register listener and immediately call it with current status" in {
val sequencer = new StubSequencer(Set()) val sequencer = new StubSequencer(Set())

View File

@ -689,43 +689,6 @@ abstract class SequencerApiTest
} }
} }
} }
"require all eligible senders be authenticated" onlyRunWhen testAggregation in { env =>
import env.*
val unauthenticatedMember =
UnauthenticatedMemberId(UniqueIdentifier.tryCreate("unauthenticated", "member"))
// TODO(i10412): See above
val aggregationRule = AggregationRule(
NonEmpty(Seq, p19, unauthenticatedMember),
PositiveInt.tryCreate(1),
testedProtocolVersion,
)
val messageId = MessageId.tryCreate("unreachable-threshold")
val request = SubmissionRequest.tryCreate(
p19,
messageId,
Batch.empty(testedProtocolVersion),
maxSequencingTime = CantonTimestamp.Epoch.add(Duration.ofSeconds(60)),
topologyTimestamp = None,
aggregationRule = Some(aggregationRule),
Option.empty[SequencingSubmissionCost],
testedProtocolVersion,
)
for {
_ <- sequencer.sendAsync(request).valueOrFailShutdown("Sent async")
reads <- readForMembers(Seq(p19), sequencer)
} yield {
checkRejection(reads, p19, messageId) {
case SequencerErrors.SubmissionRequestMalformed(reason) =>
reason should include(
"Eligible senders in aggregation rule must be authenticated, but found unauthenticated members"
)
}
}
}
} }
} }
} }

View File

@ -30,7 +30,7 @@ import com.digitalasset.canton.domain.block.{
SequencerDriverHealthStatus, SequencerDriverHealthStatus,
} }
import com.digitalasset.canton.domain.metrics.SequencerMetrics import com.digitalasset.canton.domain.metrics.SequencerMetrics
import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.SignedOrderingRequest import com.digitalasset.canton.domain.sequencing.sequencer.Sequencer.SenderSigned
import com.digitalasset.canton.domain.sequencing.sequencer.SequencerIntegration import com.digitalasset.canton.domain.sequencing.sequencer.SequencerIntegration
import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode import com.digitalasset.canton.domain.sequencing.sequencer.block.BlockSequencerFactory.OrderingTimeFixMode
import com.digitalasset.canton.domain.sequencing.traffic.RateLimitManagerTesting import com.digitalasset.canton.domain.sequencing.traffic.RateLimitManagerTesting
@ -43,6 +43,7 @@ import com.digitalasset.canton.sequencing.protocol.{
AcknowledgeRequest, AcknowledgeRequest,
SendAsyncError, SendAsyncError,
SignedContent, SignedContent,
SubmissionRequest,
} }
import com.digitalasset.canton.time.{Clock, SimClock} import com.digitalasset.canton.time.{Clock, SimClock}
import com.digitalasset.canton.topology.Member import com.digitalasset.canton.topology.Member
@ -213,7 +214,7 @@ class BlockSequencerTest
override def close(): Unit = () override def close(): Unit = ()
// No need to implement these methods for the test // No need to implement these methods for the test
override def send(signedSubmission: SignedOrderingRequest)(implicit override def send(signedSubmissionRequest: SenderSigned[SubmissionRequest])(implicit
traceContext: TraceContext traceContext: TraceContext
): EitherT[Future, SendAsyncError, Unit] = ??? ): EitherT[Future, SendAsyncError, Unit] = ???
override def health(implicit traceContext: TraceContext): Future[SequencerDriverHealthStatus] = override def health(implicit traceContext: TraceContext): Future[SequencerDriverHealthStatus] =

View File

@ -10,24 +10,13 @@ import com.daml.nonempty.{NonEmpty, NonEmptyUtil}
import com.digitalasset.canton.config.RequireTypes.NonNegativeInt import com.digitalasset.canton.config.RequireTypes.NonNegativeInt
import com.digitalasset.canton.data.{CantonTimestamp, Counter} import com.digitalasset.canton.data.{CantonTimestamp, Counter}
import com.digitalasset.canton.domain.sequencing.sequencer.DomainSequencingTestUtils.mockDeliverStoreEvent import com.digitalasset.canton.domain.sequencing.sequencer.DomainSequencingTestUtils.mockDeliverStoreEvent
import com.digitalasset.canton.domain.sequencing.sequencer.*
import com.digitalasset.canton.domain.sequencing.sequencer.store.SaveLowerBoundError.BoundLowerThanExisting import com.digitalasset.canton.domain.sequencing.sequencer.store.SaveLowerBoundError.BoundLowerThanExisting
import com.digitalasset.canton.domain.sequencing.sequencer.{
CommitMode,
DomainSequencingTestUtils,
SequencerMemberStatus,
SequencerPruningStatus,
SequencerSnapshot,
}
import com.digitalasset.canton.lifecycle.{FlagCloseable, HasCloseContext} import com.digitalasset.canton.lifecycle.{FlagCloseable, HasCloseContext}
import com.digitalasset.canton.sequencing.protocol.{MessageId, SequencerErrors} import com.digitalasset.canton.sequencing.protocol.{MessageId, SequencerErrors}
import com.digitalasset.canton.store.db.DbTest import com.digitalasset.canton.store.db.DbTest
import com.digitalasset.canton.time.NonNegativeFiniteDuration import com.digitalasset.canton.time.NonNegativeFiniteDuration
import com.digitalasset.canton.topology.{ import com.digitalasset.canton.topology.{Member, ParticipantId}
Member,
ParticipantId,
UnauthenticatedMemberId,
UniqueIdentifier,
}
import com.digitalasset.canton.util.FutureInstances.* import com.digitalasset.canton.util.FutureInstances.*
import com.digitalasset.canton.{BaseTest, ProtocolVersionChecksAsyncWordSpec, SequencerCounter} import com.digitalasset.canton.{BaseTest, ProtocolVersionChecksAsyncWordSpec, SequencerCounter}
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
@ -943,25 +932,6 @@ trait SequencerStoreTest
} yield succeed } yield succeed
} }
"unregister unauthenticated members" in {
val env = Env()
import env.*
val unauthenticatedAlice: UnauthenticatedMemberId =
UnauthenticatedMemberId(UniqueIdentifier.tryCreate("alice_unauthenticated", "fingerprint"))
for {
id <- store.registerMember(unauthenticatedAlice, ts1)
aliceLookup1 <- store.lookupMember(unauthenticatedAlice)
_ = aliceLookup1 shouldBe Some(RegisteredMember(id, ts1))
_ <- store.unregisterUnauthenticatedMember(unauthenticatedAlice)
aliceLookup2 <- store.lookupMember(unauthenticatedAlice)
_ = aliceLookup2 shouldBe empty
// should also be idempotent
_ <- store.unregisterUnauthenticatedMember(unauthenticatedAlice)
} yield succeed
}
"validating commit mode" should { "validating commit mode" should {
"be successful during tests" in { "be successful during tests" in {
val store = mk() val store = mk()

View File

@ -78,8 +78,6 @@ class GrpcSequencerServiceTest
private lazy val participant = DefaultTestIdentities.participant1 private lazy val participant = DefaultTestIdentities.participant1
private lazy val crypto = new SymbolicPureCrypto private lazy val crypto = new SymbolicPureCrypto
private lazy val unauthenticatedMember =
UnauthenticatedMemberId.tryCreate(participant.namespace)(crypto)
class Environment(member: Member) extends Matchers { class Environment(member: Member) extends Matchers {
val sequencer: Sequencer = mock[Sequencer] val sequencer: Sequencer = mock[Sequencer]
@ -251,67 +249,45 @@ class GrpcSequencerServiceTest
signedContent(request.toByteString) signedContent(request.toByteString)
def sendProto( def sendProto(
versionedRequest: ByteString, versionedSignedRequest: ByteString
versionedSignedRequest: ByteString,
authenticated: Boolean,
)(implicit )(implicit
env: Environment env: Environment
): Future[ParsingResult[SendAsyncUnauthenticatedVersionedResponse]] = { ): Future[ParsingResult[SendAsyncVersionedResponse]] = {
import env.* import env.*
if (!authenticated) { val requestP = v30.SendAsyncVersionedRequest(versionedSignedRequest)
val requestP = v30.SendAsyncUnauthenticatedVersionedRequest(versionedRequest) val response = service.sendAsyncVersioned(requestP)
val response = service.sendAsyncUnauthenticatedVersioned(requestP)
response.map(
SendAsyncUnauthenticatedVersionedResponse.fromSendAsyncUnauthenticatedVersionedResponseProto
)
} else {
val requestP = v30.SendAsyncVersionedRequest(versionedSignedRequest)
val response = service.sendAsyncVersioned(requestP)
response.map(SendAsyncUnauthenticatedVersionedResponse.fromSendAsyncVersionedResponseProto) response.map(SendAsyncVersionedResponse.fromProtoV30)
}
} }
def send(request: SubmissionRequest, authenticated: Boolean)(implicit def send(request: SubmissionRequest)(implicit
env: Environment env: Environment
): Future[ParsingResult[SendAsyncUnauthenticatedVersionedResponse]] = { ): Future[ParsingResult[SendAsyncVersionedResponse]] = {
val signedRequest = signedSubmissionReq(request) sendProto(signedSubmissionReq(request).toByteString)
sendProto(
request.toByteString,
signedRequest.toByteString,
authenticated,
)
} }
def sendAndCheckSucceed(request: SubmissionRequest)(implicit def sendAndCheckSucceed(request: SubmissionRequest)(implicit
env: Environment env: Environment
): Future[Assertion] = ): Future[Assertion] =
send(request, authenticated = true).map { responseP => send(request).map { responseP =>
responseP.value.error shouldBe None responseP.value.error shouldBe None
} }
def sendAndCheckError( def sendAndCheckError(
request: SubmissionRequest, request: SubmissionRequest
authenticated: Boolean = true,
)(assertion: PartialFunction[SendAsyncError, Assertion])(implicit )(assertion: PartialFunction[SendAsyncError, Assertion])(implicit
env: Environment env: Environment
): Future[Assertion] = ): Future[Assertion] =
send(request, authenticated).map { responseP => send(request).map { responseP =>
assertion(responseP.value.error.value) assertion(responseP.value.error.value)
} }
def sendProtoAndCheckError( def sendProtoAndCheckError(
versionedRequest: ByteString,
versionedSignedRequest: ByteString, versionedSignedRequest: ByteString,
assertion: PartialFunction[SendAsyncError, Assertion], assertion: PartialFunction[SendAsyncError, Assertion],
authenticated: Boolean = true,
)(implicit env: Environment): Future[Assertion] = )(implicit env: Environment): Future[Assertion] =
sendProto( sendProto(versionedSignedRequest).map { responseP =>
versionedRequest,
versionedSignedRequest,
authenticated,
).map { responseP =>
assertion(responseP.value.error.value) assertion(responseP.value.error.value)
} }
@ -332,7 +308,6 @@ class GrpcSequencerServiceTest
loggerFactory.assertLogs( loggerFactory.assertLogs(
sendProtoAndCheckError( sendProtoAndCheckError(
VersionedMessage(requestV1.toByteString, 0).toByteString,
signedRequestV0.toByteString, signedRequestV0.toByteString,
{ case SendAsyncError.RequestInvalid(message) => { case SendAsyncError.RequestInvalid(message) =>
message should startWith("ValueConversionError(sender,Invalid member ``") message should startWith("ValueConversionError(sender,Invalid member ``")
@ -367,7 +342,6 @@ class GrpcSequencerServiceTest
) )
loggerFactory.assertLogs( loggerFactory.assertLogs(
sendProtoAndCheckError( sendProtoAndCheckError(
VersionedMessage(requestV1.toByteString, 0).toByteString,
signedRequestV0.toByteString, signedRequestV0.toByteString,
{ case SendAsyncError.RequestInvalid(message) => { case SendAsyncError.RequestInvalid(message) =>
message should startWith( message should startWith(
@ -418,56 +392,14 @@ class GrpcSequencerServiceTest
) )
} }
"reject unauthenticated member that uses authenticated send" in { _ =>
val request = defaultRequest
.focus(_.sender)
.replace(unauthenticatedMember)
loggerFactory.assertLogs(
sendAndCheckError(request, authenticated = true) {
case SendAsyncError.RequestRefused(message) =>
message should include("needs to use unauthenticated send operation")
}(new Environment(unauthenticatedMember)),
_.warningMessage should include("needs to use unauthenticated send operation"),
)
}
"reject non domain manager authenticated member sending message to unauthenticated member" in {
implicit env =>
val request = defaultRequest
.focus(_.batch)
.replace(
Batch(
List(
ClosedEnvelope.create(
content,
Recipients.cc(unauthenticatedMember),
Seq.empty,
testedProtocolVersion,
)
),
testedProtocolVersion,
)
)
loggerFactory.assertLogs(
sendAndCheckError(request, authenticated = true) {
case SendAsyncError.RequestRefused(message) =>
message should include("Member is trying to send message to unauthenticated")
},
_.warningMessage should include(
"Member is trying to send message to unauthenticated"
),
)
}
"reject on confirmation rate excess" in { implicit env => "reject on confirmation rate excess" in { implicit env =>
def expectSuccess(): Future[Assertion] = { def expectSuccess(): Future[Assertion] = {
sendAndCheckSucceed(defaultConfirmationRequest) sendAndCheckSucceed(defaultConfirmationRequest)
} }
def expectOneSuccessOneOverloaded(): Future[Assertion] = { def expectOneSuccessOneOverloaded(): Future[Assertion] = {
val result1F = send(defaultConfirmationRequest, authenticated = true) val result1F = send(defaultConfirmationRequest)
val result2F = send(defaultConfirmationRequest, authenticated = true) val result2F = send(defaultConfirmationRequest)
for { for {
result1 <- result1F result1 <- result1F
result2 <- result2F result2 <- result2F
@ -592,63 +524,6 @@ class GrpcSequencerServiceTest
), ),
) )
"reject requests to unauthenticated members with a signing key timestamps" in { implicit env =>
val request = defaultRequest
.focus(_.topologyTimestamp)
.replace(Some(CantonTimestamp.ofEpochSecond(1)))
.focus(_.batch)
.replace(
Batch(
List(
ClosedEnvelope.create(
content,
Recipients.cc(unauthenticatedMember),
Seq.empty,
testedProtocolVersion,
)
),
testedProtocolVersion,
)
)
loggerFactory.assertLogs(
sendAndCheckError(request) { case SendAsyncError.RequestRefused(message) =>
message should include(
"Requests sent from or to unauthenticated members must not specify the topology timestamp"
)
},
_.warningMessage should include(
"Requests sent from or to unauthenticated members must not specify the topology timestamp"
),
)
}
"reject unauthenticated eligible members in aggregation rule" in { implicit env =>
val request = defaultRequest
.focus(_.topologyTimestamp)
.replace(Some(CantonTimestamp.ofEpochSecond(1)))
.focus(_.aggregationRule)
.replace(
Some(
AggregationRule(
eligibleMembers = NonEmpty(Seq, participant, unauthenticatedMember),
threshold = PositiveInt.tryCreate(1),
testedProtocolVersion,
)
)
)
loggerFactory.assertLogs(
sendAndCheckError(request) { case SendAsyncError.RequestInvalid(message) =>
message should include(
"Eligible senders in aggregation rule must be authenticated, but found unauthenticated members"
)
},
_.warningMessage should include(
"Eligible senders in aggregation rule must be authenticated, but found unauthenticated members"
),
)
}
"reject unachievable threshold in aggregation rule" in { implicit env => "reject unachievable threshold in aggregation rule" in { implicit env =>
val request = defaultRequest val request = defaultRequest
.focus(_.topologyTimestamp) .focus(_.topologyTimestamp)
@ -694,71 +569,6 @@ class GrpcSequencerServiceTest
), ),
) )
} }
"reject unauthenticated member sending message to anything other than broadcast" in { _ =>
val request = defaultRequest
.focus(_.sender)
.replace(unauthenticatedMember)
val errorMsg =
"Unauthenticated member is trying to send message to members other than the topology broadcast address All"
loggerFactory.assertLogs(
sendAndCheckError(request, authenticated = false) {
case SendAsyncError.RequestRefused(message) =>
message should include(errorMsg)
}(new Environment(unauthenticatedMember)),
_.warningMessage should include(errorMsg),
)
}
"reject authenticated member that uses unauthenticated send" in { implicit env =>
val request = defaultRequest
.focus(_.sender)
.replace(DefaultTestIdentities.participant1)
loggerFactory.assertLogs(
sendAndCheckError(request, authenticated = false) {
case SendAsyncError.RequestRefused(message) =>
message should include("needs to use authenticated send operation")
},
_.warningMessage should include("needs to use authenticated send operation"),
)
}
"reject requests from unauthenticated senders with a signing key timestamp" in { _ =>
val request = defaultRequest
.focus(_.sender)
.replace(unauthenticatedMember)
.focus(_.topologyTimestamp)
.replace(Some(CantonTimestamp.Epoch))
.focus(_.batch)
.replace(
Batch(
List(
ClosedEnvelope.create(
content,
Recipients.cc(DefaultTestIdentities.sequencerId),
Seq.empty,
testedProtocolVersion,
)
),
testedProtocolVersion,
)
)
loggerFactory.assertLogs(
sendAndCheckError(request, authenticated = false) {
case SendAsyncError.RequestRefused(message) =>
message should include(
"Requests sent from or to unauthenticated members must not specify the topology timestamp"
)
}(new Environment(unauthenticatedMember)),
_.warningMessage should include(
"Requests sent from or to unauthenticated members must not specify the topology timestamp"
),
)
}
} }
"versionedSubscribe" should { "versionedSubscribe" should {
@ -828,42 +638,6 @@ class GrpcSequencerServiceTest
case Seq(StreamError(err: StatusException)) if err.getStatus.getCode == PERMISSION_DENIED => case Seq(StreamError(err: StatusException)) if err.getStatus.getCode == PERMISSION_DENIED =>
} }
} }
"return error if authenticated member sending request unauthenticated endpoint" in { env =>
val observer = new MockServerStreamObserver[v30.VersionedSubscriptionResponse]()
val requestP =
SubscriptionRequest(
participant,
SequencerCounter.Genesis,
testedProtocolVersion,
).toProtoV30
loggerFactory.suppressWarningsAndErrors {
env.service.subscribeUnauthenticatedVersioned(requestP, observer)
}
observer.items.toSeq should matchPattern {
case Seq(StreamError(err: StatusException)) if err.getStatus.getCode == PERMISSION_DENIED =>
}
}
"return error if unauthenticated member sending request authenticated endpoint" in { env =>
val observer = new MockServerStreamObserver[v30.VersionedSubscriptionResponse]()
val requestP =
SubscriptionRequest(
unauthenticatedMember,
SequencerCounter.Genesis,
testedProtocolVersion,
).toProtoV30
loggerFactory.suppressWarningsAndErrors {
env.service.subscribeVersioned(requestP, observer)
}
observer.items.toSeq should matchPattern {
case Seq(StreamError(err: StatusException)) if err.getStatus.getCode == PERMISSION_DENIED =>
}
}
} }
def performAcknowledgeRequest(env: Environment)(request: AcknowledgeRequest) = def performAcknowledgeRequest(env: Environment)(request: AcknowledgeRequest) =

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --enable-interfaces=yes - --enable-interfaces=yes
name: carbonv1-tests name: carbonv1-tests

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --enable-interfaces=yes - --enable-interfaces=yes
name: carbonv2-tests name: carbonv2-tests

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
name: experimental-tests name: experimental-tests
source: . source: .
version: 3.1.0 version: 3.1.0

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --enable-interfaces=yes - --enable-interfaces=yes
name: model-tests name: model-tests

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
name: package-management-tests name: package-management-tests
source: . source: .
version: 3.1.0 version: 3.1.0

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --enable-interfaces=yes - --enable-interfaces=yes
name: semantic-tests name: semantic-tests

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
name: upgrade-tests name: upgrade-tests
source: . source: .
version: 1.0.0 version: 1.0.0

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
name: upgrade-tests name: upgrade-tests
source: . source: .
version: 2.0.0 version: 2.0.0

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
name: upgrade-tests name: upgrade-tests
source: . source: .
version: 3.0.0 version: 3.0.0

View File

@ -14,7 +14,7 @@ import io.netty.handler.ssl.SslContext
final case class LedgerClientChannelConfiguration( final case class LedgerClientChannelConfiguration(
sslContext: Option[SslContext], sslContext: Option[SslContext],
maxInboundMetadataSize: Int = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE, maxInboundMetadataSize: Int = GrpcUtil.DEFAULT_MAX_HEADER_LIST_SIZE,
maxInboundMessageSize: Int = GrpcUtil.DEFAULT_MAX_MESSAGE_SIZE, maxInboundMessageSize: Int = LedgerClientChannelConfiguration.DefaultMaxInboundMessageSize,
) { ) {
def builderFor(host: String, port: Int): NettyChannelBuilder = { def builderFor(host: String, port: Int): NettyChannelBuilder = {
@ -29,6 +29,7 @@ final case class LedgerClientChannelConfiguration(
object LedgerClientChannelConfiguration { object LedgerClientChannelConfiguration {
val DefaultMaxInboundMessageSize: Int = 10 * 1024 * 1024
val InsecureDefaults: LedgerClientChannelConfiguration = val InsecureDefaults: LedgerClientChannelConfiguration =
LedgerClientChannelConfiguration(sslContext = None) LedgerClientChannelConfiguration(sslContext = None)

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --target=2.1 - --target=2.1
name: JsonEncodingTest name: JsonEncodingTest

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --target=2.dev - --target=2.dev
name: JsonEncodingTestDev name: JsonEncodingTestDev

View File

@ -1,4 +1,4 @@
sdk-version: 3.1.0-snapshot.20240530.13105.0.v076ddc13 sdk-version: 3.1.0-snapshot.20240531.13108.0.v60488be0
build-options: build-options:
- --target=2.1 - --target=2.1
name: AdminWorkflows name: AdminWorkflows

View File

@ -966,9 +966,7 @@ final class RepairService(
choiceAuthorizers = None, // default (signatories + actingParties) choiceAuthorizers = None, // default (signatories + actingParties)
children = ImmArray.empty[LfNodeId], children = ImmArray.empty[LfNodeId],
exerciseResult = Some(LfValue.ValueNone), exerciseResult = Some(LfValue.ValueNone),
// Not setting the contract key as the indexer deletes contract keys along with contracts. keyOpt = c.metadata.maybeKeyWithMaintainers,
// If the contract keys were needed, we'd have to reinterpret the contract to look up the key.
keyOpt = None,
byKey = false, byKey = false,
version = c.rawContractInstance.contractInstance.version, version = c.rawContractInstance.contractInstance.version,
) )

View File

@ -166,7 +166,7 @@ trait DomainRegistryHelpers extends FlagCloseable with NamedLogging { this: HasF
def ifParticipant[C](configO: Option[C]): Member => Option[C] = { def ifParticipant[C](configO: Option[C]): Member => Option[C] = {
case _: ParticipantId => configO case _: ParticipantId => configO
case _ => None // unauthenticated members don't need it case _ => None
} }
SequencerClientFactory( SequencerClientFactory(
domainId, domainId,

View File

@ -1 +1 @@
20240531.13403.v0762c427 20240604.13418.v5318c201