update canton to 3.0.0-snapshot.20240202.12462.0.v0d2c0704 (#18384)

* update canton to 3.0.0-snapshot.20240202.12462.0.v0d2c0704

tell-slack: canton

* Fix PackageMetadata

---------

Co-authored-by: Azure Pipelines Daml Build <support@digitalasset.com>
Co-authored-by: Tudor Voicu <tudor.voicu@digitalasset.com>
This commit is contained in:
azure-pipelines[bot] 2024-02-05 13:07:42 +00:00 committed by GitHub
parent 470ee18a8a
commit e9c60f47a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
138 changed files with 2264 additions and 2312 deletions

View File

@ -1316,7 +1316,6 @@ class TopologyAdministrationGroupX(
domainId: DomainId,
participantId: ParticipantId,
permission: ParticipantPermissionX,
trustLevel: TrustLevelX = TrustLevelX.Ordinary,
loginAfter: Option[CantonTimestamp] = None,
limits: Option[ParticipantDomainLimits] = None,
synchronize: Option[NonNegativeDuration] = Some(
@ -1331,7 +1330,6 @@ class TopologyAdministrationGroupX(
domainId = domainId,
participantId = participantId,
permission = permission,
trustLevel = trustLevel,
limits = limits,
loginAfter = loginAfter,
),

View File

@ -65,7 +65,6 @@ message Informee {
string party = 1;
int32 weight = 2; // optional: only set if party is confirming
com.digitalasset.canton.protocol.v30.TrustLevel required_trust_level = 3;
}
// EncryptedViewMessage

View File

@ -275,12 +275,6 @@ message EnumsX {
Remove = 1;
}
enum TrustLevelX {
MissingTrustLevel = 0;
Ordinary = 1;
Vip = 2;
}
// enum indicating the participant permission level
enum ParticipantPermissionX {
MissingParticipantPermission = 0;
@ -391,16 +385,13 @@ message ParticipantDomainPermissionX {
// the permission level of the participant on this domain (usually submission)
EnumsX.ParticipantPermissionX permission = 3;
// the trust level of the participant on this domain
EnumsX.TrustLevelX trust_level = 4;
// optional individual limits for this participant
ParticipantDomainLimits limits = 5;
ParticipantDomainLimits limits = 4;
// optional earliest time when participant can log in (again)
// used to temporarily disable participants
// TODO(#14049) implement participant deny list
google.protobuf.Timestamp login_after = 6;
google.protobuf.Timestamp login_after = 5;
}
// the optional hosting limits for a party on a given domain

View File

@ -148,13 +148,13 @@ trait SyncCryptoApi {
hash: Hash,
signer: Member,
signature: Signature,
): EitherT[Future, SignatureCheckError, Unit]
)(implicit traceContext: TraceContext): EitherT[Future, SignatureCheckError, Unit]
def verifySignatures(
hash: Hash,
signer: Member,
signatures: NonEmpty[Seq[Signature]],
): EitherT[Future, SignatureCheckError, Unit]
)(implicit traceContext: TraceContext): EitherT[Future, SignatureCheckError, Unit]
/** Verifies a list of `signatures` to be produced by active members of a `mediatorGroup`,
* counting each member's signature only once.
@ -168,15 +168,17 @@ trait SyncCryptoApi {
signatures: NonEmpty[Seq[Signature]],
)(implicit traceContext: TraceContext): EitherT[Future, SignatureCheckError, Unit]
/** Encrypts a message for the given member
/** Encrypts a message for the given members
*
* Utility method to lookup a key on an IPS snapshot and then encrypt the given message with the
* most suitable key for the respective key owner.
*/
def encryptFor[M <: HasVersionedToByteString](
def encryptFor[M <: HasVersionedToByteString, MemberType <: Member](
message: M,
member: Member,
members: Seq[MemberType],
version: ProtocolVersion,
): EitherT[Future, SyncCryptoError, AsymmetricEncrypted[M]]
)(implicit
traceContext: TraceContext
): EitherT[Future, (MemberType, SyncCryptoError), Map[MemberType, AsymmetricEncrypted[M]]]
}
// architecture-handbook-entry-end: SyncCryptoApi

View File

@ -100,7 +100,7 @@ trait SigningPrivateStoreOps extends SigningPrivateOps {
signingKeyId: Fingerprint,
)(implicit tc: TraceContext): EitherT[Future, SigningError, Signature] =
store
.signingKey(signingKeyId)(TraceContext.todo)
.signingKey(signingKeyId)
.leftMap(storeError => SigningError.KeyStoreError(storeError.show))
.subflatMap(_.toRight(SigningError.UnknownSigningKey(signingKeyId)))
.subflatMap(signingKey => signingOps.sign(bytes, signingKey))

View File

@ -11,6 +11,7 @@ import cats.syntax.either.*
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.parallel.*
import cats.syntax.traverse.*
import com.daml.nonempty.NonEmpty
import com.digitalasset.canton.concurrent.{FutureSupervisor, HasFutureSupervision}
import com.digitalasset.canton.config.{CacheConfig, CachingConfigs, ProcessingTimeout}
@ -37,7 +38,7 @@ import com.digitalasset.canton.topology.client.{
TopologyClientApi,
TopologySnapshot,
}
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.tracing.{TraceContext, TracedScaffeine}
import com.digitalasset.canton.util.FutureInstances.*
import com.digitalasset.canton.util.LoggerUtil
import com.digitalasset.canton.version.{HasVersionedToByteString, ProtocolVersion}
@ -368,22 +369,21 @@ class DomainSyncCryptoClient(
domainId,
snapshot,
crypto,
ts => EitherT(mySigningKeyCache.get(ts)),
implicit tc => ts => EitherT(mySigningKeyCache.get(ts)),
cacheConfigs.keyCache,
loggerFactory,
)
}
private val mySigningKeyCache = cacheConfigs.mySigningKeyCache
.buildScaffeine()
.buildAsyncFuture[CantonTimestamp, Either[SyncCryptoError, Fingerprint]](
findSigningKey(_).value
)
private val mySigningKeyCache =
TracedScaffeine.buildTracedAsyncFuture[CantonTimestamp, Either[SyncCryptoError, Fingerprint]](
cache = cacheConfigs.mySigningKeyCache.buildScaffeine(),
loader = traceContext => timestamp => findSigningKey(timestamp)(traceContext).value,
)(logger)
private def findSigningKey(
referenceTime: CantonTimestamp
): EitherT[Future, SyncCryptoError, Fingerprint] = {
import TraceContext.Implicits.Empty.*
)(implicit traceContext: TraceContext): EitherT[Future, SyncCryptoError, Fingerprint] = {
for {
snapshot <- EitherT.right(ipsSnapshot(referenceTime))
signingKeys <- EitherT.right(snapshot.signingKeys(member))
@ -452,7 +452,11 @@ class DomainSnapshotSyncCryptoApi(
val domainId: DomainId,
override val ipsSnapshot: TopologySnapshot,
val crypto: Crypto,
fetchSigningKey: CantonTimestamp => EitherT[Future, SyncCryptoError, Fingerprint],
fetchSigningKey: TraceContext => CantonTimestamp => EitherT[
Future,
SyncCryptoError,
Fingerprint,
],
validKeysCacheConfig: CacheConfig,
override protected val loggerFactory: NamedLoggerFactory,
)(implicit ec: ExecutionContext)
@ -461,9 +465,32 @@ class DomainSnapshotSyncCryptoApi(
override val pureCrypto: CryptoPureApi = crypto.pureCrypto
private val validKeysCache =
validKeysCacheConfig
.buildScaffeine()
.buildAsyncFuture[Member, Map[Fingerprint, SigningPublicKey]](loadSigningKeysForMember)
TracedScaffeine
.buildTracedAsyncFuture[Member, Map[Fingerprint, SigningPublicKey]](
cache = validKeysCacheConfig.buildScaffeine(),
loader = traceContext =>
member =>
loadSigningKeysForMembers(Seq(member))(traceContext)
.map(membersWithKeys => membersWithKeys(member)),
allLoader =
Some(traceContext => members => loadSigningKeysForMembers(members.toSeq)(traceContext)),
)(logger)
private def loadSigningKeysForMembers(
members: Seq[Member]
)(implicit traceContext: TraceContext): Future[Map[Member, Map[Fingerprint, SigningPublicKey]]] =
ipsSnapshot
.signingKeys(members)
.map(membersToKeys =>
members
.map(member =>
member -> membersToKeys
.getOrElse(member, Seq.empty)
.map(key => (key.fingerprint, key))
.toMap
)
.toMap
)
/** Sign given hash with signing key for (member, domain, timestamp)
*/
@ -471,17 +498,12 @@ class DomainSnapshotSyncCryptoApi(
hash: Hash
)(implicit traceContext: TraceContext): EitherT[Future, SyncCryptoError, Signature] =
for {
fingerprint <- fetchSigningKey(ipsSnapshot.referenceTime)
fingerprint <- fetchSigningKey(traceContext)(ipsSnapshot.referenceTime)
signature <- crypto.privateCrypto
.sign(hash, fingerprint)
.leftMap[SyncCryptoError](SyncCryptoError.SyncCryptoSigningError)
} yield signature
private def loadSigningKeysForMember(
member: Member
): Future[Map[Fingerprint, SigningPublicKey]] =
ipsSnapshot.signingKeys(member).map(_.map(x => (x.fingerprint, x)).toMap)
private def verifySignature(
hash: Hash,
validKeys: Map[Fingerprint, SigningPublicKey],
@ -514,7 +536,7 @@ class DomainSnapshotSyncCryptoApi(
hash: Hash,
signer: Member,
signature: Signature,
): EitherT[Future, SignatureCheckError, Unit] = {
)(implicit traceContext: TraceContext): EitherT[Future, SignatureCheckError, Unit] = {
for {
validKeys <- EitherT.right(validKeysCache.get(signer))
res <- EitherT.fromEither[Future](
@ -527,7 +549,7 @@ class DomainSnapshotSyncCryptoApi(
hash: Hash,
signer: Member,
signatures: NonEmpty[Seq[Signature]],
): EitherT[Future, SignatureCheckError, Unit] = {
)(implicit traceContext: TraceContext): EitherT[Future, SignatureCheckError, Unit] = {
for {
validKeys <- EitherT.right(validKeysCache.get(signer))
res <- signatures.forgetNE.parTraverse_ { signature =>
@ -555,15 +577,16 @@ class DomainSnapshotSyncCryptoApi(
)
}
)
validKeysWithMember <- EitherT.right(
mediatorGroup.active
.parFlatTraverse { mediatorId =>
ipsSnapshot
.signingKeys(mediatorId)
.map(keys => keys.map(key => (key.id, (mediatorId, key))))
}
.map(_.toMap)
)
validKeysWithMember <-
EitherT.right(
ipsSnapshot
.signingKeys(mediatorGroup.active)
.map(memberToKeysMap =>
memberToKeysMap.flatMap { case (mediatorId, keys) =>
keys.map(key => (key.id, (mediatorId, key)))
}
)
)
validKeys = validKeysWithMember.view.mapValues(_._2).toMap
keyMember = validKeysWithMember.view.mapValues(_._1).toMap
validated <- EitherT.right(signatures.forgetNE.parTraverse { signature =>
@ -603,15 +626,6 @@ class DomainSnapshotSyncCryptoApi(
} yield ()
}
private def ownerIsInitialized(
validKeys: Seq[SigningPublicKey]
): EitherT[Future, SignatureCheckError, Boolean] =
member match {
case participant: ParticipantId => EitherT.right(ipsSnapshot.isParticipantActive(participant))
case _ => // we assume that a member other than a participant is initialised if at least one valid key is known
EitherT.rightT(validKeys.nonEmpty)
}
override def decrypt[M](encryptedMessage: AsymmetricEncrypted[M])(
deserialize: ByteString => Either[DeserializationError, M]
)(implicit traceContext: TraceContext): EitherT[Future, SyncCryptoError, M] = {
@ -620,29 +634,44 @@ class DomainSnapshotSyncCryptoApi(
.leftMap[SyncCryptoError](err => SyncCryptoError.SyncCryptoDecryptionError(err))
}
/** Encrypts a message for the given member
/** Encrypts a message for the given members
*
* Utility method to lookup a key on an IPS snapshot and then encrypt the given message with the
* most suitable key for the respective member.
*/
override def encryptFor[M <: HasVersionedToByteString](
override def encryptFor[M <: HasVersionedToByteString, MemberType <: Member](
message: M,
member: Member,
members: Seq[MemberType],
version: ProtocolVersion,
): EitherT[Future, SyncCryptoError, AsymmetricEncrypted[M]] =
)(implicit
traceContext: TraceContext
): EitherT[Future, (MemberType, SyncCryptoError), Map[MemberType, AsymmetricEncrypted[M]]] = {
def encryptFor(keys: Map[Member, EncryptionPublicKey])(
member: MemberType
): Either[(MemberType, SyncCryptoError), (MemberType, AsymmetricEncrypted[M])] = keys
.get(member)
.toRight(
member -> KeyNotAvailable(
member,
KeyPurpose.Encryption,
ipsSnapshot.timestamp,
Seq.empty,
)
)
.flatMap(k =>
crypto.pureCrypto
.encryptWith(message, k, version)
.bimap(error => member -> SyncCryptoEncryptionError(error), member -> _)
)
EitherT(
ipsSnapshot
.encryptionKey(member)
.map { keyO =>
keyO
.toRight(
KeyNotAvailable(member, KeyPurpose.Encryption, ipsSnapshot.timestamp, Seq.empty)
)
.flatMap(k =>
crypto.pureCrypto
.encryptWith(message, k, version)
.leftMap(SyncCryptoEncryptionError)
)
.encryptionKey(members)
.map { keys =>
members
.traverse(encryptFor(keys))
.map(_.toMap)
}
)
}
}

View File

@ -8,7 +8,6 @@ import com.digitalasset.canton.config.RequireTypes.{NonNegativeInt, PositiveInt}
import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting}
import com.digitalasset.canton.protocol.v30
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.topology.transaction.TrustLevel
import com.digitalasset.canton.{LfPartyId, ProtoDeserializationError}
/** A party that must be informed about the view.
@ -28,8 +27,6 @@ sealed trait Informee extends Product with Serializable with PrettyPrinting {
*/
def weight: NonNegativeInt
def requiredTrustLevel: TrustLevel
/** Yields an informee resulting from adding `delta` to `weight`.
*
* If the new weight is zero, the resulting informee will be a plain informee;
@ -44,11 +41,10 @@ sealed trait Informee extends Product with Serializable with PrettyPrinting {
v30.Informee(
party = party,
weight = weight.unwrap,
requiredTrustLevel = requiredTrustLevel.toProtoEnum,
)
override def pretty: Pretty[Informee] =
prettyOfString(inst => show"${inst.party}*${inst.weight} $requiredTrustLevel")
prettyOfString(inst => show"${inst.party}*${inst.weight}")
}
object Informee {
@ -56,23 +52,21 @@ object Informee {
def create(
party: LfPartyId,
weight: NonNegativeInt,
requiredTrustLevel: TrustLevel,
): Informee =
if (weight == NonNegativeInt.zero) PlainInformee(party)
else ConfirmingParty(party, PositiveInt.tryCreate(weight.unwrap), requiredTrustLevel)
else ConfirmingParty(party, PositiveInt.tryCreate(weight.unwrap))
private[data] def fromProtoV30(informeeP: v30.Informee): ParsingResult[Informee] = {
val v30.Informee(partyP, weightP, requiredTrustLevelP) = informeeP
val v30.Informee(partyP, weightP) = informeeP
for {
party <- LfPartyId
.fromString(partyP)
.leftMap(ProtoDeserializationError.ValueDeserializationError("party", _))
requiredTrustLevel <- TrustLevel.fromProtoEnum(requiredTrustLevelP)
weight <- NonNegativeInt
.create(weightP)
.leftMap(err => ProtoDeserializationError.InvariantViolation(err.message))
} yield Informee.create(party, weight, requiredTrustLevel)
} yield Informee.create(party, weight)
}
}
@ -83,7 +77,6 @@ object Informee {
final case class ConfirmingParty(
party: LfPartyId,
partyWeight: PositiveInt,
requiredTrustLevel: TrustLevel,
) extends Informee {
val weight: NonNegativeInt = partyWeight.toNonNegative
@ -98,9 +91,7 @@ final case class ConfirmingParty(
final case class PlainInformee(party: LfPartyId) extends Informee {
override val weight: NonNegativeInt = NonNegativeInt.zero
override val requiredTrustLevel: TrustLevel = TrustLevel.Ordinary
def withAdditionalWeight(delta: NonNegativeInt): Informee =
if (delta == NonNegativeInt.zero) this
else ConfirmingParty(party, PositiveInt.tryCreate(delta.unwrap), requiredTrustLevel)
else ConfirmingParty(party, PositiveInt.tryCreate(delta.unwrap))
}

View File

@ -18,7 +18,6 @@ import com.digitalasset.canton.protocol.{v30, *}
import com.digitalasset.canton.sequencing.protocol.{SequencedEvent, SignedContent}
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.serialization.{ProtoConverter, ProtocolVersionedMemoizedEvidence}
import com.digitalasset.canton.topology.transaction.TrustLevel
import com.digitalasset.canton.topology.{DomainId, MediatorRef, ParticipantId}
import com.digitalasset.canton.util.EitherUtil
import com.digitalasset.canton.version.Transfer.{SourceProtocolVersion, TargetProtocolVersion}
@ -132,7 +131,7 @@ final case class TransferInCommonData private (
TransferInCommonData.protocolVersionRepresentativeFor(targetProtocolVersion.v)
def confirmingParties: Set[Informee] =
stakeholders.map(ConfirmingParty(_, PositiveInt.one, TrustLevel.Ordinary))
stakeholders.map(ConfirmingParty(_, PositiveInt.one))
@transient override protected lazy val companionObj: TransferInCommonData.type =
TransferInCommonData

View File

@ -14,7 +14,6 @@ import com.digitalasset.canton.protocol.{v30, *}
import com.digitalasset.canton.sequencing.protocol.TimeProof
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
import com.digitalasset.canton.serialization.{ProtoConverter, ProtocolVersionedMemoizedEvidence}
import com.digitalasset.canton.topology.transaction.TrustLevel
import com.digitalasset.canton.topology.{DomainId, MediatorRef, ParticipantId}
import com.digitalasset.canton.util.EitherUtil
import com.digitalasset.canton.version.Transfer.{SourceProtocolVersion, TargetProtocolVersion}
@ -175,7 +174,7 @@ final case class TransferOutCommonData private (
override def hashPurpose: HashPurpose = HashPurpose.TransferOutCommonData
def confirmingParties: Set[Informee] =
(stakeholders ++ adminParties).map(ConfirmingParty(_, PositiveInt.one, TrustLevel.Ordinary))
(stakeholders ++ adminParties).map(ConfirmingParty(_, PositiveInt.one))
override def pretty: Pretty[TransferOutCommonData] = prettyOfClass(
param("submitter metadata", _.submitterMetadata),

View File

@ -15,6 +15,7 @@ import com.digitalasset.canton.sequencing.protocol.{
}
import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.client.PartyTopologySnapshotClient
import com.digitalasset.canton.tracing.TraceContext
import scala.concurrent.{ExecutionContext, Future}
@ -31,7 +32,10 @@ final case class Witnesses(unwrap: NonEmpty[Seq[Set[Informee]]]) {
/** Derive a recipient tree that mirrors the given hierarchy of witnesses. */
def toRecipients(
topology: PartyTopologySnapshotClient
)(implicit ec: ExecutionContext): EitherT[Future, InvalidWitnesses, Recipients] =
)(implicit
ec: ExecutionContext,
tc: TraceContext,
): EitherT[Future, InvalidWitnesses, Recipients] =
for {
recipientsList <- unwrap.forgetNE.foldLeftM(Seq.empty[RecipientsTree]) {
(children, informees) =>

View File

@ -4,7 +4,6 @@
package com.digitalasset.canton.protocol
import cats.Order
import cats.syntax.parallel.*
import com.digitalasset.canton.LfPartyId
import com.digitalasset.canton.config.RequireTypes.{NonNegativeInt, PositiveInt}
import com.digitalasset.canton.data.{ConfirmingParty, Informee, PlainInformee}
@ -15,8 +14,8 @@ import com.digitalasset.canton.serialization.{
DeterministicEncoding,
}
import com.digitalasset.canton.topology.client.TopologySnapshot
import com.digitalasset.canton.topology.transaction.{ParticipantAttributes, TrustLevel}
import com.digitalasset.canton.util.FutureInstances.*
import com.digitalasset.canton.topology.transaction.ParticipantAttributes
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.LfTransactionUtil
import com.google.protobuf.ByteString
@ -29,12 +28,10 @@ sealed trait ConfirmationPolicy extends Product with Serializable with PrettyPri
def toProtoPrimitive: ByteString = DeterministicEncoding.encodeString(name)
def informeesAndThreshold(actionNode: LfActionNode, topologySnapshot: TopologySnapshot)(implicit
ec: ExecutionContext
ec: ExecutionContext,
tc: TraceContext,
): Future[(Set[Informee], NonNegativeInt)]
/** The minimal acceptable trust level of the sender of mediator response */
def requiredTrustLevel: TrustLevel
/** The minimum threshold for views of requests with this policy.
* The mediator checks that all views have at least the given threshold.
*/
@ -45,7 +42,7 @@ sealed trait ConfirmationPolicy extends Product with Serializable with PrettyPri
adminParty: LfPartyId,
): NonNegativeInt =
informees
.collectFirst { case ConfirmingParty(`adminParty`, _, _) => NonNegativeInt.zero }
.collectFirst { case ConfirmingParty(`adminParty`, _) => NonNegativeInt.zero }
.getOrElse(NonNegativeInt.one)
def withSubmittingAdminParty(
@ -77,71 +74,28 @@ sealed trait ConfirmationPolicy extends Product with Serializable with PrettyPri
object ConfirmationPolicy {
private val havingVip: ParticipantAttributes => Boolean = _.trustLevel == TrustLevel.Vip
private val havingConfirmer: ParticipantAttributes => Boolean = _.permission.canConfirm
private def toInformeesAndThreshold(
confirmingParties: Set[LfPartyId],
plainInformees: Set[LfPartyId],
requiredTrustLevel: TrustLevel,
): (Set[Informee], NonNegativeInt) = {
// We make sure that the threshold is at least 1 so that a transaction is not vacuously approved if the confirming parties are empty.
val threshold = NonNegativeInt.tryCreate(Math.max(confirmingParties.size, 1))
val informees =
confirmingParties.map(ConfirmingParty(_, PositiveInt.one, requiredTrustLevel): Informee) ++
confirmingParties.map(ConfirmingParty(_, PositiveInt.one): Informee) ++
plainInformees.map(PlainInformee)
(informees, threshold)
}
case object Vip extends ConfirmationPolicy {
override val name = "Vip"
case object Signatory extends ConfirmationPolicy {
override val name = "Signatory"
protected override val index: Int = 0
override def informeesAndThreshold(node: LfActionNode, topologySnapshot: TopologySnapshot)(
implicit ec: ExecutionContext
): Future[(Set[Informee], NonNegativeInt)] = {
val stateVerifiers = LfTransactionUtil.stateKnownTo(node)
val confirmingPartiesF = stateVerifiers.toList
.parTraverseFilter { partyId =>
topologySnapshot
.activeParticipantsOf(partyId)
.map(participants => participants.values.find(havingVip).map(_ => partyId))
}
.map(_.toSet)
confirmingPartiesF.map { confirmingParties =>
val plainInformees = node.informeesOfNode -- confirmingParties
val informees =
confirmingParties.map(ConfirmingParty(_, PositiveInt.one, TrustLevel.Vip)) ++
plainInformees.map(PlainInformee)
// As all VIP participants are trusted, it suffices that one of them confirms.
(informees, NonNegativeInt.one)
}
}
override def requiredTrustLevel: TrustLevel = TrustLevel.Vip
override def minimumThreshold(informees: Set[Informee]): NonNegativeInt = {
// Make sure that at least one VIP needs to approve.
val weightOfOrdinary = informees.toSeq.collect {
case ConfirmingParty(_, weight, TrustLevel.Ordinary) => weight.unwrap
}.sum
NonNegativeInt.tryCreate(weightOfOrdinary + 1)
}
override protected def additionalWeightOfSubmittingAdminParty(
informees: Set[Informee],
adminParty: LfPartyId,
): NonNegativeInt =
NonNegativeInt.tryCreate(informees.toSeq.map(_.weight.unwrap).sum + 1)
}
case object Signatory extends ConfirmationPolicy {
override val name = "Signatory"
protected override val index: Int = 1
override def informeesAndThreshold(node: LfActionNode, topologySnapshot: TopologySnapshot)(
implicit ec: ExecutionContext
implicit
ec: ExecutionContext,
tc: TraceContext,
): Future[(Set[Informee], NonNegativeInt)] = {
val confirmingParties =
LfTransactionUtil.signatoriesOrMaintainers(node) | LfTransactionUtil.actingParties(node)
@ -151,14 +105,12 @@ object ConfirmationPolicy {
)
val plainInformees = node.informeesOfNode -- confirmingParties
Future.successful(
toInformeesAndThreshold(confirmingParties, plainInformees, TrustLevel.Ordinary)
toInformeesAndThreshold(confirmingParties, plainInformees)
)
}
override def requiredTrustLevel: TrustLevel = TrustLevel.Ordinary
}
val values: Seq[ConfirmationPolicy] = Seq[ConfirmationPolicy](Vip, Signatory)
val values: Seq[ConfirmationPolicy] = Seq[ConfirmationPolicy](Signatory)
require(
values.zipWithIndex.forall { case (policy, index) => policy.index == index },
@ -170,11 +122,11 @@ object ConfirmationPolicy {
Order.by[ConfirmationPolicy, Int](_.index)
/** Chooses appropriate confirmation policies for a transaction.
* It chooses [[Vip]] if every node has at least one VIP who knows the state
* It chooses [[Signatory]] if every node has a Participant that can confirm.
*/
def choose(transaction: LfVersionedTransaction, topologySnapshot: TopologySnapshot)(implicit
ec: ExecutionContext
ec: ExecutionContext,
tc: TraceContext,
): Future[Seq[ConfirmationPolicy]] = {
val actionNodes = transaction.nodes.values.collect { case an: LfActionNode => an }
@ -187,24 +139,18 @@ object ConfirmationPolicy {
}
val allParties =
(vipCheckPartiesPerNode.flatten ++ signatoriesCheckPartiesPerNode.flatten).toSet
// TODO(i4930) - potentially batch this lookup
val eligibleParticipantsF =
allParties.toList
.parTraverse(partyId =>
topologySnapshot.activeParticipantsOf(partyId).map { res =>
(partyId, (res.values.exists(havingVip), res.values.exists(havingConfirmer)))
}
)
.map(_.toMap)
topologySnapshot.activeParticipantsOfPartiesWithAttributes(allParties.toSeq).map { result =>
result.map { case (party, attributesMap) =>
(party, attributesMap.values.exists(havingConfirmer))
}
}
eligibleParticipantsF.map { eligibleParticipants =>
val hasVipForEachNode = vipCheckPartiesPerNode.forall {
_.exists(eligibleParticipants(_)._1)
}
val hasConfirmersForEachNode = signatoriesCheckPartiesPerNode.forall { signatoriesForNode =>
signatoriesForNode.nonEmpty && signatoriesForNode.forall(eligibleParticipants(_)._2)
signatoriesForNode.nonEmpty && signatoriesForNode.forall(eligibleParticipants(_))
}
List(hasVipForEachNode -> Vip, hasConfirmersForEachNode -> Signatory)
List(hasConfirmersForEachNode -> Signatory)
.filter(_._1)
.map(_._2)
}
@ -214,7 +160,6 @@ object ConfirmationPolicy {
encodedName: ByteString
): Either[DeserializationError, ConfirmationPolicy] =
DeterministicEncoding.decodeString(encodedName).flatMap {
case (Vip.name, _) => Right(Vip)
case (Signatory.name, _) => Right(Signatory)
case (badName, _) =>
Left(DefaultDeserializationError(s"Invalid confirmation policy $badName"))

View File

@ -4,22 +4,15 @@
package com.digitalasset.canton.protocol.messages
import cats.syntax.alternative.*
import cats.syntax.parallel.*
import com.daml.nonempty.NonEmpty
import com.digitalasset.canton.LfPartyId
import com.digitalasset.canton.lifecycle.FutureUnlessShutdown
import com.digitalasset.canton.logging.{HasLoggerName, NamedLoggingContext}
import com.digitalasset.canton.sequencing.protocol.{
MemberRecipient,
ParticipantsOfParty,
Recipient,
Recipients,
RecipientsTree,
}
import com.digitalasset.canton.sequencing.protocol.*
import com.digitalasset.canton.topology.client.TopologySnapshot
import com.digitalasset.canton.topology.{MediatorRef, ParticipantId, PartyId}
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.ErrorUtil
import com.digitalasset.canton.util.FutureInstances.*
import com.digitalasset.canton.util.ShowUtil.*
import scala.concurrent.{ExecutionContext, Future}
@ -33,6 +26,7 @@ object RootHashMessageRecipients extends HasLoggerName {
loggingContext: NamedLoggingContext,
executionContext: ExecutionContext,
): Future[Seq[Recipient]] = {
implicit val tc = loggingContext.traceContext
val informeesList = informees.toList
for {
participantsAddressedByInformees <- ipsSnapshot
@ -159,7 +153,10 @@ object RootHashMessageRecipients extends HasLoggerName {
rootHashMessagesRecipients: Seq[Recipient],
request: MediatorRequest,
topologySnapshot: TopologySnapshot,
)(implicit executionContext: ExecutionContext): Future[WrongMembers] = {
)(implicit
executionContext: ExecutionContext,
traceContext: TraceContext,
): Future[WrongMembers] = {
val informeesAddressedAsGroup = rootHashMessagesRecipients.collect {
case ParticipantsOfParty(informee) =>
informee.toLf
@ -170,13 +167,13 @@ object RootHashMessageRecipients extends HasLoggerName {
val informeesNotAddressedAsGroups = request.allInformees -- informeesAddressedAsGroup.toSet
val superfluousInformees = informeesAddressedAsGroup.toSet -- request.allInformees
for {
allNonGroupAddressedInformeeParticipants <-
informeesNotAddressedAsGroups.toList
.parTraverse(topologySnapshot.activeParticipantsOf)
.map(_.flatMap(_.keySet).toSet)
participantsAddressedAsGroup <- informeesAddressedAsGroup.toList
.parTraverse(topologySnapshot.activeParticipantsOf)
.map(_.flatMap(_.keySet).toSet)
allNonGroupAddressedInformeeParticipants <- topologySnapshot
.activeParticipantsOfPartiesWithAttributes(informeesNotAddressedAsGroups.toList)
.map(_.values.flatMap(_.keySet).toSet)
participantsAddressedAsGroup <-
topologySnapshot
.activeParticipantsOfPartiesWithAttributes(informeesAddressedAsGroup.toList)
.map(_.values.flatMap(_.keySet).toSet)
} yield {
val participantsSet = participants.toSet
val missingInformeeParticipants =

View File

@ -57,7 +57,7 @@ case class SignedProtocolMessage[+M <: SignedProtocolMessageContent](
def verifySignature(
snapshot: SyncCryptoApi,
member: Member,
): EitherT[Future, SignatureCheckError, Unit] =
)(implicit traceContext: TraceContext): EitherT[Future, SignatureCheckError, Unit] =
if (
representativeProtocolVersion >=
companionObj.protocolVersionRepresentativeFor(ProtocolVersion.v30)

View File

@ -371,7 +371,7 @@ object SequencedEventValidator extends HasLoggerName {
if (signingTimestamp > sequencingTimestamp) {
EitherT.leftT[F, SyncCryptoApi](SigningTimestampAfterSequencingTime)
} else if (optimistic) {
val approximateSnapshot = syncCryptoApi.currentSnapshotApproximation
val approximateSnapshot = syncCryptoApi.currentSnapshotApproximation(traceContext)
val approximateSnapshotTime = approximateSnapshot.ipsSnapshot.timestamp
// If the topology client has caught up to the signing timestamp,
// use the right snapshot

View File

@ -112,7 +112,10 @@ final case class ClosedEnvelope private (
def verifySignatures(
snapshot: SyncCryptoApi,
sender: Member,
)(implicit ec: ExecutionContext): EitherT[Future, SignatureCheckError, Unit] = {
)(implicit
ec: ExecutionContext,
traceContext: TraceContext,
): EitherT[Future, SignatureCheckError, Unit] = {
NonEmpty
.from(signatures)
.traverse_(ClosedEnvelope.verifySignatures(snapshot, sender, bytes, _))
@ -213,7 +216,7 @@ object ClosedEnvelope extends HasProtocolVersionedCompanion[ClosedEnvelope] {
sender: Member,
content: ByteString,
signatures: NonEmpty[Seq[Signature]],
): EitherT[Future, SignatureCheckError, Unit] = {
)(implicit traceContext: TraceContext): EitherT[Future, SignatureCheckError, Unit] = {
val hash = snapshot.pureCrypto.digest(HashPurpose.SignedProtocolMessageSignature, content)
snapshot.verifySignatures(hash, sender, signatures)
}

View File

@ -71,7 +71,7 @@ final case class SignedContent[+A <: HasCryptographicEvidence] private (
snapshot: SyncCryptoApi,
member: Member,
purpose: HashPurpose,
): EitherT[Future, SignatureCheckError, Unit] = {
)(implicit traceContext: TraceContext): EitherT[Future, SignatureCheckError, Unit] = {
val hash = SignedContent.hashContent(snapshot.pureCrypto, content, purpose)
snapshot.verifySignature(hash, member, signature)
}

View File

@ -17,11 +17,13 @@ final case class KeyCollection(
def hasBothKeys(): Boolean = signingKeys.nonEmpty && encryptionKeys.nonEmpty
def addTo(key: PublicKey): KeyCollection = (key: @unchecked) match {
def add(key: PublicKey): KeyCollection = (key: @unchecked) match {
case sigKey: SigningPublicKey => copy(signingKeys = signingKeys :+ sigKey)
case encKey: EncryptionPublicKey => copy(encryptionKeys = encryptionKeys :+ encKey)
}
def addAll(keys: Seq[PublicKey]): KeyCollection = keys.foldLeft(this)(_.add(_))
def removeFrom(key: PublicKey): KeyCollection =
(key: @unchecked) match {
case _: SigningPublicKey => copy(signingKeys = signingKeys.filter(_.id != key.id))

View File

@ -362,6 +362,7 @@ final case class MediatorGroup(
object MediatorGroup {
type MediatorGroupIndex = NonNegativeInt
val MediatorGroupIndex = NonNegativeInt
}
final case class MediatorId(uid: UniqueIdentifier) extends DomainMember with NodeIdentity {

View File

@ -4,7 +4,6 @@
package com.digitalasset.canton.topology.client
import cats.data.EitherT
import cats.syntax.parallel.*
import com.daml.lf.data.Ref.PackageId
import com.digitalasset.canton.concurrent.FutureSupervisor
import com.digitalasset.canton.config.{BatchingConfig, CachingConfigs, ProcessingTimeout}
@ -20,7 +19,7 @@ import com.digitalasset.canton.topology.processing.*
import com.digitalasset.canton.topology.store.{TopologyStore, TopologyStoreId, TopologyStoreX}
import com.digitalasset.canton.topology.transaction.SignedTopologyTransactionX.GenericSignedTopologyTransactionX
import com.digitalasset.canton.topology.transaction.*
import com.digitalasset.canton.tracing.{NoTracing, TraceContext}
import com.digitalasset.canton.tracing.{TraceContext, TracedScaffeine}
import com.digitalasset.canton.util.FutureInstances.*
import com.digitalasset.canton.util.{ErrorUtil, MonadUtil}
import com.digitalasset.canton.version.ProtocolVersion
@ -333,55 +332,63 @@ private class ForwardingTopologySnapshotClient(
extends TopologySnapshotLoader {
override def referenceTime: CantonTimestamp = parent.timestamp
override def participants(): Future[Seq[(ParticipantId, ParticipantPermission)]] =
override def participants()(implicit
traceContext: TraceContext
): Future[Seq[(ParticipantId, ParticipantPermission)]] =
parent.participants()
override def allKeys(owner: Member): Future[KeyCollection] = parent.allKeys(owner)
override def findParticipantState(
participantId: ParticipantId
): Future[Option[ParticipantAttributes]] = parent.findParticipantState(participantId)
override def allKeys(owner: Member)(implicit traceContext: TraceContext): Future[KeyCollection] =
parent.allKeys(owner)
override def allKeys(members: Seq[Member])(implicit
traceContext: TraceContext
): Future[Map[Member, KeyCollection]] = parent.allKeys(members)
override def loadParticipantStates(
participants: Seq[ParticipantId]
): Future[Map[ParticipantId, ParticipantAttributes]] = parent.loadParticipantStates(participants)
)(implicit traceContext: TraceContext): Future[Map[ParticipantId, ParticipantAttributes]] =
parent.loadParticipantStates(participants)
override private[client] def loadActiveParticipantsOf(
party: PartyId,
participantStates: Seq[ParticipantId] => Future[Map[ParticipantId, ParticipantAttributes]],
): Future[PartyInfo] =
)(implicit traceContext: TraceContext): Future[PartyInfo] =
parent.loadActiveParticipantsOf(party, participantStates)
override def inspectKeys(
filterOwner: String,
filterOwnerType: Option[MemberCode],
limit: Int,
): Future[Map[Member, KeyCollection]] =
)(implicit traceContext: TraceContext): Future[Map[Member, KeyCollection]] =
parent.inspectKeys(filterOwner, filterOwnerType, limit)
override def inspectKnownParties(
filterParty: String,
filterParticipant: String,
limit: Int,
): Future[Set[PartyId]] =
)(implicit traceContext: TraceContext): Future[Set[PartyId]] =
parent.inspectKnownParties(filterParty, filterParticipant, limit)
override def findUnvettedPackagesOrDependencies(
participantId: ParticipantId,
packages: Set[PackageId],
): EitherT[Future, PackageId, Set[PackageId]] =
)(implicit traceContext: TraceContext): EitherT[Future, PackageId, Set[PackageId]] =
parent.findUnvettedPackagesOrDependencies(participantId, packages)
override private[client] def loadUnvettedPackagesOrDependencies(
participant: ParticipantId,
packageId: PackageId,
): EitherT[Future, PackageId, Set[PackageId]] =
)(implicit traceContext: TraceContext): EitherT[Future, PackageId, Set[PackageId]] =
parent.loadUnvettedPackagesOrDependencies(participant, packageId)
/** returns the list of currently known mediators */
override def mediatorGroups(): Future[Seq[MediatorGroup]] = parent.mediatorGroups()
override def mediatorGroups()(implicit traceContext: TraceContext): Future[Seq[MediatorGroup]] =
parent.mediatorGroups()
/** returns the sequencer group if known */
override def sequencerGroup(): Future[Option[SequencerGroup]] = parent.sequencerGroup()
override def sequencerGroup()(implicit
traceContext: TraceContext
): Future[Option[SequencerGroup]] = parent.sequencerGroup()
override def allMembers(): Future[Set[Member]] = parent.allMembers()
override def allMembers()(implicit traceContext: TraceContext): Future[Set[Member]] =
parent.allMembers()
override def isMemberKnown(member: Member): Future[Boolean] =
override def isMemberKnown(member: Member)(implicit traceContext: TraceContext): Future[Boolean] =
parent.isMemberKnown(member)
override def findDynamicDomainParameters()(implicit
@ -398,11 +405,12 @@ private class ForwardingTopologySnapshotClient(
override private[client] def loadBatchActiveParticipantsOf(
parties: Seq[PartyId],
loadParticipantStates: Seq[ParticipantId] => Future[Map[ParticipantId, ParticipantAttributes]],
) = parent.loadBatchActiveParticipantsOf(parties, loadParticipantStates)
)(implicit traceContext: TraceContext) =
parent.loadBatchActiveParticipantsOf(parties, loadParticipantStates)
override def trafficControlStatus(
members: Seq[Member]
): Future[Map[Member, Option[MemberTrafficControlState]]] =
)(implicit traceContext: TraceContext): Future[Map[Member, Option[MemberTrafficControlState]]] =
parent.trafficControlStatus(members)
/** Returns the Authority-Of delegations for consortium parties. Non-consortium parties delegate to themselves
@ -410,7 +418,8 @@ private class ForwardingTopologySnapshotClient(
*/
override def authorityOf(
parties: Set[LfPartyId]
): Future[PartyTopologySnapshotClient.AuthorityOfResponse] = parent.authorityOf(parties)
)(implicit traceContext: TraceContext): Future[PartyTopologySnapshotClient.AuthorityOfResponse] =
parent.authorityOf(parties)
}
class CachingTopologySnapshot(
@ -421,32 +430,61 @@ class CachingTopologySnapshot(
)(implicit
val executionContext: ExecutionContext
) extends TopologySnapshotLoader
with NamedLogging
with NoTracing {
with NamedLogging {
override def timestamp: CantonTimestamp = parent.timestamp
private val partyCache = cachingConfigs.partyCache
.buildScaffeine()
.buildAsyncFuture[PartyId, PartyInfo](
loader = party => parent.loadActiveParticipantsOf(party, loadParticipantStates),
allLoader =
Some(parties => parent.loadBatchActiveParticipantsOf(parties.toSeq, loadParticipantStates)),
)
private val partyCache =
TracedScaffeine.buildTracedAsyncFuture[PartyId, PartyInfo](
cache = cachingConfigs.partyCache.buildScaffeine(),
loader = traceContext =>
party =>
parent
.loadActiveParticipantsOf(party, loadParticipantStates(_)(traceContext))(traceContext),
allLoader = Some(traceContext =>
parties =>
parent
.loadBatchActiveParticipantsOf(parties.toSeq, loadParticipantStates(_)(traceContext))(
traceContext
)
),
)(logger)
private val participantCache =
cachingConfigs.participantCache
.buildScaffeine()
.buildAsyncFuture[ParticipantId, Option[ParticipantAttributes]](parent.findParticipantState)
private val keyCache = cachingConfigs.keyCache
.buildScaffeine()
.buildAsyncFuture[Member, KeyCollection](parent.allKeys)
private val participantCache = TracedScaffeine
.buildTracedAsyncFuture[ParticipantId, Option[ParticipantAttributes]](
cache = cachingConfigs.participantCache.buildScaffeine(),
loader = traceContext => pid => parent.findParticipantState(pid)(traceContext),
allLoader = Some(traceContext =>
pids =>
parent.loadParticipantStates(pids.toSeq)(traceContext).map { attributes =>
// make sure that the returned map contains an entry for each input element
pids.map(pid => pid -> attributes.get(pid)).toMap
}
),
)(logger)
private val keyCache = TracedScaffeine
.buildTracedAsyncFuture[Member, KeyCollection](
cache = cachingConfigs.keyCache.buildScaffeine(),
loader = traceContext => member => parent.allKeys(member)(traceContext),
allLoader = Some(traceContext =>
members =>
parent
.allKeys(members.toSeq)(traceContext)
.map(foundKeys =>
// make sure that the returned map contains an entry for each input element
members
.map(member => member -> foundKeys.getOrElse(member, KeyCollection.empty))
.toMap
)
),
)(logger)
private val packageVettingCache = cachingConfigs.packageVettingCache
.buildScaffeine()
.buildAsyncFuture[(ParticipantId, PackageId), Either[PackageId, Set[PackageId]]](x =>
loadUnvettedPackagesOrDependencies(x._1, x._2).value
)
private val packageVettingCache =
TracedScaffeine
.buildTracedAsyncFuture[(ParticipantId, PackageId), Either[PackageId, Set[PackageId]]](
cache = cachingConfigs.packageVettingCache.buildScaffeine(),
traceContext => x => loadUnvettedPackagesOrDependencies(x._1, x._2)(traceContext).value,
)(logger)
private val mediatorsCache = new AtomicReference[Option[Future[Seq[MediatorGroup]]]](None)
@ -454,9 +492,12 @@ class CachingTopologySnapshot(
new AtomicReference[Option[Future[Option[SequencerGroup]]]](None)
private val allMembersCache = new AtomicReference[Option[Future[Set[Member]]]](None)
private val memberCache = cachingConfigs.memberCache
.buildScaffeine()
.buildAsyncFuture[Member, Boolean](parent.isMemberKnown)
private val memberCache =
TracedScaffeine
.buildTracedAsyncFuture[Member, Boolean](
cache = cachingConfigs.memberCache.buildScaffeine(),
traceContext => member => parent.isMemberKnown(member)(traceContext),
)(logger)
private val domainParametersCache =
new AtomicReference[Option[Future[Either[String, DynamicDomainParametersWithValidity]]]](None)
@ -466,64 +507,66 @@ class CachingTopologySnapshot(
Option[Future[Seq[DynamicDomainParametersWithValidity]]]
](None)
private val domainTrafficControlStateCache = cachingConfigs.trafficStatusCache
.buildScaffeine()
.buildAsyncFuture[Member, Option[MemberTrafficControlState]](
loader = member =>
parent
.trafficControlStatus(Seq(member))
.map(_.get(member).flatten),
allLoader = Some(members => parent.trafficControlStatus(members.toSeq)),
)
private val domainTrafficControlStateCache =
TracedScaffeine
.buildTracedAsyncFuture[Member, Option[MemberTrafficControlState]](
cache = cachingConfigs.trafficStatusCache.buildScaffeine(),
loader = traceContext =>
member =>
parent
.trafficControlStatus(Seq(member))(traceContext)
.map(_.get(member).flatten),
allLoader =
Some(traceContext => members => parent.trafficControlStatus(members.toSeq)(traceContext)),
)(logger)
private val authorityOfCache = cachingConfigs.partyCache
.buildScaffeine()
.buildAsyncFuture[Set[LfPartyId], PartyTopologySnapshotClient.AuthorityOfResponse](
loader = party => parent.authorityOf(party)
)
private val authorityOfCache =
TracedScaffeine
.buildTracedAsyncFuture[Set[LfPartyId], PartyTopologySnapshotClient.AuthorityOfResponse](
cache = cachingConfigs.partyCache.buildScaffeine(),
loader = traceContext => party => parent.authorityOf(party)(traceContext),
)(logger)
override def participants(): Future[Seq[(ParticipantId, ParticipantPermission)]] =
override def participants()(implicit
traceContext: TraceContext
): Future[Seq[(ParticipantId, ParticipantPermission)]] =
parent.participants()
override def allKeys(owner: Member): Future[KeyCollection] = keyCache.get(owner)
override def allKeys(owner: Member)(implicit traceContext: TraceContext): Future[KeyCollection] =
keyCache.get(owner)
override def findParticipantState(
participantId: ParticipantId
): Future[Option[ParticipantAttributes]] =
participantCache.get(participantId)
override def allKeys(
members: Seq[Member]
)(implicit traceContext: TraceContext): Future[Map[Member, KeyCollection]] =
keyCache.getAll(members)
override def loadActiveParticipantsOf(
party: PartyId,
participantStates: Seq[ParticipantId] => Future[Map[ParticipantId, ParticipantAttributes]],
): Future[PartyInfo] =
)(implicit traceContext: TraceContext): Future[PartyInfo] =
partyCache.get(party)
override private[client] def loadBatchActiveParticipantsOf(
parties: Seq[PartyId],
loadParticipantStates: Seq[ParticipantId] => Future[Map[ParticipantId, ParticipantAttributes]],
) = {
)(implicit traceContext: TraceContext) = {
// split up the request into separate chunks so that we don't block the cache for too long
// when loading very large batches
MonadUtil
.batchedSequentialTraverse(batchingConfig.parallelism, batchingConfig.maxItemsInSqlClause)(
parties
)(
partyCache.getAll(_).map(_.toSeq)
)
)(parties => partyCache.getAll(parties)(traceContext).map(_.toSeq))
.map(_.toMap)
}
override def loadParticipantStates(
participants: Seq[ParticipantId]
): Future[Map[ParticipantId, ParticipantAttributes]] =
participants
.parTraverse(participant => participantState(participant).map((participant, _)))
.map(_.toMap)
)(implicit traceContext: TraceContext): Future[Map[ParticipantId, ParticipantAttributes]] =
participantCache.getAll(participants).map(_.collect { case (k, Some(v)) => (k, v) })
override def findUnvettedPackagesOrDependencies(
participantId: ParticipantId,
packages: Set[PackageId],
): EitherT[Future, PackageId, Set[PackageId]] =
)(implicit traceContext: TraceContext): EitherT[Future, PackageId, Set[PackageId]] =
findUnvettedPackagesOrDependenciesUsingLoader(
participantId,
packages,
@ -533,35 +576,38 @@ class CachingTopologySnapshot(
private[client] def loadUnvettedPackagesOrDependencies(
participant: ParticipantId,
packageId: PackageId,
): EitherT[Future, PackageId, Set[PackageId]] =
)(implicit traceContext: TraceContext): EitherT[Future, PackageId, Set[PackageId]] =
parent.loadUnvettedPackagesOrDependencies(participant, packageId)
override def inspectKeys(
filterOwner: String,
filterOwnerType: Option[MemberCode],
limit: Int,
): Future[Map[Member, KeyCollection]] =
)(implicit traceContext: TraceContext): Future[Map[Member, KeyCollection]] =
parent.inspectKeys(filterOwner, filterOwnerType, limit)
override def inspectKnownParties(
filterParty: String,
filterParticipant: String,
limit: Int,
): Future[Set[PartyId]] =
)(implicit traceContext: TraceContext): Future[Set[PartyId]] =
parent.inspectKnownParties(filterParty, filterParticipant, limit)
/** returns the list of currently known mediators */
override def mediatorGroups(): Future[Seq[MediatorGroup]] =
override def mediatorGroups()(implicit traceContext: TraceContext): Future[Seq[MediatorGroup]] =
getAndCache(mediatorsCache, parent.mediatorGroups())
/** returns the sequencer group if known */
override def sequencerGroup(): Future[Option[SequencerGroup]] =
override def sequencerGroup()(implicit
traceContext: TraceContext
): Future[Option[SequencerGroup]] =
getAndCache(sequencerGroupCache, parent.sequencerGroup())
/** returns the set of all known members */
override def allMembers(): Future[Set[Member]] = getAndCache(allMembersCache, parent.allMembers())
override def allMembers()(implicit traceContext: TraceContext): Future[Set[Member]] =
getAndCache(allMembersCache, parent.allMembers())
override def isMemberKnown(member: Member): Future[Boolean] =
override def isMemberKnown(member: Member)(implicit traceContext: TraceContext): Future[Boolean] =
memberCache.get(member)
/** Returns the value if it is present in the cache. Otherwise, use the
@ -593,12 +639,12 @@ class CachingTopologySnapshot(
*/
override def authorityOf(
parties: Set[LfPartyId]
): Future[PartyTopologySnapshotClient.AuthorityOfResponse] =
)(implicit traceContext: TraceContext): Future[PartyTopologySnapshotClient.AuthorityOfResponse] =
authorityOfCache.get(parties)
override def trafficControlStatus(
members: Seq[Member]
): Future[Map[Member, Option[MemberTrafficControlState]]] = {
)(implicit traceContext: TraceContext): Future[Map[Member, Option[MemberTrafficControlState]]] = {
domainTrafficControlStateCache.getAll(members)
}
}

View File

@ -5,6 +5,7 @@ package com.digitalasset.canton.topology.client
import com.digitalasset.canton.config.RequireTypes.PositiveLong
import com.digitalasset.canton.topology.Member
import com.digitalasset.canton.tracing.TraceContext
import scala.concurrent.Future
@ -20,5 +21,5 @@ trait DomainTrafficControlStateClient {
*/
def trafficControlStatus(
members: Seq[Member]
): Future[Map[Member, Option[MemberTrafficControlState]]]
)(implicit traceContext: TraceContext): Future[Map[Member, Option[MemberTrafficControlState]]]
}

View File

@ -37,7 +37,6 @@ import com.digitalasset.canton.util.FutureInstances.*
import com.digitalasset.canton.version.ProtocolVersion
import com.digitalasset.canton.{LfPartyId, checked}
import scala.Ordered.orderingToOrdered
import scala.collection.concurrent.TrieMap
import scala.collection.immutable
import scala.concurrent.duration.*
@ -221,10 +220,12 @@ trait PartyTopologySnapshotClient {
/** Load the set of active participants for the given parties */
def activeParticipantsOfParties(
parties: Seq[LfPartyId]
): Future[Map[LfPartyId, Set[ParticipantId]]]
)(implicit traceContext: TraceContext): Future[Map[LfPartyId, Set[ParticipantId]]]
def activeParticipantsOfPartiesWithAttributes(
parties: Seq[LfPartyId]
)(implicit
traceContext: TraceContext
): Future[Map[LfPartyId, Map[ParticipantId, ParticipantAttributes]]]
/** Returns the set of active participants the given party is represented by as of the snapshot timestamp
@ -233,55 +234,58 @@ trait PartyTopologySnapshotClient {
*/
def activeParticipantsOf(
party: LfPartyId
): Future[Map[ParticipantId, ParticipantAttributes]]
)(implicit traceContext: TraceContext): Future[Map[ParticipantId, ParticipantAttributes]]
/** Returns Right if all parties have at least an active participant passing the check. Otherwise, all parties not passing are passed as Left */
def allHaveActiveParticipants(
parties: Set[LfPartyId],
check: (ParticipantPermission => Boolean) = _.isActive,
): EitherT[Future, Set[LfPartyId], Unit]
)(implicit traceContext: TraceContext): EitherT[Future, Set[LfPartyId], Unit]
/** Returns the consortium thresholds (how many votes from different participants that host the consortium party
* are required for the confirmation to become valid). For normal parties returns 1.
*/
def consortiumThresholds(parties: Set[LfPartyId]): Future[Map[LfPartyId, PositiveInt]]
def consortiumThresholds(parties: Set[LfPartyId])(implicit
traceContext: TraceContext
): Future[Map[LfPartyId, PositiveInt]]
/** Returns the Authority-Of delegations for consortium parties. Non-consortium parties delegate to themselves
* with threshold one
*/
def authorityOf(parties: Set[LfPartyId]): Future[AuthorityOfResponse]
def authorityOf(parties: Set[LfPartyId])(implicit
traceContext: TraceContext
): Future[AuthorityOfResponse]
/** Returns true if there is at least one participant that satisfies the predicate */
def isHostedByAtLeastOneParticipantF(
party: LfPartyId,
check: ParticipantAttributes => Boolean,
): Future[Boolean]
parties: Set[LfPartyId],
check: (LfPartyId, ParticipantAttributes) => Boolean,
)(implicit traceContext: TraceContext): Future[Set[LfPartyId]]
/** Returns the participant permission for that particular participant (if there is one) */
def hostedOn(
partyId: LfPartyId,
partyIds: Set[LfPartyId],
participantId: ParticipantId,
): Future[Option[ParticipantAttributes]]
)(implicit traceContext: TraceContext): Future[Map[LfPartyId, ParticipantAttributes]]
/** Returns true of all given party ids are hosted on a certain participant */
def allHostedOn(
partyIds: Set[LfPartyId],
participantId: ParticipantId,
permissionCheck: ParticipantAttributes => Boolean = _.permission.isActive,
): Future[Boolean]
)(implicit traceContext: TraceContext): Future[Boolean]
/** Returns whether a participant can confirm on behalf of a party. */
def canConfirm(
participant: ParticipantId,
party: LfPartyId,
requiredTrustLevel: TrustLevel = TrustLevel.Ordinary,
): Future[Boolean]
parties: Set[LfPartyId],
)(implicit traceContext: TraceContext): Future[Set[LfPartyId]]
/** Returns the subset of parties the given participant can NOT submit on behalf of */
def canNotSubmit(
participant: ParticipantId,
parties: Seq[LfPartyId],
): Future[immutable.Iterable[LfPartyId]]
)(implicit traceContext: TraceContext): Future[immutable.Iterable[LfPartyId]]
/** Returns all active participants of all the given parties. Returns a Left if some of the parties don't have active
* participants, in which case the parties with missing active participants are returned. Note that it will return
@ -289,18 +293,18 @@ trait PartyTopologySnapshotClient {
*/
def activeParticipantsOfAll(
parties: List[LfPartyId]
): EitherT[Future, Set[LfPartyId], Set[ParticipantId]]
)(implicit traceContext: TraceContext): EitherT[Future, Set[LfPartyId], Set[ParticipantId]]
def partiesWithGroupAddressing(
parties: Seq[LfPartyId]
): Future[Set[LfPartyId]]
)(implicit traceContext: TraceContext): Future[Set[LfPartyId]]
/** Returns a list of all known parties on this domain */
def inspectKnownParties(
filterParty: String,
filterParticipant: String,
limit: Int,
): Future[
)(implicit traceContext: TraceContext): Future[
Set[PartyId]
] // TODO(#14048): Decide on whether to standarize APIs on LfPartyId or PartyId and unify interfaces
@ -334,23 +338,42 @@ trait KeyTopologySnapshotClient {
this: BaseTopologySnapshotClient =>
/** returns newest signing public key */
def signingKey(owner: Member): Future[Option[SigningPublicKey]]
def signingKey(owner: Member)(implicit
traceContext: TraceContext
): Future[Option[SigningPublicKey]]
/** returns all signing keys */
def signingKeys(owner: Member): Future[Seq[SigningPublicKey]]
def signingKeys(owner: Member)(implicit traceContext: TraceContext): Future[Seq[SigningPublicKey]]
def signingKeys(members: Seq[Member])(implicit
traceContext: TraceContext
): Future[Map[Member, Seq[SigningPublicKey]]]
/** returns newest encryption public key */
def encryptionKey(owner: Member): Future[Option[EncryptionPublicKey]]
def encryptionKey(owner: Member)(implicit
traceContext: TraceContext
): Future[Option[EncryptionPublicKey]]
/** returns newest encryption public key */
def encryptionKey(members: Seq[Member])(implicit
traceContext: TraceContext
): Future[Map[Member, EncryptionPublicKey]]
/** returns all encryption keys */
def encryptionKeys(owner: Member): Future[Seq[EncryptionPublicKey]]
def encryptionKeys(owner: Member)(implicit
traceContext: TraceContext
): Future[Seq[EncryptionPublicKey]]
def encryptionKeys(members: Seq[Member])(implicit
traceContext: TraceContext
): Future[Map[Member, Seq[EncryptionPublicKey]]]
/** Returns a list of all known parties on this domain */
def inspectKeys(
filterOwner: String,
filterOwnerType: Option[MemberCode],
limit: Int,
): Future[Map[Member, KeyCollection]]
)(implicit traceContext: TraceContext): Future[Map[Member, KeyCollection]]
}
@ -361,10 +384,14 @@ trait ParticipantTopologySnapshotClient {
// used by domain to fetch all participants
@Deprecated(since = "3.0")
def participants(): Future[Seq[(ParticipantId, ParticipantPermission)]]
def participants()(implicit
traceContext: TraceContext
): Future[Seq[(ParticipantId, ParticipantPermission)]]
/** Checks whether the provided participant exists and is active */
def isParticipantActive(participantId: ParticipantId): Future[Boolean]
def isParticipantActive(participantId: ParticipantId)(implicit
traceContext: TraceContext
): Future[Boolean]
/** Checks whether the provided participant exists, is active and can login at the given point in time
*
@ -373,7 +400,7 @@ trait ParticipantTopologySnapshotClient {
def isParticipantActiveAndCanLoginAt(
participantId: ParticipantId,
timestamp: CantonTimestamp,
): Future[Boolean]
)(implicit traceContext: TraceContext): Future[Boolean]
}
@ -381,16 +408,20 @@ trait ParticipantTopologySnapshotClient {
trait MediatorDomainStateClient {
this: BaseTopologySnapshotClient =>
def mediatorGroups(): Future[Seq[MediatorGroup]]
def mediatorGroups()(implicit traceContext: TraceContext): Future[Seq[MediatorGroup]]
def isMediatorActive(mediatorId: MediatorId): Future[Boolean] =
def isMediatorActive(mediatorId: MediatorId)(implicit
traceContext: TraceContext
): Future[Boolean] =
mediatorGroups().map(_.exists { group =>
// Note: mediator in group.passive should still be able to authenticate and process MediatorResponses,
// only sending the verdicts is disabled and verdicts from a passive mediator should not pass the checks
group.isActive && (group.active.contains(mediatorId) || group.passive.contains(mediatorId))
})
def isMediatorActive(mediator: MediatorRef): Future[Boolean] = {
def isMediatorActive(
mediator: MediatorRef
)(implicit traceContext: TraceContext): Future[Boolean] = {
mediator match {
case MediatorRef.Single(mediatorId) =>
isMediatorActive(mediatorId)
@ -404,6 +435,8 @@ trait MediatorDomainStateClient {
def mediatorGroupsOfAll(
groups: Seq[MediatorGroupIndex]
)(implicit
traceContext: TraceContext
): EitherT[Future, Seq[MediatorGroupIndex], Seq[MediatorGroup]] =
if (groups.isEmpty) EitherT.rightT(Seq.empty)
else
@ -420,7 +453,9 @@ trait MediatorDomainStateClient {
}
)
def mediatorGroup(index: MediatorGroupIndex): Future[Option[MediatorGroup]] = {
def mediatorGroup(
index: MediatorGroupIndex
)(implicit traceContext: TraceContext): Future[Option[MediatorGroup]] = {
mediatorGroups().map(_.find(_.index == index))
}
}
@ -430,7 +465,7 @@ trait SequencerDomainStateClient {
this: BaseTopologySnapshotClient =>
/** returns the sequencer group */
def sequencerGroup(): Future[Option[SequencerGroup]]
def sequencerGroup()(implicit traceContext: TraceContext): Future[Option[SequencerGroup]]
}
trait VettedPackagesSnapshotClient {
@ -447,7 +482,7 @@ trait VettedPackagesSnapshotClient {
def findUnvettedPackagesOrDependencies(
participantId: ParticipantId,
packages: Set[PackageId],
): EitherT[Future, PackageId, Set[PackageId]]
)(implicit traceContext: TraceContext): EitherT[Future, PackageId, Set[PackageId]]
}
@ -494,9 +529,9 @@ trait DomainGovernanceSnapshotClient {
trait MembersTopologySnapshotClient {
this: BaseTopologySnapshotClient =>
def allMembers(): Future[Set[Member]]
def allMembers()(implicit traceContext: TraceContext): Future[Set[Member]]
def isMemberKnown(member: Member): Future[Boolean]
def isMemberKnown(member: Member)(implicit traceContext: TraceContext): Future[Boolean]
}
trait TopologySnapshot
@ -609,20 +644,47 @@ private[client] trait KeyTopologySnapshotClientLoader extends KeyTopologySnapsho
this: BaseTopologySnapshotClient =>
/** abstract loading function used to obtain the full key collection for a key owner */
def allKeys(owner: Member): Future[KeyCollection]
def allKeys(owner: Member)(implicit traceContext: TraceContext): Future[KeyCollection]
override def signingKey(owner: Member): Future[Option[SigningPublicKey]] =
def allKeys(members: Seq[Member])(implicit
traceContext: TraceContext
): Future[Map[Member, KeyCollection]]
override def signingKey(owner: Member)(implicit
traceContext: TraceContext
): Future[Option[SigningPublicKey]] =
allKeys(owner).map(_.signingKeys.lastOption)
override def signingKeys(owner: Member): Future[Seq[SigningPublicKey]] =
override def signingKeys(members: Seq[Member])(implicit
traceContext: TraceContext
): Future[Map[Member, Seq[SigningPublicKey]]] =
allKeys(members).map(_.view.mapValues(_.signingKeys).toMap)
override def signingKeys(owner: Member)(implicit
traceContext: TraceContext
): Future[Seq[SigningPublicKey]] =
allKeys(owner).map(_.signingKeys)
override def encryptionKey(owner: Member): Future[Option[EncryptionPublicKey]] =
override def encryptionKey(owner: Member)(implicit
traceContext: TraceContext
): Future[Option[EncryptionPublicKey]] =
allKeys(owner).map(_.encryptionKeys.lastOption)
override def encryptionKeys(owner: Member): Future[Seq[EncryptionPublicKey]] =
/** returns newest encryption public key */
def encryptionKey(members: Seq[Member])(implicit
traceContext: TraceContext
): Future[Map[Member, EncryptionPublicKey]] =
encryptionKeys(members).map(_.mapFilter(_.lastOption))
override def encryptionKeys(owner: Member)(implicit
traceContext: TraceContext
): Future[Seq[EncryptionPublicKey]] =
allKeys(owner).map(_.encryptionKeys)
override def encryptionKeys(members: Seq[Member])(implicit
traceContext: TraceContext
): Future[Map[Member, Seq[EncryptionPublicKey]]] =
allKeys(members).map(_.view.mapValues(_.encryptionKeys).toMap)
}
/** An internal interface with a simpler lookup function which can be implemented efficiently with caching and reading from a store */
@ -630,45 +692,39 @@ private[client] trait ParticipantTopologySnapshotLoader extends ParticipantTopol
this: BaseTopologySnapshotClient =>
override def isParticipantActive(participantId: ParticipantId): Future[Boolean] =
participantState(participantId).map(_.permission.isActive)
override def isParticipantActive(participantId: ParticipantId)(implicit
traceContext: TraceContext
): Future[Boolean] =
findParticipantState(participantId).map(_.exists(_.permission.isActive))
override def isParticipantActiveAndCanLoginAt(
participantId: ParticipantId,
timestamp: CantonTimestamp,
): Future[Boolean] =
participantState(participantId).map { attributes =>
attributes.permission.isActive && attributes.loginAfter.forall(_ <= timestamp)
)(implicit traceContext: TraceContext): Future[Boolean] =
findParticipantState(participantId).map { attributesO =>
attributesO.exists(attr => attr.permission.isActive && attr.loginAfter.forall(_ <= timestamp))
}
def findParticipantState(participantId: ParticipantId): Future[Option[ParticipantAttributes]]
def participantState(participantId: ParticipantId): Future[ParticipantAttributes] =
findParticipantState(participantId).map(
_.getOrElse(
ParticipantAttributes(
ParticipantPermission.Disabled,
TrustLevel.Ordinary,
loginAfter = None,
)
)
)
final def findParticipantState(participantId: ParticipantId)(implicit
traceContext: TraceContext
): Future[Option[ParticipantAttributes]] =
loadParticipantStates(Seq(participantId)).map(_.get(participantId))
/** abstract loading function used to load the participant state for the given set of participant-ids */
def loadParticipantStates(
participants: Seq[ParticipantId]
): Future[Map[ParticipantId, ParticipantAttributes]]
)(implicit traceContext: TraceContext): Future[Map[ParticipantId, ParticipantAttributes]]
}
private[client] trait PartyTopologySnapshotBaseClient {
this: PartyTopologySnapshotClient with BaseTopologySnapshotClient =>
this: PartyTopologySnapshotClient & BaseTopologySnapshotClient =>
override def allHaveActiveParticipants(
parties: Set[LfPartyId],
check: (ParticipantPermission => Boolean) = _.isActive,
): EitherT[Future, Set[LfPartyId], Unit] = {
)(implicit traceContext: TraceContext): EitherT[Future, Set[LfPartyId], Unit] = {
val fetchedF = activeParticipantsOfPartiesWithAttributes(parties.toSeq)
EitherT(
fetchedF
@ -687,46 +743,56 @@ private[client] trait PartyTopologySnapshotBaseClient {
}
override def isHostedByAtLeastOneParticipantF(
party: LfPartyId,
check: ParticipantAttributes => Boolean,
): Future[Boolean] =
activeParticipantsOf(party).map(_.values.exists(check))
parties: Set[LfPartyId],
check: (LfPartyId, ParticipantAttributes) => Boolean,
)(implicit traceContext: TraceContext): Future[Set[LfPartyId]] =
activeParticipantsOfPartiesWithAttributes(parties.toSeq).map(partiesWithAttributes =>
parties.filter(party =>
partiesWithAttributes.get(party).exists(_.values.exists(check(party, _)))
)
)
override def hostedOn(
partyId: LfPartyId,
partyIds: Set[LfPartyId],
participantId: ParticipantId,
): Future[Option[ParticipantAttributes]] =
)(implicit traceContext: TraceContext): Future[Map[LfPartyId, ParticipantAttributes]] =
// TODO(i4930) implement directly, must not return DISABLED
activeParticipantsOf(partyId).map(_.get(participantId))
activeParticipantsOfPartiesWithAttributes(partyIds.toSeq).map(
_.flatMap { case (party, participants) =>
participants.get(participantId).map(party -> _)
}
)
override def allHostedOn(
partyIds: Set[LfPartyId],
participantId: ParticipantId,
permissionCheck: ParticipantAttributes => Boolean = _.permission.isActive,
): Future[Boolean] =
partyIds.toList
.parTraverse(hostedOn(_, participantId).map(_.exists(permissionCheck)))
.map(_.forall(x => x))
)(implicit traceContext: TraceContext): Future[Boolean] =
hostedOn(partyIds, participantId).map(partiesWithAttributes =>
partiesWithAttributes.view
.filter { case (_, attributes) => permissionCheck(attributes) }
.sizeCompare(partyIds) == 0
)
override def canConfirm(
participant: ParticipantId,
party: LfPartyId,
requiredTrustLevel: TrustLevel = TrustLevel.Ordinary,
): Future[Boolean] =
hostedOn(party, participant)
.map(
_.exists(relationship =>
relationship.permission.canConfirm && relationship.trustLevel >= requiredTrustLevel
)
parties: Set[LfPartyId],
)(implicit traceContext: TraceContext): Future[Set[LfPartyId]] =
hostedOn(parties, participant)
.map(partiesWithAttributes =>
parties.toSeq.mapFilter { case party =>
partiesWithAttributes
.get(party)
.filter(_.permission.canConfirm)
.map(_ => party)
}.toSet
)(executionContext)
override def activeParticipantsOfAll(
parties: List[LfPartyId]
): EitherT[Future, Set[LfPartyId], Set[ParticipantId]] =
)(implicit traceContext: TraceContext): EitherT[Future, Set[LfPartyId], Set[ParticipantId]] =
EitherT(for {
withActiveParticipants <- parties.parTraverse(p =>
activeParticipantsOf(p).map(pMap => p -> pMap)
)
withActiveParticipants <- activeParticipantsOfPartiesWithAttributes(parties)
(noActive, allActive) = withActiveParticipants.foldLeft(
Set.empty[LfPartyId] -> Set.empty[ParticipantId]
) { case ((noActive, allActive), (p, active)) =>
@ -739,11 +805,11 @@ private[client] trait PartyTopologySnapshotLoader
extends PartyTopologySnapshotClient
with PartyTopologySnapshotBaseClient {
this: BaseTopologySnapshotClient with ParticipantTopologySnapshotLoader =>
this: BaseTopologySnapshotClient & ParticipantTopologySnapshotLoader =>
final override def activeParticipantsOf(
party: LfPartyId
): Future[Map[ParticipantId, ParticipantAttributes]] =
)(implicit traceContext: TraceContext): Future[Map[ParticipantId, ParticipantAttributes]] =
PartyId
.fromLfParty(party)
.map(loadActiveParticipantsOf(_, loadParticipantStates).map(_.participants))
@ -752,30 +818,34 @@ private[client] trait PartyTopologySnapshotLoader
private[client] def loadActiveParticipantsOf(
party: PartyId,
participantStates: Seq[ParticipantId] => Future[Map[ParticipantId, ParticipantAttributes]],
): Future[PartyInfo]
)(implicit traceContext: TraceContext): Future[PartyInfo]
final override def activeParticipantsOfParties(
parties: Seq[LfPartyId]
): Future[Map[LfPartyId, Set[ParticipantId]]] =
)(implicit traceContext: TraceContext): Future[Map[LfPartyId, Set[ParticipantId]]] =
loadAndMapPartyInfos(parties, _.participants.keySet)
final override def activeParticipantsOfPartiesWithAttributes(
parties: Seq[LfPartyId]
)(implicit
traceContext: TraceContext
): Future[Map[LfPartyId, Map[ParticipantId, ParticipantAttributes]]] =
loadAndMapPartyInfos(parties, _.participants)
final override def partiesWithGroupAddressing(parties: Seq[LfPartyId]): Future[Set[LfPartyId]] =
final override def partiesWithGroupAddressing(parties: Seq[LfPartyId])(implicit
traceContext: TraceContext
): Future[Set[LfPartyId]] =
loadAndMapPartyInfos(parties, identity, _.groupAddressing).map(_.keySet)
final override def consortiumThresholds(
parties: Set[LfPartyId]
): Future[Map[LfPartyId, PositiveInt]] =
)(implicit traceContext: TraceContext): Future[Map[LfPartyId, PositiveInt]] =
loadAndMapPartyInfos(parties.toSeq, _.threshold)
final override def canNotSubmit(
participant: ParticipantId,
parties: Seq[LfPartyId],
): Future[immutable.Iterable[LfPartyId]] =
)(implicit traceContext: TraceContext): Future[immutable.Iterable[LfPartyId]] =
loadAndMapPartyInfos(
parties,
_ => (),
@ -790,7 +860,7 @@ private[client] trait PartyTopologySnapshotLoader
lfParties: Seq[LfPartyId],
f: PartyInfo => T,
filter: PartyInfo => Boolean = _ => true,
): Future[Map[LfPartyId, T]] =
)(implicit traceContext: TraceContext): Future[Map[LfPartyId, T]] =
loadBatchActiveParticipantsOf(
lfParties.mapFilter(PartyId.fromLfParty(_).toOption),
loadParticipantStates,
@ -801,16 +871,16 @@ private[client] trait PartyTopologySnapshotLoader
private[client] def loadBatchActiveParticipantsOf(
parties: Seq[PartyId],
loadParticipantStates: Seq[ParticipantId] => Future[Map[ParticipantId, ParticipantAttributes]],
): Future[Map[PartyId, PartyInfo]]
)(implicit traceContext: TraceContext): Future[Map[PartyId, PartyInfo]]
}
trait VettedPackagesSnapshotLoader extends VettedPackagesSnapshotClient {
this: BaseTopologySnapshotClient with PartyTopologySnapshotLoader =>
this: BaseTopologySnapshotClient & PartyTopologySnapshotLoader =>
private[client] def loadUnvettedPackagesOrDependencies(
participant: ParticipantId,
packageId: PackageId,
): EitherT[Future, PackageId, Set[PackageId]]
)(implicit traceContext: TraceContext): EitherT[Future, PackageId, Set[PackageId]]
protected def findUnvettedPackagesOrDependenciesUsingLoader(
participantId: ParticipantId,
@ -824,7 +894,7 @@ trait VettedPackagesSnapshotLoader extends VettedPackagesSnapshotClient {
override def findUnvettedPackagesOrDependencies(
participantId: ParticipantId,
packages: Set[PackageId],
): EitherT[Future, PackageId, Set[PackageId]] =
)(implicit traceContext: TraceContext): EitherT[Future, PackageId, Set[PackageId]] =
findUnvettedPackagesOrDependenciesUsingLoader(
participantId,
packages,

View File

@ -447,7 +447,7 @@ class StoreBasedTopologySnapshot(
fetchParticipantStates: Seq[ParticipantId] => Future[
Map[ParticipantId, ParticipantAttributes]
],
): Future[PartyInfo] =
)(implicit traceContext: TraceContext): Future[PartyInfo] =
loadBatchActiveParticipantsOf(Seq(party), fetchParticipantStates).map(
_.getOrElse(party, PartyInfo.EmptyPartyInfo)
)
@ -457,7 +457,7 @@ class StoreBasedTopologySnapshot(
fetchParticipantStates: Seq[ParticipantId] => Future[
Map[ParticipantId, ParticipantAttributes]
],
): Future[Map[PartyId, PartyInfo]] = {
)(implicit traceContext: TraceContext): Future[Map[PartyId, PartyInfo]] = {
def update(
party: PartyId,
mp: Map[PartyId, PartyAggregation],
@ -500,7 +500,7 @@ class StoreBasedTopologySnapshot(
val participantState =
participantStateMap.getOrElse(
participantId,
ParticipantAttributes(ParticipantPermission.Disabled, TrustLevel.Ordinary, None),
ParticipantAttributes(ParticipantPermission.Disabled, None),
)
// using the lowest permission available
val reducedPerm = ParticipantPermission.lowerOf(
@ -511,7 +511,7 @@ class StoreBasedTopologySnapshot(
participantState.permission,
),
)
(participantId, ParticipantAttributes(reducedPerm, participantState.trustLevel, None))
(participantId, ParticipantAttributes(reducedPerm, None))
}
// filter out in-active
.filter(_._2.permission.isActive)
@ -527,7 +527,7 @@ class StoreBasedTopologySnapshot(
}
}
override def allKeys(owner: Member): Future[KeyCollection] =
override def allKeys(owner: Member)(implicit traceContext: TraceContext): Future[KeyCollection] =
findTransactions(
asOfInclusive = false,
includeSecondary = false,
@ -539,19 +539,27 @@ class StoreBasedTopologySnapshot(
case TopologyStateUpdateElement(_, OwnerToKeyMapping(foundOwner, key))
if foundOwner.code == owner.code =>
key
}.foldLeft(KeyCollection(Seq(), Seq()))((acc, key) => acc.addTo(key)))
}.foldLeft(KeyCollection(Seq(), Seq()))((acc, key) => acc.add(key)))
.map { collection =>
// add initialisation keys if necessary
if (collection.signingKeys.isEmpty) {
initKeys
.get(owner)
.fold(collection)(_.foldLeft(collection)((acc, elem) => acc.addTo(elem)))
.fold(collection)(_.foldLeft(collection)((acc, elem) => acc.add(elem)))
} else {
collection
}
}
override def participants(): Future[Seq[(ParticipantId, ParticipantPermission)]] =
override def allKeys(members: Seq[Member])(implicit
traceContext: TraceContext
): Future[Map[Member, KeyCollection]] = throw new NotImplementedError(
"programming error: allKeysForMembers called on canton 2.x code path."
)
override def participants()(implicit
traceContext: TraceContext
): Future[Seq[(ParticipantId, ParticipantPermission)]] =
findTransactions(
asOfInclusive = false,
includeSecondary = false,
@ -574,13 +582,13 @@ class StoreBasedTopologySnapshot(
override def loadParticipantStates(
participants: Seq[ParticipantId]
): Future[Map[ParticipantId, ParticipantAttributes]] = {
)(implicit traceContext: TraceContext): Future[Map[ParticipantId, ParticipantAttributes]] = {
def merge(
current: (Option[ParticipantAttributes], Option[ParticipantAttributes]),
ps: ParticipantState,
): (Option[ParticipantAttributes], Option[ParticipantAttributes]) = {
val (from, to) = current
val rel = ParticipantAttributes(ps.permission, ps.trustLevel, None)
val rel = ParticipantAttributes(ps.permission, None)
def mix(cur: Option[ParticipantAttributes]) = Some(cur.getOrElse(rel).merge(rel))
@ -621,7 +629,6 @@ class StoreBasedTopologySnapshot(
Some(
ParticipantAttributes(
ParticipantPermission.lowerOf(lft.permission, rght.permission),
lft.trustLevel,
None,
)
)
@ -633,17 +640,12 @@ class StoreBasedTopologySnapshot(
}
}
override def findParticipantState(
participantId: ParticipantId
): Future[Option[ParticipantAttributes]] =
loadParticipantStates(Seq(participantId)).map(_.get(participantId))
/** Returns a list of all known parties on this domain */
override def inspectKeys(
filterOwner: String,
filterOwnerType: Option[MemberCode],
limit: Int,
): Future[Map[Member, KeyCollection]] = {
)(implicit traceContext: TraceContext): Future[Map[Member, KeyCollection]] = {
store
.inspect(
stateStore = useStateTxs,
@ -668,7 +670,7 @@ class StoreBasedTopologySnapshot(
(
owner,
keys.foldLeft(KeyCollection.empty) { case (col, (_, publicKey)) =>
col.addTo(publicKey)
col.add(publicKey)
},
)
}
@ -681,13 +683,13 @@ class StoreBasedTopologySnapshot(
filterParty: String,
filterParticipant: String,
limit: Int,
): Future[Set[PartyId]] =
)(implicit traceContext: TraceContext): Future[Set[PartyId]] =
store.inspectKnownParties(timestamp, filterParty, filterParticipant, limit)
override private[client] def loadUnvettedPackagesOrDependencies(
participant: ParticipantId,
packageId: PackageId,
): EitherT[Future, PackageId, Set[PackageId]] = {
)(implicit traceContext: TraceContext): EitherT[Future, PackageId, Set[PackageId]] = {
val vettedET = EitherT.right[PackageId](
findTransactions(
@ -726,60 +728,65 @@ class StoreBasedTopologySnapshot(
* for singular mediators each one must be wrapped into its own group with threshold = 1
* group index in 2.0 topology management is not used and the order of output does not need to be stable
*/
override def mediatorGroups(): Future[Seq[MediatorGroup]] = findTransactions(
asOfInclusive = false,
includeSecondary = false,
types = Seq(DomainTopologyTransactionType.MediatorDomainState),
filterUid = None,
filterNamespace = None,
).map { res =>
ArraySeq
.from(
res.toTopologyState
.foldLeft(Map.empty[MediatorId, (Boolean, Boolean)]) {
case (acc, TopologyStateUpdateElement(_, MediatorDomainState(side, _, mediator))) =>
acc + (mediator -> RequestSide
.accumulateSide(acc.getOrElse(mediator, (false, false)), side))
case (acc, _) => acc
}
.filter { case (_, (lft, rght)) =>
lft && rght
}
.keys
)
.zipWithIndex
.map { case (id, index) =>
MediatorGroup(
index = NonNegativeInt.tryCreate(index),
Seq(id),
Seq.empty,
threshold = PositiveInt.one,
override def mediatorGroups()(implicit traceContext: TraceContext): Future[Seq[MediatorGroup]] =
findTransactions(
asOfInclusive = false,
includeSecondary = false,
types = Seq(DomainTopologyTransactionType.MediatorDomainState),
filterUid = None,
filterNamespace = None,
).map { res =>
ArraySeq
.from(
res.toTopologyState
.foldLeft(Map.empty[MediatorId, (Boolean, Boolean)]) {
case (acc, TopologyStateUpdateElement(_, MediatorDomainState(side, _, mediator))) =>
acc + (mediator -> RequestSide
.accumulateSide(acc.getOrElse(mediator, (false, false)), side))
case (acc, _) => acc
}
.filter { case (_, (lft, rght)) =>
lft && rght
}
.keys
)
}
}
.zipWithIndex
.map { case (id, index) =>
MediatorGroup(
index = NonNegativeInt.tryCreate(index),
Seq(id),
Seq.empty,
threshold = PositiveInt.one,
)
}
}
/** returns the current sequencer group if known
* TODO(#14048): Decide whether it is advantageous e.g. for testing to expose a sequencer-group on daml 2.*
* perhaps we cook up a SequencerId based on the domainId assuming that the sequencer (or sequencers all with the
* same sequencerId) is/are active
*/
override def sequencerGroup(): Future[Option[SequencerGroup]] = Future.failed(
override def sequencerGroup()(implicit
traceContext: TraceContext
): Future[Option[SequencerGroup]] = Future.failed(
new UnsupportedOperationException(
"SequencerGroup lookup not supported by StoreBasedDomainTopologyClient. This is a coding bug."
)
)
override def allMembers(): Future[Set[Member]] = Future.failed(
new UnsupportedOperationException(
"Lookup of all members is not supported by StoredBasedDomainTopologyClient. This is a coding bug."
override def allMembers()(implicit traceContext: TraceContext): Future[Set[Member]] =
Future.failed(
new UnsupportedOperationException(
"Lookup of all members is not supported by StoredBasedDomainTopologyClient. This is a coding bug."
)
)
)
override def isMemberKnown(member: Member): Future[Boolean] = Future.failed(
new UnsupportedOperationException(
"Lookup of members via isMemberKnown is not supported by StoredBasedDomainTopologyClient. This is a coding bug."
override def isMemberKnown(member: Member)(implicit traceContext: TraceContext): Future[Boolean] =
Future.failed(
new UnsupportedOperationException(
"Lookup of members via isMemberKnown is not supported by StoredBasedDomainTopologyClient. This is a coding bug."
)
)
)
override def findDynamicDomainParameters()(implicit
traceContext: TraceContext
@ -853,7 +860,7 @@ class StoreBasedTopologySnapshot(
override def trafficControlStatus(
members: Seq[Member]
): Future[Map[Member, Option[MemberTrafficControlState]]] = {
)(implicit traceContext: TraceContext): Future[Map[Member, Option[MemberTrafficControlState]]] = {
// Non-X topology management does not support traffic control transactions
Future.successful(members.map(_ -> None).toMap)
}
@ -864,7 +871,7 @@ class StoreBasedTopologySnapshot(
*/
override def authorityOf(
parties: Set[LfPartyId]
): Future[PartyTopologySnapshotClient.AuthorityOfResponse] =
)(implicit traceContext: TraceContext): Future[PartyTopologySnapshotClient.AuthorityOfResponse] =
Future.successful(
PartyTopologySnapshotClient.AuthorityOfResponse(
parties.map(partyId => partyId -> nonConsortiumPartyDelegation(partyId)).toMap

View File

@ -9,6 +9,7 @@ import com.daml.lf.data.Ref.PackageId
import com.digitalasset.canton.LfPartyId
import com.digitalasset.canton.concurrent.FutureSupervisor
import com.digitalasset.canton.config.ProcessingTimeout
import com.digitalasset.canton.crypto.KeyPurpose
import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.logging.{NamedLoggerFactory, NamedLogging}
import com.digitalasset.canton.protocol.DynamicDomainParametersWithValidity
@ -20,8 +21,9 @@ import com.digitalasset.canton.topology.client.PartyTopologySnapshotClient.{
PartyInfo,
}
import com.digitalasset.canton.topology.store.*
import com.digitalasset.canton.topology.transaction.TopologyChangeOpX.Replace
import com.digitalasset.canton.topology.transaction.*
import com.digitalasset.canton.tracing.{NoTracing, TraceContext}
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.ErrorUtil
import com.digitalasset.canton.version.ProtocolVersion
@ -76,8 +78,7 @@ class StoreBasedTopologySnapshotX(
val loggerFactory: NamedLoggerFactory,
)(implicit val executionContext: ExecutionContext)
extends TopologySnapshotLoader
with NamedLogging
with NoTracing {
with NamedLogging {
private def findTransactions(
asOfInclusive: Boolean,
@ -100,7 +101,7 @@ class StoreBasedTopologySnapshotX(
override private[client] def loadUnvettedPackagesOrDependencies(
participant: ParticipantId,
packageId: PackageId,
): EitherT[Future, PackageId, Set[PackageId]] = {
)(implicit traceContext: TraceContext): EitherT[Future, PackageId, Set[PackageId]] = {
val vettedET = EitherT.right[PackageId](
findTransactions(
@ -216,7 +217,7 @@ class StoreBasedTopologySnapshotX(
override private[client] def loadActiveParticipantsOf(
party: PartyId,
participantStates: Seq[ParticipantId] => Future[Map[ParticipantId, ParticipantAttributes]],
): Future[PartyInfo] =
)(implicit traceContext: TraceContext): Future[PartyInfo] =
loadBatchActiveParticipantsOf(Seq(party), participantStates).map(
_.getOrElse(party, PartyInfo.EmptyPartyInfo)
)
@ -224,7 +225,7 @@ class StoreBasedTopologySnapshotX(
override private[client] def loadBatchActiveParticipantsOf(
parties: Seq[PartyId],
loadParticipantStates: Seq[ParticipantId] => Future[Map[ParticipantId, ParticipantAttributes]],
): Future[Map[PartyId, PartyInfo]] = {
)(implicit traceContext: TraceContext): Future[Map[PartyId, PartyInfo]] = {
def collectLatestByType[M <: TopologyMappingX: ClassTag](
storedTransactions: StoredTopologyTransactionsX[
@ -324,7 +325,6 @@ class StoreBasedTopologySnapshotX(
}
participantId -> ParticipantAttributes(
reducedPermission,
participantAttributes.trustLevel,
None,
)
}
@ -350,27 +350,32 @@ class StoreBasedTopologySnapshotX(
}
/** returns the list of currently known mediator groups */
override def mediatorGroups(): Future[Seq[MediatorGroup]] = findTransactions(
asOfInclusive = false,
types = Seq(TopologyMappingX.Code.MediatorDomainStateX),
filterUid = None,
filterNamespace = None,
).map(
_.collectOfMapping[MediatorDomainStateX].result
.groupBy(_.transaction.transaction.mapping.group)
.map { case (groupId, seq) =>
val mds = collectLatestMapping(
TopologyMappingX.Code.MediatorDomainStateX,
seq.sortBy(_.validFrom),
)
.getOrElse(throw new IllegalStateException("Group-by would not have produced empty seq"))
MediatorGroup(groupId, mds.active, mds.observers, mds.threshold)
}
.toSeq
.sortBy(_.index)
)
override def mediatorGroups()(implicit traceContext: TraceContext): Future[Seq[MediatorGroup]] =
findTransactions(
asOfInclusive = false,
types = Seq(TopologyMappingX.Code.MediatorDomainStateX),
filterUid = None,
filterNamespace = None,
).map(
_.collectOfMapping[MediatorDomainStateX].result
.groupBy(_.transaction.transaction.mapping.group)
.map { case (groupId, seq) =>
val mds = collectLatestMapping(
TopologyMappingX.Code.MediatorDomainStateX,
seq.sortBy(_.validFrom),
)
.getOrElse(
throw new IllegalStateException("Group-by would not have produced empty seq")
)
MediatorGroup(groupId, mds.active, mds.observers, mds.threshold)
}
.toSeq
.sortBy(_.index)
)
override def sequencerGroup(): Future[Option[SequencerGroup]] = findTransactions(
override def sequencerGroup()(implicit
traceContext: TraceContext
): Future[Option[SequencerGroup]] = findTransactions(
asOfInclusive = false,
types = Seq(TopologyMappingX.Code.SequencerDomainStateX),
filterUid = None,
@ -386,40 +391,43 @@ class StoreBasedTopologySnapshotX(
def trafficControlStatus(
members: Seq[Member]
): Future[Map[Member, Option[MemberTrafficControlState]]] = findTransactions(
asOfInclusive = false,
types = Seq(TopologyMappingX.Code.TrafficControlStateX),
filterUid = Some(members.map(_.uid)),
filterNamespace = None,
).map { txs =>
val membersWithState = txs
.collectOfMapping[TrafficControlStateX]
.result
.groupBy(_.transaction.transaction.mapping.member)
.flatMap { case (member, mappings) =>
collectLatestMapping(
TopologyMappingX.Code.TrafficControlStateX,
mappings.sortBy(_.validFrom),
).map(mapping =>
Some(MemberTrafficControlState(totalExtraTrafficLimit = mapping.totalExtraTrafficLimit))
).map(member -> _)
}
)(implicit traceContext: TraceContext): Future[Map[Member, Option[MemberTrafficControlState]]] =
findTransactions(
asOfInclusive = false,
types = Seq(TopologyMappingX.Code.TrafficControlStateX),
filterUid = Some(members.map(_.uid)),
filterNamespace = None,
).map { txs =>
val membersWithState = txs
.collectOfMapping[TrafficControlStateX]
.result
.groupBy(_.transaction.transaction.mapping.member)
.flatMap { case (member, mappings) =>
collectLatestMapping(
TopologyMappingX.Code.TrafficControlStateX,
mappings.sortBy(_.validFrom),
).map(mapping =>
Some(MemberTrafficControlState(totalExtraTrafficLimit = mapping.totalExtraTrafficLimit))
).map(member -> _)
}
val membersWithoutState = members.toSet.diff(membersWithState.keySet).map(_ -> None).toMap
val membersWithoutState = members.toSet.diff(membersWithState.keySet).map(_ -> None).toMap
membersWithState ++ membersWithoutState
}
membersWithState ++ membersWithoutState
}
/** Returns a list of all known parties on this domain */
override def inspectKnownParties(
filterParty: String,
filterParticipant: String,
limit: Int,
): Future[Set[PartyId]] =
)(implicit traceContext: TraceContext): Future[Set[PartyId]] =
store.inspectKnownParties(timestamp, filterParty, filterParticipant, limit)
/** Returns authority-of delegations for consortium parties or self/1 for non consortium parties */
override def authorityOf(parties: Set[LfPartyId]): Future[AuthorityOfResponse] = findTransactions(
override def authorityOf(
parties: Set[LfPartyId]
)(implicit traceContext: TraceContext): Future[AuthorityOfResponse] = findTransactions(
asOfInclusive = false,
types = Seq(TopologyMappingX.Code.AuthorityOfX),
filterUid = None,
@ -457,7 +465,7 @@ class StoreBasedTopologySnapshotX(
filterOwner: String,
filterOwnerType: Option[MemberCode],
limit: Int,
): Future[Map[Member, KeyCollection]] = {
)(implicit traceContext: TraceContext): Future[Map[Member, KeyCollection]] = {
store
.inspect(
proposals = false,
@ -485,19 +493,77 @@ class StoreBasedTopologySnapshotX(
)
owner -> okm
.fold(keys)(_.keys.take(limit).foldLeft(keys) { case (keys, key) =>
keys.addTo(key)
keys.add(key)
})
}
)
}
override def findParticipantState(
participantId: ParticipantId
): Future[Option[ParticipantAttributes]] =
loadParticipantStates(Seq(participantId)).map(_.get(participantId))
private val keysRequiredForParticipants = Set(KeyPurpose.Signing, KeyPurpose.Encryption)
private def getParticipantsWithCertificates(
storedTxs: StoredTopologyTransactionsX[Replace, TopologyMappingX]
)(implicit traceContext: TraceContext): Set[ParticipantId] = storedTxs
.collectOfMapping[DomainTrustCertificateX]
.result
.groupBy(_.transaction.transaction.mapping.participantId)
.collect { case (pid, seq) =>
// invoke collectLatestMapping only to warn in case a participantId's domain trust certificate is not unique
collectLatestMapping(
TopologyMappingX.Code.DomainTrustCertificateX,
seq.sortBy(_.validFrom),
).discard
pid
}
.toSet
private def getParticipantsWithCertAndKeys(
storedTxs: StoredTopologyTransactionsX[Replace, TopologyMappingX],
participantsWithCertificates: Set[ParticipantId],
)(implicit traceContext: TraceContext): Set[ParticipantId] = {
storedTxs
.collectOfMapping[OwnerToKeyMappingX]
.result
.groupBy(_.transaction.transaction.mapping.member)
.collect {
case (pid: ParticipantId, seq)
if participantsWithCertificates(pid) && collectLatestMapping(
TopologyMappingX.Code.OwnerToKeyMappingX,
seq.sortBy(_.validFrom),
).exists(otk =>
keysRequiredForParticipants.diff(otk.keys.forgetNE.map(_.purpose).toSet).isEmpty
) =>
pid
}
.toSet
}
private def getParticipantDomainPermissions(
storedTxs: StoredTopologyTransactionsX[Replace, TopologyMappingX],
participantsWithCertAndKeys: Set[ParticipantId],
)(implicit traceContext: TraceContext): Map[ParticipantId, ParticipantDomainPermissionX] = {
storedTxs
.collectOfMapping[ParticipantDomainPermissionX]
.result
.groupBy(_.transaction.transaction.mapping.participantId)
.collect {
case (pid, seq) if participantsWithCertAndKeys(pid) =>
val mapping =
collectLatestMapping(
TopologyMappingX.Code.ParticipantDomainPermissionX,
seq.sortBy(_.validFrom),
)
.getOrElse(
throw new IllegalStateException("Group-by would not have produced empty seq")
)
pid -> mapping
}
}
private def loadParticipantStatesHelper(
participantsFilter: Seq[ParticipantId]
)(implicit
traceContext: TraceContext
): Future[Map[ParticipantId, ParticipantDomainPermissionX]] = {
for {
// Looks up domain parameters for default rate limits.
@ -514,77 +580,35 @@ class StoreBasedTopologySnapshotX(
transactions.collectOfMapping[DomainParametersStateX].result,
).getOrElse(throw new IllegalStateException("Unable to locate domain parameters state"))
)
// 1. Participant needs to have requested access to domain by issuing a domain trust certificate
participantsWithCertificates <- findTransactions(
storedTxs <- findTransactions(
asOfInclusive = false,
types = Seq(
TopologyMappingX.Code.DomainTrustCertificateX
TopologyMappingX.Code.DomainTrustCertificateX,
TopologyMappingX.Code.OwnerToKeyMappingX,
TopologyMappingX.Code.ParticipantDomainPermissionX,
),
filterUid = Some(participantsFilter.map(_.uid)),
filterNamespace = None,
).map(
_.collectOfMapping[DomainTrustCertificateX].result
.groupBy(_.transaction.transaction.mapping.participantId)
.collect { case (pid, seq) =>
// invoke collectLatestMapping only to warn in case a participantId's domain trust certificate is not unique
collectLatestMapping(
TopologyMappingX.Code.DomainTrustCertificateX,
seq.sortBy(_.validFrom),
).discard
pid
}
.toSeq
)
} yield {
// 1. Participant needs to have requested access to domain by issuing a domain trust certificate
val participantsWithCertificates = getParticipantsWithCertificates(storedTxs)
// 2. Participant needs to have keys registered on the domain
participantsWithCertAndKeys <- findTransactions(
asOfInclusive = false,
types = Seq(TopologyMappingX.Code.OwnerToKeyMappingX),
filterUid = Some(participantsWithCertificates.map(_.uid)),
filterNamespace = None,
).map(
_.collectOfMapping[OwnerToKeyMappingX].result
.groupBy(_.transaction.transaction.mapping.member)
.collect {
case (pid: ParticipantId, seq)
if collectLatestMapping(
TopologyMappingX.Code.OwnerToKeyMappingX,
seq.sortBy(_.validFrom),
).nonEmpty =>
pid
}
)
val participantsWithCertAndKeys =
getParticipantsWithCertAndKeys(storedTxs, participantsWithCertificates)
// Warn about participants with cert but no keys
_ = (participantsWithCertificates.toSet -- participantsWithCertAndKeys.toSet).foreach { pid =>
(participantsWithCertificates -- participantsWithCertAndKeys).foreach { pid =>
logger.warn(
s"Participant ${pid} has a domain trust certificate, but no keys on domain ${domainParametersState.domain}"
)
}
// 3. Attempt to look up permissions/trust from participant domain permission
participantDomainPermissions <- findTransactions(
asOfInclusive = false,
types = Seq(
TopologyMappingX.Code.ParticipantDomainPermissionX
),
filterUid = Some(participantsWithCertAndKeys.map(_.uid).toSeq),
filterNamespace = None,
).map(
_.collectOfMapping[ParticipantDomainPermissionX].result
.groupBy(_.transaction.transaction.mapping.participantId)
.map { case (pid, seq) =>
val mapping =
collectLatestMapping(
TopologyMappingX.Code.ParticipantDomainPermissionX,
seq.sortBy(_.validFrom),
)
.getOrElse(
throw new IllegalStateException("Group-by would not have produced empty seq")
)
pid -> mapping
}
)
val participantDomainPermissions =
getParticipantDomainPermissions(storedTxs, participantsWithCertAndKeys)
// 4. Apply default permissions/trust of submission/ordinary if missing participant domain permission and
// grab rate limits from dynamic domain parameters if not specified
participantIdDomainPermissionsMap = participantsWithCertAndKeys.map { pid =>
val participantIdDomainPermissionsMap = participantsWithCertAndKeys.map { pid =>
pid -> participantDomainPermissions
.getOrElse(
pid,
@ -592,13 +616,14 @@ class StoreBasedTopologySnapshotX(
)
.setDefaultLimitIfNotSet(domainParametersState.parameters.v2DefaultParticipantLimits)
}.toMap
} yield participantIdDomainPermissionsMap
participantIdDomainPermissionsMap
}
}
/** abstract loading function used to load the participant state for the given set of participant-ids */
override def loadParticipantStates(
participants: Seq[ParticipantId]
): Future[Map[ParticipantId, ParticipantAttributes]] =
)(implicit traceContext: TraceContext): Future[Map[ParticipantId, ParticipantAttributes]] =
if (participants.isEmpty)
Future.successful(Map())
else
@ -606,7 +631,9 @@ class StoreBasedTopologySnapshotX(
pid -> pdp.toParticipantAttributes
})
override def participants(): Future[Seq[(ParticipantId, ParticipantPermission)]] =
override def participants()(implicit
traceContext: TraceContext
): Future[Seq[(ParticipantId, ParticipantPermission)]] =
Future.failed(
new UnsupportedOperationException(
s"Participants lookup not supported by StoreBasedDomainTopologyClientX. This is a coding bug."
@ -614,21 +641,32 @@ class StoreBasedTopologySnapshotX(
)
/** abstract loading function used to obtain the full key collection for a key owner */
override def allKeys(owner: Member): Future[KeyCollection] = findTransactions(
asOfInclusive = false,
types = Seq(TopologyMappingX.Code.OwnerToKeyMappingX),
filterUid = Some(Seq(owner.uid)),
filterNamespace = None,
)
.map { transactions =>
val keys = KeyCollection(Seq(), Seq())
collectLatestMapping[OwnerToKeyMappingX](
TopologyMappingX.Code.OwnerToKeyMappingX,
transactions.collectOfMapping[OwnerToKeyMappingX].result,
).fold(keys)(_.keys.foldLeft(keys) { case (keys, key) => keys.addTo(key) })
override def allKeys(owner: Member)(implicit traceContext: TraceContext): Future[KeyCollection] =
allKeys(Seq(owner)).map(_.getOrElse(owner, KeyCollection.empty))
override def allKeys(
members: Seq[Member]
)(implicit traceContext: TraceContext): Future[Map[Member, KeyCollection]] =
findTransactions(
asOfInclusive = false,
types = Seq(TopologyMappingX.Code.OwnerToKeyMappingX),
filterUid = Some(members.map(_.uid)),
filterNamespace = None,
).map { transactions =>
transactions
.collectOfMapping[OwnerToKeyMappingX]
.result
.groupBy(_.mapping.member)
.map { case (member, otks) =>
val keys = collectLatestMapping[OwnerToKeyMappingX](
TopologyMappingX.Code.OwnerToKeyMappingX,
otks.sortBy(_.validFrom),
).toList.flatMap(_.keys.forgetNE)
member -> KeyCollection.empty.addAll(keys)
}
}
override def allMembers(): Future[Set[Member]] = {
override def allMembers()(implicit traceContext: TraceContext): Future[Set[Member]] = {
findTransactions(
asOfInclusive = false,
types = Seq(
@ -651,7 +689,9 @@ class StoreBasedTopologySnapshotX(
)
}
override def isMemberKnown(member: Member): Future[Boolean] = {
override def isMemberKnown(
member: Member
)(implicit traceContext: TraceContext): Future[Boolean] = {
member match {
case ParticipantId(pid) =>
findTransactions(
@ -692,11 +732,14 @@ class StoreBasedTopologySnapshotX(
private def collectLatestMapping[T <: TopologyMappingX](
typ: TopologyMappingX.Code,
transactions: Seq[StoredTopologyTransactionX[TopologyChangeOpX.Replace, T]],
): Option[T] = collectLatestTransaction(typ, transactions).map(_.transaction.transaction.mapping)
)(implicit traceContext: TraceContext): Option[T] =
collectLatestTransaction(typ, transactions).map(_.transaction.transaction.mapping)
private def collectLatestTransaction[T <: TopologyMappingX](
typ: TopologyMappingX.Code,
transactions: Seq[StoredTopologyTransactionX[TopologyChangeOpX.Replace, T]],
)(implicit
traceContext: TraceContext
): Option[StoredTopologyTransactionX[TopologyChangeOpX.Replace, T]] = {
if (transactions.sizeCompare(1) > 0) {
logger.warn(

View File

@ -145,7 +145,7 @@ object DomainTopologyTransactionMessageValidator {
.filter(_.transaction.op == TopologyChangeOp.Add)
.map(_.transaction.element.mapping)
.exists {
case ParticipantState(RequestSide.From, domain, participant, permission, _) =>
case ParticipantState(RequestSide.From, domain, participant, permission) =>
permission.isActive && domain == client.domainId && participant == member
case _ => false
},

View File

@ -927,7 +927,7 @@ object TopologyStore {
false
case OwnerToKeyMapping(pid, _) => pid == participantId
case SignedLegalIdentityClaim(uid, _, _) => uid == participantId.uid
case ParticipantState(_, _, pid, _, _) => pid == participantId
case ParticipantState(_, _, pid, _) => pid == participantId
case PartyToParticipant(_, _, _, _) => false
case VettedPackages(_, _) => false
case MediatorDomainState(_, _, _) => false

View File

@ -10,25 +10,14 @@ import com.digitalasset.canton.logging.pretty.{Pretty, PrettyPrinting}
import com.digitalasset.canton.protocol.v30
import com.digitalasset.canton.serialization.ProtoConverter.ParsingResult
/** If [[trustLevel]] is [[TrustLevel.Vip]],
* then [[permission]]`.`[[ParticipantPermission.canConfirm canConfirm]] must hold.
*/
final case class ParticipantAttributes(
permission: ParticipantPermission,
trustLevel: TrustLevel,
loginAfter: Option[CantonTimestamp] = None,
) {
// Make sure that VIPs can always confirm so that
// downstream code does not have to handle VIPs that cannot confirm.
require(
trustLevel != TrustLevel.Vip || permission.canConfirm,
"Found a Vip that cannot confirm. This is not supported.",
)
def merge(elem: ParticipantAttributes): ParticipantAttributes =
ParticipantAttributes(
permission = ParticipantPermission.lowerOf(permission, elem.permission),
trustLevel = TrustLevel.lowerOf(trustLevel, elem.trustLevel),
loginAfter = loginAfter.max(elem.loginAfter),
)
@ -118,11 +107,6 @@ sealed trait TrustLevel extends Product with Serializable with PrettyPrinting {
def rank: Byte
override def pretty: Pretty[TrustLevel] = prettyOfObject[TrustLevel]
def toX: TrustLevelX = this match {
case TrustLevel.Ordinary => TrustLevelX.Ordinary
case TrustLevel.Vip => TrustLevelX.Vip
}
}
object TrustLevel {

View File

@ -405,17 +405,11 @@ final case class ParticipantState(
domain: DomainId,
participant: ParticipantId,
permission: ParticipantPermission,
trustLevel: TrustLevel,
) extends TopologyStateUpdateMapping {
require(
permission.canConfirm || trustLevel == TrustLevel.Ordinary,
"participant trust level must either be ordinary or permission must be confirming",
)
// architecture-handbook-entry-end: ParticipantState
def toParticipantAttributes: ParticipantAttributes =
ParticipantAttributes(permission, trustLevel, None)
ParticipantAttributes(permission, None)
def toProtoV30: v30.ParticipantState = {
v30.ParticipantState(
@ -423,7 +417,8 @@ final case class ParticipantState(
domain = domain.toProtoPrimitive,
participant = participant.uid.toProtoPrimitive,
permission = permission.toProtoEnum,
trustLevel = trustLevel.toProtoEnum,
// this will be removed with removal of canton 2 protos
trustLevel = TrustLevel.Ordinary.toProtoEnum,
)
}
@ -460,9 +455,8 @@ object ParticipantState {
side <- RequestSide.fromProtoEnum(parsed.side)
domain <- DomainId.fromProtoPrimitive(parsed.domain, "domain")
permission <- ParticipantPermission.fromProtoEnum(parsed.permission)
trustLevel <- TrustLevel.fromProtoEnum(parsed.trustLevel)
uid <- UniqueIdentifier.fromProtoPrimitive(parsed.participant, "participant")
} yield ParticipantState(side, domain, ParticipantId(uid), permission, trustLevel)
} yield ParticipantState(side, domain, ParticipantId(uid), permission)
}

View File

@ -813,34 +813,6 @@ object ParticipantPermissionX {
}
}
sealed trait TrustLevelX {
def toProtoV30: v30.EnumsX.TrustLevelX
def toNonX: TrustLevel
}
object TrustLevelX {
case object Ordinary extends TrustLevelX {
lazy val toProtoV30 = v30.EnumsX.TrustLevelX.Ordinary
def toNonX: TrustLevel = TrustLevel.Ordinary
}
case object Vip extends TrustLevelX {
lazy val toProtoV30 = v30.EnumsX.TrustLevelX.Vip
def toNonX: TrustLevel = TrustLevel.Vip
}
def fromProtoV30(value: v30.EnumsX.TrustLevelX): ParsingResult[TrustLevelX] = value match {
case v30.EnumsX.TrustLevelX.Ordinary => Right(Ordinary)
case v30.EnumsX.TrustLevelX.Vip => Right(Vip)
case v30.EnumsX.TrustLevelX.MissingTrustLevel => Left(FieldNotSet(value.name))
case v30.EnumsX.TrustLevelX.Unrecognized(x) => Left(UnrecognizedEnum(value.name, x))
}
implicit val orderingTrustLevelX: Ordering[TrustLevelX] = {
val participantTrustLevelXOrderMap =
Seq[TrustLevelX](Ordinary, Vip).zipWithIndex.toMap
Ordering.by[TrustLevelX, Int](participantTrustLevelXOrderMap(_))
}
}
final case class ParticipantDomainLimits(maxRate: Int, maxNumParties: Int, maxNumPackages: Int) {
def toProto: v30.ParticipantDomainLimits =
v30.ParticipantDomainLimits(maxRate, maxNumParties, maxNumPackages)
@ -854,20 +826,18 @@ final case class ParticipantDomainPermissionX(
domainId: DomainId,
participantId: ParticipantId,
permission: ParticipantPermissionX,
trustLevel: TrustLevelX,
limits: Option[ParticipantDomainLimits],
loginAfter: Option[CantonTimestamp],
) extends TopologyMappingX {
def toParticipantAttributes: ParticipantAttributes =
ParticipantAttributes(permission.toNonX, trustLevel.toNonX, loginAfter)
ParticipantAttributes(permission.toNonX, loginAfter)
def toProto: v30.ParticipantDomainPermissionX =
v30.ParticipantDomainPermissionX(
domain = domainId.toProtoPrimitive,
participant = participantId.toProtoPrimitive,
permission = permission.toProtoV2,
trustLevel = trustLevel.toProtoV30,
limits = limits.map(_.toProto),
loginAfter = loginAfter.map(_.toProtoPrimitive),
)
@ -904,7 +874,6 @@ final case class ParticipantDomainPermissionX(
domainId,
participantId,
permission,
trustLevel,
Some(defaultLimits),
loginAfter,
)
@ -927,7 +896,6 @@ object ParticipantDomainPermissionX {
domainId,
participantId,
ParticipantPermissionX.Submission,
TrustLevelX.Ordinary,
None,
None,
)
@ -939,7 +907,6 @@ object ParticipantDomainPermissionX {
domainId <- DomainId.fromProtoPrimitive(value.domain, "domain")
participantId <- ParticipantId.fromProtoPrimitive(value.participant, "participant")
permission <- ParticipantPermissionX.fromProtoV30(value.permission)
trustLevel <- TrustLevelX.fromProtoV30(value.trustLevel)
limits = value.limits.map(ParticipantDomainLimits.fromProtoV30)
loginAfter <- value.loginAfter.fold[ParsingResult[Option[CantonTimestamp]]](Right(None))(
CantonTimestamp.fromProtoPrimitive(_).map(_.some)
@ -948,7 +915,6 @@ object ParticipantDomainPermissionX {
domainId,
participantId,
permission,
trustLevel,
limits,
loginAfter,
)

View File

@ -0,0 +1,60 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.canton.tracing
import com.digitalasset.canton.concurrent.DirectExecutionContext
import com.digitalasset.canton.logging.TracedLogger
import com.github.blemale.scaffeine.{AsyncLoadingCache, Scaffeine}
import scala.concurrent.{ExecutionContext, Future}
private[tracing] final case class TracedKey[K](key: K)(val traceContext: TraceContext)
object TracedScaffeine {
def buildTracedAsyncFuture[K1, V1](
cache: Scaffeine[Any, Any],
loader: TraceContext => K1 => Future[V1],
allLoader: Option[TraceContext => Iterable[K1] => Future[Map[K1, V1]]] = None,
)(
tracedLogger: TracedLogger
)(implicit ec: ExecutionContext): TracedAsyncLoadingCache[K1, V1] = {
new TracedAsyncLoadingCache[K1, V1](
cache.buildAsyncFuture[TracedKey[K1], V1](
loader = tracedKey => loader(tracedKey.traceContext)(tracedKey.key),
allLoader = allLoader.map { tracedFunction => (tracedKeys: Iterable[TracedKey[K1]]) =>
{
val traceContext = tracedKeys.headOption
.map(_.traceContext)
.getOrElse(TraceContext.empty)
val keys = tracedKeys.map(_.key)
tracedFunction(traceContext)(keys)
.map(_.map { case (key, value) => TracedKey(key)(traceContext) -> value })
}
},
)
)(tracedLogger)
}
}
class TracedAsyncLoadingCache[K, V](
underlying: AsyncLoadingCache[TracedKey[K], V]
)(tracedLogger: TracedLogger) {
implicit private[this] val ec: ExecutionContext = DirectExecutionContext(tracedLogger)
/** @see com.github.blemale.scaffeine.AsyncLoadingCache.get
*/
def get(key: K)(implicit traceContext: TraceContext): Future[V] =
underlying.get(TracedKey(key)(traceContext))
/** @see com.github.blemale.scaffeine.AsyncLoadingCache.getAll
*/
def getAll(keys: Iterable[K])(implicit traceContext: TraceContext): Future[Map[K, V]] =
underlying
.getAll(keys.map(TracedKey(_)(traceContext)))
.map(_.map { case (tracedKey, value) => tracedKey.key -> value })(ec)
override def toString = s"TracedAsyncLoadingCache($underlying)"
}

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
build-options:
- --target=2.1
name: CantonExamples

View File

@ -10,6 +10,7 @@ import com.digitalasset.canton.data.TransactionViewDecomposition.{NewView, SameV
import com.digitalasset.canton.protocol.WellFormedTransaction.WithoutSuffixes
import com.digitalasset.canton.protocol.*
import com.digitalasset.canton.topology.client.TopologySnapshot
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.LfTransactionUtil
import scala.concurrent.{ExecutionContext, Future}
@ -24,7 +25,7 @@ trait TransactionViewDecompositionFactory {
transaction: WellFormedTransaction[WithoutSuffixes],
viewRbContext: RollbackContext,
submittingAdminPartyO: Option[LfPartyId],
)(implicit ec: ExecutionContext): Future[Seq[NewView]]
)(implicit ec: ExecutionContext, tc: TraceContext): Future[Seq[NewView]]
}
object TransactionViewDecompositionFactory {
@ -158,7 +159,7 @@ object TransactionViewDecompositionFactory {
transaction: WellFormedTransaction[WithoutSuffixes],
viewRbContext: RollbackContext,
submittingAdminPartyO: Option[LfPartyId],
)(implicit ec: ExecutionContext): Future[Seq[NewView]] = {
)(implicit ec: ExecutionContext, tc: TraceContext): Future[Seq[NewView]] = {
val tx: LfVersionedTransaction = transaction.unwrap

View File

@ -83,7 +83,7 @@ abstract class GrpcTopologyAggregationServiceCommon[
filterParty: String,
filterParticipant: String,
limit: Int,
): Future[Set[PartyId]] = MonadUtil
)(implicit traceContext: TraceContext): Future[Set[PartyId]] = MonadUtil
.foldLeftM((Set.empty[PartyId], false), clients) { case ((res, isDone), (_, client)) =>
if (isDone) Future.successful((res, true))
else
@ -97,6 +97,8 @@ abstract class GrpcTopologyAggregationServiceCommon[
private def findParticipants(
clients: List[(DomainId, TopologySnapshotLoader)],
partyId: PartyId,
)(implicit
traceContext: TraceContext
): Future[Map[ParticipantId, Map[DomainId, ParticipantPermission]]] =
clients
.parFlatTraverse { case (domainId, client) =>

View File

@ -188,8 +188,7 @@ final class GrpcTopologyManagerWriteService[T <: CantonError](
participant <- ParticipantId
.fromProtoPrimitive(request.participant, "participant")
permission <- ParticipantPermission.fromProtoEnum(request.permission)
trustLevel <- TrustLevel.fromProtoEnum(request.trustLevel)
} yield ParticipantState(side, domain, participant, permission, trustLevel)
} yield ParticipantState(side, domain, participant, permission)
process(request.authorization, item.leftMap(ProtoDeserializationFailure.Wrap(_)))
}

View File

@ -551,13 +551,13 @@ class GenTransactionTreeTest
}
val topology = mock[PartyTopologySnapshotClient]
when(topology.activeParticipantsOfParties(any[Seq[LfPartyId]]))
when(topology.activeParticipantsOfParties(any[List[LfPartyId]])(anyTraceContext))
.thenAnswer[Seq[LfPartyId]] { parties =>
Future.successful(topologyMap.collect {
case (party, map) if parties.contains(party) => (party, map.keySet)
})
}
when(topology.partiesWithGroupAddressing(any[List[LfPartyId]]))
when(topology.partiesWithGroupAddressing(any[Seq[LfPartyId]])(anyTraceContext))
// parties 3 and 6 will use group addressing
.thenReturn(Future.successful(Set(party(3), party(6))))

View File

@ -12,7 +12,6 @@ import com.digitalasset.canton.protocol.RollbackContext.{RollbackScope, Rollback
import com.digitalasset.canton.protocol.WellFormedTransaction.WithoutSuffixes
import com.digitalasset.canton.protocol.*
import com.digitalasset.canton.topology.client.TopologySnapshot
import com.digitalasset.canton.topology.transaction.TrustLevel
import com.digitalasset.canton.topology.{PartyId, UniqueIdentifier}
import com.digitalasset.canton.util.LfTransactionUtil
import com.digitalasset.canton.{
@ -68,7 +67,7 @@ class TransactionViewDecompositionTest
val node = createNode(unsuffixedId(0))
val informees =
Set[Informee](ConfirmingParty(signatory, PositiveInt.one, TrustLevel.Ordinary))
Set[Informee](ConfirmingParty(signatory, PositiveInt.one))
val rootSeed = ExampleTransactionFactory.lfHash(-1)
val child =
NewView(

View File

@ -3,18 +3,16 @@
package com.digitalasset.canton.protocol
import com.daml.lf.value.Value
import com.digitalasset.canton.config.RequireTypes.{NonNegativeInt, PositiveInt}
import com.digitalasset.canton.data.{ConfirmingParty, PlainInformee}
import com.digitalasset.canton.protocol.ConfirmationPolicy.{Signatory, Vip}
import com.digitalasset.canton.protocol.ConfirmationPolicy.Signatory
import com.digitalasset.canton.protocol.ExampleTransactionFactory.{
signatoryParticipant,
submittingParticipant,
templateId,
}
import com.digitalasset.canton.topology.client.TopologySnapshot
import com.digitalasset.canton.topology.transaction.ParticipantAttributes
import com.digitalasset.canton.topology.transaction.ParticipantPermission.{Observation, Submission}
import com.digitalasset.canton.topology.transaction.{ParticipantAttributes, TrustLevel}
import com.digitalasset.canton.{BaseTest, HasExecutionContext, LfPartyId}
import org.scalatest.wordspec.AnyWordSpec
@ -22,7 +20,7 @@ import scala.concurrent.Future
class ConfirmationPolicyTest extends AnyWordSpec with BaseTest with HasExecutionContext {
private lazy val gen = new ExampleTransactionFactory()(confirmationPolicy = Vip)
private lazy val gen = new ExampleTransactionFactory()(confirmationPolicy = Signatory)
private lazy val alice: LfPartyId = LfPartyId.assertFromString("alice")
private lazy val bob: LfPartyId = LfPartyId.assertFromString("bob")
@ -37,17 +35,23 @@ class ConfirmationPolicyTest extends AnyWordSpec with BaseTest with HasExecution
.SingleExerciseWithNonstakeholderActor(ExampleTransactionFactory.lfHash(0))
.versionedUnsuffixedTransaction
when(topologySnapshot.activeParticipantsOf(any[LfPartyId]))
.thenAnswer[LfPartyId] {
case ExampleTransactionFactory.signatory =>
Future.successful(
when(
topologySnapshot.activeParticipantsOfPartiesWithAttributes(any[Seq[LfPartyId]])(
anyTraceContext
)
)
.thenAnswer[Seq[LfPartyId]] { parties =>
Future.successful(parties.map {
case ExampleTransactionFactory.signatory =>
// Give the signatory Observation permission, which shouldn't be enough to get a valid confirmation policy
Map(signatoryParticipant -> ParticipantAttributes(Observation, TrustLevel.Ordinary))
)
case _ =>
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Ordinary))
)
ExampleTransactionFactory.signatory -> Map(
signatoryParticipant -> ParticipantAttributes(Observation)
)
case otherParty =>
otherParty -> Map(
submittingParticipant -> ParticipantAttributes(Submission)
)
}.toMap)
}
val policies = ConfirmationPolicy
@ -65,10 +69,20 @@ class ConfirmationPolicyTest extends AnyWordSpec with BaseTest with HasExecution
.SingleExerciseWithoutConfirmingParties(ExampleTransactionFactory.lfHash(0))
.versionedUnsuffixedTransaction
when(topologySnapshot.activeParticipantsOf(any[LfPartyId]))
.thenReturn(
when(
topologySnapshot.activeParticipantsOfPartiesWithAttributes(any[Seq[LfPartyId]])(
anyTraceContext
)
)
.thenAnswer[Seq[LfPartyId]](parties =>
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Ordinary))
parties
.map(
_ -> Map(
submittingParticipant -> ParticipantAttributes(Submission)
)
)
.toMap
)
)
@ -80,29 +94,23 @@ class ConfirmationPolicyTest extends AnyWordSpec with BaseTest with HasExecution
}
}
"all views have at least one Vip participant" should {
"favor the VIP policy" in {
val topologySnapshot = mock[TopologySnapshot]
when(topologySnapshot.activeParticipantsOf(any[LfPartyId]))
.thenReturn(
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Vip))
)
)
val policies = gen.standardHappyCases
.map(_.versionedUnsuffixedTransaction)
.map(ConfirmationPolicy.choose(_, topologySnapshot).futureValue)
assert(policies.forall(_.headOption === Some(Vip)))
}
}
"some views have no VIP participant" should {
"fall back to Signatory policy" in {
val topologySnapshot = mock[TopologySnapshot]
when(topologySnapshot.activeParticipantsOf(any[LfPartyId]))
.thenReturn(
when(
topologySnapshot.activeParticipantsOfPartiesWithAttributes(any[Seq[LfPartyId]])(
anyTraceContext
)
)
.thenAnswer[Seq[LfPartyId]](parties =>
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Ordinary))
parties
.map(
_ -> Map(
submittingParticipant -> ParticipantAttributes(Submission)
)
)
.toMap
)
)
val policies = gen.standardHappyCases
@ -118,24 +126,31 @@ class ConfirmationPolicyTest extends AnyWordSpec with BaseTest with HasExecution
"a view's VIPs are not stakeholders" should {
"fall back to Signatory policy" in {
val topologySnapshot = mock[TopologySnapshot]
when(topologySnapshot.activeParticipantsOf(eqTo(ExampleTransactionFactory.submitter)))
.thenReturn(
when(
topologySnapshot.activeParticipantsOfPartiesWithAttributes(any[Seq[LfPartyId]])(
anyTraceContext
)
)
.thenAnswer[Seq[LfPartyId]](parties =>
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Vip))
)
)
when(topologySnapshot.activeParticipantsOf(eqTo(ExampleTransactionFactory.signatory)))
.thenReturn(
Future.successful(
Map(signatoryParticipant -> ParticipantAttributes(Submission, TrustLevel.Ordinary))
)
)
when(topologySnapshot.activeParticipantsOf(eqTo(ExampleTransactionFactory.observer)))
.thenReturn(
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Ordinary))
parties.map {
case ExampleTransactionFactory.submitter =>
ExampleTransactionFactory.submitter -> Map(
submittingParticipant -> ParticipantAttributes(Submission)
)
case ExampleTransactionFactory.signatory =>
ExampleTransactionFactory.signatory -> Map(
signatoryParticipant -> ParticipantAttributes(Submission)
)
case ExampleTransactionFactory.observer =>
ExampleTransactionFactory.observer -> Map(
submittingParticipant -> ParticipantAttributes(Submission)
)
case otherwise => sys.error(s"unexpected party: $otherwise")
}.toMap
)
)
val policies = ConfirmationPolicy
.choose(
gen
@ -147,74 +162,6 @@ class ConfirmationPolicyTest extends AnyWordSpec with BaseTest with HasExecution
assert(policies == Seq(Signatory))
}
}
val txCreateWithKey = gen
.SingleCreate(
seed = gen.deriveNodeSeed(0),
signatories = Set(ExampleTransactionFactory.signatory),
observers = Set(ExampleTransactionFactory.submitter, ExampleTransactionFactory.observer),
key = Some(
LfGlobalKeyWithMaintainers.assertBuild(
templateId,
Value.ValueUnit,
Set(ExampleTransactionFactory.signatory),
shared = true,
)
),
)
.versionedUnsuffixedTransaction
"a view's VIPs are not key maintainers" should {
"fall back to Signatory policy" in {
val topologySnapshot = mock[TopologySnapshot]
when(topologySnapshot.activeParticipantsOf(eqTo(ExampleTransactionFactory.submitter)))
.thenReturn(
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Vip))
)
)
when(topologySnapshot.activeParticipantsOf(eqTo(ExampleTransactionFactory.signatory)))
.thenReturn(
Future.successful(
Map(signatoryParticipant -> ParticipantAttributes(Submission, TrustLevel.Ordinary))
)
)
when(topologySnapshot.activeParticipantsOf(eqTo(ExampleTransactionFactory.observer)))
.thenReturn(
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Ordinary))
)
)
val policies = ConfirmationPolicy.choose(txCreateWithKey, topologySnapshot).futureValue
assert(policies == Seq(Signatory))
}
}
"only some VIPs of a view are key maintainers" should {
"favor the VIP policy" in {
val topologySnapshot = mock[TopologySnapshot]
when(topologySnapshot.activeParticipantsOf(eqTo(ExampleTransactionFactory.submitter)))
.thenReturn(
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Vip))
)
)
when(topologySnapshot.activeParticipantsOf(eqTo(ExampleTransactionFactory.signatory)))
.thenReturn(
Future.successful(
Map(signatoryParticipant -> ParticipantAttributes(Submission, TrustLevel.Vip))
)
)
when(topologySnapshot.activeParticipantsOf(eqTo(ExampleTransactionFactory.observer)))
.thenReturn(
Future.successful(
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Ordinary))
)
)
val policies = ConfirmationPolicy.choose(txCreateWithKey, topologySnapshot).futureValue
assert(policies == Seq(Vip, Signatory))
}
}
}
"The signatory policy" when {
@ -222,8 +169,8 @@ class ConfirmationPolicyTest extends AnyWordSpec with BaseTest with HasExecution
"correctly update informees and thresholds" in {
val oldInformees = Set(
PlainInformee(alice),
ConfirmingParty(bob, PositiveInt.one, TrustLevel.Ordinary),
ConfirmingParty(charlie, PositiveInt.one, TrustLevel.Vip),
ConfirmingParty(bob, PositiveInt.one),
ConfirmingParty(charlie, PositiveInt.one),
)
val oldThreshold = NonNegativeInt.tryCreate(2)
@ -234,9 +181,9 @@ class ConfirmationPolicyTest extends AnyWordSpec with BaseTest with HasExecution
ConfirmationPolicy.Signatory
.withSubmittingAdminParty(Some(alice))(oldInformees, oldThreshold) shouldBe
Set(
ConfirmingParty(alice, PositiveInt.one, TrustLevel.Ordinary),
ConfirmingParty(bob, PositiveInt.one, TrustLevel.Ordinary),
ConfirmingParty(charlie, PositiveInt.one, TrustLevel.Vip),
ConfirmingParty(alice, PositiveInt.one),
ConfirmingParty(bob, PositiveInt.one),
ConfirmingParty(charlie, PositiveInt.one),
) -> NonNegativeInt.tryCreate(3)
ConfirmationPolicy.Signatory
@ -249,47 +196,9 @@ class ConfirmationPolicyTest extends AnyWordSpec with BaseTest with HasExecution
ConfirmationPolicy.Signatory
.withSubmittingAdminParty(Some(david))(oldInformees, oldThreshold) shouldBe
oldInformees + ConfirmingParty(david, PositiveInt.one, TrustLevel.Ordinary) ->
oldInformees + ConfirmingParty(david, PositiveInt.one) ->
NonNegativeInt.tryCreate(3)
}
}
}
"The VIP policy" when {
"adding a submitting admin party" should {
"correctly update informees and thresholds" in {
val oldInformees = Set(
PlainInformee(alice),
ConfirmingParty(bob, PositiveInt.one, TrustLevel.Vip),
ConfirmingParty(charlie, PositiveInt.one, TrustLevel.Vip),
)
val oldThreshold = NonNegativeInt.one
ConfirmationPolicy.Vip
.withSubmittingAdminParty(None)(oldInformees, oldThreshold) shouldBe
oldInformees -> oldThreshold
ConfirmationPolicy.Vip
.withSubmittingAdminParty(Some(alice))(oldInformees, oldThreshold) shouldBe
Set(
ConfirmingParty(alice, PositiveInt.tryCreate(3), TrustLevel.Ordinary),
ConfirmingParty(bob, PositiveInt.one, TrustLevel.Vip),
ConfirmingParty(charlie, PositiveInt.one, TrustLevel.Vip),
) -> NonNegativeInt.tryCreate(4)
ConfirmationPolicy.Vip
.withSubmittingAdminParty(Some(bob))(oldInformees, oldThreshold) shouldBe
Set(
PlainInformee(alice),
ConfirmingParty(bob, PositiveInt.tryCreate(4), TrustLevel.Vip),
ConfirmingParty(charlie, PositiveInt.one, TrustLevel.Vip),
) -> NonNegativeInt.tryCreate(4)
ConfirmationPolicy.Vip
.withSubmittingAdminParty(Some(david))(oldInformees, oldThreshold) shouldBe
oldInformees + ConfirmingParty(david, PositiveInt.tryCreate(3), TrustLevel.Ordinary) ->
NonNegativeInt.tryCreate(4)
}
}
}
}

View File

@ -33,11 +33,7 @@ import com.digitalasset.canton.topology.transaction.ParticipantPermission.{
Observation,
Submission,
}
import com.digitalasset.canton.topology.transaction.{
ParticipantAttributes,
TrustLevel,
VettedPackages,
}
import com.digitalasset.canton.topology.transaction.{ParticipantAttributes, VettedPackages}
import com.digitalasset.canton.topology.{
DomainId,
MediatorId,
@ -48,6 +44,7 @@ import com.digitalasset.canton.topology.{
UniqueIdentifier,
}
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.tracing.TraceContext.Implicits.Empty.*
import com.digitalasset.canton.util.LfTransactionUtil.{
metadataFromCreate,
metadataFromExercise,
@ -339,8 +336,7 @@ object ExampleTransactionFactory {
signatoryParticipant -> Observation
),
),
participants =
Map(submittingParticipant -> ParticipantAttributes(Submission, TrustLevel.Vip)),
participants = Map(submittingParticipant -> ParticipantAttributes(Submission)),
packages = Seq(submittingParticipant, signatoryParticipant).map(
VettedPackages(_, Seq(ExampleTransactionFactory.packageId))
),

View File

@ -3,7 +3,10 @@
package com.digitalasset.canton.protocol.messages
import com.daml.nonempty.NonEmpty
import com.digitalasset.canton.BaseTest
import com.digitalasset.canton.config.RequireTypes.PositiveInt
import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.protocol.TestDomainParameters
import com.digitalasset.canton.serialization.HasCryptographicEvidenceTest
import com.digitalasset.canton.topology.*
@ -27,14 +30,15 @@ class TopologyTransactionTest extends AnyWordSpec with BaseTest with HasCryptogr
.getOrElse(sys.error("no key"))
private val defaultDynamicDomainParameters = TestDomainParameters.defaultDynamic
private def mk[T <: TopologyStateUpdateMapping](
private def mk[T <: TopologyMappingX](
mapping: T
): TopologyStateUpdate[TopologyChangeOp.Add] =
TopologyStateUpdate.createAdd(mapping, testedProtocolVersion)
): TopologyTransactionX[TopologyChangeOpX.Replace, T] = {
TopologyTransactionX(TopologyChangeOpX.Replace, PositiveInt.one, mapping, testedProtocolVersion)
}
private val deserialize: ByteString => TopologyTransaction[TopologyChangeOp] =
private val deserialize: ByteString => TopologyTransactionX[TopologyChangeOpX, TopologyMappingX] =
bytes =>
TopologyTransaction.fromByteString(
TopologyTransactionX.fromByteString(
testedProtocolVersionValidation
)(
bytes
@ -44,8 +48,8 @@ class TopologyTransactionTest extends AnyWordSpec with BaseTest with HasCryptogr
}
private def runTest(
t1: TopologyTransaction[TopologyChangeOp],
t2: TopologyTransaction[TopologyChangeOp],
t1: TopologyTransactionX[TopologyChangeOpX, TopologyMappingX],
t2: TopologyTransactionX[TopologyChangeOpX, TopologyMappingX],
): Unit = {
behave like hasCryptographicEvidenceSerialization(t1, t2)
behave like hasCryptographicEvidenceDeserialization(t1, t1.getCryptographicEvidence)(
@ -57,64 +61,73 @@ class TopologyTransactionTest extends AnyWordSpec with BaseTest with HasCryptogr
"namespace mappings" should {
val nsd = mk(NamespaceDelegation(uid.namespace, publicKey, true))
val nsd2 = mk(NamespaceDelegation(uid2.namespace, publicKey, false))
val nsd =
mk(NamespaceDelegationX.tryCreate(uid.namespace, publicKey, isRootDelegation = true))
val nsd2 =
mk(NamespaceDelegationX.tryCreate(uid2.namespace, publicKey, isRootDelegation = false))
runTest(nsd, nsd2)
}
"identifier delegations" should {
val id1 = mk(IdentifierDelegation(uid, publicKey))
val id2 = mk(IdentifierDelegation(uid2, publicKey))
val id1 = mk(IdentifierDelegationX(uid, publicKey))
val id2 = mk(IdentifierDelegationX(uid2, publicKey))
runTest(id1, id2)
}
"key to owner mappings" should {
val k1 = mk(OwnerToKeyMapping(managerId, publicKey))
val k2 = mk(OwnerToKeyMapping(managerId, publicKey))
val k1 = mk(OwnerToKeyMappingX(managerId, None, NonEmpty(Seq, publicKey)))
val k2 = mk(OwnerToKeyMappingX(managerId, None, NonEmpty(Seq, publicKey)))
runTest(k1, k2)
}
"party to participant" should {
val p1 =
mk(
PartyToParticipant(
RequestSide.From,
PartyToParticipantX(
PartyId(uid),
ParticipantId(uid2),
ParticipantPermission.Observation,
None,
PositiveInt.one,
Seq(HostingParticipant(ParticipantId(uid2), ParticipantPermissionX.Observation)),
groupAddressing = false,
)
)
val p2 =
mk(
PartyToParticipant(
RequestSide.To,
PartyId(uid2),
ParticipantId(uid),
ParticipantPermission.Observation,
PartyToParticipantX(
PartyId(uid),
Some(domainId),
PositiveInt.two,
Seq(
HostingParticipant(ParticipantId(uid2), ParticipantPermissionX.Observation),
HostingParticipant(ParticipantId(uid), ParticipantPermissionX.Submission),
),
groupAddressing = true,
)
)
runTest(p1, p2)
}
"participant state" should {
val ps1 = mk(
ParticipantState(
RequestSide.From,
ParticipantDomainPermissionX(
domainId,
ParticipantId(uid),
ParticipantPermission.Submission,
TrustLevel.Vip,
ParticipantPermissionX.Submission,
limits = None,
loginAfter = None,
)
)
val ps2 = mk(
ParticipantState(
RequestSide.To,
ParticipantDomainPermissionX(
domainId,
ParticipantId(uid2),
ParticipantPermission.Confirmation,
TrustLevel.Ordinary,
ParticipantId(uid),
ParticipantPermissionX.Observation,
limits = Some(ParticipantDomainLimits(13, 37, 42)),
loginAfter = Some(CantonTimestamp.MinValue.plusSeconds(17)),
)
)
@ -123,14 +136,8 @@ class TopologyTransactionTest extends AnyWordSpec with BaseTest with HasCryptogr
}
"domain parameters change" should {
val dmp1 = DomainGovernanceTransaction(
DomainParametersChange(DomainId(uid), defaultDynamicDomainParameters),
testedProtocolVersion,
)
val dmp2 = DomainGovernanceTransaction(
DomainParametersChange(DomainId(uid), defaultDynamicDomainParameters),
testedProtocolVersion,
)
val dmp1 = mk(DomainParametersStateX(DomainId(uid), defaultDynamicDomainParameters))
val dmp2 = mk(DomainParametersStateX(DomainId(uid), defaultDynamicDomainParameters))
runTest(dmp1, dmp2)
}

View File

@ -52,11 +52,11 @@ class SequencedEventTestFixture(
lazy val defaultDomainId: DomainId = DefaultTestIdentities.domainId
lazy val subscriberId: ParticipantId = ParticipantId("participant1-id")
lazy val sequencerAlice: SequencerId = DefaultTestIdentities.sequencerId
lazy val sequencerAlice: SequencerId = DefaultTestIdentities.sequencerIdX
lazy val subscriberCryptoApi: DomainSyncCryptoClient =
TestingIdentityFactory(loggerFactory).forOwnerAndDomain(subscriberId, defaultDomainId)
TestingIdentityFactoryX(loggerFactory).forOwnerAndDomain(subscriberId, defaultDomainId)
private lazy val sequencerCryptoApi: DomainSyncCryptoClient =
TestingIdentityFactory(loggerFactory).forOwnerAndDomain(sequencerAlice, defaultDomainId)
TestingIdentityFactoryX(loggerFactory).forOwnerAndDomain(sequencerAlice, defaultDomainId)
lazy val updatedCounter: Long = 42L
val sequencerBob: SequencerId = SequencerId(
UniqueIdentifier(Identifier.tryCreate("da2"), namespace)

View File

@ -46,7 +46,7 @@ class SequencedEventValidatorTest
val priorEvent = createEvent().futureValue
val validator = mkValidator()
validator
.validateOnReconnect(Some(priorEvent), priorEvent, DefaultTestIdentities.sequencerId)
.validateOnReconnect(Some(priorEvent), priorEvent, DefaultTestIdentities.sequencerIdX)
.valueOrFail("successful reconnect")
.failOnShutdown
.futureValue
@ -66,7 +66,7 @@ class SequencedEventValidatorTest
fixtureTraceContext
)
validator
.validateOnReconnect(Some(priorEvent), eventWithNewSig, DefaultTestIdentities.sequencerId)
.validateOnReconnect(Some(priorEvent), eventWithNewSig, DefaultTestIdentities.sequencerIdX)
.valueOrFail("event with regenerated signature")
.failOnShutdown
.futureValue
@ -83,7 +83,7 @@ class SequencedEventValidatorTest
val validator = mkValidator()
validator
.validateOnReconnect(Some(deliver1), deliver2, DefaultTestIdentities.sequencerId)
.validateOnReconnect(Some(deliver1), deliver2, DefaultTestIdentities.sequencerIdX)
.valueOrFail("Different serialization should be accepted")
.failOnShutdown
.futureValue
@ -102,7 +102,7 @@ class SequencedEventValidatorTest
)
),
wrongDomain,
DefaultTestIdentities.sequencerId,
DefaultTestIdentities.sequencerIdX,
)
.leftOrFail("wrong domain ID on reconnect")
.failOnShutdown
@ -132,7 +132,7 @@ class SequencedEventValidatorTest
.validateOnReconnect(
Some(priorEvent),
differentCounter,
DefaultTestIdentities.sequencerId,
DefaultTestIdentities.sequencerIdX,
)
.leftOrFail("fork on counter")
)
@ -142,7 +142,7 @@ class SequencedEventValidatorTest
.validateOnReconnect(
Some(priorEvent),
differentTimestamp,
DefaultTestIdentities.sequencerId,
DefaultTestIdentities.sequencerIdX,
)
.leftOrFail("fork on timestamp")
)
@ -157,7 +157,7 @@ class SequencedEventValidatorTest
.validateOnReconnect(
Some(priorEvent),
differentContent,
DefaultTestIdentities.sequencerId,
DefaultTestIdentities.sequencerIdX,
)
.leftOrFail("fork on content")
)
@ -205,7 +205,7 @@ class SequencedEventValidatorTest
val badEvent = createEvent(signatureOverride = Some(badSig)).futureValue
val validator = mkValidator()
val result = validator
.validateOnReconnect(Some(priorEvent), badEvent, DefaultTestIdentities.sequencerId)
.validateOnReconnect(Some(priorEvent), badEvent, DefaultTestIdentities.sequencerIdX)
.leftOrFail("invalid signature on reconnect")
.failOnShutdown
.futureValue
@ -220,7 +220,7 @@ class SequencedEventValidatorTest
val event = createEvent(incorrectDomainId, counter = 0L).futureValue
val validator = mkValidator()
val result = validator
.validate(None, event, DefaultTestIdentities.sequencerId)
.validate(None, event, DefaultTestIdentities.sequencerIdX)
.leftOrFail("wrong domain ID")
.failOnShutdown
.futureValue
@ -239,7 +239,7 @@ class SequencedEventValidatorTest
).futureValue
val validator = mkValidator()
val result = validator
.validate(Some(priorEvent), badEvent, DefaultTestIdentities.sequencerId)
.validate(Some(priorEvent), badEvent, DefaultTestIdentities.sequencerIdX)
.leftOrFail("invalid signature")
.failOnShutdown
.futureValue
@ -258,7 +258,9 @@ class SequencedEventValidatorTest
val deliver =
createEventWithCounterAndTs(42, ts(2), timestampOfSigningKey = Some(ts(1))).futureValue
valueOrFail(validator.validate(Some(priorEvent), deliver, DefaultTestIdentities.sequencerId))(
valueOrFail(
validator.validate(Some(priorEvent), deliver, DefaultTestIdentities.sequencerIdX)
)(
"validate"
).failOnShutdown.futureValue
}
@ -272,12 +274,12 @@ class SequencedEventValidatorTest
val deliver = createEventWithCounterAndTs(42, CantonTimestamp.Epoch).futureValue
validator
.validate(Some(priorEvent), deliver, DefaultTestIdentities.sequencerId)
.validate(Some(priorEvent), deliver, DefaultTestIdentities.sequencerIdX)
.valueOrFail("validate1")
.failOnShutdown
.futureValue
val err = validator
.validate(Some(deliver), deliver, DefaultTestIdentities.sequencerId)
.validate(Some(deliver), deliver, DefaultTestIdentities.sequencerIdX)
.leftOrFail("validate2")
.failOnShutdown
.futureValue
@ -298,22 +300,22 @@ class SequencedEventValidatorTest
val deliver3 = createEventWithCounterAndTs(42L, CantonTimestamp.ofEpochSecond(2)).futureValue
val error1 = validator
.validate(Some(priorEvent), deliver1, DefaultTestIdentities.sequencerId)
.validate(Some(priorEvent), deliver1, DefaultTestIdentities.sequencerIdX)
.leftOrFail("deliver1")
.failOnShutdown
.futureValue
val error2 = validator
.validate(Some(priorEvent), deliver2, DefaultTestIdentities.sequencerId)
.validate(Some(priorEvent), deliver2, DefaultTestIdentities.sequencerIdX)
.leftOrFail("deliver2")
.failOnShutdown
.futureValue
validator
.validate(Some(priorEvent), deliver3, DefaultTestIdentities.sequencerId)
.validate(Some(priorEvent), deliver3, DefaultTestIdentities.sequencerIdX)
.valueOrFail("deliver3")
.failOnShutdown
.futureValue
val error3 = validator
.validate(Some(deliver3), deliver2, DefaultTestIdentities.sequencerId)
.validate(Some(deliver3), deliver2, DefaultTestIdentities.sequencerIdX)
.leftOrFail("deliver4")
.failOnShutdown
.futureValue
@ -340,19 +342,19 @@ class SequencedEventValidatorTest
val deliver3 = createEventWithCounterAndTs(44L, CantonTimestamp.ofEpochSecond(3)).futureValue
val result1 = validator
.validate(Some(priorEvent), deliver1, DefaultTestIdentities.sequencerId)
.validate(Some(priorEvent), deliver1, DefaultTestIdentities.sequencerIdX)
.leftOrFail("deliver1")
.failOnShutdown
.futureValue
validator
.validate(Some(priorEvent), deliver2, DefaultTestIdentities.sequencerId)
.validate(Some(priorEvent), deliver2, DefaultTestIdentities.sequencerIdX)
.valueOrFail("deliver2")
.failOnShutdown
.futureValue
val result3 = validator
.validate(Some(deliver2), deliver3, DefaultTestIdentities.sequencerId)
.validate(Some(deliver2), deliver3, DefaultTestIdentities.sequencerIdX)
.leftOrFail("deliver3")
.failOnShutdown
.futureValue
@ -377,7 +379,7 @@ class SequencedEventValidatorTest
.watchTermination()((_, doneF) => noOpKillSwitch -> doneF)
val subscription = SequencerSubscriptionPekko(source, alwaysHealthyComponent)
val validatedSubscription =
validator.validatePekko(subscription, None, DefaultTestIdentities.sequencerId)
validator.validatePekko(subscription, None, DefaultTestIdentities.sequencerIdX)
val validatedEventsF = validatedSubscription.source.runWith(Sink.seq)
validatedEventsF.futureValue.map(_.unwrap) shouldBe Seq(
Left(UpstreamSubscriptionError("error1"))
@ -399,7 +401,7 @@ class SequencedEventValidatorTest
).watchTermination()((_, doneF) => noOpKillSwitch -> doneF)
val subscription = SequencerSubscriptionPekko[String](source, alwaysHealthyComponent)
val validatedSubscription =
validator.validatePekko(subscription, Some(deliver1), DefaultTestIdentities.sequencerId)
validator.validatePekko(subscription, Some(deliver1), DefaultTestIdentities.sequencerIdX)
val validatedEventsF = validatedSubscription.source.runWith(Sink.seq)
// deliver1 should be filtered out because it's the prior event
validatedEventsF.futureValue.map(_.unwrap) shouldBe Seq(Right(deliver2), Right(deliver3))
@ -419,7 +421,7 @@ class SequencedEventValidatorTest
).watchTermination()((_, doneF) => noOpKillSwitch -> doneF)
val subscription = SequencerSubscriptionPekko(source, alwaysHealthyComponent)
val validatedSubscription =
validator.validatePekko(subscription, Some(deliver1), DefaultTestIdentities.sequencerId)
validator.validatePekko(subscription, Some(deliver1), DefaultTestIdentities.sequencerIdX)
val validatedEventsF = validatedSubscription.source.runWith(Sink.seq)
// deliver1 should be filtered out because it's the prior event
validatedEventsF.futureValue.map(_.unwrap) shouldBe Seq(
@ -442,7 +444,7 @@ class SequencedEventValidatorTest
).watchTermination()((_, doneF) => noOpKillSwitch -> doneF)
val subscription = SequencerSubscriptionPekko(source, alwaysHealthyComponent)
val validatedSubscription =
validator.validatePekko(subscription, Some(deliver1a), DefaultTestIdentities.sequencerId)
validator.validatePekko(subscription, Some(deliver1a), DefaultTestIdentities.sequencerIdX)
loggerFactory.assertLogs(
validatedSubscription.source.runWith(Sink.seq).futureValue.map(_.unwrap) shouldBe Seq(
Left(
@ -463,7 +465,7 @@ class SequencedEventValidatorTest
"not request a topology snapshot after a validation failure" in { fixture =>
import fixture.*
val syncCryptoApi = TestingIdentityFactory(loggerFactory)
val syncCryptoApi = TestingIdentityFactoryX(loggerFactory)
.forOwnerAndDomain(subscriberId, defaultDomainId, CantonTimestamp.ofEpochSecond(2))
val validator = mkValidator(syncCryptoApi)
val deliver1 = createEventWithCounterAndTs(1L, CantonTimestamp.Epoch).futureValue
@ -488,7 +490,7 @@ class SequencedEventValidatorTest
).watchTermination()((_, doneF) => noOpKillSwitch -> doneF)
val subscription = SequencerSubscriptionPekko(source, alwaysHealthyComponent)
val validatedSubscription =
validator.validatePekko(subscription, Some(deliver1), DefaultTestIdentities.sequencerId)
validator.validatePekko(subscription, Some(deliver1), DefaultTestIdentities.sequencerIdX)
val ((killSwitch, doneF), validatedEventsF) =
validatedSubscription.source.toMat(Sink.seq)(Keep.both).run()
// deliver1 should be filtered out because it's the prior event

View File

@ -5,21 +5,11 @@ package com.digitalasset.canton.topology
import cats.data.EitherT
import cats.syntax.either.*
import com.digitalasset.canton.crypto.{
Hash,
HashOps,
SignatureCheckError,
SyncCryptoError,
TestHash,
}
import com.digitalasset.canton.crypto.*
import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.protocol.{DomainParameters, DynamicDomainParameters}
import com.digitalasset.canton.time.NonNegativeFiniteDuration
import com.digitalasset.canton.topology.transaction.{
ParticipantAttributes,
ParticipantPermission,
TrustLevel,
}
import com.digitalasset.canton.topology.transaction.{ParticipantAttributes, ParticipantPermission}
import com.digitalasset.canton.{BaseTest, HasExecutionContext}
import org.scalatest.wordspec.AnyWordSpec
@ -55,7 +45,7 @@ class TestingIdentityFactoryTest extends AnyWordSpec with BaseTest with HasExecu
"testing topology" when {
def compare(setup: TestingIdentityFactory): Unit = {
def compare(setup: TestingIdentityFactoryX): Unit = {
val p1 = setup.forOwnerAndDomain(participant1)
val p2 = setup.forOwnerAndDomain(participant2)
val hash = getMyHash(p1.pureCrypto)
@ -92,17 +82,15 @@ class TestingIdentityFactoryTest extends AnyWordSpec with BaseTest with HasExecu
"participant1 is active" in {
Seq(p1, p2).foreach(
_.currentSnapshotApproximation.ipsSnapshot
.participants()
.futureValue
.find(_._1 == participant1)
.map(_._2) shouldBe Some(ParticipantPermission.Confirmation)
.isParticipantActive(participant1)
.futureValue shouldBe true
)
}
"party1 is active" in {
p1.currentSnapshotApproximation.ipsSnapshot
.activeParticipantsOf(party1.toLf)
.futureValue shouldBe (Map(
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation, TrustLevel.Vip)
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation)
))
}
"participant2 can't sign messages without appropriate keys" in {
@ -112,23 +100,41 @@ class TestingIdentityFactoryTest extends AnyWordSpec with BaseTest with HasExecu
.value shouldBe a[SyncCryptoError]
}
def checkDomainKeys(did: UniqueIdentifier, expectedLength: Int): Unit = {
Seq[Member](MediatorId(did), DomainTopologyManagerId(did), SequencerId(did)).foreach(
member =>
p1.currentSnapshotApproximation.ipsSnapshot
.signingKeys(member)
.futureValue should have length (expectedLength.toLong)
)
def checkDomainKeys(
sequencers: Seq[SequencerId],
mediators: Seq[MediatorId],
expectedLength: Int,
): Unit = {
val allMembers = sequencers ++ mediators
val membersToKeys = p1.currentSnapshotApproximation.ipsSnapshot
.signingKeys(allMembers)
.futureValue
allMembers
.flatMap(membersToKeys.get(_))
.foreach(_ should have length (expectedLength.toLong))
}
"domain entities have keys" in {
val did = p1.currentSnapshotApproximation.domainId.unwrap
checkDomainKeys(did, 1)
val sequencers = p1.currentSnapshotApproximation.ipsSnapshot
.sequencerGroup()
.futureValue
.valueOrFail("did not find SequencerDomainStateX")
.active
val mediators =
p1.currentSnapshotApproximation.ipsSnapshot.mediatorGroups().futureValue.flatMap(_.all)
checkDomainKeys(sequencers, mediators, 1)
}
"invalid domain entities don't have keys" in {
val did = participant2.uid
require(did != DefaultTestIdentities.domainId.unwrap)
checkDomainKeys(did, 0)
checkDomainKeys(
sequencers =
Seq(SequencerId(Identifier.tryCreate("fake-sequencer"), participant2.uid.namespace)),
mediators =
Seq(MediatorId(Identifier.tryCreate("fake-mediator"), participant2.uid.namespace)),
0,
)
}
"serve domain parameters corresponding to correct timestamp" in {
@ -156,22 +162,19 @@ class TestingIdentityFactoryTest extends AnyWordSpec with BaseTest with HasExecu
participant1 -> ParticipantPermission.Confirmation
)
)
val setup = TestingTopology(
val setup = TestingTopologyX(
topology = topology,
domainParameters = domainParameters,
participants = Map(
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation, TrustLevel.Vip)
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation)
),
).build()
compare(setup)
// extend with admin parties should give participant2 a signing key
val crypto2 = TestingTopology(topology = topology, domainParameters = domainParameters)
val crypto2 = TestingTopologyX(topology = topology, domainParameters = domainParameters)
.withParticipants(
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation, TrustLevel.Vip),
participant2 -> ParticipantAttributes(
ParticipantPermission.Submission,
TrustLevel.Ordinary,
),
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation),
participant2 -> ParticipantAttributes(ParticipantPermission.Submission),
)
.build()
val p1 = crypto2.forOwnerAndDomain(participant1)
@ -207,19 +210,19 @@ class TestingIdentityFactoryTest extends AnyWordSpec with BaseTest with HasExecu
}
"using reverse topology" should {
val setup = TestingTopology(domainParameters = domainParameters)
val setup = TestingTopologyX(domainParameters = domainParameters)
.withReversedTopology(
Map(participant1 -> Map(party1.toLf -> ParticipantPermission.Confirmation))
)
.withParticipants(
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation, TrustLevel.Vip)
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation)
)
.build()
compare(setup)
"preserve topology and permissions" in {
val syncCryptoApi =
TestingTopology()
TestingTopologyX()
.withReversedTopology(
Map(
participant1 -> Map(
@ -233,7 +236,7 @@ class TestingIdentityFactoryTest extends AnyWordSpec with BaseTest with HasExecu
.forOwnerAndDomain(participant1)
.currentSnapshotApproximation
def ol(permission: ParticipantPermission) =
ParticipantAttributes(permission, TrustLevel.Ordinary)
ParticipantAttributes(permission)
syncCryptoApi.ipsSnapshot.activeParticipantsOf(party1.toLf).futureValue shouldBe Map(
participant1 -> ol(ParticipantPermission.Observation),
participant2 -> ol(ParticipantPermission.Submission),
@ -246,13 +249,13 @@ class TestingIdentityFactoryTest extends AnyWordSpec with BaseTest with HasExecu
}
"withTopology" should {
val setup = TestingTopology(domainParameters = domainParameters)
val setup = TestingTopologyX(domainParameters = domainParameters)
.withTopology(
Map(party1.toLf -> participant1),
ParticipantPermission.Confirmation,
)
.withParticipants(
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation, TrustLevel.Vip)
participant1 -> ParticipantAttributes(ParticipantPermission.Confirmation)
)
.build()
compare(setup)

View File

@ -6,11 +6,8 @@ package com.digitalasset.canton.topology.client
import com.digitalasset.canton.config.RequireTypes.PositiveInt
import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.transaction.{
ParticipantAttributes,
ParticipantPermission,
TrustLevel,
}
import com.digitalasset.canton.topology.transaction.{ParticipantAttributes, ParticipantPermission}
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.{BaseTest, LfPartyId}
import org.scalatest.wordspec.AsyncWordSpec
@ -25,20 +22,11 @@ class PartyTopologySnapshotClientTest extends AsyncWordSpec with BaseTest {
"party topology snapshot client" should {
lazy val topology = Map(
party1.toLf -> Map(
participant1 -> ParticipantAttributes(
ParticipantPermission.Submission,
TrustLevel.Ordinary,
),
participant2 -> ParticipantAttributes(
ParticipantPermission.Observation,
TrustLevel.Ordinary,
),
participant1 -> ParticipantAttributes(ParticipantPermission.Submission),
participant2 -> ParticipantAttributes(ParticipantPermission.Observation),
),
party2.toLf -> Map(
participant2 -> ParticipantAttributes(
ParticipantPermission.Observation,
TrustLevel.Ordinary,
)
participant2 -> ParticipantAttributes(ParticipantPermission.Observation)
),
)
lazy val client = new PartyTopologySnapshotClient
@ -46,7 +34,7 @@ class PartyTopologySnapshotClientTest extends AsyncWordSpec with BaseTest {
with PartyTopologySnapshotBaseClient {
override def activeParticipantsOf(
party: LfPartyId
): Future[Map[ParticipantId, ParticipantAttributes]] =
)(implicit traceContext: TraceContext): Future[Map[ParticipantId, ParticipantAttributes]] =
Future.successful(topology.getOrElse(party, Map()))
override protected implicit def executionContext: ExecutionContext =
PartyTopologySnapshotClientTest.this.executionContext
@ -55,15 +43,17 @@ class PartyTopologySnapshotClientTest extends AsyncWordSpec with BaseTest {
filterParty: String,
filterParticipant: String,
limit: Int,
): Future[Set[PartyId]] =
)(implicit traceContext: TraceContext): Future[Set[PartyId]] =
???
override def activeParticipantsOfParties(
parties: Seq[LfPartyId]
): Future[Map[LfPartyId, Set[ParticipantId]]] = ???
)(implicit traceContext: TraceContext): Future[Map[LfPartyId, Set[ParticipantId]]] = ???
override def activeParticipantsOfPartiesWithAttributes(
parties: Seq[LfPartyId]
)(implicit
traceContext: TraceContext
): Future[Map[LfPartyId, Map[ParticipantId, ParticipantAttributes]]] =
Future.successful(
parties.map { party =>
@ -76,20 +66,24 @@ class PartyTopologySnapshotClientTest extends AsyncWordSpec with BaseTest {
*/
override def authorityOf(
parties: Set[LfPartyId]
)(implicit
traceContext: TraceContext
): Future[PartyTopologySnapshotClient.AuthorityOfResponse] =
Future.successful(PartyTopologySnapshotClient.AuthorityOfResponse(Map.empty))
override def partiesWithGroupAddressing(parties: Seq[LfPartyId]): Future[Set[LfPartyId]] =
override def partiesWithGroupAddressing(parties: Seq[LfPartyId])(implicit
traceContext: TraceContext
): Future[Set[LfPartyId]] =
???
override def consortiumThresholds(
parties: Set[LfPartyId]
): Future[Map[LfPartyId, PositiveInt]] = ???
)(implicit traceContext: TraceContext): Future[Map[LfPartyId, PositiveInt]] = ???
override def canNotSubmit(
participant: ParticipantId,
parties: Seq[LfPartyId],
): Future[immutable.Iterable[LfPartyId]] = ???
)(implicit traceContext: TraceContext): Future[immutable.Iterable[LfPartyId]] = ???
}
"allHaveActiveParticipants should yield correct results" in {
@ -136,13 +130,13 @@ class PartyTopologySnapshotClientTest extends AsyncWordSpec with BaseTest {
"canConfirm should yield correct results" in {
for {
yes1 <- client.canConfirm(participant1, party1.toLf)
no1 <- client.canConfirm(participant1, party1.toLf, TrustLevel.Vip)
no2 <- client.canConfirm(participant2, party1.toLf)
yes1 <- client.canConfirm(participant1, Set(party1.toLf))
no1 <- client.canConfirm(participant1, Set(party2.toLf))
no2 <- client.canConfirm(participant2, Set(party1.toLf))
} yield {
yes1 shouldBe true
no1 shouldBe false
no2 shouldBe false
yes1 shouldBe Set(party1.toLf)
no1 shouldBe Set.empty
no2 shouldBe Set.empty
}
}
}

View File

@ -3,8 +3,8 @@
package com.digitalasset.canton.topology.client
import cats.syntax.parallel.*
import com.digitalasset.canton.concurrent.{DirectExecutionContext, FutureSupervisor}
import com.digitalasset.canton.config.RequireTypes.PositiveInt
import com.digitalasset.canton.config.{DefaultProcessingTimeouts, ProcessingTimeout}
import com.digitalasset.canton.crypto.{CryptoPureApi, SigningPublicKey}
import com.digitalasset.canton.data.CantonTimestamp
@ -14,18 +14,16 @@ import com.digitalasset.canton.store.db.DbTest
import com.digitalasset.canton.time.Clock
import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.processing.{EffectiveTime, SequencedTime}
import com.digitalasset.canton.topology.store.db.DbTopologyStore
import com.digitalasset.canton.topology.store.memory.InMemoryTopologyStore
import com.digitalasset.canton.topology.store.db.DbTopologyStoreX
import com.digitalasset.canton.topology.store.memory.InMemoryTopologyStoreX
import com.digitalasset.canton.topology.store.{
SignedTopologyTransactions,
TopologyStore,
TopologyStoreId,
TopologyStoreX,
ValidatedTopologyTransactionX,
}
import com.digitalasset.canton.topology.transaction.ParticipantPermission.*
import com.digitalasset.canton.topology.transaction.TrustLevel.*
import com.digitalasset.canton.topology.transaction.ParticipantPermissionX.*
import com.digitalasset.canton.topology.transaction.*
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.FutureInstances.*
import com.digitalasset.canton.version.ProtocolVersion
import com.digitalasset.canton.{BaseTest, BaseTestWordSpec, HasExecutionContext, SequencerCounter}
import org.scalatest.wordspec.AsyncWordSpec
@ -35,7 +33,7 @@ import scala.concurrent.{ExecutionContext, Future}
class BaseDomainTopologyClientTest extends BaseTestWordSpec {
private class TestClient() extends BaseDomainTopologyClientOld {
private class TestClient() extends BaseDomainTopologyClientX {
override def protocolVersion: ProtocolVersion = testedProtocolVersion
@ -109,39 +107,47 @@ trait StoreBasedTopologySnapshotTest extends AsyncWordSpec with BaseTest with Ha
import EffectiveTimeTestHelpers.*
def topologySnapshot(mk: () => TopologyStore[TopologyStoreId]): Unit = {
def topologySnapshot(mk: () => TopologyStoreX[TopologyStoreId]): Unit = {
val factory = new TestingOwnerWithKeys(
val factory = new TestingOwnerWithKeysX(
DefaultTestIdentities.participant1,
loggerFactory,
parallelExecutionContext,
)
import DefaultTestIdentities.*
import factory.TestingTransactions.*
import factory.*
import factory.TestingTransactions.*
lazy val party2participant1 = mkAdd(
PartyToParticipant(RequestSide.Both, party1, participant1, Confirmation)
lazy val party1participant1 = mkAdd(
PartyToParticipantX(
party1,
None,
PositiveInt.one,
Seq(HostingParticipant(participant1, Confirmation)),
groupAddressing = false,
)
)
lazy val party2participant2a = mkAdd(
PartyToParticipant(RequestSide.From, party2, participant1, Submission)
)
lazy val party2participant2b = mkAdd(
PartyToParticipant(RequestSide.To, party2, participant1, Submission)
)
lazy val party2participant3 = mkAdd(
PartyToParticipant(RequestSide.Both, party2, participant2, Submission)
lazy val party2participant1_2 = mkAdd(
PartyToParticipantX(
party2,
None,
PositiveInt.one,
Seq(
HostingParticipant(participant1, Submission),
HostingParticipant(participant2, Submission),
),
groupAddressing = false,
)
)
class Fixture(initialKeys: Map[Member, Seq[SigningPublicKey]] = Map()) {
class Fixture() {
val store = mk()
val client =
new StoreBasedDomainTopologyClient(
new StoreBasedDomainTopologyClientX(
mock[Clock],
domainId,
testedProtocolVersion,
store,
initialKeys,
StoreBasedDomainTopologyClient.NoPackageDependencies,
DefaultProcessingTimeouts.testing,
FutureSupervisor.Noop,
@ -150,17 +156,16 @@ trait StoreBasedTopologySnapshotTest extends AsyncWordSpec with BaseTest with Ha
def add(
timestamp: CantonTimestamp,
transactions: Seq[SignedTopologyTransaction[TopologyChangeOp]],
transactions: Seq[SignedTopologyTransactionX[TopologyChangeOpX, TopologyMappingX]],
): Future[Unit] = {
val (adds, removes, _) = SignedTopologyTransactions(transactions).split
for {
_ <- store.updateState(
_ <- store.update(
SequencedTime(timestamp),
EffectiveTime(timestamp),
removes.result.map(_.uniquePath),
adds.result,
removeMapping = transactions.map(_.mapping.uniqueKey).toSet,
removeTxs = transactions.map(_.transaction.hash).toSet,
additions = transactions.map(ValidatedTopologyTransactionX(_)),
)
_ <- client
.observed(timestamp, timestamp, SequencerCounter(1), transactions)
@ -177,11 +182,9 @@ trait StoreBasedTopologySnapshotTest extends AsyncWordSpec with BaseTest with Ha
val mrt = client.approximateTimestamp
val sp = client.trySnapshot(mrt)
for {
participants <- sp.participants()
parties <- sp.activeParticipantsOf(party1.toLf)
keys <- sp.signingKeys(participant1)
} yield {
participants shouldBe empty
parties shouldBe empty
keys shouldBe empty
}
@ -202,13 +205,13 @@ trait StoreBasedTopologySnapshotTest extends AsyncWordSpec with BaseTest with Ha
_ <- fixture.add(
ts,
Seq(
ns1k2,
okm1,
party2participant1,
party2participant2a,
party2participant2b,
ps1,
party2participant3,
dpc1,
p1_nsk2,
p1_otk,
p1_dtc,
p2_nsk2,
party1participant1,
party2participant1_2,
),
)
_ = fixture.client.observed(
@ -220,20 +223,24 @@ trait StoreBasedTopologySnapshotTest extends AsyncWordSpec with BaseTest with Ha
recent = fixture.client.currentSnapshotApproximation
party1Mappings <- recent.activeParticipantsOf(party1.toLf)
party2Mappings <- recent.activeParticipantsOf(party2.toLf)
keys <- recent.signingKeys(domainManager)
keys <- recent.signingKeys(participant1)
} yield {
party1Mappings.keySet shouldBe Set(participant1)
party1Mappings.get(participant1).map(_.permission) should contain(Confirmation)
party1Mappings.get(participant1).map(_.permission) shouldBe Some(
ParticipantPermission.Confirmation
)
party2Mappings.keySet shouldBe Set(participant1)
party2Mappings.get(participant1).map(_.permission) should contain(Submission)
keys.map(_.id) shouldBe Seq(namespaceKey.id)
party2Mappings.get(participant1).map(_.permission) shouldBe Some(
ParticipantPermission.Submission
)
keys.map(_.id) shouldBe Seq(SigningKeys.key1.id)
}
}
"properly deals with participants with lower domain privileges" in {
val fixture = new Fixture()
for {
_ <- fixture.add(ts, Seq(ns1k2, okm1, party2participant1, ps2))
_ <- fixture.add(ts, Seq(dpc1, p1_otk, p1_dtc, party1participant1, p1_pdp_observation))
_ = fixture.client.observed(
ts.immediateSuccessor,
ts.immediateSuccessor,
@ -243,81 +250,10 @@ trait StoreBasedTopologySnapshotTest extends AsyncWordSpec with BaseTest with Ha
snapshot <- fixture.client.snapshot(ts.immediateSuccessor)
party1Mappings <- snapshot.activeParticipantsOf(party1.toLf)
} yield {
compareMappings(party1Mappings, Map(participant1 -> Observation))
compareMappings(party1Mappings, Map(participant1 -> ParticipantPermission.Observation))
}
}
"distinguishes between removed and disabled participants" in {
val fixture = new Fixture()
def genPs(
participant: ParticipantId,
permission: ParticipantPermission,
side: RequestSide,
) =
mkAdd(
ParticipantState(
side,
domainId,
participant,
permission,
TrustLevel.Ordinary,
)
)
val ps1 = genPs(participant1, ParticipantPermission.Submission, RequestSide.Both)
val ps2 = genPs(participant2, ParticipantPermission.Submission, RequestSide.From)
for {
_ <- fixture.add(
ts,
Seq(
ps1,
ps2,
genPs(participant3, ParticipantPermission.Submission, RequestSide.To),
),
)
_ <- fixture.add(
ts.plusMillis(1),
Seq(
revert(ps1), // revert first one, so there should be no cert, so None
genPs(
participant2,
ParticipantPermission.Submission,
RequestSide.To,
), // happy path on both sides
genPs(
participant3,
ParticipantPermission.Disabled,
RequestSide.From,
), // should be Some(Disabled)
genPs(
participant3,
ParticipantPermission.Submission,
RequestSide.From,
), // should not confuse the previous statement (Disabled should be stronger)
),
)
_ <- fixture.add(ts.plusMillis(2), Seq(revert(ps2)))
sp1 <- fixture.client.snapshot(ts.immediateSuccessor)
sp2 <- fixture.client.snapshot(ts.plusMillis(1).immediateSuccessor)
sp3 <- fixture.client.snapshot(ts.plusMillis(2).immediateSuccessor)
participants1 <- sp1.participants()
participants2 <- sp2.participants()
participants3 <- sp3.participants()
participants = Seq(participant1, participant2, participant3)
st1ps <- participants.parTraverse(p => sp1.findParticipantState(p).map(_.map(_.permission)))
st2ps <- participants.parTraverse(p => sp2.findParticipantState(p).map(_.map(_.permission)))
st3ps <- participants.parTraverse(p => sp3.findParticipantState(p).map(_.map(_.permission)))
} yield {
// note: the domain topology dispatcher relies on this behaviour here for protocol version v5
participants1 shouldBe Seq(participant1 -> Submission)
participants2 shouldBe Seq(participant2 -> Submission, participant3 -> Disabled)
participants3 shouldBe Seq(participant3 -> Disabled)
st1ps shouldBe Seq(Some(Submission), None, None)
st2ps shouldBe Seq(None, Some(Submission), Some(Disabled))
st3ps shouldBe Seq(None, None, Some(Disabled))
}
}
"work properly with updates" in {
val fixture = new Fixture()
val ts2 = ts1.plusSeconds(1)
@ -325,20 +261,26 @@ trait StoreBasedTopologySnapshotTest extends AsyncWordSpec with BaseTest with Ha
_ <- fixture.add(
ts,
Seq(
ns1k2,
okm1,
party2participant1,
party2participant2a,
party2participant2b,
ps1,
party2participant3,
seq_okm_k2,
dpc1,
p1_otk,
p1_dtc,
party1participant1,
party2participant1_2,
),
)
_ <- fixture.add(ts1, Seq(rokm1, okm2, rps1, ps2, ps3))
_ <- fixture.add(
ts2,
Seq(factory.revert(ps2), factory.mkAdd(ps1m.copy(permission = Disabled))),
ts1,
Seq(
mkRemoveTx(seq_okm_k2),
med_okm_k3,
p2_otk,
p2_dtc,
p1_pdp_observation,
p2_pdp_confirmation,
),
)
_ <- fixture.add(ts2, Seq(mkRemoveTx(p1_pdp_observation), mkRemoveTx(p1_dtc)))
_ = fixture.client.observed(
ts2.immediateSuccessor,
ts2.immediateSuccessor,
@ -353,150 +295,52 @@ trait StoreBasedTopologySnapshotTest extends AsyncWordSpec with BaseTest with Ha
party2Ma <- snapshotA.activeParticipantsOf(party2.toLf)
party2Mb <- snapshotB.activeParticipantsOf(party2.toLf)
party2Mc <- snapshotC.activeParticipantsOf(party2.toLf)
keysDMa <- snapshotA.signingKeys(domainManager)
keysDMb <- snapshotB.signingKeys(domainManager)
keysSa <- snapshotA.signingKeys(sequencerId)
keysSb <- snapshotB.signingKeys(sequencerId)
partPermA <- snapshotA.participantState(participant1)
partPermB <- snapshotB.participantState(participant1)
partPermC <- snapshotC.participantState(participant1)
keysMa <- snapshotA.signingKeys(mediatorIdX)
keysMb <- snapshotB.signingKeys(mediatorIdX)
keysSa <- snapshotA.signingKeys(sequencerIdX)
keysSb <- snapshotB.signingKeys(sequencerIdX)
partPermA <- snapshotA.findParticipantState(participant1)
partPermB <- snapshotB.findParticipantState(participant1)
partPermC <- snapshotC.findParticipantState(participant1)
admin1a <- snapshotA.activeParticipantsOf(participant1.adminParty.toLf)
admin1b <- snapshotB.activeParticipantsOf(participant1.adminParty.toLf)
} yield {
compareMappings(party1Ma, Map(participant1 -> Confirmation))
compareMappings(party1Mb, Map(participant1 -> Observation))
compareMappings(party2Ma, Map(participant1 -> Submission))
compareMappings(party2Mb, Map(participant1 -> Observation, participant2 -> Confirmation))
compareMappings(party2Mc, Map(participant2 -> Confirmation))
compareKeys(keysDMa, Seq(namespaceKey))
compareKeys(keysDMb, Seq())
compareKeys(keysSa, Seq())
compareKeys(keysSb, Seq(SigningKeys.key2))
partPermA.permission shouldBe Submission
partPermB.permission shouldBe Observation
partPermC.permission shouldBe Disabled
compareMappings(admin1a, Map(participant1 -> Submission))
compareMappings(admin1b, Map(participant1 -> Observation))
compareMappings(party1Ma, Map(participant1 -> ParticipantPermission.Confirmation))
compareMappings(party1Mb, Map(participant1 -> ParticipantPermission.Observation))
compareMappings(party2Ma, Map(participant1 -> ParticipantPermission.Submission))
compareMappings(
party2Mb,
Map(
participant1 -> ParticipantPermission.Observation,
participant2 -> ParticipantPermission.Confirmation,
),
)
compareMappings(party2Mc, Map(participant2 -> ParticipantPermission.Confirmation))
compareKeys(keysMa, Seq())
compareKeys(keysMb, Seq(SigningKeys.key3))
compareKeys(keysSa, Seq(SigningKeys.key2))
compareKeys(keysSb, Seq())
partPermA
.valueOrFail("No permission for particiant1 in snapshotA")
.permission shouldBe ParticipantPermission.Submission
partPermB
.valueOrFail("No permission for particiant1 in snapshotB")
.permission shouldBe ParticipantPermission.Observation
partPermC shouldBe None
compareMappings(admin1a, Map(participant1 -> ParticipantPermission.Submission))
compareMappings(admin1b, Map(participant1 -> ParticipantPermission.Observation))
}
}
"mixin initialisation keys" in {
val f = new Fixture(Map(sequencerId -> Seq(SigningKeys.key6)))
for {
_ <- f.add(ts, Seq(ns1k2, okm1))
_ <- f.add(ts1, Seq(okm2))
_ = f.client.observed(
ts1.immediateSuccessor,
ts1.immediateSuccessor,
SequencerCounter(0),
Seq(),
)
spA <- f.client.snapshot(ts1)
spB <- f.client.snapshot(ts1.immediateSuccessor)
dmKeys <- spA.signingKeys(domainManager)
seqKeyA <- spA.signingKeys(sequencerId)
seqKeyB <- spB.signingKeys(sequencerId)
} yield {
compareKeys(dmKeys, Seq(namespaceKey))
compareKeys(seqKeyA, Seq(SigningKeys.key6))
compareKeys(seqKeyB, Seq(SigningKeys.key2))
}
}
"not show single sided party to participant mappings" in {
val f = new Fixture()
for {
_ <- f.add(ts, Seq(ps1, party2participant2b))
_ <- f.add(ts1, Seq(party2participant2a))
_ = f.client.observed(
ts1.immediateSuccessor,
ts1.immediateSuccessor,
SequencerCounter(0),
Seq(),
)
snapshot1 <- f.client.snapshot(ts1)
snapshot2 <- f.client.snapshot(ts1.immediateSuccessor)
res1 <- snapshot1.activeParticipantsOf(party2.toLf)
res2 <- snapshot2.activeParticipantsOf(party2.toLf)
} yield {
res1 shouldBe empty
compareMappings(res2, Map(participant1 -> Submission))
}
}
"compute correct permissions for multiple mappings" in {
val txs = Seq(
PartyToParticipant(RequestSide.From, party1, participant1, Confirmation),
PartyToParticipant(RequestSide.From, party1, participant1, Submission),
PartyToParticipant(RequestSide.To, party1, participant1, Submission),
ParticipantState(
RequestSide.From,
domainId,
participant1,
Observation,
TrustLevel.Ordinary,
),
ParticipantState(RequestSide.To, domainId, participant1, Submission, TrustLevel.Vip),
PartyToParticipant(RequestSide.From, party2, participant2, Submission),
PartyToParticipant(RequestSide.To, party2, participant2, Observation),
ParticipantState(
RequestSide.From,
domainId,
participant2,
Confirmation,
TrustLevel.Ordinary,
),
ParticipantState(RequestSide.To, domainId, participant2, Confirmation, TrustLevel.Vip),
PartyToParticipant(RequestSide.Both, party3, participant3, Submission),
ParticipantState(RequestSide.Both, domainId, participant3, Confirmation, TrustLevel.Vip),
ParticipantState(RequestSide.To, domainId, participant3, Submission, TrustLevel.Ordinary),
)
val f = new Fixture()
def get(tp: TopologySnapshot, party: PartyId) = {
tp.activeParticipantsOf(party.toLf).map { res =>
res.map { case (p, r) =>
(p, (r.permission, r.trustLevel))
}
}
}
val party4 = PartyId(UniqueIdentifier(Identifier.tryCreate(s"unrelated"), namespace))
for {
_ <- f.add(ts, txs.map(mkAdd(_)))
_ = f.client.observed(
ts1.immediateSuccessor,
ts1.immediateSuccessor,
SequencerCounter(0),
Seq(),
)
sp <- f.client.snapshot(ts1)
p1 <- get(sp, party1)
p2 <- get(sp, party2)
p3 <- get(sp, party3)
bulk <- sp.activeParticipantsOfParties(List(party1, party2, party3, party4).map(_.toLf))
} yield {
p1 shouldBe Map(participant1 -> ((Observation, Ordinary)))
p2 shouldBe Map(participant2 -> ((Observation, Ordinary)))
p3 shouldBe Map(participant3 -> ((Confirmation, Vip)))
bulk shouldBe Map(
party1.toLf -> Set(participant1),
party2.toLf -> Set(participant2),
party3.toLf -> Set(participant3),
party4.toLf -> Set(),
)
}
}
}
}
class StoreBasedTopologySnapshotTestInMemory extends StoreBasedTopologySnapshotTest {
"InMemoryTopologyStore" should {
behave like topologySnapshot(() =>
new InMemoryTopologyStore(
new InMemoryTopologyStoreX(
TopologyStoreId.AuthorizedStore,
loggerFactory,
timeouts,
futureSupervisor,
)
)
}
@ -510,12 +354,11 @@ trait DbStoreBasedTopologySnapshotTest extends StoreBasedTopologySnapshotTest {
"DbStoreBasedTopologySnapshot" should {
behave like topologySnapshot(() =>
new DbTopologyStore(
new DbTopologyStoreX(
storage,
TopologyStoreId.DomainStore(DefaultTestIdentities.domainId),
timeouts,
loggerFactory,
futureSupervisor,
)
)
}

View File

@ -1,16 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.canton.topology.client
import com.digitalasset.canton.{BaseTest, HasExecutionContext}
import org.scalatest.wordspec.AsyncWordSpec
import scala.concurrent.Future
class StoreBasedTopologySnapshotXTest extends AsyncWordSpec with BaseTest with HasExecutionContext {
"implement me" in {
// TODO(#12942) implement me
Future.successful(succeed)
}
}

View File

@ -16,16 +16,7 @@ import com.digitalasset.canton.topology.store.TopologyStoreId.DomainStore
import com.digitalasset.canton.topology.store.ValidatedTopologyTransaction
import com.digitalasset.canton.topology.store.memory.InMemoryTopologyStore
import com.digitalasset.canton.topology.transaction.SignedTopologyTransaction.GenericSignedTopologyTransaction
import com.digitalasset.canton.topology.transaction.{
NamespaceDelegation,
OwnerToKeyMapping,
ParticipantPermission,
ParticipantState,
RequestSide,
SignedTopologyTransaction,
TopologyChangeOp,
TrustLevel,
}
import com.digitalasset.canton.topology.transaction.*
import com.digitalasset.canton.topology.{
DefaultTestIdentities,
TestingIdentityFactory,
@ -106,7 +97,6 @@ class DomainTopologyTransactionMessageValidatorTest
domainId,
participant1,
ParticipantPermission.Submission,
TrustLevel.Ordinary,
)
)

View File

@ -115,7 +115,6 @@ class IncomingTopologyTransactionAuthorizationValidatorTest
wrongDomain,
pid,
ParticipantPermission.Submission,
TrustLevel.Ordinary,
),
Factory.SigningKeys.key1,
)
@ -359,7 +358,7 @@ class IncomingTopologyTransactionAuthorizationValidatorTest
val store = mkStore()
val validator = mk(store)
import Factory.*
import Factory.SigningKeys.*
import Factory.SigningKeys.{ec as _, *}
// scenario: we have id1ak4_k2 previously loaded. now we get a removal on k2. we need to ensure that
// nothing can be added by k4
val Rns1k2_k1 = revert(ns1k2_k1)

View File

@ -7,27 +7,8 @@ import com.digitalasset.canton.logging.NamedLoggerFactory
import com.digitalasset.canton.protocol.TestDomainParameters
import com.digitalasset.canton.time.NonNegativeFiniteDuration
import com.digitalasset.canton.topology.DefaultTestIdentities.domainManager
import com.digitalasset.canton.topology.transaction.{
DomainParametersChange,
IdentifierDelegation,
NamespaceDelegation,
OwnerToKeyMapping,
ParticipantPermission,
ParticipantState,
PartyToParticipant,
RequestSide,
TrustLevel,
}
import com.digitalasset.canton.topology.{
DomainId,
Identifier,
Namespace,
ParticipantId,
PartyId,
SequencerId,
TestingOwnerWithKeys,
UniqueIdentifier,
}
import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.transaction.*
import scala.concurrent.ExecutionContext
@ -76,7 +57,6 @@ class TopologyTransactionTestFactory(loggerFactory: NamedLoggerFactory, initEc:
domainManager.domainId,
participant1,
ParticipantPermission.Submission,
TrustLevel.Ordinary,
),
key3,
)
@ -86,7 +66,6 @@ class TopologyTransactionTestFactory(loggerFactory: NamedLoggerFactory, initEc:
domainManager.domainId,
participant1,
ParticipantPermission.Submission,
TrustLevel.Ordinary,
),
key1,
)

View File

@ -620,7 +620,7 @@ trait TopologyStoreTest
"namespace filter behaves correctly" in {
val store = mk()
import factory.SigningKeys.*
import factory.SigningKeys.{ec as _, *}
val ns2k3 =
factory.mkAdd(
NamespaceDelegation(Namespace(key2.fingerprint), key2, isRootDelegation = true),
@ -750,7 +750,7 @@ trait TopologyStoreTest
"state query combines uid and ns queries correctly" in {
val store = mk()
import factory.SigningKeys.*
import factory.SigningKeys.{ec as _, *}
val ns1 = Namespace(key1.fingerprint)
val ns2 = Namespace(key2.fingerprint)
val ns3 = Namespace(key3.fingerprint)
@ -831,7 +831,6 @@ trait TopologyStoreTest
domainId,
participant1,
ParticipantPermission.Submission,
TrustLevel.Ordinary,
)
)

View File

@ -5,7 +5,9 @@ package com.digitalasset.canton.topology.store
import cats.syntax.option.*
import com.daml.nonempty.NonEmpty
import com.digitalasset.canton.config.CantonRequireTypes.String255
import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.topology.DefaultTestIdentities
import com.digitalasset.canton.topology.processing.{EffectiveTime, SequencedTime}
import com.digitalasset.canton.topology.transaction.SignedTopologyTransactionX.GenericSignedTopologyTransactionX
import com.digitalasset.canton.topology.transaction.*
@ -17,6 +19,111 @@ trait TopologyStoreXTest extends AsyncWordSpec with TopologyStoreXTestBase {
val testData = new TopologyStoreXTestData(loggerFactory, executionContext)
import testData.*
private lazy val submissionId = String255.tryCreate("submissionId")
private lazy val submissionId2 = String255.tryCreate("submissionId2")
protected def partyMetadataStore(mk: () => PartyMetadataStore): Unit = {
import DefaultTestIdentities.*
"inserting new succeeds" in {
val store = mk()
for {
_ <- store.insertOrUpdatePartyMetadata(
party1,
Some(participant1),
None,
CantonTimestamp.Epoch,
submissionId,
)
fetch <- store.metadataForParty(party1)
} yield {
fetch shouldBe Some(
PartyMetadata(party1, None, Some(participant1))(CantonTimestamp.Epoch, submissionId)
)
}
}
"updating existing succeeds" in {
val store = mk()
for {
_ <- store.insertOrUpdatePartyMetadata(
party1,
None,
None,
CantonTimestamp.Epoch,
submissionId,
)
_ <- store.insertOrUpdatePartyMetadata(
party2,
None,
None,
CantonTimestamp.Epoch,
submissionId,
)
_ <- store.insertOrUpdatePartyMetadata(
party1,
Some(participant1),
Some(String255.tryCreate("MoreName")),
CantonTimestamp.Epoch,
submissionId,
)
_ <- store.insertOrUpdatePartyMetadata(
party2,
Some(participant3),
Some(String255.tryCreate("Boooh")),
CantonTimestamp.Epoch,
submissionId,
)
meta1 <- store.metadataForParty(party1)
meta2 <- store.metadataForParty(party2)
} yield {
meta1 shouldBe Some(
PartyMetadata(party1, Some(String255.tryCreate("MoreName")), Some(participant1))(
CantonTimestamp.Epoch,
String255.empty,
)
)
meta2 shouldBe Some(
PartyMetadata(party2, Some(String255.tryCreate("Boooh")), Some(participant3))(
CantonTimestamp.Epoch,
String255.empty,
)
)
}
}
"deal with delayed notifications" in {
val store = mk()
val rec1 =
PartyMetadata(party1, None, Some(participant1))(CantonTimestamp.Epoch, submissionId)
val rec2 =
PartyMetadata(party2, Some(String255.tryCreate("Boooh")), Some(participant3))(
CantonTimestamp.Epoch,
submissionId2,
)
for {
_ <- store.insertOrUpdatePartyMetadata(
rec1.partyId,
rec1.participantId,
rec1.displayName,
rec1.effectiveTimestamp,
rec1.submissionId,
)
_ <- store.insertOrUpdatePartyMetadata(
rec2.partyId,
rec2.participantId,
rec2.displayName,
rec2.effectiveTimestamp,
rec2.submissionId,
)
_ <- store.markNotified(rec2)
notNotified <- store.fetchNotNotified()
} yield {
notNotified shouldBe Seq(rec1)
}
}
}
// TODO(#14066): Test coverage is rudimentary - enough to convince ourselves that queries basically seem to work.
// Increase coverage.
def topologyStore(mk: () => TopologyStoreX[TopologyStoreId]): Unit = {

View File

@ -1,176 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.digitalasset.canton.topology.store.db
import com.digitalasset.canton.DiscardOps
import com.digitalasset.canton.config.RequireTypes.PositiveInt
import com.digitalasset.canton.crypto.CryptoPureApi
import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.protocol.TestDomainParameters
import com.digitalasset.canton.resource.DbStorage
import com.digitalasset.canton.store.db.{DbTest, H2Test, PostgresTest}
import com.digitalasset.canton.time.NonNegativeFiniteDuration
import com.digitalasset.canton.topology.client.DbStoreBasedTopologySnapshotTest
import com.digitalasset.canton.topology.processing.{EffectiveTime, SequencedTime}
import com.digitalasset.canton.topology.store.TopologyStoreId.DomainStore
import com.digitalasset.canton.topology.store.{
SignedTopologyTransactions,
TopologyStoreId,
TopologyStoreTest,
ValidatedTopologyTransaction,
}
import com.digitalasset.canton.topology.transaction.{
DomainParametersChange,
SignedTopologyTransaction,
TopologyChangeOp,
}
import com.digitalasset.canton.topology.{
DefaultTestIdentities,
TestingIdentityFactory,
TestingOwnerWithKeys,
}
import scala.concurrent.Future
trait DbTopologyStoreTest extends TopologyStoreTest {
this: DbTest =>
val pureCryptoApi: CryptoPureApi = TestingIdentityFactory.pureCrypto()
override def cleanDb(storage: DbStorage): Future[Unit] = {
import storage.api.*
storage.update(
DBIO.seq(
sqlu"truncate table party_metadata",
sqlu"truncate table topology_transactions",
),
operationName = s"${this.getClass}: Truncate tables party_metadata and topology_transactions",
)
}
"DbPartyMetadataStore" should {
behave like partyMetadataStore(() => new DbPartyMetadataStore(storage, timeouts, loggerFactory))
}
private def createTopologyStore(
storeId: TopologyStoreId = TopologyStoreId.AuthorizedStore
): DbTopologyStore[TopologyStoreId] =
new DbTopologyStore(
storage,
storeId,
timeouts,
loggerFactory,
futureSupervisor,
maxItemsInSqlQuery = PositiveInt.one,
)
"DbTopologyStore" should {
behave like topologyStore(() => createTopologyStore())
}
// TODO(#15208) remove once we've deprecated this in 3.0
"backfill legacy topology transactions sequencing times" in {
val store = createTopologyStore(DomainStore(DefaultTestIdentities.domainId))
val owner = new TestingOwnerWithKeys(
DefaultTestIdentities.domainManager,
loggerFactory,
parallelExecutionContext,
)
import owner.TestingTransactions.*
def ts(ms: Long) = CantonTimestamp.Epoch.plusMillis(ms)
def validated(
txs: SignedTopologyTransaction[TopologyChangeOp]*
): List[ValidatedTopologyTransaction] = {
txs.toList.map(ValidatedTopologyTransaction(_, None))
}
def append(
sequenced: CantonTimestamp,
effective: CantonTimestamp,
txs: List[ValidatedTopologyTransaction],
): Future[Unit] = {
val appendF = store.append(SequencedTime(sequenced), EffectiveTime(effective), txs)
val (deactivate, positive) =
SignedTopologyTransactions(txs.map(_.transaction)).splitForStateUpdate
val updateF =
store.updateState(SequencedTime(sequenced), EffectiveTime(effective), deactivate, positive)
for {
_ <- appendF
_ <- updateF
} yield ()
}
val defaultDomainParameters = TestDomainParameters.defaultDynamic
val dpc1 = owner.mkDmGov(
DomainParametersChange(
DefaultTestIdentities.domainId,
defaultDomainParameters
.tryUpdate(topologyChangeDelay = NonNegativeFiniteDuration.tryOfMillis(10)),
),
namespaceKey,
)
val dpc2 = owner.mkDmGov(
DomainParametersChange(
DefaultTestIdentities.domainId,
defaultDomainParameters
.tryUpdate(topologyChangeDelay = NonNegativeFiniteDuration.tryOfMillis(100)),
),
namespaceKey,
)
for {
_ <- append(ts(0), ts(0), validated(ns1k1))
_ <- append(ts(10), ts(10), validated(id1k1, dpc1))
_ <- append(ts(50), ts(60), validated(ns1k2))
_ <- append(ts(100), ts(110), validated(dpc2))
/// will be projected by dpc1
_ <- append(ts(100).immediateSuccessor, ts(110).immediateSuccessor, validated(okm1))
_ <- append(ts(110), ts(120), validated(ps1))
// will be projected by dpc2
_ <- append(ts(110).immediateSuccessor, ts(210).immediateSuccessor, validated(ps2))
_ <- {
// purge sequenced
import storage.api.*
storage.update_(sqlu"UPDATE topology_transactions SET sequenced = NULL", "testing")
}
// load transactions (triggers recomputation)
txs <- store.headTransactions
} yield {
def grabTs(tx: SignedTopologyTransaction[TopologyChangeOp]): CantonTimestamp = {
txs.result
.find(_.transaction == tx)
.valueOrFail(s"can not find transaction ${tx}")
.sequenced
.value
}
grabTs(ns1k1) shouldBe ts(0)
grabTs(id1k1) shouldBe ts(10)
grabTs(ns1k2) shouldBe ts(50)
grabTs(okm1) shouldBe ts(100).immediateSuccessor
grabTs(ps1) shouldBe ts(110)
grabTs(ps2) shouldBe ts(110).immediateSuccessor
}
}
"Storage implicits" should {
"should be stack safe" in {
val tmp = storage
import DbStorage.Implicits.BuilderChain.*
import tmp.api.*
(1 to 100000).map(_ => sql" 1 == 1").toList.intercalate(sql" AND ").discard
assert(true)
}
}
}
class TopologyStoreTestH2
extends DbTopologyStoreTest
with DbStoreBasedTopologySnapshotTest
with H2Test
// merging both tests into a single runner, as both tests will write to topology_transactions, creating conflicts
class TopologyStoreTestPostgres
extends DbTopologyStoreTest
with DbStoreBasedTopologySnapshotTest
with PostgresTest

View File

@ -15,6 +15,10 @@ import com.digitalasset.canton.topology.store.{
trait DbTopologyStoreXTest extends TopologyStoreXTest with DbTopologyStoreXHelper {
this: DbTest =>
"DbPartyMetadataStore" should {
behave like partyMetadataStore(() => new DbPartyMetadataStore(storage, timeouts, loggerFactory))
}
"DbTopologyStore" should {
behave like topologyStore(() => createTopologyStore())

View File

@ -57,9 +57,7 @@ final class GeneratorsTransaction(
domain <- Arbitrary.arbitrary[DomainId]
participant <- Arbitrary.arbitrary[ParticipantId]
permission <- Arbitrary.arbitrary[ParticipantPermission]
trustLevel <-
if (permission.canConfirm) Gen.oneOf(trustLevels) else Gen.const(TrustLevel.Ordinary)
} yield ParticipantState(side, domain, participant, permission, trustLevel))
} yield ParticipantState(side, domain, participant, permission))
implicit val topologyStateUpdateMappingArb: Arbitrary[TopologyStateUpdateMapping] = genArbitrary
implicit val topologyStateUpdateElementArb: Arbitrary[TopologyStateUpdateElement] = genArbitrary

View File

@ -249,7 +249,6 @@ class ValidatingTopologyMappingXChecksTest
domainId,
participant1,
ParticipantPermissionX.Submission,
TrustLevelX.Ordinary,
None,
None,
)

View File

@ -61,13 +61,13 @@ class SerializationDeserializationTest
)
import generatorsData.*
import generatorsTransferData.*
import generatorsMessages.*
import generatorsTransferData.*
import generatorsVerdict.*
import generatorsLocalVerdict.*
import generatorsProtocol.*
import generatorsProtocolSeq.*
import generatorsTransaction.*
import generatorsProtocol.*
s"Serialization and deserialization methods using protocol version $version" should {
"compose to the identity" in {
@ -108,12 +108,10 @@ class SerializationDeserializationTest
testMemoizedProtocolVersionedWithCtx(TransferOutCommonData, TestHash)
testMemoizedProtocolVersionedWithCtx(TransferOutView, TestHash)
Seq(ConfirmationPolicy.Vip, ConfirmationPolicy.Signatory).map { confirmationPolicy =>
testMemoizedProtocolVersionedWithCtx(
ViewCommonData,
(TestHash, confirmationPolicy),
)
}
testMemoizedProtocolVersionedWithCtx(
ViewCommonData,
(TestHash, ConfirmationPolicy.Signatory),
)
testMemoizedProtocolVersionedWithCtx(
SignedTopologyTransaction,

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
build-options:
- --target=2.1
name: ai-analysis

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
build-options:
- --target=2.1
name: bank

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
build-options:
- --target=2.1
name: doctor

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
build-options:
- --target=2.1
name: health-insurance

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
build-options:
- --target=2.1
name: medical-records

View File

@ -1247,7 +1247,10 @@ class BlockUpdateGenerator(
private def resolveGroupsToMembers(
groupRecipients: Set[GroupRecipient],
topologySnapshot: TopologySnapshot,
)(implicit executionContext: ExecutionContext): Future[Map[GroupRecipient, Set[Member]]] = {
)(implicit
executionContext: ExecutionContext,
traceContext: TraceContext,
): Future[Map[GroupRecipient, Set[Member]]] = {
if (groupRecipients.isEmpty) Future.successful(Map.empty)
else
for {

View File

@ -25,7 +25,6 @@ import com.digitalasset.canton.sequencing.protocol.*
import com.digitalasset.canton.time.DomainTimeTracker
import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.client.TopologySnapshot
import com.digitalasset.canton.topology.transaction.TrustLevel
import com.digitalasset.canton.tracing.{Spanning, TraceContext, Traced}
import com.digitalasset.canton.util.EitherUtil.RichEither
import com.digitalasset.canton.util.FutureInstances.*
@ -403,7 +402,7 @@ private[mediator] class ConfirmationResponseProcessor(
case MediatorRef.Group(declaredMediatorGroup) =>
for {
mediatorGroupO <- EitherT.right(
topologySnapshot.mediatorGroup(declaredMediatorGroup.group)
topologySnapshot.mediatorGroup(declaredMediatorGroup.group)(loggingContext)
)
mediatorGroup <- EitherT.fromOption[Future](
mediatorGroupO,
@ -488,7 +487,7 @@ private[mediator] class ConfirmationResponseProcessor(
rootHashMessagesRecipients,
request,
topologySnapshot,
)
)(ec, loggingContext)
_ <- for {
_ <- EitherTUtil
.condUnitET[Future](wrongHashes.isEmpty, show"Wrong root hashes: $wrongHashes")
@ -519,7 +518,7 @@ private[mediator] class ConfirmationResponseProcessor(
s"Received a mediator request with id $requestId with invalid root hash messages. Rejecting... Reason: $rejectionReason",
v30.MediatorRejection.Code.InvalidRootHashMessage,
)
.reported()
.reported()(loggingContext)
MediatorVerdict.MediatorReject(rejection)
}
}
@ -561,32 +560,25 @@ private[mediator] class ConfirmationResponseProcessor(
for {
partitionedConfirmingParties <- EitherT.right[MediatorVerdict.MediatorReject](
declaredConfirmingParties.parTraverse { p =>
for {
canConfirm <- snapshot.isHostedByAtLeastOneParticipantF(
p.party,
attr =>
attr.permission.canConfirm && attr.trustLevel.rank >= p.requiredTrustLevel.rank,
snapshot
.isHostedByAtLeastOneParticipantF(
declaredConfirmingParties.map(_.party).toSet,
(p, attr) => attr.permission.canConfirm,
)(loggingContext)
.map { hostedConfirmingParties =>
declaredConfirmingParties.map(cp =>
Either.cond(hostedConfirmingParties.contains(cp.party), cp, cp)
)
} yield Either.cond(canConfirm, p, p)
}
}
)
(unauthorized, authorized) = partitionedConfirmingParties.separate
_ <- EitherTUtil.condUnitET[Future](
authorized.map(_.weight.unwrap).sum >= threshold.value, {
// This partitioning is correct, because a VIP hosted party can always confirm.
// So if the required trust level is VIP, the problem must be the actual trust level.
val (insufficientTrustLevel, insufficientPermission) =
unauthorized.partition(_.requiredTrustLevel == TrustLevel.Vip)
val insufficientTrustLevelHint =
if (insufficientTrustLevel.nonEmpty)
show"\nParties without VIP participant: ${insufficientTrustLevel.map(_.party)}"
else ""
val insufficientPermissionHint =
if (insufficientPermission.nonEmpty)
show"\nParties without participant having permission to confirm: ${insufficientPermission
if (unauthorized.nonEmpty)
show"\nParties without participant having permission to confirm: ${unauthorized
.map(_.party)}"
else ""
@ -598,7 +590,6 @@ private[mediator] class ConfirmationResponseProcessor(
s"Received a mediator request with id $requestId with insufficient authorized confirming parties for transaction view at $viewPosition. " +
s"Rejecting request. Threshold: $threshold." +
insufficientPermissionHint +
insufficientTrustLevelHint +
authorizedPartiesHint,
v30.MediatorRejection.Code.NotEnoughConfirmingParties,
)

View File

@ -159,7 +159,7 @@ final case class ResponseAggregation[VKEY](
newlyResponded.foldLeft(consortiumVoting)((votes, confirmingParty) => {
votes + (confirmingParty.party -> votes(confirmingParty.party).approveBy(sender))
})
val newlyRespondedFullVotes = newlyResponded.filter { case ConfirmingParty(party, _, _) =>
val newlyRespondedFullVotes = newlyResponded.filter { case ConfirmingParty(party, _) =>
consortiumVotingUpdated(party).isApproved
}
loggingContext.debug(
@ -356,7 +356,7 @@ object ResponseAggregation {
private def mkInitialState[K](
informeesAndThresholdByView: Map[K, (Set[Informee], NonNegativeInt)],
topologySnapshot: TopologySnapshot,
)(implicit ec: ExecutionContext): Future[Map[K, ViewState]] = {
)(implicit ec: ExecutionContext, tc: TraceContext): Future[Map[K, ViewState]] = {
informeesAndThresholdByView.toSeq
.parTraverse { case (viewKey, (informees, threshold)) =>
val confirmingParties = informees.collect { case cp: ConfirmingParty => cp }

View File

@ -64,6 +64,7 @@ trait ResponseAggregator extends HasLoggerName with Product with Serializable {
ec: ExecutionContext,
loggingContext: NamedLoggingContext,
): OptionT[Future, List[(VKEY, Set[LfPartyId])]] = {
implicit val tc = loggingContext.traceContext
def authorizedPartiesOfSender(
viewKey: VKEY,
declaredConfirmingParties: Set[ConfirmingParty],
@ -74,14 +75,15 @@ trait ResponseAggregator extends HasLoggerName with Product with Serializable {
Map("requestId" -> requestId.toString, "reportedBy" -> show"$sender")
)
val hostedConfirmingPartiesF =
declaredConfirmingParties.toList
.parFilterA(p => topologySnapshot.canConfirm(sender, p.party, p.requiredTrustLevel))
.map(_.toSet)
topologySnapshot.canConfirm(
sender,
declaredConfirmingParties.map(_.party),
)
val res = hostedConfirmingPartiesF.map { hostedConfirmingParties =>
loggingContext.debug(
show"Malformed response $responseTimestamp for $viewKey considered as a rejection on behalf of $hostedConfirmingParties"
)
Some(hostedConfirmingParties.map(_.party)): Option[Set[LfPartyId]]
Some(hostedConfirmingParties): Option[Set[LfPartyId]]
}
OptionT(res)
@ -103,12 +105,14 @@ trait ResponseAggregator extends HasLoggerName with Product with Serializable {
expectedConfirmingParties =
declaredConfirmingParties.filter(p => confirmingParties.contains(p.party))
unauthorizedConfirmingParties <- OptionT.liftF(
expectedConfirmingParties.toList
.parFilterA { p =>
topologySnapshot.canConfirm(sender, p.party, p.requiredTrustLevel).map(x => !x)
topologySnapshot
.canConfirm(
sender,
expectedConfirmingParties.map(_.party),
)
.map { confirmingParties =>
(expectedConfirmingParties.map(_.party) -- confirmingParties)
}
.map(_.map(_.party))
.map(_.toSet)
)
_ <-
if (unauthorizedConfirmingParties.isEmpty) OptionT.some[Future](())
@ -153,17 +157,13 @@ trait ResponseAggregator extends HasLoggerName with Product with Serializable {
val informeesByView = ViewKey[VKEY].informeesAndThresholdByKey(request)
val ret = informeesByView.toList
.parTraverseFilter { case (viewKey, (informees, _threshold)) =>
val hostedConfirmingPartiesF = informees.toList.parTraverseFilter {
case ConfirmingParty(party, _, requiredTrustLevel) =>
topologySnapshot
.canConfirm(sender, party, requiredTrustLevel)
.map(x => if (x) Some(party) else None)
case _ => Future.successful(None)
}
hostedConfirmingPartiesF.map { hostedConfirmingParties =>
if (hostedConfirmingParties.nonEmpty)
Some(viewKey -> hostedConfirmingParties.toSet)
else None
val confirmingParties = informees.collect { case cp: ConfirmingParty => cp.party }
topologySnapshot.canConfirm(sender, confirmingParties).map { partiesCanConfirm =>
val hostedConfirmingParties = confirmingParties.toSeq
.filter(partiesCanConfirm.contains(_))
Option.when(hostedConfirmingParties.nonEmpty)(
viewKey -> hostedConfirmingParties.toSet
)
}
}
.map { viewsWithConfirmingPartiesForSender =>

View File

@ -225,6 +225,8 @@ private[mediator] class DefaultVerdictSender(
private def informeesByParticipantAndWithGroupAddressing(
informees: List[LfPartyId],
topologySnapshot: TopologySnapshot,
)(implicit
traceContext: TraceContext
): Future[(Map[ParticipantId, Set[LfPartyId]], Set[LfPartyId])] =
for {
partiesWithGroupAddressing <- topologySnapshot.partiesWithGroupAddressing(informees)

View File

@ -277,7 +277,7 @@ class MemberAuthenticationServiceOld(
)(implicit traceContext: TraceContext): FutureUnlessShutdown[Unit] =
FutureUnlessShutdown.lift(performUnlessClosing(functionFullName) {
transactions.map(_.transaction.element.mapping).foreach {
case ParticipantState(_, _, participant, ParticipantPermission.Disabled, _) =>
case ParticipantState(_, _, participant, ParticipantPermission.Disabled) =>
invalidateAndExpire(isParticipantActive)(participant)
case _ =>
}

View File

@ -31,6 +31,7 @@ import com.digitalasset.canton.sequencing.client.{
}
import com.digitalasset.canton.sequencing.protocol.*
import com.digitalasset.canton.time.{Clock, DomainTimeTracker, NonNegativeFiniteDuration}
import com.digitalasset.canton.topology.MediatorGroup.MediatorGroupIndex
import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.client.TopologySnapshot
import com.digitalasset.canton.topology.transaction.*
@ -74,6 +75,10 @@ abstract class ConfirmationResponseProcessorTestV5Base(minimumPV: ProtocolVersio
threshold = PositiveInt.tryCreate(2),
)
protected val sequencer = SequencerId(UniqueIdentifier.tryCreate("sequencer", "one"))
protected val sequencerGroup = SequencerGroup(active = Seq(sequencer), Seq.empty, PositiveInt.one)
protected def mediatorId: MediatorId
protected def mediatorRef: MediatorRef
@ -188,7 +193,7 @@ abstract class ConfirmationResponseProcessorTestV5Base(minimumPV: ProtocolVersio
}
private lazy val domainSyncCryptoApi2: DomainSyncCryptoClient =
identityFactory2.forOwnerAndDomain(SequencerId(domainId), domainId)
identityFactory2.forOwnerAndDomain(sequencer, domainId)
def signedResponse(
confirmers: Set[LfPartyId],
@ -376,9 +381,9 @@ abstract class ConfirmationResponseProcessorTestV5Base(minimumPV: ProtocolVersio
new InformeeMessage(fullInformeeTree, sign(fullInformeeTree))(testedProtocolVersion) {
override val informeesAndThresholdByViewPosition
: Map[ViewPosition, (Set[Informee], NonNegativeInt)] = {
val submitterI = Informee.create(submitter, NonNegativeInt.one, TrustLevel.Ordinary)
val signatoryI = Informee.create(signatory, NonNegativeInt.one, TrustLevel.Ordinary)
val observerI = Informee.create(observer, NonNegativeInt.one, TrustLevel.Ordinary)
val submitterI = Informee.create(submitter, NonNegativeInt.one)
val signatoryI = Informee.create(signatory, NonNegativeInt.one)
val observerI = Informee.create(observer, NonNegativeInt.one)
Map(
ViewPosition.root -> (Set(
submitterI,
@ -703,12 +708,18 @@ abstract class ConfirmationResponseProcessorTestV5Base(minimumPV: ProtocolVersio
SerializedRootHashMessagePayload.empty,
)
val mockTopologySnapshot = mock[TopologySnapshot]
when(mockTopologySnapshot.canConfirm(any[ParticipantId], any[LfPartyId], any[TrustLevel]))
.thenReturn(Future.successful(true))
when(mockTopologySnapshot.consortiumThresholds(any[Set[LfPartyId]])).thenAnswer {
(parties: Set[LfPartyId]) =>
when(
mockTopologySnapshot.canConfirm(any[ParticipantId], any[Set[LfPartyId]])(
anyTraceContext
)
)
.thenAnswer { (participant: ParticipantId, parties: Set[LfPartyId]) =>
Future.successful(parties)
}
when(mockTopologySnapshot.consortiumThresholds(any[Set[LfPartyId]])(anyTraceContext))
.thenAnswer { (parties: Set[LfPartyId]) =>
Future.successful(parties.map(x => x -> PositiveInt.one).toMap)
}
}
for {
_ <- sut.processor.processRequest(
@ -802,7 +813,7 @@ abstract class ConfirmationResponseProcessorTestV5Base(minimumPV: ProtocolVersio
),
view1Position ->
ResponseAggregation.ViewState(
Set(ConfirmingParty(signatory, PositiveInt.one, TrustLevel.Ordinary)),
Set(ConfirmingParty(signatory, PositiveInt.one)),
Map(
submitter -> ConsortiumVotingState(approvals =
Set(ExampleTransactionFactory.submittingParticipant)
@ -814,14 +825,14 @@ abstract class ConfirmationResponseProcessorTestV5Base(minimumPV: ProtocolVersio
),
view10Position ->
ResponseAggregation.ViewState(
Set(ConfirmingParty(signatory, PositiveInt.one, TrustLevel.Ordinary)),
Set(ConfirmingParty(signatory, PositiveInt.one)),
Map(signatory -> ConsortiumVotingState()),
1,
Nil,
),
view11Position ->
ResponseAggregation.ViewState(
Set(ConfirmingParty(signatory, PositiveInt.one, TrustLevel.Ordinary)),
Set(ConfirmingParty(signatory, PositiveInt.one)),
Map(
submitter -> ConsortiumVotingState(approvals =
Set(ExampleTransactionFactory.submittingParticipant)
@ -890,9 +901,9 @@ abstract class ConfirmationResponseProcessorTestV5Base(minimumPV: ProtocolVersio
new InformeeMessage(fullInformeeTree, sign(fullInformeeTree))(testedProtocolVersion) {
override val informeesAndThresholdByViewPosition
: Map[ViewPosition, (Set[Informee], NonNegativeInt)] = {
val submitterI = Informee.create(submitter, NonNegativeInt.one, TrustLevel.Ordinary)
val signatoryI = Informee.create(signatory, NonNegativeInt.one, TrustLevel.Ordinary)
val observerI = Informee.create(observer, NonNegativeInt.one, TrustLevel.Ordinary)
val submitterI = Informee.create(submitter, NonNegativeInt.one)
val signatoryI = Informee.create(signatory, NonNegativeInt.one)
val observerI = Informee.create(observer, NonNegativeInt.one)
Map(
view0Position -> (Set(
submitterI,
@ -1188,12 +1199,13 @@ abstract class ConfirmationResponseProcessorTestV5Base(minimumPV: ProtocolVersio
class ConfirmationResponseProcessorTestV5
extends ConfirmationResponseProcessorTestV5Base(ProtocolVersion.v30) {
override lazy val mediatorId: MediatorId = MediatorId(
UniqueIdentifier.tryCreate("mediator", "one")
)
override lazy val mediatorRef: MediatorRef = MediatorRef(mediatorId)
override lazy val mediatorId: MediatorId = activeMediator2
override lazy val mediatorRef: MediatorRef = MediatorRef(mediatorGroup)
lazy val topology: TestingTopology = TestingTopology(
private def mediatorGroup0(mediators: MediatorId*) =
MediatorGroup(MediatorGroupIndex.zero, mediators, Seq.empty, PositiveInt.one)
lazy val topology: TestingTopologyX = TestingTopologyX(
Set(domainId),
Map(
submitter -> Map(participant -> ParticipantPermission.Confirmation),
@ -1202,9 +1214,10 @@ class ConfirmationResponseProcessorTestV5
observer ->
Map(participant -> ParticipantPermission.Observation),
),
Set(mediatorId),
Set(mediatorGroup),
sequencerGroup,
)
override lazy val identityFactory: TestingIdentityFactoryBase = TestingIdentityFactory(
override lazy val identityFactory: TestingIdentityFactoryBase = TestingIdentityFactoryX(
topology,
loggerFactory,
dynamicDomainParameters =
@ -1212,22 +1225,23 @@ class ConfirmationResponseProcessorTestV5
)
override lazy val identityFactory2: TestingIdentityFactoryBase = {
val topology2 = TestingTopology(
val topology2 = TestingTopologyX(
Set(domainId),
Map(
submitter -> Map(participant1 -> ParticipantPermission.Confirmation),
signatory -> Map(participant2 -> ParticipantPermission.Confirmation),
observer -> Map(participant3 -> ParticipantPermission.Confirmation),
),
Set(mediatorId),
Set(mediatorGroup),
sequencerGroup,
)
TestingIdentityFactory(topology2, loggerFactory, initialDomainParameters)
TestingIdentityFactoryX(topology2, loggerFactory, initialDomainParameters)
}
lazy val identityFactory3: TestingIdentityFactoryBase = {
val otherMediatorId = MediatorId(UniqueIdentifier.tryCreate("mediator", "other"))
val topology3 = topology.copy(mediators = Set(otherMediatorId))
TestingIdentityFactory(
val topology3 = topology.copy(mediatorGroups = Set(mediatorGroup0(otherMediatorId)))
TestingIdentityFactoryX(
topology3,
loggerFactory,
dynamicDomainParameters = initialDomainParameters,
@ -1235,13 +1249,14 @@ class ConfirmationResponseProcessorTestV5
}
override lazy val identityFactoryOnlySubmitter: TestingIdentityFactoryBase =
TestingIdentityFactory(
TestingTopology(
TestingIdentityFactoryX(
TestingTopologyX(
Set(domainId),
Map(
submitter -> Map(participant1 -> ParticipantPermission.Confirmation)
),
Set(mediatorId),
Set(mediatorGroup0(mediatorId)),
sequencerGroup,
),
loggerFactory,
dynamicDomainParameters = initialDomainParameters,
@ -1312,59 +1327,3 @@ class ConfirmationResponseProcessorTestV5
}
}
}
class ConfirmationResponseProcessorTestV5X
extends ConfirmationResponseProcessorTestV5Base(ProtocolVersion.v30) {
override lazy val mediatorRef: MediatorRef = MediatorRef(mediatorGroup)
override lazy val mediatorId: MediatorId = activeMediator2
override lazy val factory: ExampleTransactionFactory =
new ExampleTransactionFactory()(domainId = domainId, mediatorRef = mediatorRef)
override lazy val identityFactory: TestingIdentityFactoryBase = {
val topology = TestingTopologyX(
Set(domainId),
Map(
submitter -> Map(participant -> ParticipantPermission.Confirmation),
signatory ->
Map(participant -> ParticipantPermission.Confirmation),
observer ->
Map(participant -> ParticipantPermission.Observation),
),
Set(mediatorGroup),
)
TestingIdentityFactoryX(
topology,
loggerFactory,
dynamicDomainParameters =
initialDomainParameters.tryUpdate(participantResponseTimeout = participantResponseTimeout),
)
}
override lazy val identityFactory2: TestingIdentityFactoryBase = {
val topology2 = TestingTopologyX(
Set(domainId),
Map(
submitter -> Map(participant1 -> ParticipantPermission.Confirmation),
signatory -> Map(participant2 -> ParticipantPermission.Confirmation),
observer -> Map(participant3 -> ParticipantPermission.Confirmation),
),
Set(mediatorGroup),
)
TestingIdentityFactoryX(topology2, loggerFactory, initialDomainParameters)
}
override lazy val identityFactoryOnlySubmitter: TestingIdentityFactoryBase = {
TestingIdentityFactoryX(
TestingTopologyX(
Set(domainId),
Map(
submitter -> Map(participant1 -> ParticipantPermission.Confirmation)
),
Set(mediatorGroup),
),
loggerFactory,
dynamicDomainParameters = initialDomainParameters,
)
}
}

View File

@ -35,7 +35,7 @@ import scala.concurrent.Future
class MediatorEventStageProcessorTest extends AsyncWordSpec with BaseTest with HasTestCloseContext {
self =>
private lazy val domainId = DefaultTestIdentities.domainId
private lazy val mediatorId = DefaultTestIdentities.mediator
private lazy val mediatorId = DefaultTestIdentities.mediatorIdX
private lazy val mediatorMetrics = MediatorTestMetrics
private lazy val participantResponseTimeout = NonNegativeFiniteDuration.tryOfSeconds(10)
private lazy val factory = new ExampleTransactionFactory()(domainId = domainId)
@ -322,9 +322,10 @@ class MediatorEventStageProcessorTest extends AsyncWordSpec with BaseTest with H
private def responseAggregation(requestId: RequestId): Future[ResponseAggregation[?]] = {
val mockTopologySnapshot = mock[TopologySnapshot]
when(mockTopologySnapshot.consortiumThresholds(any[Set[LfPartyId]])).thenAnswer {
(parties: Set[LfPartyId]) => Future.successful(parties.map(x => x -> PositiveInt.one).toMap)
}
when(mockTopologySnapshot.consortiumThresholds(any[Set[LfPartyId]])(anyTraceContext))
.thenAnswer { (parties: Set[LfPartyId]) =>
Future.successful(parties.map(x => x -> PositiveInt.one).toMap)
}
ResponseAggregation.fromRequest(
requestId,
InformeeMessage(fullInformeeTree, Signature.noSignature)(testedProtocolVersion),

View File

@ -21,7 +21,6 @@ import com.digitalasset.canton.protocol.*
import com.digitalasset.canton.protocol.messages.InformeeMessage
import com.digitalasset.canton.time.Clock
import com.digitalasset.canton.topology.client.TopologySnapshot
import com.digitalasset.canton.topology.transaction.TrustLevel
import com.digitalasset.canton.topology.{DefaultTestIdentities, MediatorRef}
import com.digitalasset.canton.version.HasTestCloseContext
import com.digitalasset.canton.{ApplicationId, BaseTest, CommandId, HasExecutionContext, LfPartyId}
@ -49,7 +48,6 @@ class MediatorStateTest
val bob = ConfirmingParty(
LfPartyId.assertFromString("bob"),
PositiveInt.tryCreate(2),
TrustLevel.Ordinary,
)
val hashOps: HashOps = new SymbolicPureCrypto
val h: Int => Hash = TestHash.digest
@ -101,16 +99,17 @@ class MediatorStateTest
val informeeMessage =
InformeeMessage(fullInformeeTree, Signature.noSignature)(testedProtocolVersion)
val mockTopologySnapshot = mock[TopologySnapshot]
when(mockTopologySnapshot.consortiumThresholds(any[Set[LfPartyId]])).thenAnswer {
(parties: Set[LfPartyId]) => Future.successful(parties.map(x => x -> PositiveInt.one).toMap)
}
when(mockTopologySnapshot.consortiumThresholds(any[Set[LfPartyId]])(anyTraceContext))
.thenAnswer { (parties: Set[LfPartyId]) =>
Future.successful(parties.map(x => x -> PositiveInt.one).toMap)
}
val currentVersion =
ResponseAggregation
.fromRequest(
requestId,
informeeMessage,
mockTopologySnapshot,
)(anyTraceContext, executorService)
)(traceContext, executorService)
.futureValue // without explicit ec it deadlocks on AnyTestSuite.serialExecutionContext
def mediatorState: MediatorState = {

View File

@ -5,8 +5,8 @@ package com.digitalasset.canton.domain.mediator
import com.daml.nonempty.NonEmpty
import com.digitalasset.canton.config.RequireTypes.{NonNegativeInt, PositiveInt}
import com.digitalasset.canton.crypto.*
import com.digitalasset.canton.crypto.provider.symbolic.SymbolicPureCrypto
import com.digitalasset.canton.crypto.{HashOps, Salt, Signature, TestHash, TestSalt}
import com.digitalasset.canton.data.ViewPosition.MerkleSeqIndex
import com.digitalasset.canton.data.ViewPosition.MerkleSeqIndex.Direction
import com.digitalasset.canton.data.*
@ -21,7 +21,6 @@ import com.digitalasset.canton.protocol.*
import com.digitalasset.canton.protocol.messages.*
import com.digitalasset.canton.topology.*
import com.digitalasset.canton.topology.client.TopologySnapshot
import com.digitalasset.canton.topology.transaction.TrustLevel
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.ShowUtil.*
import com.digitalasset.canton.{ApplicationId, BaseTest, CommandId, LfPartyId}
@ -46,30 +45,21 @@ class ResponseAggregationTestV5 extends PathAnyFunSpec with BaseTest {
def salt(i: Int): Salt = TestSalt.generateSalt(i)
val domainId = DefaultTestIdentities.domainId
val mediator = MediatorRef(DefaultTestIdentities.mediator)
val mediator = MediatorRef(DefaultTestIdentities.mediatorIdX)
val participantId = DefaultTestIdentities.participant1
val aliceParty = LfPartyId.assertFromString("alice")
val alice = ConfirmingParty(
aliceParty,
PositiveInt.tryCreate(3),
TrustLevel.Ordinary,
)
val aliceVip = ConfirmingParty(
aliceParty,
PositiveInt.tryCreate(3),
TrustLevel.Vip,
)
val bob = ConfirmingParty(
LfPartyId.assertFromString("bob"),
PositiveInt.tryCreate(2),
TrustLevel.Ordinary,
)
val bobVip =
ConfirmingParty(LfPartyId.assertFromString("bob"), PositiveInt.tryCreate(2), TrustLevel.Vip)
val charlie = PlainInformee(LfPartyId.assertFromString("charlie"))
val dave =
ConfirmingParty(LfPartyId.assertFromString("dave"), PositiveInt.one, TrustLevel.Ordinary)
ConfirmingParty(LfPartyId.assertFromString("dave"), PositiveInt.one)
val solo = ParticipantId("solo")
val uno = ParticipantId("uno")
val duo = ParticipantId("duo")
@ -107,22 +97,8 @@ class ResponseAggregationTestV5 extends PathAnyFunSpec with BaseTest {
testedProtocolVersion,
)
val viewVip =
TransactionView.tryCreate(hashOps)(
ViewCommonData.create(hashOps)(
Set(aliceVip, bobVip, charlie),
NonNegativeInt.tryCreate(4),
salt(54171),
testedProtocolVersion,
),
b(9),
emptySubviews,
testedProtocolVersion,
)
val view1Position = ViewPosition(List(MerkleSeqIndex(List.empty)))
val view2Position = ViewPosition(List(MerkleSeqIndex(List.empty), MerkleSeqIndex(List.empty)))
val viewVipPosition = ViewPosition(List(MerkleSeqIndex(List.empty)))
val requestId = RequestId(CantonTimestamp.Epoch)
@ -189,7 +165,7 @@ class ResponseAggregationTestV5 extends PathAnyFunSpec with BaseTest {
val someOtherRootHash = RootHash(TestHash.digest(12345))
val topologySnapshot: TopologySnapshot = mock[TopologySnapshot]
when(topologySnapshot.consortiumThresholds(any[Set[LfPartyId]]))
when(topologySnapshot.consortiumThresholds(any[Set[LfPartyId]])(anyTraceContext))
.thenAnswer((parties: Set[LfPartyId]) =>
Future.successful(parties.map(x => x -> PositiveInt.one).toMap)
)
@ -248,8 +224,12 @@ class ResponseAggregationTestV5 extends PathAnyFunSpec with BaseTest {
result shouldBe None
}
when(topologySnapshot.canConfirm(eqTo(solo), any[LfPartyId], any[TrustLevel]))
.thenReturn(Future.successful(true))
when(
topologySnapshot.canConfirm(eqTo(solo), any[Set[LfPartyId]])(anyTraceContext)
)
.thenAnswer { (participantId: ParticipantId, parties: Set[LfPartyId]) =>
Future.successful(parties)
}
describe("rejection") {
val changeTs1 = requestId.unwrap.plusSeconds(1)
@ -528,16 +508,26 @@ class ResponseAggregationTestV5 extends PathAnyFunSpec with BaseTest {
val view2Position = ViewPosition(List(MerkleSeqIndex(List(Direction.Right))))
val topologySnapshot: TopologySnapshot = mock[TopologySnapshot]
when(topologySnapshot.consortiumThresholds(any[Set[LfPartyId]]))
when(topologySnapshot.consortiumThresholds(any[Set[LfPartyId]])(anyTraceContext))
.thenAnswer((parties: Set[LfPartyId]) =>
Future.successful(parties.map(x => x -> PositiveInt.one).toMap)
)
when(topologySnapshot.canConfirm(eqTo(solo), eqTo(bob.party), any[TrustLevel]))
.thenReturn(Future.successful(true))
when(topologySnapshot.canConfirm(eqTo(solo), eqTo(dave.party), any[TrustLevel]))
.thenReturn(Future.successful(true))
when(topologySnapshot.canConfirm(eqTo(solo), eqTo(alice.party), any[TrustLevel]))
.thenReturn(Future.successful(false))
when(
topologySnapshot.canConfirm(any[ParticipantId], any[Set[LfPartyId]])(
anyTraceContext
)
)
.thenAnswer { (participantId: ParticipantId, parties: Set[LfPartyId]) =>
if (participantId != solo)
Future.failed(new IllegalArgumentException(s"unexpected participant: $participantId"))
Future.successful(parties.flatMap {
case `bob`.party => Set(bob.party)
case `dave`.party => Set(dave.party)
case `alice`.party => Set.empty
case otherwise => throw new IllegalArgumentException(s"unexpected party: $otherwise")
})
}
val sut = ResponseAggregation
.fromRequest(
@ -658,154 +648,6 @@ class ResponseAggregationTestV5 extends PathAnyFunSpec with BaseTest {
}
}
describe("under the VIP policy") {
val commonMetadata = CommonMetadata
.create(hashOps, testedProtocolVersion)(
ConfirmationPolicy.Vip,
domainId,
mediator,
salt(5417),
new UUID(0L, 0L),
)
val fullInformeeTree = FullInformeeTree.tryCreate(
GenTransactionTree.tryCreate(hashOps)(
submitterMetadata,
commonMetadata,
b(2),
MerkleSeq.fromSeq(hashOps, testedProtocolVersion)(viewVip :: Nil),
),
testedProtocolVersion,
)
val informeeMessage =
InformeeMessage(fullInformeeTree, Signature.noSignature)(testedProtocolVersion)
val rootHash = informeeMessage.rootHash
val nonVip = ParticipantId("notAVip")
val topologySnapshotVip: TopologySnapshot = mock[TopologySnapshot]
when(topologySnapshotVip.consortiumThresholds(any[Set[LfPartyId]]))
.thenAnswer((parties: Set[LfPartyId]) =>
Future.successful(parties.map(x => x -> PositiveInt.one).toMap)
)
when(topologySnapshotVip.canConfirm(eqTo(solo), eqTo(alice.party), eqTo(TrustLevel.Vip)))
.thenReturn(Future.successful(true))
when(topologySnapshotVip.canConfirm(eqTo(solo), eqTo(bob.party), eqTo(TrustLevel.Vip)))
.thenReturn(Future.successful(false))
when(topologySnapshotVip.canConfirm(eqTo(solo), eqTo(bob.party), eqTo(TrustLevel.Ordinary)))
.thenReturn(Future.successful(true))
when(topologySnapshotVip.canConfirm(eqTo(nonVip), any[LfPartyId], eqTo(TrustLevel.Vip)))
.thenReturn(Future.successful(false))
when(
topologySnapshotVip.canConfirm(eqTo(nonVip), any[LfPartyId], eqTo(TrustLevel.Ordinary))
)
.thenReturn(Future.successful(true))
val sut = ResponseAggregation
.fromRequest(
requestId,
informeeMessage,
topologySnapshotVip,
)
.futureValue
val initialState =
Map(
viewVipPosition -> ViewState(
pendingConfirmingParties = Set(aliceVip, bobVip),
consortiumVoting = Map(
alice.party -> ConsortiumVotingState(),
bob.party -> ConsortiumVotingState(),
),
distanceToThreshold = viewVip.viewCommonData.tryUnwrap.threshold.unwrap,
rejections = Nil,
)
)
it("should have all pending confirming parties listed") {
sut.state shouldBe Right(initialState)
}
it("should reject non-VIP responses") {
val response =
mkResponse(
viewVipPosition,
LocalApprove(testedProtocolVersion),
Set(alice.party, bob.party),
rootHash,
)
val responseTs = requestId.unwrap.plusSeconds(1)
loggerFactory.assertLogs(
sut
.validateAndProgress(responseTs, response, topologySnapshotVip)
.futureValue shouldBe None,
_.shouldBeCantonError(
MediatorError.MalformedMessage,
_ shouldBe show"Received an unauthorized mediator response at $responseTs by $solo for request $requestId on behalf of Set(bob). Discarding response...",
),
)
}
it("should ignore malformed non-VIP responses") {
val rejectMsg = "malformed request"
val reject =
LocalReject.MalformedRejects.Payloads.Reject(rejectMsg)(localVerdictProtocolVersion)
val response =
MediatorResponse.tryCreate(
requestId,
nonVip,
None,
reject,
None,
Set.empty,
domainId,
testedProtocolVersion,
)
val result = loggerFactory.assertLogs(
valueOrFail(
sut
.validateAndProgress(requestId.unwrap.plusSeconds(1), response, topologySnapshotVip)
.futureValue
)(s"$nonVip responds Malformed"),
_.shouldBeCantonError(
LocalReject.MalformedRejects.Payloads,
_ shouldBe s"Rejected transaction due to malformed payload within views $rejectMsg",
_ should (contain("reportedBy" -> s"$nonVip") and contain(
"requestId" -> requestId.toString
)),
),
)
result.state shouldBe Right(initialState)
}
it("should accept VIP responses") {
val changeTs = requestId.unwrap.plusSeconds(1)
val response = mkResponse(
viewVipPosition,
LocalApprove(testedProtocolVersion),
Set(alice.party),
rootHash,
)
val result = valueOrFail(
sut.validateAndProgress(changeTs, response, topologySnapshotVip).futureValue
)("solo confirms with VIP trust level for alice")
result.version shouldBe changeTs
result.state shouldBe
Right(
Map(
viewVipPosition -> ViewState(
Set(bobVip),
Map(
alice.party -> ConsortiumVotingState(approvals = Set(solo)),
bob.party -> ConsortiumVotingState(),
),
1,
Nil,
)
)
)
}
}
describe("consortium state") {
it("should work for threshold = 1") {
ConsortiumVotingState(approvals = Set(solo)).isApproved shouldBe (true)
@ -885,7 +727,7 @@ class ResponseAggregationTestV5 extends PathAnyFunSpec with BaseTest {
val rootHash = informeeMessage.rootHash
val topologySnapshot: TopologySnapshot = mock[TopologySnapshot]
when(topologySnapshot.consortiumThresholds(any[Set[LfPartyId]]))
when(topologySnapshot.consortiumThresholds(any[Set[LfPartyId]])(anyTraceContext))
.thenAnswer((parties: Set[LfPartyId]) =>
Future.successful(
Map(
@ -925,8 +767,14 @@ class ResponseAggregationTestV5 extends PathAnyFunSpec with BaseTest {
)
}
when(topologySnapshot.canConfirm(any[ParticipantId], any[LfPartyId], any[TrustLevel]))
.thenReturn(Future.successful(true))
when(
topologySnapshot.canConfirm(any[ParticipantId], any[Set[LfPartyId]])(
anyTraceContext
)
)
.thenAnswer { (participantId: ParticipantId, parties: Set[LfPartyId]) =>
Future.successful(parties)
}
describe("should prevent response stuffing") {
describe("for reject by Bob with 3 votes from the same participant") {

View File

@ -18,7 +18,6 @@ import com.digitalasset.canton.protocol.messages.InformeeMessage
import com.digitalasset.canton.protocol.{ConfirmationPolicy, RequestId, RootHash}
import com.digitalasset.canton.resource.DbStorage
import com.digitalasset.canton.store.db.{DbTest, H2Test, PostgresTest}
import com.digitalasset.canton.topology.transaction.TrustLevel
import com.digitalasset.canton.topology.{DefaultTestIdentities, MediatorRef, TestingIdentityFactory}
import com.digitalasset.canton.tracing.TraceContext
import com.digitalasset.canton.util.FutureInstances.*
@ -48,7 +47,6 @@ trait FinalizedResponseStoreTest extends BeforeAndAfterAll {
val bobConfirmingParty = ConfirmingParty(
LfPartyId.assertFromString("bob"),
PositiveInt.tryCreate(2),
TrustLevel.Ordinary,
)
val hashOps = new SymbolicPureCrypto

View File

@ -30,7 +30,7 @@ class MemberAuthenticationServiceTest extends AsyncWordSpec with BaseTest {
val clock: SimClock = new SimClock(loggerFactory = loggerFactory)
val topology = TestingTopology().withSimpleParticipants(participant1).build()
val topology = TestingTopologyX().withSimpleParticipants(participant1).build()
val syncCrypto = topology.forOwnerAndDomain(participant1, domainId)
def service(

View File

@ -20,8 +20,8 @@ class DatabaseSequencerApiTest extends NonBftDomainSequencerApiTest {
crypto: DomainSyncCryptoClient
)(implicit materializer: Materializer): CantonSequencer = {
val clock = new SimClock(loggerFactory = loggerFactory)
val crypto = TestingIdentityFactory(
TestingTopology(),
val crypto = TestingIdentityFactoryX(
TestingTopologyX(),
loggerFactory,
DynamicDomainParameters.initialValues(clock, testedProtocolVersion),
).forOwnerAndDomain(owner = mediatorId, domainId)

View File

@ -78,7 +78,7 @@ abstract class SequencerApiTest
def createClock(): Clock = new SimClock(loggerFactory = loggerFactory)
def domainId: DomainId = DefaultTestIdentities.domainId
def mediatorId: MediatorId = DefaultTestIdentities.mediator
def mediatorId: MediatorId = DefaultTestIdentities.mediatorIdX
def topologyClientMember: Member = DefaultTestIdentities.sequencerId
def createSequencer(crypto: DomainSyncCryptoClient)(implicit

View File

@ -6,7 +6,7 @@ package com.digitalasset.canton.domain.sequencing.sequencer.store
import com.digitalasset.canton.domain.sequencing.sequencer.SequencerApiTest
import com.digitalasset.canton.logging.NamedLoggerFactory
import com.digitalasset.canton.sequencing.protocol.RecipientsTest.*
import com.digitalasset.canton.topology.{TestingIdentityFactory, TestingTopology}
import com.digitalasset.canton.topology.{TestingIdentityFactoryX, TestingTopologyX}
abstract class NonBftDomainSequencerApiTest extends SequencerApiTest {
@ -16,8 +16,8 @@ abstract class NonBftDomainSequencerApiTest extends SequencerApiTest {
NonBftDomainSequencerApiTest.this.loggerFactory
override lazy val topologyFactory =
new TestingIdentityFactory(
topology = TestingTopology().withSimpleParticipants(p11, p12, p13, p14, p15),
new TestingIdentityFactoryX(
topology = TestingTopologyX().withSimpleParticipants(p11, p12, p13, p14, p15),
loggerFactory,
List.empty,
)

View File

@ -8,15 +8,12 @@ import cats.syntax.semigroup.*
import com.daml.daml_lf_dev.DamlLf
import com.daml.lf.archive.Decode
import com.daml.lf.data.Ref
import com.daml.lf.language.LanguageVersion
import com.daml.nonempty.NonEmpty
import com.digitalasset.canton.platform.store.packagemeta.PackageMetadata.{
InterfacesImplementedBy,
PackageResolution,
}
import scala.math.Ordered.orderingToOrdered
final case class PackageMetadata(
interfaces: Set[Ref.Identifier] = Set.empty,
templates: Set[Ref.Identifier] = Set.empty,
@ -41,32 +38,22 @@ object PackageMetadata {
def from(archive: DamlLf.Archive): PackageMetadata = {
val ((packageId, pkg), packageInfo) = Decode.assertDecodeInfoPackage(archive)
val packageLanguageVersion = pkg.languageVersion
val nonUpgradablePackageMetadata = PackageMetadata(
packageNameMap = Map.empty,
val packageName = pkg.metadata.name
val packageVersion = pkg.metadata.version
val packageNameMap = Map(
packageName -> PackageResolution(
preference = LocalPackagePreference(packageVersion, packageId),
allPackageIdsForName = NonEmpty(Set, packageId),
)
)
PackageMetadata(
packageNameMap = packageNameMap,
interfaces = packageInfo.definedInterfaces,
templates = packageInfo.definedTemplates,
interfacesImplementedBy = packageInfo.interfaceInstances,
packageIdVersionMap = Map.empty,
packageIdVersionMap = Map(packageId -> (packageName, packageVersion)),
)
// TODO(#16362): Replace with own feature
if (packageLanguageVersion >= LanguageVersion.Features.sharedKeys) {
val packageName = pkg.metadata.name
val packageVersion = pkg.metadata.version
// Update with upgradable package metadata
nonUpgradablePackageMetadata.copy(
packageNameMap = Map(
packageName -> PackageResolution(
preference = LocalPackagePreference(packageVersion, packageId),
allPackageIdsForName = NonEmpty(Set, packageId),
)
),
packageIdVersionMap = Map(packageId -> (packageName, packageVersion)),
)
}
else nonUpgradablePackageMetadata
}
object Implicits {

View File

@ -5,7 +5,6 @@ module Foo where
import DA.Functor (void)
template Divulger
with
divulgees: [Party] -- Parties to whom something is divulged
@ -92,6 +91,9 @@ template Foo1
do
return ()
interface instance FooI1 for Foo1 where
view = FooData with templateName = "Foo1", ..
template Foo2
with
signatory : Party
@ -119,6 +121,10 @@ template Foo2
do
return ()
interface instance FooI2 for Foo2 where
view = foo2ToFooData $ foo2Roundtrip 10 this
template Foo3
with
signatory : Party
@ -146,8 +152,60 @@ template Foo3
do
return ()
interface instance FooI3 for Foo3 where
view = foo3ToFooData $ foo3Roundtrip 100 this
template Dummy
with
signatory: Party
where
signatory signatory
data FooData = FooData
with
signatory : Party
observers : [Party]
payload : Text
keyId: Text
templateName: Text
deriving (Eq, Show)
-- FooI1 is exposing the most simple case of the interface views - just copying the data from within the template
interface FooI1 where
viewtype FooData
foo2ToFooData : Foo2 -> FooData
foo2ToFooData Foo2{..} = FooData with templateName = "Foo2", ..
fooDataToFoo2 : FooData -> Foo2
fooDataToFoo2 FooData{..}
| templateName == "Foo2" = Foo2 {..}
| otherwise = error "fooDataToFoo2 called non non-foo2"
foo2Roundtrip : Int -> Foo2 -> Foo2
foo2Roundtrip n x
| n <= 0 = x
| otherwise = foo2Roundtrip (n - 1) (fooDataToFoo2 $ foo2ToFooData x)
-- FooI2 is exposing a FooData view through 10 round-trips in the recursion calls
interface FooI2 where
viewtype FooData
foo3ToFooData : Foo3 -> FooData
foo3ToFooData Foo3{..} = FooData with templateName = "Foo3", ..
fooDataToFoo3 : FooData -> Foo3
fooDataToFoo3 FooData{..}
| templateName == "Foo3" = Foo3 {..}
| otherwise = error "fooDataToFoo3 called non non-foo3"
foo3Roundtrip : Int -> Foo3 -> Foo3
foo3Roundtrip n x
| n <= 0 = x
| otherwise = foo3Roundtrip (n - 1) (fooDataToFoo3 $ foo3ToFooData x)
-- FooI3 is exposing a FooData view through 100 round-trips in the recursion calls
interface FooI3 where
viewtype FooData

View File

@ -1,63 +0,0 @@
-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
module InterfaceSubscription where
import Foo
data FooData = FooData
with
signatory : Party
observers : [Party]
payload : Text
keyId: Text
templateName: Text
deriving (Eq, Show)
-- FooI1 is exposing the most simple case of the interface views - just copying the data from within the template
interface FooI1 where
viewtype FooData
interface instance FooI1 for Foo.Foo1 where
view = FooData with templateName = "Foo1", ..
foo2ToFooData : Foo.Foo2 -> FooData
foo2ToFooData Foo.Foo2{..} = FooData with templateName = "Foo2", ..
fooDataToFoo2 : FooData -> Foo.Foo2
fooDataToFoo2 FooData{..}
| templateName == "Foo2" = Foo.Foo2 {..}
| otherwise = error "fooDataToFoo2 called non non-foo2"
foo2Roundtrip : Int -> Foo.Foo2 -> Foo.Foo2
foo2Roundtrip n x
| n <= 0 = x
| otherwise = foo2Roundtrip (n - 1) (fooDataToFoo2 $ foo2ToFooData x)
-- FooI2 is exposing a FooData view through 10 round-trips in the recursion calls
interface FooI2 where
viewtype FooData
interface instance FooI2 for Foo.Foo2 where
view = foo2ToFooData $ foo2Roundtrip 10 this
foo3ToFooData : Foo.Foo3 -> FooData
foo3ToFooData Foo.Foo3{..} = FooData with templateName = "Foo3", ..
fooDataToFoo3 : FooData -> Foo.Foo3
fooDataToFoo3 FooData{..}
| templateName == "Foo3" = Foo.Foo3 {..}
| otherwise = error "fooDataToFoo3 called non non-foo3"
foo3Roundtrip : Int -> Foo.Foo3 -> Foo.Foo3
foo3Roundtrip n x
| n <= 0 = x
| otherwise = foo3Roundtrip (n - 1) (fooDataToFoo3 $ foo3ToFooData x)
-- FooI3 is exposing a FooData view through 100 round-trips in the recursion calls
interface FooI3 where
viewtype FooData
interface instance FooI3 for Foo.Foo3 where
view = foo3ToFooData $ foo3Roundtrip 100 this

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
name: benchtool-tests
source: .
version: 3.0.0

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
name: carbonv1-tests
source: .
version: 3.0.0

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
name: carbonv2-tests
data-dependencies:
- ../../../../scala-2.13/resource_managed/main/carbonv1-tests.dar

View File

@ -1,17 +0,0 @@
-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
module CarbonV3 where
import CarbonV2
data RetroView = RetroView with
value : Int
interface RetroI where
viewtype RetroView
getA: Int
interface instance RetroI for CarbonV2.T where
getA = a
view = RetroView with
value = a

View File

@ -1,10 +0,0 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
name: carbonv3-tests
data-dependencies:
- ../../../../scala-2.13/resource_managed/main/carbonv2-tests.dar
source: .
version: 3.0.0
dependencies:
- daml-prim
- daml-stdlib
- daml3-script

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
name: experimental-tests
source: .
version: 3.0.0

View File

@ -60,6 +60,9 @@ template Iou
controller owner
do create this with observers = filter (/= oldObserver) observers
interface instance IIou for Iou where
view = AmountAndCurrency currency amount
template IouTransfer
with
iou : Iou
@ -81,3 +84,13 @@ template IouTransfer
do create iou with
owner = newOwner
observers = []
data AmountAndCurrency = AmountAndCurrency with
-- pointlessly different field names to ensure that template
-- payloads aren't being returned
icurrency : Text
iamount : Decimal
interface IIou where
viewtype AmountAndCurrency

View File

@ -117,6 +117,9 @@ template ParameterShowcase
where
signatory operator
key (operator, text) : (Party, Text)
maintainer key._1
-- multiple argument choice
choice Choice1 : ContractId ParameterShowcase
with
@ -377,3 +380,50 @@ template CreateAndFetch
do cid <- create CreateAndFetch with p
_ <- fetch cid
return ()
template MultiPartyContract
with
parties: [Party]
value: Text
where
signatory parties
key this: MultiPartyContract
maintainer key.parties
choice MPAddSignatories : ContractId MultiPartyContract
with
newParties: [Party]
controller parties ++ newParties
do
create this with
parties = parties ++ newParties
nonconsuming choice MPFetchOther : ()
with
cid: ContractId MultiPartyContract
actors: [Party]
controller actors
do
actualContract <- fetch cid
return ()
nonconsuming choice MPFetchOtherByKey : ()
with
keyToFetch: MultiPartyContract
actors: [Party]
controller actors
do
(actualCid, actualContract) <- fetchByKey @MultiPartyContract keyToFetch
return ()
nonconsuming choice MPLookupOtherByKey : ()
with
keyToFetch: MultiPartyContract
actors: [Party]
expectedCid: Optional (ContractId MultiPartyContract)
controller actors
do
actualCid <- lookupByKey @MultiPartyContract keyToFetch
assertMsg "LookupOtherByKey value matches" (expectedCid == actualCid)

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
name: model-tests
source: .
version: 3.0.0

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
name: package-management-tests
source: .
version: 3.0.0

View File

@ -1,22 +0,0 @@
-- Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
module Interface3 where
import qualified Interface
data EmptyInterfaceView = EmptyInterfaceView {}
interface I where
viewtype EmptyInterfaceView
getOwner : Party
nonconsuming choice ChoiceI3: ()
controller getOwner this
do pure ()
choice MyArchive : Text
controller getOwner this
do pure "Interface3.I"
interface instance I for Interface.T where
view = EmptyInterfaceView
getOwner = owner

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
name: semantic-tests
source: .
version: 3.0.0

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
build-options:
- --target=2.1
name: JsonEncodingTest

View File

@ -10,3 +10,5 @@ Forked or extended Daml and third-party libraries:
- [slick](https://github.com/slick/slick) - Modified sql interpolations to set read and write effects
- [wartremover](http://www.wartremover.org/) - Implementations of custom warts
- [magnolify](https://github.com/spotify/magnolify) - Implementations of our own type class derivations

View File

@ -0,0 +1,62 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates.
// Proprietary code. All rights reserved.
package magnolify.scalacheck.shrink
import org.scalacheck.Shrink
import org.scalacheck.util.Buildable
import scala.annotation.nowarn
import scala.concurrent.duration.{Duration, FiniteDuration}
/** A copy of [[org.scalacheck.Shrink]] so that we can get rid of the [[org.scalacheck.Shrink.shrinkAny]]
* implicit that would be picked up by the derivation macro. Unfortunately, there does not seem
* to be any way to prevent the compiler from picking up this implicit from the companion object.
* Even introducing a another copy that causes an ambiguity does not seem to work.
*/
trait DerivedShrink[A] {
def shrink: Shrink[A]
}
@nowarn("cat=deprecation")
object DerivedShrink {
def apply[A](implicit ev: DerivedShrink[A]): DerivedShrink[A] = ev
def of[A](s: Shrink[A]): DerivedShrink[A] = new DerivedShrink[A] {
override def shrink: Shrink[A] = s
}
def from[A](f: A => Stream[A]): DerivedShrink[A] = of(Shrink(f))
// Copies of the pre-defined shrink instances
// No need to copy over the implicits for Tuples and Either as they can be auto-derived.
implicit def shrinkContainer[C[_], T](implicit
v: C[T] => Traversable[T],
s: DerivedShrink[T],
b: Buildable[T, C[T]],
): DerivedShrink[C[T]] = of(Shrink.shrinkContainer[C, T](v, s.shrink, b))
implicit def shrinkContainer2[C[_, _], T, U](implicit
v: C[T, U] => Traversable[(T, U)],
s: DerivedShrink[(T, U)],
b: Buildable[(T, U), C[T, U]],
): DerivedShrink[C[T, U]] = of(Shrink.shrinkContainer2[C, T, U](v, s.shrink, b))
implicit def shrinkFractional[T: Fractional]: DerivedShrink[T] =
of(Shrink.shrinkFractional[T])
implicit def shrinkIntegral[T: Integral]: DerivedShrink[T] =
of(Shrink.shrinkIntegral[T])
implicit lazy val shrinkString: DerivedShrink[String] = of(Shrink.shrinkString)
// Not equivalent to the auto-generated one because Some can be shrunk to None
implicit def shrinkOption[T: DerivedShrink]: DerivedShrink[Option[T]] =
of(Shrink.shrinkOption[T](DerivedShrink[T].shrink))
implicit val shrinkFiniteDuration: DerivedShrink[FiniteDuration] =
of(Shrink.shrinkFiniteDuration)
implicit val shrinkDuration: DerivedShrink[Duration] = of(Shrink.shrinkDuration)
}

View File

@ -0,0 +1,79 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates.
// Proprietary code. All rights reserved.
package magnolify.scalacheck
import magnolia1.{CaseClass, Magnolia, SealedTrait}
import org.scalacheck.Shrink
import scala.annotation.nowarn
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
package object shrink {
object semiauto {
/** Semi-automatic derivation of [[DerivedShrink]] instances for case classes and sealed traits thereof.
*/
@nowarn("cat=deprecation")
object DerivedShrinkDerivation {
type Typeclass[T] = DerivedShrink[T]
def join[T](caseClass: CaseClass[Typeclass, T]): Typeclass[T] = DerivedShrink.from { x =>
// Shrink each parameter individually rather than looking at all combinations
// similar to how `Shrink.shrinkTuple*` works. This makes sense because if a shrunk value
// is a witness to the property violation, the shrinking algorithm will try to shrink this value again.
caseClass.parameters.toStream.flatMap { param =>
param.typeclass.shrink
.shrink(param.dereference(x))
.map { shrunkParamVal =>
caseClass.construct { p =>
if (p == param) shrunkParamVal else p.dereference(x)
}
}
}
}
def split[T](sealedTrait: SealedTrait[Typeclass, T]): Typeclass[T] = DerivedShrink.from { x =>
sealedTrait.split(x)(subtype => subtype.typeclass.shrink.shrink(subtype.cast(x)))
}
implicit def apply[T]: Typeclass[T] = macro Magnolia.gen[T]
}
/** Semi-automatic derivation of [[org.scalacheck.Shrink]] instances for case classes and sealed traits thereof.
* Derivation goes via [[magnolify.scalacheck.shrink.DerivedShrink]] so that derivation does not fall
* back to the unshrinkable [[org.scalacheck.Shrink.shrinkAny]] default. This means that implicits
* for [[DerivedShrink]] must be in scope for all non-derived data types, even if the derivation is for
* [[org.scalacheck.Shrink]].
*/
object ShrinkDerivation {
def genShrinkMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe.*
val wtt = weakTypeTag[T]
q"""_root_.magnolify.scalacheck.shrink.semiauto.DerivedShrinkDerivation.apply[$wtt].shrink"""
}
def apply[T]: Shrink[T] = macro genShrinkMacro[T]
}
}
/** Automatic derivation of [[DerivedShrink]] and [[org.scalacheck.Shrink]] instances for case classes and
* sealed traits thereof.
*/
object auto {
implicit def genShrink[T]: Shrink[T] = macro semiauto.ShrinkDerivation.genShrinkMacro[T]
def genDeriveShrinkMacro[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe.*
val wtt = weakTypeTag[T]
q"""_root_.magnolify.scalacheck.shrink.semiauto.DerivedShrinkDerivation.apply[$wtt]"""
}
/** This implicit must be in scope for fully automatic derivation so that the compiler picks it up
* when asked to derive instances for argument types.
*/
implicit def genDerivedShrink[T]: DerivedShrink[T] = macro genDeriveShrinkMacro[T]
}
}

View File

@ -0,0 +1,119 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates.
// Proprietary code. All rights reserved.
package magnolify.scalacheck.shrink
import org.scalacheck.Shrink
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import scala.annotation.nowarn
class ShrinkDerivationTest extends AnyWordSpec with Matchers {
import ShrinkDerivationTest.*
"ShrinkDerivation" should {
"derive a Shrink instance for a case class" in {
import magnolify.scalacheck.shrink.semiauto.*
val shrinkFoo = ShrinkDerivation[Foo]
val shrunkFoos = shrinkFoo.shrink(Foo(4, -2))
// Should behave exactly like the existing shrinks for tuples
val expected = Shrink
.shrinkTuple2[Int, Int](Shrink.shrinkIntegral[Int], Shrink.shrinkIntegral[Int])
.shrink((4, -2))
.map(Foo.tupled)
shrunkFoos shouldBe expected
}
"auto-derive a Shrink for a recursive case class" in {
// This test works only with fully automatic derivation because
// because it checks that we pick up the custom DerivedShrink instance for Option.
// With semi-automatic derivation, magnolia generates its own DerivedShrink instance
// for Option through which the recursion goes through.
// The latter does not shrink Some to None though.
import magnolify.scalacheck.shrink.auto.*
val shrinkRec = implicitly[Shrink[Rec]]
val shrunkRecs = shrinkRec.shrink(Rec(4, Some(Rec(1, None))))
@nowarn("msg=dead code following this construct")
val expected = Shrink
.shrinkTuple2[Int, Option[(Int, Option[Nothing])]](
// Let's be very explicit about the Shrink implicits as auto-derivation is in scope
Shrink.shrinkIntegral,
Shrink.shrinkOption(
Shrink.shrinkTuple2[Int, Option[Nothing]](
Shrink.shrinkIntegral,
Shrink.shrinkOption[Nothing](Shrink.shrinkAny[Nothing]),
)
),
)
.shrink((4, Some((1, None))))
.map {
case (a, None) => Rec(a, None)
case (a, Some((b, None))) => Rec(a, Some(Rec(b, None)))
case (_, Some((_, Some(nothing)))) => nothing
}
shrunkRecs shouldBe expected
}
"semiauto-derive a Shrink for a recursive case class" in {
// This test uses semi-automatic derivation
// and therefore generates its own DerivedShrink instance for Option following the sealed-trait construction.
// Accordingly, Option behaves like an Either[Unit, *].
import magnolify.scalacheck.shrink.semiauto.*
val shrinkRec = ShrinkDerivation[Rec]
val shrunkRecs = shrinkRec.shrink(Rec(4, Some(Rec(1, None))))
@nowarn("msg=dead code following this construct")
val expected = Shrink
.shrinkTuple2[Int, Either[Unit, (Int, Option[Nothing])]](
Shrink.shrinkIntegral,
Shrink.shrinkEither(
Shrink.shrinkAny[Unit],
Shrink.shrinkTuple2[Int, Option[Nothing]](
Shrink.shrinkIntegral,
Shrink.shrinkOption[Nothing](Shrink.shrinkAny[Nothing]),
),
),
)
.shrink((4, Right((1, None))))
.map {
case (a, Left(_)) => Rec(a, None)
case (a, Right((b, None))) => Rec(a, Some(Rec(b, None)))
case (_, Right((_, Some(nothing)))) => nothing
}
shrunkRecs shouldBe expected
}
"derive a Shrink for nested case classes" in {
import magnolify.scalacheck.shrink.auto.*
val nested = Nested(Foo(4, 0), Rec(-2, None))
val shrunkNested = Shrink.shrink(nested)
val expected = Shrink
.shrinkTuple2[(Int, Int), Int](
Shrink.shrinkTuple2(Shrink.shrinkIntegral, Shrink.shrinkIntegral),
Shrink.shrinkIntegral,
)
.shrink(((4, 0), -2))
.map { case ((a, b), c) => Nested(Foo(a, b), Rec(c, None)) }
shrunkNested shouldBe expected
}
}
}
object ShrinkDerivationTest {
private final case class Foo(a: Int, b: Int)
private final case class Rec(a: Int, rec: Option[Rec])
private final case class Nested(a: Foo, b: Rec)
}

View File

@ -1,4 +1,4 @@
sdk-version: 3.0.0-snapshot.20240131.12677.0.v910a4a9e
sdk-version: 3.0.0-snapshot.20240201.12683.0.v3b292d00
build-options:
- --target=2.1
name: AdminWorkflows

View File

@ -3,8 +3,7 @@
package com.digitalasset.canton.participant.admin.repair
import cats.data.{EitherT, OptionT}
import cats.syntax.foldable.*
import cats.data.EitherT
import cats.syntax.parallel.*
import cats.syntax.traverse.*
import com.daml.lf.data.Bytes
@ -154,14 +153,19 @@ private final class ChangeAssignation(
private def atLeastOneHostedStakeholderAtTarget(
contractId: LfContractId,
stakeholders: Set[LfPartyId],
)(implicit executionContext: ExecutionContext): EitherT[Future, String, Unit] =
OptionT(
stakeholders.toSeq
.findM(hostsParty(repairTarget.domain.topologySnapshot, participantId))
).map(_.discard)
.toRight(
show"Not allowed to move contract $contractId without at least one stakeholder of $stakeholders existing locally on the target domain asOf=${repairTarget.domain.topologySnapshot.timestamp}"
)
)(implicit
executionContext: ExecutionContext,
traceContext: TraceContext,
): EitherT[Future, String, Unit] = {
EitherT(hostsParties(repairTarget.domain.topologySnapshot, stakeholders, participantId).map {
hosted =>
Either.cond(
hosted.nonEmpty,
(),
show"Not allowed to move contract $contractId without at least one stakeholder of $stakeholders existing locally on the target domain asOf=${repairTarget.domain.topologySnapshot.timestamp}",
)
})
}
private def readContractsFromSource(
contractIdsWithTransferCounters: List[
@ -306,14 +310,18 @@ private final class ChangeAssignation(
repair: RepairRequest,
contracts: List[SerializableContract],
participantId: ParticipantId,
)(implicit executionContext: ExecutionContext): Future[Set[LfPartyId]] =
contracts
.flatMap(_.metadata.stakeholders)
.distinct
.parTraverseFilter(party =>
hostsParty(repair.domain.topologySnapshot, participantId)(party).map(Option.when(_)(party))
)
.map(_.toSet)
)(implicit
executionContext: ExecutionContext,
traceContext: TraceContext,
): Future[Set[LfPartyId]] = {
hostsParties(
repair.domain.topologySnapshot,
contracts
.flatMap(_.metadata.stakeholders)
.toSet,
participantId,
)
}
private def insertMany(repair: RepairRequest, events: List[TimestampedEvent])(implicit
executionContext: ExecutionContext,

View File

@ -7,7 +7,6 @@ import cats.Eval
import cats.data.{EitherT, OptionT}
import cats.implicits.catsSyntaxTuple2Semigroupal
import cats.syntax.either.*
import cats.syntax.foldable.*
import cats.syntax.parallel.*
import cats.syntax.traverse.*
import com.daml.ledger.api.v1.value.{Identifier, Record}
@ -368,11 +367,13 @@ final class RepairService(
.parTraverse_(packageKnown)
hostedParties <- EitherT.right(
filteredContracts
.flatMap(_.witnesses)
.distinct
.parFilterA(hostsParty(repair.domain.topologySnapshot, participantId))
.map(_.toSet)
hostsParties(
repair.domain.topologySnapshot,
filteredContracts
.flatMap(_.witnesses)
.toSet,
participantId,
)
)
_ = logger.debug(s"Publishing ${filteredContracts.size} added contracts")
@ -645,14 +646,17 @@ final class RepairService(
)
// Witnesses all known locally.
_witnessesKnownLocally <- contractToAdd.witnesses.toList.parTraverse_ { p =>
EitherT(hostsParty(topologySnapshot, participantId)(p).map { hosted =>
EitherUtil.condUnitE(
hosted,
log(s"Witness $p not active on domain ${repair.domain.alias} and local participant"),
)
})
}
_witnessesKnownLocally <-
EitherT(
hostsParties(topologySnapshot, contractToAdd.witnesses, participantId).map { hosted =>
contractToAdd.witnesses.toList.traverse(p =>
EitherUtil.condUnitE(
hosted.contains(p),
log(s"Witness $p not active on domain ${repair.domain.alias} and local participant"),
)
)
}
)
_ <-
if (ignoreStakeholderCheck) EitherT.rightT[Future, String](())
@ -660,17 +664,15 @@ final class RepairService(
for {
// At least one stakeholder is hosted locally if no witnesses are defined
_localStakeholderOrWitnesses <- EitherT(
contract.metadata.stakeholders.toList
.findM(hostsParty(topologySnapshot, participantId))
.map(_.isDefined)
.map { oneStakeholderIsLocal =>
hostsParties(topologySnapshot, contract.metadata.stakeholders, participantId).map {
localStakeholders =>
EitherUtil.condUnitE(
contractToAdd.witnesses.nonEmpty || oneStakeholderIsLocal,
contractToAdd.witnesses.nonEmpty || localStakeholders.nonEmpty,
log(
s"Contract ${contract.contractId} has no local stakeholders ${contract.metadata.stakeholders} and no witnesses defined"
),
)
}
}
)
// All stakeholders exist on the domain
_ <- topologySnapshot

Some files were not shown because too many files have changed in this diff Show More