mirror of
https://github.com/digital-asset/daml.git
synced 2024-11-10 10:46:11 +03:00
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:
parent
470ee18a8a
commit
e9c60f47a0
@ -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,
|
||||
),
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
|
@ -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) =>
|
||||
|
@ -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"))
|
||||
|
@ -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 =
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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]]]
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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)"
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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) =>
|
||||
|
@ -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(_)))
|
||||
}
|
||||
|
||||
|
@ -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))))
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
),
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -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 = {
|
||||
|
@ -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
|
@ -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())
|
||||
|
||||
|
@ -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
|
||||
|
@ -249,7 +249,6 @@ class ValidatingTopologyMappingXChecksTest
|
||||
domainId,
|
||||
participant1,
|
||||
ParticipantPermissionX.Submission,
|
||||
TrustLevelX.Ordinary,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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 }
|
||||
|
@ -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 =>
|
||||
|
@ -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)
|
||||
|
@ -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 _ =>
|
||||
}
|
||||
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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 = {
|
||||
|
@ -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") {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
BIN
canton/community/ledger/ledger-common/src/test/resources/test-models/benchtool-tests-2.1.dar
Executable file → Normal file
BIN
canton/community/ledger/ledger-common/src/test/resources/test-models/benchtool-tests-2.1.dar
Executable file → Normal file
Binary file not shown.
BIN
canton/community/ledger/ledger-common/src/test/resources/test-models/model-tests-2.1.dar
Executable file → Normal file
BIN
canton/community/ledger/ledger-common/src/test/resources/test-models/model-tests-2.1.dar
Executable file → Normal file
Binary file not shown.
BIN
canton/community/ledger/ledger-common/src/test/resources/test-models/semantic-tests-2.1.dar
Executable file → Normal file
BIN
canton/community/ledger/ledger-common/src/test/resources/test-models/semantic-tests-2.1.dar
Executable file → Normal file
Binary file not shown.
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
@ -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]
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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
Loading…
Reference in New Issue
Block a user