mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-20 01:07:18 +03:00
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:
parent
18e4e155fb
commit
79929ac266
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
|
@ -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 */
|
||||||
|
@ -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] =
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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,8 +426,6 @@ 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
|
|
||||||
if (requiresAuthentication) {
|
|
||||||
val (sequencerId, transport, patienceO) =
|
val (sequencerId, transport, patienceO) =
|
||||||
sequencersTransportState.nextAmplifiedTransport(Seq.empty)
|
sequencersTransportState.nextAmplifiedTransport(Seq.empty)
|
||||||
// Do not add an aggregation rule for amplifiable requests if amplification has not been configured
|
// Do not add an aggregation rule for amplifiable requests if amplification has not been configured
|
||||||
@ -582,9 +460,7 @@ abstract class SequencerClientImpl(
|
|||||||
peekAtSendResult,
|
peekAtSendResult,
|
||||||
)
|
)
|
||||||
} yield ()
|
} yield ()
|
||||||
} else
|
|
||||||
sequencersTransportState.transport
|
|
||||||
.sendAsyncUnauthenticatedVersioned(request, timeout)
|
|
||||||
}
|
}
|
||||||
.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,7 +914,6 @@ 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(
|
||||||
@ -1078,7 +929,6 @@ class RichSequencerClientImpl(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// we may have actually not created a subscription if we have been closed
|
// we may have actually not created a subscription if we have been closed
|
||||||
val loggedAbortF = subscriptionF.unwrap.map {
|
val loggedAbortF = subscriptionF.unwrap.map {
|
||||||
@ -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,7 +1673,6 @@ 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(
|
||||||
@ -1841,7 +1686,6 @@ class SequencerClientImplPekko[E: Pretty](
|
|||||||
)
|
)
|
||||||
.some
|
.some
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
replayCompleted.futureUS
|
replayCompleted.futureUS
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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 =>
|
|
||||||
new SequencerClientNoAuthentication(domainId, unauthenticatedMember)
|
|
||||||
case authenticatedMember: AuthenticatedMember =>
|
|
||||||
SequencerClientTokenAuthentication(
|
|
||||||
domainId,
|
domainId,
|
||||||
authenticatedMember,
|
member,
|
||||||
obtainTokenPerEndpoint,
|
obtainTokenPerEndpoint,
|
||||||
tokenProvider.isClosing,
|
tokenProvider.isClosing,
|
||||||
tokenManagerConfig,
|
tokenManagerConfig,
|
||||||
clock,
|
clock,
|
||||||
loggerFactory,
|
loggerFactory,
|
||||||
)
|
)
|
||||||
}
|
|
||||||
clientAuthentication(client)
|
clientAuthentication(client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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)(_)(_))
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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]
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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),
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
|
@ -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)))*
|
||||||
)
|
)
|
||||||
|
@ -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"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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,
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
)
|
)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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 ()
|
||||||
}
|
}
|
||||||
|
@ -364,8 +364,6 @@ 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)
|
|
||||||
else
|
|
||||||
sequencingSnapshot.ipsSnapshot
|
sequencingSnapshot.ipsSnapshot
|
||||||
.isMemberKnown(member)
|
.isMemberKnown(member)
|
||||||
.map(Option.when(_)(member))
|
.map(Option.when(_)(member))
|
||||||
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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(
|
||||||
|
@ -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))
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
@ -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,16 +228,6 @@ 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 {
|
|
||||||
isRegistered <- EitherT.right[RegisterError](isRegistered(member))
|
|
||||||
_ <- EitherTUtil.ifThenET[Future, RegisterError](
|
|
||||||
!isRegistered
|
|
||||||
) {
|
|
||||||
registerMemberInternal(member, clock.now)
|
|
||||||
}
|
|
||||||
} yield ()
|
|
||||||
} else {
|
|
||||||
for {
|
for {
|
||||||
firstKnownAtO <- EitherT.right[RegisterError](
|
firstKnownAtO <- EitherT.right[RegisterError](
|
||||||
cryptoApi.headSnapshot.ipsSnapshot.memberFirstKnownAt(member)
|
cryptoApi.headSnapshot.ipsSnapshot.memberFirstKnownAt(member)
|
||||||
@ -273,7 +249,6 @@ class DatabaseSequencer(
|
|||||||
}
|
}
|
||||||
} yield ()
|
} yield ()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** Package private to use access method in tests, see `TestDatabaseSequencerWrapper`.
|
/** Package private to use access method in tests, see `TestDatabaseSequencerWrapper`.
|
||||||
*/
|
*/
|
||||||
@ -328,12 +303,6 @@ class DatabaseSequencer(
|
|||||||
): EitherT[Future, CreateSubscriptionError, Sequencer.EventSource] = {
|
): EitherT[Future, CreateSubscriptionError, Sequencer.EventSource] = {
|
||||||
if (!unifiedSequencer) {
|
if (!unifiedSequencer) {
|
||||||
reader.read(member, offset)
|
reader.read(member, offset)
|
||||||
} else {
|
|
||||||
if (!member.isAuthenticated) {
|
|
||||||
// allowing unauthenticated members to read events is the same as automatically registering an unauthenticated member
|
|
||||||
// and then proceeding with the subscription.
|
|
||||||
// optimization: if the member is unauthenticated, we don't need to fetch all members from the snapshot
|
|
||||||
reader.read(member, offset)
|
|
||||||
} else {
|
} else {
|
||||||
for {
|
for {
|
||||||
isKnown <- EitherT.right[CreateSubscriptionError](
|
isKnown <- EitherT.right[CreateSubscriptionError](
|
||||||
@ -351,7 +320,6 @@ class DatabaseSequencer(
|
|||||||
} yield eventSource
|
} yield eventSource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override protected def acknowledgeSignedInternal(
|
override protected def acknowledgeSignedInternal(
|
||||||
signedAcknowledgeRequest: SignedContent[AcknowledgeRequest]
|
signedAcknowledgeRequest: SignedContent[AcknowledgeRequest]
|
||||||
@ -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 {
|
|
||||||
// Unauthenticated members being disabled get automatically unregistered
|
|
||||||
case unauthenticated: UnauthenticatedMemberId =>
|
|
||||||
store.unregisterUnauthenticatedMember(unauthenticated)
|
|
||||||
case _: AuthenticatedMember =>
|
|
||||||
store.disableMember(memberId)
|
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.
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 =
|
||||||
|
@ -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,
|
||||||
|
@ -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 ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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]
|
||||||
|
|
||||||
|
@ -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 ()
|
||||||
@ -312,12 +285,6 @@ class BlockSequencer(
|
|||||||
logger.debug(s"Answering readInternal(member = $member, offset = $offset)")
|
logger.debug(s"Answering readInternal(member = $member, offset = $offset)")
|
||||||
if (unifiedSequencer) {
|
if (unifiedSequencer) {
|
||||||
super.readInternal(member, offset)
|
super.readInternal(member, offset)
|
||||||
} else {
|
|
||||||
if (!member.isAuthenticated) {
|
|
||||||
// allowing unauthenticated members to read events is the same as automatically registering an unauthenticated member
|
|
||||||
// and then proceeding with the subscription.
|
|
||||||
// optimization: if the member is unauthenticated, we don't need to fetch all members from the snapshot
|
|
||||||
EitherT.fromEither[Future](stateManager.readEventsForMember(member, offset))
|
|
||||||
} else {
|
} else {
|
||||||
EitherT
|
EitherT
|
||||||
.right(cryptoApi.currentSnapshotApproximation.ipsSnapshot.isMemberKnown(member))
|
.right(cryptoApi.currentSnapshotApproximation.ipsSnapshot.isMemberKnown(member))
|
||||||
@ -330,7 +297,6 @@ class BlockSequencer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
override def isRegistered(
|
override def isRegistered(
|
||||||
member: Member
|
member: 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
|
||||||
|
@ -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)
|
||||||
|
@ -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"
|
||||||
)
|
)
|
||||||
|
@ -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]] =
|
||||||
|
@ -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]] =
|
||||||
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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())
|
||||||
|
@ -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"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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] =
|
||||||
|
@ -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()
|
||||||
|
@ -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.SendAsyncUnauthenticatedVersionedRequest(versionedRequest)
|
|
||||||
val response = service.sendAsyncUnauthenticatedVersioned(requestP)
|
|
||||||
response.map(
|
|
||||||
SendAsyncUnauthenticatedVersionedResponse.fromSendAsyncUnauthenticatedVersionedResponseProto
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
val requestP = v30.SendAsyncVersionedRequest(versionedSignedRequest)
|
val requestP = v30.SendAsyncVersionedRequest(versionedSignedRequest)
|
||||||
val response = service.sendAsyncVersioned(requestP)
|
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) =
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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,
|
||||||
|
@ -1 +1 @@
|
|||||||
20240531.13403.v0762c427
|
20240604.13418.v5318c201
|
||||||
|
Loading…
Reference in New Issue
Block a user