diff --git a/ledger/ledger-api-domain/src/main/scala/com/digitalasset/ledger/api/domain.scala b/ledger/ledger-api-domain/src/main/scala/com/digitalasset/ledger/api/domain.scala index 0c7c54ac906f..466a094fadd9 100644 --- a/ledger/ledger-api-domain/src/main/scala/com/digitalasset/ledger/api/domain.scala +++ b/ledger/ledger-api-domain/src/main/scala/com/digitalasset/ledger/api/domain.scala @@ -204,15 +204,9 @@ object domain { override val description: String = "DuplicateKey: contract key is not unique" } - final case class PartiesNotKnownOnLedger(parties: Set[String]) extends RejectionReason { - override val description: String = "Some parties are unallocated" - } - /** The ledger time of the submission violated some constraint on the ledger time. */ final case class InvalidLedgerTime(description: String) extends RejectionReason - final case class LedgerConfigNotFound(description: String) extends RejectionReason - /** The transaction relied on contracts being active that were no longer * active at the point where it was sequenced. */ diff --git a/ledger/metrics/src/main/scala/com/daml/metrics/Metrics.scala b/ledger/metrics/src/main/scala/com/daml/metrics/Metrics.scala index bb98203b9b4f..cbbae4c83d4a 100644 --- a/ledger/metrics/src/main/scala/com/daml/metrics/Metrics.scala +++ b/ledger/metrics/src/main/scala/com/daml/metrics/Metrics.scala @@ -400,7 +400,6 @@ final class Metrics(val registry: MetricRegistry) { private val Prefix: MetricName = index.Prefix :+ "db" val storePartyEntry: Timer = registry.timer(Prefix :+ "store_party_entry") - val storeInitialState: Timer = registry.timer(Prefix :+ "store_initial_state") val storePackageEntry: Timer = registry.timer(Prefix :+ "store_package_entry") val storeTransaction: Timer = registry.timer(Prefix :+ "store_ledger_entry") @@ -473,7 +472,6 @@ final class Metrics(val registry: MetricRegistry) { val prepareBatches: Timer = registry.timer(dbPrefix :+ "prepare_batches") // in order within SQL transaction - val commitValidation: Timer = registry.timer(dbPrefix :+ "commit_validation") val eventsBatch: Timer = registry.timer(dbPrefix :+ "events_batch") val deleteContractWitnessesBatch: Timer = registry.timer(dbPrefix :+ "delete_contract_witnesses_batch") @@ -490,9 +488,6 @@ final class Metrics(val registry: MetricRegistry) { val storeRejectionDbMetrics: DatabaseMetrics = createDbMetrics( "store_rejection" ) // FIXME Base name conflicts with storeRejection - val storeInitialStateFromScenario: DatabaseMetrics = createDbMetrics( - "store_initial_state_from_scenario" - ) val loadParties: DatabaseMetrics = createDbMetrics("load_parties") val loadAllParties: DatabaseMetrics = createDbMetrics("load_all_parties") val loadPackages: DatabaseMetrics = createDbMetrics("load_packages") diff --git a/ledger/participant-integration-api/BUILD.bazel b/ledger/participant-integration-api/BUILD.bazel index b4c4305ad5dc..3429dda4617a 100644 --- a/ledger/participant-integration-api/BUILD.bazel +++ b/ledger/participant-integration-api/BUILD.bazel @@ -59,7 +59,6 @@ compile_deps = [ "//libs-scala/build-info", "//libs-scala/contextualized-logging", "//libs-scala/concurrent", - "//libs-scala/grpc-utils", "//libs-scala/logging-entries", "//libs-scala/ports", "//libs-scala/resources", diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/Conversions.scala b/ledger/participant-integration-api/src/main/scala/platform/store/Conversions.scala index 15ad6804935f..8e08ed34bb1e 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/Conversions.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/Conversions.scala @@ -5,26 +5,18 @@ package com.daml.platform.store import anorm.Column.nonNull import anorm._ -import com.daml.error.ContextualizedErrorLogger -import com.daml.error.definitions.LedgerApiErrors -import com.daml.grpc.GrpcStatus -import com.daml.ledger.api.domain import com.daml.ledger.offset.Offset -import com.daml.ledger.participant.state.v2.Update.CommandRejected -import com.daml.ledger.participant.state.{v2 => state} import com.daml.lf.crypto.Hash import com.daml.lf.data.Ref import com.daml.lf.ledger.EventId import com.daml.lf.value.Value -import com.daml.platform.server.api.validation.ErrorFactories import spray.json.DefaultJsonProtocol._ import spray.json._ -import scala.util.Try import java.io.BufferedReader import java.sql.{PreparedStatement, SQLNonTransientException, Types} import java.util.stream.Collectors -import scala.annotation.nowarn +import scala.util.Try // TODO append-only: split this file on cleanup, and move anorm/db conversion related stuff to the right place @@ -390,67 +382,4 @@ private[platform] object Conversions { override val sqlType: String = ParameterMetaData.StringParameterMetaData.sqlType override val jdbcType: Int = ParameterMetaData.StringParameterMetaData.jdbcType } - - implicit class RejectionReasonOps(rejectionReason: domain.RejectionReason) { - @nowarn("msg=deprecated") - def toParticipantStateRejectionReason( - errorFactories: ErrorFactories - )(implicit - contextualizedErrorLogger: ContextualizedErrorLogger - ): state.Update.CommandRejected.RejectionReasonTemplate = - rejectionReason match { - case domain.RejectionReason.ContractsNotFound(missingContractIds) => - CommandRejected.FinalReason( - errorFactories.CommandRejections.contractsNotFound(missingContractIds) - ) - case domain.RejectionReason.Inconsistent(reason) => - CommandRejected.FinalReason( - errorFactories.CommandRejections.inconsistent(reason) - ) - case domain.RejectionReason.InconsistentContractKeys(lookupResult, currentResult) => - CommandRejected.FinalReason( - errorFactories.CommandRejections - .inconsistentContractKeys(lookupResult, currentResult) - ) - case rejection @ domain.RejectionReason.DuplicateContractKey(key) => - CommandRejected.FinalReason( - errorFactories.CommandRejections - .duplicateContractKey(rejection.description, key) - ) - case domain.RejectionReason.Disputed(reason) => - CommandRejected.FinalReason( - errorFactories.CommandRejections.Deprecated.disputed(reason) - ) - case domain.RejectionReason.OutOfQuota(reason) => - CommandRejected.FinalReason( - errorFactories.CommandRejections.Deprecated.outOfQuota(reason) - ) - case domain.RejectionReason.PartiesNotKnownOnLedger(parties) => - CommandRejected.FinalReason( - errorFactories.CommandRejections.partiesNotKnownToLedger(parties) - ) - case domain.RejectionReason.PartyNotKnownOnLedger(description) => - CommandRejected.FinalReason( - errorFactories.CommandRejections.partyNotKnownOnLedger(description) - ) - case domain.RejectionReason.SubmitterCannotActViaParticipant(reason) => - CommandRejected.FinalReason( - errorFactories.CommandRejections.submitterCannotActViaParticipant(reason) - ) - case domain.RejectionReason.InvalidLedgerTime(reason) => - CommandRejected.FinalReason( - errorFactories.CommandRejections.invalidLedgerTime(reason) - ) - case domain.RejectionReason.LedgerConfigNotFound(description) => - // This rejection is returned only for V2 error codes already so we don't need to - // wrap it in ErrorFactories (see [[com.daml.platform.sandbox.stores.ledger.Rejection.NoLedgerConfiguration]] - CommandRejected.FinalReason( - GrpcStatus.toProto( - LedgerApiErrors.RequestValidation.NotFound.LedgerConfiguration - .RejectWithMessage(description) - .asGrpcStatusFromContext - ) - ) - } - } } diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/JdbcLedgerDao.scala b/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/JdbcLedgerDao.scala index 6b2da3896d09..99f22e7d82ef 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/JdbcLedgerDao.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/JdbcLedgerDao.scala @@ -3,6 +3,7 @@ package com.daml.platform.store.appendonlydao import akka.NotUsed +import akka.stream.Materializer import akka.stream.scaladsl.Source import com.daml.daml_lf_dev.DamlLf.Archive import com.daml.error.DamlContextualizedErrorLogger @@ -26,9 +27,8 @@ import com.daml.lf.transaction.{BlindingInfo, CommittedTransaction} import com.daml.logging.LoggingContext.withEnrichedLoggingContext import com.daml.logging.entries.LoggingEntry import com.daml.logging.{ContextualizedLogger, LoggingContext} -import com.daml.metrics.{Metrics, Timed} +import com.daml.metrics.Metrics import com.daml.platform.server.api.validation.ErrorFactories -import com.daml.platform.store.Conversions._ import com.daml.platform.store._ import com.daml.platform.store.appendonlydao.events._ import com.daml.platform.store.backend.ParameterStorageBackend.LedgerEnd @@ -38,17 +38,9 @@ import com.daml.platform.store.backend.{ ReadStorageBackend, } import com.daml.platform.store.cache.LedgerEndCache -import com.daml.platform.store.entries.{ - ConfigurationEntry, - LedgerEntry, - PackageLedgerEntry, - PartyLedgerEntry, -} +import com.daml.platform.store.entries.{ConfigurationEntry, PackageLedgerEntry, PartyLedgerEntry} import com.daml.platform.store.interning.StringInterning import com.daml.platform.store.utils.QueueBasedConcurrencyLimiter -import java.sql.Connection - -import akka.stream.Materializer import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success} @@ -63,10 +55,8 @@ private class JdbcLedgerDao( acsContractFetchingParallelism: Int, acsGlobalParallelism: Int, acsIdQueueLimit: Int, - performPostCommitValidation: Boolean, metrics: Metrics, lfValueTranslationCache: LfValueTranslationCache.Cache, - validatePartyAllocation: Boolean, enricher: Option[ValueEnricher], sequentialIndexer: SequentialWriteDao, participantId: Ref.ParticipantId, @@ -157,47 +147,12 @@ private class JdbcLedgerDao( dbDispatcher.executeSql( metrics.daml.index.db.storeConfigurationEntryDbMetrics ) { implicit conn => - val optCurrentConfig = - readStorageBackend.configurationStorageBackend.ledgerConfiguration(conn) - val optExpectedGeneration: Option[Long] = - optCurrentConfig.map { case (_, c) => c.generation + 1 } - val finalRejectionReason: Option[String] = - optExpectedGeneration match { - case Some(expGeneration) - if rejectionReason.isEmpty && expGeneration != configuration.generation => - // If we're not storing a rejection and the new generation is not succ of current configuration, then - // we store a rejection. This code path is only expected to be taken in sandbox. This follows the same - // pattern as with transactions. - Some( - s"Generation mismatch: expected=$expGeneration, actual=${configuration.generation}" - ) - - case _ => - // Rejection reason was set, or we have no previous configuration generation, in which case we accept any - // generation. - rejectionReason - } - - val update = finalRejectionReason match { - case None => - state.Update.ConfigurationChanged( - recordTime = recordedAt, - submissionId = Ref.SubmissionId.assertFromString(submissionId), - participantId = - Ref.ParticipantId.assertFromString("1"), // not used for DbDto generation - newConfiguration = configuration, - ) - - case Some(reason) => - state.Update.ConfigurationChangeRejected( - recordTime = recordedAt, - submissionId = Ref.SubmissionId.assertFromString(submissionId), - participantId = - Ref.ParticipantId.assertFromString("1"), // not used for DbDto generation - proposedConfiguration = configuration, - rejectionReason = reason, - ) - } + val update = state.Update.ConfigurationChanged( + recordTime = recordedAt, + submissionId = Ref.SubmissionId.assertFromString(submissionId), + participantId = Ref.ParticipantId.assertFromString("1"), // not used for DbDto generation + newConfiguration = configuration, + ) sequentialIndexer.store(conn, offset, Some(update)) PersistenceResponse.Ok @@ -271,20 +226,6 @@ private class JdbcLedgerDao( } } - private def validate( - ledgerEffectiveTime: Timestamp, - transaction: CommittedTransaction, - divulged: Iterable[state.DivulgedContract], - )(implicit connection: Connection): Option[PostCommitValidation.Rejection] = - Timed.value( - metrics.daml.index.db.storeTransactionDbMetrics.commitValidation, - postCommitValidation.validate( - transaction = transaction, - transactionLedgerEffectiveTime = ledgerEffectiveTime, - divulged = divulged.iterator.map(_.contractId).toSet, - ), - ) - override def storeRejection( completionInfo: Option[state.CompletionInfo], recordTime: Timestamp, @@ -307,88 +248,6 @@ private class JdbcLedgerDao( PersistenceResponse.Ok } - override def storeInitialState( - ledgerEntries: Vector[(Offset, LedgerEntry)], - newLedgerEnd: Offset, - )(implicit loggingContext: LoggingContext): Future[Unit] = { - logger.info("Storing initial state") - dbDispatcher.executeSql(metrics.daml.index.db.storeInitialStateFromScenario) { - implicit connection => - ledgerEntries.foreach { case (offset, entry) => - entry match { - case tx: LedgerEntry.Transaction => - val completionInfo = for { - actAs <- if (tx.actAs.isEmpty) None else Some(tx.actAs) - applicationId <- tx.applicationId - commandId <- tx.commandId - submissionId <- tx.submissionId - } yield state.CompletionInfo( - actAs, - applicationId, - commandId, - None, - Some(submissionId), - None, // TODO Ledger Metering - ) - - sequentialIndexer.store( - connection, - offset, - Some( - state.Update.TransactionAccepted( - optCompletionInfo = completionInfo, - transactionMeta = state.TransactionMeta( - ledgerEffectiveTime = tx.ledgerEffectiveTime, - workflowId = tx.workflowId, - submissionTime = null, // not used for DbDto generation - submissionSeed = null, // not used for DbDto generation - optUsedPackages = None, // not used for DbDto generation - optNodeSeeds = None, // not used for DbDto generation - optByKeyNodes = None, // not used for DbDto generation - ), - transaction = tx.transaction, - transactionId = tx.transactionId, - recordTime = tx.recordedAt, - divulgedContracts = Nil, - blindingInfo = None, - ) - ), - ) - case LedgerEntry.Rejection( - recordTime, - commandId, - applicationId, - submissionId, - actAs, - reason, - ) => - sequentialIndexer.store( - connection, - offset, - Some( - state.Update.CommandRejected( - recordTime = recordTime, - completionInfo = state - .CompletionInfo( - actAs, - applicationId, - commandId, - None, - submissionId, - None, // TODO Ledger Metering - ), - reasonTemplate = reason.toParticipantStateRejectionReason(errorFactories)( - new DamlContextualizedErrorLogger(logger, loggingContext, submissionId) - ), - ) - ), - ) - } - } - sequentialIndexer.store(connection, newLedgerEnd, None) - } - } - private val PageSize = 100 override def getParties( @@ -688,16 +547,6 @@ private class JdbcLedgerDao( metrics, ) - private val postCommitValidation = - if (performPostCommitValidation) - new PostCommitValidation.BackedBy( - readStorageBackend.partyStorageBackend, - readStorageBackend.contractStorageBackend, - validatePartyAllocation, - ) - else - PostCommitValidation.Skip - /** This is a combined store transaction method to support sandbox-classic and tests * !!! Usage of this is discouraged, with the removal of sandbox-classic this will be removed */ @@ -718,48 +567,29 @@ private class JdbcLedgerDao( sequentialIndexer.store( conn, offset, - validate(ledgerEffectiveTime, transaction, divulgedContracts) match { - case None => - Some( - state.Update.TransactionAccepted( - optCompletionInfo = completionInfo, - transactionMeta = state.TransactionMeta( - ledgerEffectiveTime = ledgerEffectiveTime, - workflowId = workflowId, - submissionTime = null, // not used for DbDto generation - submissionSeed = null, // not used for DbDto generation - optUsedPackages = None, // not used for DbDto generation - optNodeSeeds = None, // not used for DbDto generation - optByKeyNodes = None, // not used for DbDto generation - ), - transaction = transaction, - transactionId = transactionId, - recordTime = recordTime, - divulgedContracts = divulgedContracts.toList, - blindingInfo = blindingInfo, - ) - ) - - case Some(reason) => - completionInfo.map(info => - state.Update.CommandRejected( - recordTime = recordTime, - completionInfo = info, - reasonTemplate = reason.toStateV2RejectionReason(errorFactories)( - new DamlContextualizedErrorLogger( - logger, - loggingContext, - info.submissionId, - ) - ), - ) - ) - }, + Some( + state.Update.TransactionAccepted( + optCompletionInfo = completionInfo, + transactionMeta = state.TransactionMeta( + ledgerEffectiveTime = ledgerEffectiveTime, + workflowId = workflowId, + submissionTime = null, // not used for DbDto generation + submissionSeed = null, // not used for DbDto generation + optUsedPackages = None, // not used for DbDto generation + optNodeSeeds = None, // not used for DbDto generation + optByKeyNodes = None, // not used for DbDto generation + ), + transaction = transaction, + transactionId = transactionId, + recordTime = recordTime, + divulgedContracts = divulgedContracts.toList, + blindingInfo = blindingInfo, + ) + ), ) PersistenceResponse.Ok } } - } private[platform] object JdbcLedgerDao { @@ -802,10 +632,8 @@ private[platform] object JdbcLedgerDao { acsContractFetchingParallelism, acsGlobalParallelism, acsIdQueueLimit, - false, metrics, lfValueTranslationCache, - false, enricher, SequentialWriteDao.noop, participantId, @@ -849,10 +677,8 @@ private[platform] object JdbcLedgerDao { acsContractFetchingParallelism, acsGlobalParallelism, acsIdQueueLimit, - false, metrics, lfValueTranslationCache, - false, enricher, sequentialWriteDao, participantId, @@ -865,54 +691,6 @@ private[platform] object JdbcLedgerDao { metrics, ) - def validatingWrite( - dbSupport: DbSupport, - sequentialWriteDao: SequentialWriteDao, - eventsPageSize: Int, - eventsProcessingParallelism: Int, - acsIdPageSize: Int, - acsIdFetchingParallelism: Int, - acsContractFetchingParallelism: Int, - acsGlobalParallelism: Int, - acsIdQueueLimit: Int, - servicesExecutionContext: ExecutionContext, - metrics: Metrics, - lfValueTranslationCache: LfValueTranslationCache.Cache, - validatePartyAllocation: Boolean = false, - enricher: Option[ValueEnricher], - participantId: Ref.ParticipantId, - errorFactories: ErrorFactories, - ledgerEndCache: LedgerEndCache, - stringInterning: StringInterning, - materializer: Materializer, - ): LedgerDao = - new MeteredLedgerDao( - new JdbcLedgerDao( - dbSupport.dbDispatcher, - servicesExecutionContext, - eventsPageSize, - eventsProcessingParallelism, - acsIdPageSize, - acsIdFetchingParallelism, - acsContractFetchingParallelism, - acsGlobalParallelism, - acsIdQueueLimit, - true, - metrics, - lfValueTranslationCache, - validatePartyAllocation, - enricher, - sequentialWriteDao, - participantId, - dbSupport.storageBackendFactory.readStorageBackend(ledgerEndCache, stringInterning), - dbSupport.storageBackendFactory.createParameterStorageBackend, - dbSupport.storageBackendFactory.createDeduplicationStorageBackend, - errorFactories, - materializer, - ), - metrics, - ) - val acceptType = "accept" val rejectType = "reject" } diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/LedgerDao.scala b/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/LedgerDao.scala index 72a57b8e976a..c809f26718df 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/LedgerDao.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/LedgerDao.scala @@ -26,12 +26,7 @@ import com.daml.lf.transaction.{BlindingInfo, CommittedTransaction} import com.daml.logging.LoggingContext import com.daml.platform.store.appendonlydao.events.{ContractStateEvent, FilterRelation} import com.daml.platform.store.backend.ParameterStorageBackend.LedgerEnd -import com.daml.platform.store.entries.{ - ConfigurationEntry, - LedgerEntry, - PackageLedgerEntry, - PartyLedgerEntry, -} +import com.daml.platform.store.entries.{ConfigurationEntry, PackageLedgerEntry, PartyLedgerEntry} import com.daml.platform.store.interfaces.{LedgerDaoContractsReader, TransactionLogUpdate} import scala.concurrent.Future @@ -222,6 +217,8 @@ private[platform] trait LedgerReadDao extends ReportsHealth { ): Future[Unit] } +// TODO sandbox-classic clean-up: This interface and its implementation is only used in the JdbcLedgerDao suite +// It should be removed when the assertions in that suite are covered by other suites private[platform] trait LedgerWriteDao extends ReportsHealth { /** Initializes the database with the given ledger identity. @@ -249,19 +246,6 @@ private[platform] trait LedgerWriteDao extends ReportsHealth { reason: state.Update.CommandRejected.RejectionReasonTemplate, )(implicit loggingContext: LoggingContext): Future[PersistenceResponse] - /** !!! Please kindly not use this. - * !!! This method is solely for supporting sandbox-classic. Targeted for removal as soon sandbox classic is removed. - * Stores the initial ledger state, e.g., computed by the scenario loader. - * Must be called at most once, before any call to storeLedgerEntry. - * - * @param ledgerEntries the list of LedgerEntries to save - * @return Ok when the operation was successful - */ - def storeInitialState( - ledgerEntries: Vector[(Offset, LedgerEntry)], - newLedgerEnd: Offset, - )(implicit loggingContext: LoggingContext): Future[Unit] - /** Stores a party allocation or rejection thereof. * * @param Offset Pair of previous offset and the offset to store the party entry at diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/MeteredLedgerDao.scala b/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/MeteredLedgerDao.scala index ad0ae737fcc4..77d9a08bfd4d 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/MeteredLedgerDao.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/MeteredLedgerDao.scala @@ -18,12 +18,7 @@ import com.daml.lf.transaction.{BlindingInfo, CommittedTransaction} import com.daml.logging.LoggingContext import com.daml.metrics.{Metrics, Timed} import com.daml.platform.store.backend.ParameterStorageBackend.LedgerEnd -import com.daml.platform.store.entries.{ - ConfigurationEntry, - LedgerEntry, - PackageLedgerEntry, - PartyLedgerEntry, -} +import com.daml.platform.store.entries.{ConfigurationEntry, PackageLedgerEntry, PartyLedgerEntry} import com.daml.platform.store.interfaces.LedgerDaoContractsReader import scala.concurrent.Future @@ -151,15 +146,6 @@ private[platform] class MeteredLedgerDao(ledgerDao: LedgerDao, metrics: Metrics) ledgerDao.storeRejection(completionInfo, recordTime, offset, reason), ) - override def storeInitialState( - ledgerEntries: Vector[(Offset, LedgerEntry)], - newLedgerEnd: Offset, - )(implicit loggingContext: LoggingContext): Future[Unit] = - Timed.future( - metrics.daml.index.db.storeInitialState, - ledgerDao.storeInitialState(ledgerEntries, newLedgerEnd), - ) - override def initialize( ledgerId: LedgerId, participantId: ParticipantId, diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/events/PostCommitValidation.scala b/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/events/PostCommitValidation.scala deleted file mode 100644 index 984aac4b6413..000000000000 --- a/ledger/participant-integration-api/src/main/scala/platform/store/appendonlydao/events/PostCommitValidation.scala +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.daml.platform.store.appendonlydao.events - -import java.sql.Connection - -import com.daml.error.{ContextualizedErrorLogger, ErrorCodesVersionSwitcher} -import com.daml.ledger.api.domain -import com.daml.ledger.participant.state.v2 -import com.daml.lf.data.Time.Timestamp -import com.daml.lf.transaction.Transaction.ChildrenRecursion -import com.daml.lf.transaction.{CommittedTransaction, GlobalKey} -import com.daml.platform.apiserver.execution.MissingContracts -import com.daml.platform.server.api.validation.ErrorFactories -import com.daml.platform.store.appendonlydao.events.PostCommitValidation._ -import com.daml.platform.store.backend.{ContractStorageBackend, PartyStorageBackend} - -import scala.util.{Failure, Success} - -/** Performs post-commit validation on transactions for Sandbox Classic. - * This is intended exclusively as a temporary replacement for - * [[com.daml.platform.store.ActiveLedgerState]] and [[com.daml.platform.store.ActiveLedgerStateManager]] - * so that the old post-commit validation backed by the old participant schema can be - * dropped and the Daml-on-X-backed implementation of the Sandbox can skip it entirely. - * - * Post-commit validation is relevant for three reasons: - * - keys can be referenced by two concurrent interpretations, potentially leading to - * either create nodes with duplicate active keys or lookup-by-key nodes referring to - * inactive keys - * - contracts may have been consumed by a concurrent interpretation, potentially leading - * to double spends - * - the transaction's ledger effective time is determined after interpretation, - * meaning that causal monotonicity cannot be verified while interpreting a command - */ -private[appendonlydao] sealed trait PostCommitValidation { - def validate( - transaction: CommittedTransaction, - transactionLedgerEffectiveTime: Timestamp, - divulged: Set[ContractId], - )(implicit connection: Connection): Option[Rejection] -} - -private[appendonlydao] object PostCommitValidation { - - /** Accept unconditionally a transaction. - * - * Designed to be used by a ledger integration that - * already performs post-commit validation. - */ - object Skip extends PostCommitValidation { - @inline override def validate( - committedTransaction: CommittedTransaction, - transactionLedgerEffectiveTime: Timestamp, - divulged: Set[ContractId], - )(implicit connection: Connection): Option[Rejection] = - None - } - - final class BackedBy( - partyStorageBackend: PartyStorageBackend, - contractStorageBackend: ContractStorageBackend, - validatePartyAllocation: Boolean, - ) extends PostCommitValidation { - - def validate( - transaction: CommittedTransaction, - transactionLedgerEffectiveTime: Timestamp, - divulged: Set[ContractId], - )(implicit connection: Connection): Option[Rejection] = { - - val causalMonotonicityViolation = - validateCausalMonotonicity(transaction, transactionLedgerEffectiveTime, divulged) - - val invalidKeyUsage = validateKeyUsages(transaction) - - val unallocatedParties = - if (validatePartyAllocation) - validateParties(transaction) - else - None - - unallocatedParties.orElse(invalidKeyUsage.orElse(causalMonotonicityViolation)) - } - - /** Do all exercise, fetch and lookup-by-key nodes - * 1. exist, and - * 2. refer exclusively to contracts with a ledger effective time smaller than or equal to the transaction's? - */ - private def validateCausalMonotonicity( - transaction: CommittedTransaction, - transactionLedgerEffectiveTime: Timestamp, - divulged: Set[ContractId], - )(implicit connection: Connection): Option[Rejection] = { - val referredContracts = collectReferredContracts(transaction, divulged) - if (referredContracts.isEmpty) { - None - } else - contractStorageBackend.maximumLedgerTime(referredContracts)(connection) match { - case Failure(MissingContracts(missingContractIds)) => - Some(Rejection.UnknownContracts(missingContractIds.map(_.coid))) - case Failure(_) => Some(Rejection.MaximumLedgerTimeLookupFailure) - case Success(value) => validateCausalMonotonicity(value, transactionLedgerEffectiveTime) - } - } - - private def validateCausalMonotonicity( - maximumLedgerEffectiveTime: Option[Timestamp], - transactionLedgerEffectiveTime: Timestamp, - ): Option[Rejection] = - maximumLedgerEffectiveTime - .filter(_ > transactionLedgerEffectiveTime) - .fold(Option.empty[Rejection])(contractLedgerEffectiveTime => { - Some( - Rejection.CausalMonotonicityViolation( - contractLedgerEffectiveTime = contractLedgerEffectiveTime, - transactionLedgerEffectiveTime = transactionLedgerEffectiveTime, - ) - ) - }) - - private def validateParties( - transaction: CommittedTransaction - )(implicit connection: Connection): Option[Rejection] = { - val informees = transaction.informees - val allocatedInformees = - partyStorageBackend.parties(informees.toSeq)(connection).iterator.map(_.party).toSet - if (allocatedInformees == informees) - None - else - Some( - Rejection.UnallocatedParties((informees diff allocatedInformees).toSet) - ) - } - - private def collectReferredContracts( - transaction: CommittedTransaction, - divulged: Set[ContractId], - ): Set[ContractId] = { - transaction.inputContracts.diff(divulged) - } - - private def validateKeyUsages( - transaction: CommittedTransaction - )(implicit connection: Connection): Option[Rejection] = - transaction - .foldInExecutionOrder[Result](Right(State.empty(contractStorageBackend)))( - exerciseBegin = (acc, _, exe) => { - val newAcc = acc.flatMap(validateKeyUsages(exe, _)) - (newAcc, ChildrenRecursion.DoRecurse) - }, - exerciseEnd = (acc, _, _) => acc, - rollbackBegin = (acc, _, _) => (acc.map(_.beginRollback()), ChildrenRecursion.DoRecurse), - rollbackEnd = (acc, _, _) => acc.map(_.endRollback()), - leaf = (acc, _, leaf) => acc.flatMap(validateKeyUsages(leaf, _)), - ) - .fold(Some(_), _ => None) - - private def validateKeyUsages( - node: Node, - state: State, - )(implicit connection: Connection): Result = - node match { - case c: Create => - state.validateCreate(c.key.map(convert(c.templateId, _)), c.coid) - case l: LookupByKey => - state.validateLookupByKey(convert(l.templateId, l.key), l.result) - case e: Exercise if e.consuming => - state.removeKeyIfDefined(e.key.map(convert(e.templateId, _))) - case _ => - // fetch and non-consuming exercise nodes don't need to validate - // anything with regards to contract keys and do not alter the - // state in a way which is relevant for the validation of - // subsequent nodes - Right(state) - } - - } - - private type Result = Either[Rejection, State] - - /** The active ledger key state during validation. - * After a rollback node, we restore the state at the - * beginning of the rollback. - * - * @param contracts Active contracts created in - * the current transaction that have a key indexed - * by a hash of their key. - * @param removed Hashes of contract keys that are known to - * to be archived. Note that a later create with the same - * key will remove the entry again. - */ - private final case class ActiveState( - contracts: Map[Hash, ContractId], - removed: Set[Hash], - ) { - def add(key: Key, id: ContractId): ActiveState = - copy( - contracts = contracts.updated(key.hash, id), - removed = removed - key.hash, - ) - - def remove(key: Key): ActiveState = - copy( - contracts = contracts - key.hash, - removed = removed + key.hash, - ) - } - - /** Represents the state of an ongoing validation. - * It must be carried over as the transaction is - * validated one node at a time in pre-order - * traversal for this to make sense. - * - * @param currentState The current active ledger state. - * @param rollbackStack Stack of states at the beginning of rollback nodes so we can - * restore the state at the end of the rollback. The most recent rollback - * comes first. - * @param contractStorageBackend For getting committed contracts for post-commit validation purposes. - * This is never changed during the traversal of the transaction. - */ - private final case class State( - private val currentState: ActiveState, - private val rollbackStack: List[ActiveState], - private val contractStorageBackend: ContractStorageBackend, - ) { - - def validateCreate( - maybeKey: Option[Key], - id: ContractId, - )(implicit connection: Connection): Result = - maybeKey.fold[Result](Right(this)) { key => - lookup(key).fold[Result](Right(add(key, id)))(_ => Left(Rejection.DuplicateKey(key))) - } - - // `causalMonotonicity` already reports unknown contracts, no need to check it here - def removeKeyIfDefined(maybeKey: Option[Key]): Result = - Right(maybeKey.fold(this)(remove)) - - def validateLookupByKey( - key: Key, - expectation: Option[ContractId], - )(implicit connection: Connection): Result = { - val result = lookup(key) - if (result == expectation) Right(this) - else Left(Rejection.MismatchingLookup(expectation, result)) - } - - def beginRollback(): State = - copy( - rollbackStack = currentState :: rollbackStack - ) - - def endRollback(): State = rollbackStack match { - case Nil => - throw new IllegalStateException( - "Internal error: rollback ended but rollbackStack was empty" - ) - case head :: tail => - copy( - currentState = head, - rollbackStack = tail, - ) - } - - private def add(key: Key, id: ContractId): State = - copy(currentState = currentState.add(key, id)) - - private def remove(key: Key): State = - copy( - currentState = currentState.remove(key) - ) - - private def lookup(key: Key)(implicit connection: Connection): Option[ContractId] = - currentState.contracts.get(key.hash).orElse { - if (currentState.removed(key.hash)) None - else contractStorageBackend.contractKeyGlobally(key)(connection) - } - - } - - private object State { - def empty( - contractStorageBackend: ContractStorageBackend - ): State = - State(ActiveState(Map.empty, Set.empty), Nil, contractStorageBackend) - } - - sealed trait Rejection { - def description: String - - def toStateV2RejectionReason(errorFactories: ErrorFactories)(implicit - contextualizedErrorLogger: ContextualizedErrorLogger - ): v2.Update.CommandRejected.RejectionReasonTemplate - } - - object Rejection { - - import com.daml.platform.store.Conversions.RejectionReasonOps - - object MaximumLedgerTimeLookupFailure extends Rejection { - override val description = "An unhandled failure occurred during ledger time lookup." - - override def toStateV2RejectionReason(errorFactories: ErrorFactories)(implicit - contextualizedErrorLogger: ContextualizedErrorLogger - ): v2.Update.CommandRejected.RejectionReasonTemplate = - domain.RejectionReason - .Disputed(description) - .toParticipantStateRejectionReason(ErrorFactories(new ErrorCodesVersionSwitcher(false))) - } - - final case class UnknownContracts( - missingContractIds: Set[String] - ) extends Rejection { - override val description = - s"Unknown contracts: ${missingContractIds.mkString("[", ", ", "]")}" - - override def toStateV2RejectionReason(errorFactories: ErrorFactories)(implicit - contextualizedErrorLogger: ContextualizedErrorLogger - ): v2.Update.CommandRejected.RejectionReasonTemplate = - domain.RejectionReason - // TODO error codes: Return specialized error codes - .ContractsNotFound(missingContractIds) - .toParticipantStateRejectionReason(errorFactories) - } - - final case class DuplicateKey(key: GlobalKey) extends Rejection { - override val description = - "DuplicateKey: contract key is not unique" - - override def toStateV2RejectionReason(errorFactories: ErrorFactories)(implicit - contextualizedErrorLogger: ContextualizedErrorLogger - ): v2.Update.CommandRejected.RejectionReasonTemplate = - domain.RejectionReason - .DuplicateContractKey(key) - .toParticipantStateRejectionReason(errorFactories) - } - - final case class MismatchingLookup( - expectation: Option[ContractId], - result: Option[ContractId], - ) extends Rejection { - override lazy val description: String = - s"Contract key lookup with different results: expected [$expectation], actual [$result]" - - override def toStateV2RejectionReason(errorFactories: ErrorFactories)(implicit - contextualizedErrorLogger: ContextualizedErrorLogger - ): v2.Update.CommandRejected.RejectionReasonTemplate = - domain.RejectionReason - .InconsistentContractKeys(expectation, result) - .toParticipantStateRejectionReason(errorFactories) - } - - final case class CausalMonotonicityViolation( - contractLedgerEffectiveTime: Timestamp, - transactionLedgerEffectiveTime: Timestamp, - ) extends Rejection { - override lazy val description: String = - s"Encountered contract with LET [$contractLedgerEffectiveTime] greater than the LET of the transaction [$transactionLedgerEffectiveTime]" - - override def toStateV2RejectionReason(errorFactories: ErrorFactories)(implicit - contextualizedErrorLogger: ContextualizedErrorLogger - ): v2.Update.CommandRejected.RejectionReasonTemplate = - domain.RejectionReason - .InvalidLedgerTime(description) - .toParticipantStateRejectionReason(errorFactories) - } - - final case class UnallocatedParties( - unallocatedParties: Set[String] - ) extends Rejection { - override def description: String = "Some parties are unallocated" - - override def toStateV2RejectionReason(errorFactories: ErrorFactories)(implicit - contextualizedErrorLogger: ContextualizedErrorLogger - ): v2.Update.CommandRejected.RejectionReasonTemplate = - domain.RejectionReason - .PartiesNotKnownOnLedger(unallocatedParties) - .toParticipantStateRejectionReason(errorFactories) - } - } -} diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/StorageBackend.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/StorageBackend.scala index f89c36e8e3e8..19dc5fa6b742 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/StorageBackend.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/StorageBackend.scala @@ -191,7 +191,6 @@ trait CompletionStorageBackend { } trait ContractStorageBackend { - def contractKeyGlobally(key: Key)(connection: Connection): Option[ContractId] def maximumLedgerTime(ids: Set[ContractId])(connection: Connection): Try[Option[Timestamp]] def keyState(key: Key, validAt: Long)(connection: Connection): KeyState def contractState(contractId: ContractId, before: Long)( diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/ContractStorageBackendTemplate.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/ContractStorageBackendTemplate.scala index d9e9e4bf8b7d..b5b137430e44 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/ContractStorageBackendTemplate.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/ContractStorageBackendTemplate.scala @@ -33,16 +33,6 @@ class ContractStorageBackendTemplate( ) extends ContractStorageBackend { import com.daml.platform.store.Conversions.ArrayColumnToIntArray._ - override def contractKeyGlobally(key: Key)(connection: Connection): Option[ContractId] = - contractKey( - resultColumns = List("contract_id"), - resultParser = contractId("contract_id"), - )( - readers = None, - key = key, - validAt = ledgerEndCache()._2, - )(connection) - private def emptyContractIds: Throwable = new IllegalArgumentException( "Cannot lookup the maximum ledger time for an empty set of contract identifiers" diff --git a/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoConfigurationAppendOnlySpec.scala b/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoConfigurationAppendOnlySpec.scala index b1ecd8761042..ea7c0c27a225 100644 --- a/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoConfigurationAppendOnlySpec.scala +++ b/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoConfigurationAppendOnlySpec.scala @@ -40,39 +40,24 @@ trait JdbcLedgerDaoConfigurationAppendOnlySpec { ) config2 <- ledgerDao.lookupLedgerConfiguration().map(_.map(_._2).get) - // Submission with mismatching generation is rejected + // Submission with unique submissionId and correct generation is accepted. offset2 = nextOffset() offsetString2 = offset2.toLong - resp2 <- storeConfigurationEntry( - offset2, - s"refuse-config-$offsetString2", - config0, - ) - - // Submission with unique submissionId and correct generation is accepted. - offset3 = nextOffset() - offsetString3 = offset3.toLong lastConfig = config1.copy(generation = config1.generation + 2) - resp3 <- storeConfigurationEntry(offset3, s"refuse-config-$offsetString3", lastConfig) + resp3 <- storeConfigurationEntry(offset2, s"refuse-config-$offsetString2", lastConfig) lastConfigActual <- ledgerDao.lookupLedgerConfiguration().map(_.map(_._2).get) - entries <- ledgerDao.getConfigurationEntries(startExclusive, offset3).runWith(Sink.seq) + entries <- ledgerDao.getConfigurationEntries(startExclusive, offset2).runWith(Sink.seq) } yield { resp0 shouldEqual PersistenceResponse.Ok resp1 shouldEqual PersistenceResponse.Ok - resp2 shouldEqual PersistenceResponse.Ok resp3 shouldEqual PersistenceResponse.Ok lastConfig shouldEqual lastConfigActual entries.toList shouldEqual List( offset0 -> ConfigurationEntry.Accepted(s"refuse-config-$offsetString0", config1), /* offset1 is duplicate */ offset1 -> ConfigurationEntry.Accepted(s"refuse-config-$offsetString0", config2), - offset2 -> ConfigurationEntry.Rejected( - s"refuse-config-${offset2.toLong}", - "Generation mismatch: expected=3, actual=0", - config0, - ), - offset3 -> ConfigurationEntry.Accepted(s"refuse-config-$offsetString3", lastConfig), + offset2 -> ConfigurationEntry.Accepted(s"refuse-config-$offsetString2", lastConfig), ) } } diff --git a/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoConfigurationSpec.scala b/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoConfigurationSpec.scala index b1c5bd66e825..80f59a420c8c 100644 --- a/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoConfigurationSpec.scala +++ b/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoConfigurationSpec.scala @@ -3,9 +3,7 @@ package com.daml.platform.store.dao -import akka.stream.scaladsl.Sink import com.daml.platform.store.appendonlydao._ -import com.daml.platform.store.entries.ConfigurationEntry import org.scalatest.flatspec.AsyncFlatSpec import org.scalatest.matchers.should.Matchers @@ -35,32 +33,4 @@ trait JdbcLedgerDaoConfigurationSpec { endingOffset.lastOffset should be > startingOffset.lastOffset } } - - it should "be able to persist configuration rejection" in { - val startExclusive = nextOffset() - val offset = nextOffset() - val offsetString = offset.toLong - for { - startingConfig <- ledgerDao.lookupLedgerConfiguration().map(_.map(_._2)) - proposedConfig = startingConfig.getOrElse(defaultConfig) - response <- storeConfigurationEntry( - offset, - s"config-rejection-$offsetString", - proposedConfig, - Some("bad config"), - ) - storedConfig <- ledgerDao.lookupLedgerConfiguration().map(_.map(_._2)) - entries <- ledgerDao - .getConfigurationEntries(startExclusive, offset) - .runWith(Sink.seq) - - } yield { - response shouldEqual PersistenceResponse.Ok - startingConfig shouldEqual storedConfig - entries shouldEqual List( - offset -> ConfigurationEntry - .Rejected(s"config-rejection-$offsetString", "bad config", proposedConfig) - ) - } - } } diff --git a/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoPostCommitValidationSpec.scala b/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoPostCommitValidationSpec.scala deleted file mode 100644 index c6df550d90c5..000000000000 --- a/ledger/participant-integration-api/src/test/lib/scala/platform/store/dao/JdbcLedgerDaoPostCommitValidationSpec.scala +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.daml.platform.store.dao - -import java.util.UUID - -import com.codahale.metrics.MetricRegistry -import com.daml.ledger.resources.ResourceOwner -import com.daml.lf.crypto.Hash -import com.daml.lf.data.Ref -import com.daml.lf.transaction.BlindingInfo -import com.daml.lf.value.Value.ContractId -import com.daml.logging.LoggingContext -import com.daml.metrics.Metrics -import com.daml.platform.configuration.ServerRole -import com.daml.platform.server.api.validation.ErrorFactories -import com.daml.platform.store.{DbSupport, DbType, LfValueTranslationCache} -import com.daml.platform.store.appendonlydao.events.CompressionStrategy -import com.daml.platform.store.appendonlydao.{JdbcLedgerDao, LedgerDao, SequentialWriteDao} -import com.daml.platform.store.backend.StorageBackendFactory -import com.daml.platform.store.interning.StringInterningView -import org.scalatest.LoneElement -import org.scalatest.flatspec.AsyncFlatSpec -import org.scalatest.matchers.should.Matchers - -import scala.concurrent.duration.DurationInt - -private[dao] trait JdbcLedgerDaoPostCommitValidationSpec extends LoneElement { - this: AsyncFlatSpec with Matchers with JdbcLedgerDaoSuite => - - override protected def daoOwner( - eventsPageSize: Int, - eventsProcessingParallelism: Int, - acsIdPageSize: Int, - acsIdFetchingParallelism: Int, - acsContractFetchingParallelism: Int, - acsGlobalParallelism: Int, - acsIdQueueLimit: Int, - errorFactories: ErrorFactories, - )(implicit - loggingContext: LoggingContext - ): ResourceOwner[LedgerDao] = { - val metrics = new Metrics(new MetricRegistry) - val dbType = DbType.jdbcType(jdbcUrl) - val storageBackendFactory = StorageBackendFactory.of(dbType) - val participantId = Ref.ParticipantId.assertFromString("JdbcLedgerDaoPostCommitValidationSpec") - DbSupport - .migratedOwner( - jdbcUrl = jdbcUrl, - serverRole = ServerRole.Testing(getClass), - connectionPoolSize = dbType.maxSupportedWriteConnections(16), - connectionTimeout = 250.millis, - metrics = metrics, - ) - .map { dbSupport => - val stringInterningStorageBackend = - storageBackendFactory.createStringInterningStorageBackend - val stringInterningView = new StringInterningView( - loadPrefixedEntries = (fromExclusive, toInclusive) => - implicit loggingContext => - dbSupport.dbDispatcher.executeSql(metrics.daml.index.db.loadStringInterningEntries) { - stringInterningStorageBackend.loadStringInterningEntries(fromExclusive, toInclusive) - } - ) - JdbcLedgerDao.validatingWrite( - dbSupport = dbSupport, - sequentialWriteDao = SequentialWriteDao( - participantId = participantId, - lfValueTranslationCache = LfValueTranslationCache.Cache.none, - metrics = metrics, - compressionStrategy = CompressionStrategy.none(metrics), - ledgerEndCache = ledgerEndCache, - stringInterningView = stringInterningView, - ingestionStorageBackend = storageBackendFactory.createIngestionStorageBackend, - parameterStorageBackend = storageBackendFactory.createParameterStorageBackend, - ), - eventsPageSize = eventsPageSize, - eventsProcessingParallelism = eventsProcessingParallelism, - acsIdPageSize = acsIdPageSize, - acsIdFetchingParallelism = acsIdFetchingParallelism, - acsContractFetchingParallelism = acsContractFetchingParallelism, - acsGlobalParallelism = acsGlobalParallelism, - acsIdQueueLimit = acsIdQueueLimit, - servicesExecutionContext = executionContext, - metrics = metrics, - lfValueTranslationCache = LfValueTranslationCache.Cache.none, - enricher = None, - participantId = participantId, - errorFactories = errorFactories, - ledgerEndCache = ledgerEndCache, - stringInterning = stringInterningView, - materializer = materializer, - ) - } - } - - private val ok = io.grpc.Status.Code.OK.value() - private val alreadyExists = io.grpc.Status.Code.ALREADY_EXISTS.value() - private val failedPrecondition = io.grpc.Status.Code.FAILED_PRECONDITION.value() - private val notFound = io.grpc.Status.Code.NOT_FOUND.value() - - behavior of "JdbcLedgerDao (post-commit validation)" - - it should "refuse to serialize duplicate contract keys" in { - val keyValue = s"duplicate-key" - - // Scenario: Two concurrent commands create the same contract key. - // At command interpretation time, the keys do not exist yet. - // At serialization time, the ledger should refuse to serialize one of them. - for { - from <- ledgerDao.lookupLedgerEnd() - original @ (_, originalAttempt) = txCreateContractWithKey(alice, keyValue) - duplicate @ (_, duplicateAttempt) = txCreateContractWithKey(alice, keyValue) - _ <- store(original) - _ <- store(duplicate) - to <- ledgerDao.lookupLedgerEnd() - completions <- getCompletions(from.lastOffset, to.lastOffset, defaultAppId, Set(alice)) - } yield { - completions should contain.allOf( - originalAttempt.commandId.get -> ok, - duplicateAttempt.commandId.get -> alreadyExists, - ) - } - } - - it should "refuse to serialize invalid negative lookupByKey" in { - val keyValue = s"no-invalid-negative-lookup" - - // Scenario: Two concurrent commands: one create and one lookupByKey. - // At command interpretation time, the lookupByKey does not find any contract. - // At serialization time, it should be rejected because now the key is there. - for { - from <- ledgerDao.lookupLedgerEnd() - (_, create) <- store(txCreateContractWithKey(alice, keyValue)) - (_, lookup) <- store(txLookupByKey(alice, keyValue, None)) - to <- ledgerDao.lookupLedgerEnd() - completions <- getCompletions(from.lastOffset, to.lastOffset, defaultAppId, Set(alice)) - } yield { - completions should contain.allOf( - create.commandId.get -> ok, - lookup.commandId.get -> failedPrecondition, - ) - } - } - - it should "refuse to serialize invalid positive lookupByKey" in { - val keyValue = s"no-invalid-positive-lookup" - - // Scenario: Two concurrent commands: one exercise and one lookupByKey. - // At command interpretation time, the lookupByKey finds a contract. - // At serialization time, it should be rejected because now the contract was archived. - for { - from <- ledgerDao.lookupLedgerEnd() - (_, create) <- store(txCreateContractWithKey(alice, keyValue)) - createdContractId = nonTransient(create).loneElement - (_, archive) <- store(txArchiveContract(alice, createdContractId -> Some(keyValue))) - (_, lookup) <- store(txLookupByKey(alice, keyValue, Some(createdContractId))) - to <- ledgerDao.lookupLedgerEnd() - completions <- getCompletions(from.lastOffset, to.lastOffset, defaultAppId, Set(alice)) - } yield { - completions should contain.allOf( - create.commandId.get -> ok, - archive.commandId.get -> ok, - lookup.commandId.get -> failedPrecondition, - ) - } - } - - it should "refuse to serialize invalid fetch" in { - val keyValue = s"no-invalid-fetch" - - // Scenario: Two concurrent commands: one exercise and one fetch. - // At command interpretation time, the fetch finds a contract. - // At serialization time, it should be rejected because now the contract was archived. - for { - from <- ledgerDao.lookupLedgerEnd() - (_, create) <- store(txCreateContractWithKey(alice, keyValue)) - createdContractId = nonTransient(create).loneElement - (_, archive) <- store(txArchiveContract(alice, createdContractId -> Some(keyValue))) - (_, fetch) <- store(txFetch(alice, createdContractId)) - to <- ledgerDao.lookupLedgerEnd() - completions <- getCompletions(from.lastOffset, to.lastOffset, defaultAppId, Set(alice)) - } yield { - completions should contain.allOf( - create.commandId.get -> ok, - archive.commandId.get -> ok, - fetch.commandId.get -> notFound, - ) - } - } - - it should "be able to use divulged contract in later transaction" in { - - val divulgedContractId: ContractId = - ContractId.V1(Hash.hashPrivateKey(UUID.randomUUID.toString)) - val divulgedContracts = - Map((divulgedContractId, someVersionedContractInstance) -> Set(alice)) - - val blindingInfo = BlindingInfo( - disclosure = Map.empty, - divulgence = Map(divulgedContractId -> Set(alice)), - ) - - for { - from <- ledgerDao.lookupLedgerEnd() - (_, fetch1) <- store(txFetch(alice, divulgedContractId)) - (_, divulgence) <- store( - divulgedContracts, - blindingInfo = Some(blindingInfo), - emptyTransaction(alice), - ) - (_, fetch2) <- store(txFetch(alice, divulgedContractId)) - to <- ledgerDao.lookupLedgerEnd() - completions <- getCompletions(from.lastOffset, to.lastOffset, defaultAppId, Set(alice)) - } yield { - completions should contain.allOf( - fetch1.commandId.get -> notFound, - divulgence.commandId.get -> ok, - fetch2.commandId.get -> ok, - ) - } - } - - it should "do not refuse to insert entries with conflicting transaction ids" in { - val original = txCreateContractWithKey(alice, "some-key", Some("1337")) - val duplicateTxId = txCreateContractWithKey(alice, "another-key", Some("1337")) - - // Post-commit validation does not prevent duplicate transaction ids - for { - _ <- store(original) - _ <- store(duplicateTxId) - } yield succeed - } -} diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/ConversionsSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/ConversionsSpec.scala deleted file mode 100644 index 9a002296eb26..000000000000 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/ConversionsSpec.scala +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.daml.platform.store - -import com.daml.error.{ - ContextualizedErrorLogger, - DamlContextualizedErrorLogger, - ErrorCodesVersionSwitcher, -} -import com.daml.ledger.api.domain -import com.daml.ledger.api.domain.RejectionReason -import com.daml.lf.crypto.Hash -import com.daml.lf.data.Ref -import com.daml.lf.transaction.GlobalKey -import com.daml.lf.value.Value.{ContractId, ValueText} -import com.daml.logging.{ContextualizedLogger, LoggingContext} -import com.daml.platform.server.api.validation.ErrorFactories -import com.daml.platform.store.Conversions._ -import io.grpc.Status -import org.scalatest.Assertion -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AsyncWordSpec - -import scala.annotation.nowarn - -@nowarn("msg=deprecated") -class ConversionsSpec extends AsyncWordSpec with Matchers { - private implicit val contextualizedErrorLogger: ContextualizedErrorLogger = - new DamlContextualizedErrorLogger( - ContextualizedLogger.get(getClass), - LoggingContext.ForTesting, - None, - ) - - private def cid(key: String): ContractId = ContractId.V1(Hash.hashPrivateKey(key)) - - "converting rejection reasons" should { - "convert an 'Inconsistent' rejection reason" in { - assertConversion(domain.RejectionReason.Inconsistent("This was not very consistent."))( - v1expectedCode = Status.Code.ABORTED.value(), - v1expectedMessage = "Inconsistent: This was not very consistent.", - v2expectedCode = Status.Code.FAILED_PRECONDITION.value(), - v2expectedMessage = "INCONSISTENT(9,0): Inconsistent: This was not very consistent.", - ) - } - - "convert a 'ContractsNotFound' rejection reason" in { - assertConversion(domain.RejectionReason.ContractsNotFound(Set("cId_1", "cId_2")))( - v1expectedCode = Status.Code.ABORTED.value(), - v1expectedMessage = "Inconsistent: Could not lookup contracts: [cId_1, cId_2]", - v2expectedCode = Status.Code.NOT_FOUND.value(), - v2expectedMessage = "CONTRACT_NOT_FOUND(11,0): Unknown contracts: [cId_1, cId_2]", - ) - } - - "convert an 'InconsistentContractKeys' rejection reason" in { - val cId = cid("#cId1") - assertConversion( - domain.RejectionReason - .InconsistentContractKeys(Some(cId), None) - )( - v1expectedCode = Status.Code.ABORTED.value(), - v1expectedMessage = - s"Inconsistent: Contract key lookup with different results: expected [Some($cId)], actual [$None]", - v2expectedCode = Status.Code.FAILED_PRECONDITION.value(), - v2expectedMessage = - s"INCONSISTENT_CONTRACT_KEY(9,0): Contract key lookup with different results: expected [Some($cId)], actual [$None]", - ) - } - - "convert a 'DuplicateContractKey' rejection reason" in { - val key = GlobalKey.assertBuild( - Ref.Identifier.assertFromString("some:template:value"), - ValueText("value"), - ) - assertConversion(domain.RejectionReason.DuplicateContractKey(key))( - v1expectedCode = Status.Code.ABORTED.value(), - v1expectedMessage = "Inconsistent: DuplicateKey: contract key is not unique", - v2expectedCode = Status.Code.ALREADY_EXISTS.value(), - v2expectedMessage = "DUPLICATE_CONTRACT_KEY(10,0): DuplicateKey: contract key is not unique", - ) - } - - "convert a 'Disputed' rejection reason" in { - assertConversion(domain.RejectionReason.Disputed("I dispute that."))( - v1expectedCode = Status.Code.INVALID_ARGUMENT.value(), - v1expectedMessage = "Disputed: I dispute that.", - v2expectedCode = Status.Code.INTERNAL.value(), - v2expectedMessage = - "An error occurred. Please contact the operator and inquire about the request ", - ) - } - - "convert an 'OutOfQuota' rejection reason" in { - assertConversion(domain.RejectionReason.OutOfQuota("Insert coins to continue."))( - v1expectedCode = Status.Code.ABORTED.value(), - v1expectedMessage = "Resources exhausted: Insert coins to continue.", - v2expectedCode = Status.Code.ABORTED.value(), - v2expectedMessage = "OUT_OF_QUOTA(2,0): Insert coins to continue.", - ) - } - - "convert a 'PartiesNotKnownOnLedger' rejection reason" in { - assertConversion(domain.RejectionReason.PartiesNotKnownOnLedger(Set("Alice")))( - v1expectedCode = Status.Code.INVALID_ARGUMENT.value(), - v1expectedMessage = "Parties not known on ledger: [Alice]", - v2expectedCode = Status.Code.NOT_FOUND.value(), - v2expectedMessage = "PARTY_NOT_KNOWN_ON_LEDGER(11,0): Parties not known on ledger: [Alice]", - ) - } - - "convert a 'PartyNotKnownOnLedger' rejection reason" in { - assertConversion(domain.RejectionReason.PartyNotKnownOnLedger("reason"))( - v1expectedCode = Status.Code.INVALID_ARGUMENT.value(), - v1expectedMessage = "Parties not known on ledger: reason", - v2expectedCode = Status.Code.NOT_FOUND.value(), - v2expectedMessage = "PARTY_NOT_KNOWN_ON_LEDGER(11,0): Party not known on ledger: reason", - ) - } - - "convert a 'SubmitterCannotActViaParticipant' rejection reason" in { - assertConversion(domain.RejectionReason.SubmitterCannotActViaParticipant("Wrong box."))( - v1expectedCode = Status.Code.PERMISSION_DENIED.value(), - v1expectedMessage = "Submitted cannot act via participant: Wrong box.", - v2expectedCode = Status.Code.PERMISSION_DENIED.value(), - v2expectedMessage = - "An error occurred. Please contact the operator and inquire about the request ", - ) - } - - "convert an 'InvalidLedgerTime' rejection reason" in { - assertConversion(domain.RejectionReason.InvalidLedgerTime("Too late."))( - v1expectedCode = Status.Code.ABORTED.value(), - v1expectedMessage = "Invalid ledger time: Too late.", - v2expectedCode = Status.Code.FAILED_PRECONDITION.value(), - v2expectedMessage = "INVALID_LEDGER_TIME(9,0): Too late.", - ) - } - } - - private def assertConversion(actualRejectionReason: RejectionReason)( - v1expectedCode: Int, - v1expectedMessage: String, - v2expectedCode: Int, - v2expectedMessage: String, - ): Assertion = { - val errorFactoriesV1 = ErrorFactories( - new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = false) - ) - val errorFactoriesV2 = ErrorFactories( - new ErrorCodesVersionSwitcher(enableSelfServiceErrorCodes = true) - ) - - val convertedV1 = actualRejectionReason.toParticipantStateRejectionReason(errorFactoriesV1) - convertedV1.code shouldBe v1expectedCode - convertedV1.message shouldBe v1expectedMessage - - val convertedV2 = actualRejectionReason.toParticipantStateRejectionReason(errorFactoriesV2) - convertedV2.code shouldBe v2expectedCode - convertedV2.message shouldBe v2expectedMessage - } -} diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/appendonlydao/events/PostCommitValidationSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/appendonlydao/events/PostCommitValidationSpec.scala deleted file mode 100644 index 42d3fac8166e..000000000000 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/appendonlydao/events/PostCommitValidationSpec.scala +++ /dev/null @@ -1,642 +0,0 @@ -// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.daml.platform.store.appendonlydao.events - -import com.daml.ledger.api.domain.PartyDetails -import com.daml.ledger.offset.Offset -import com.daml.lf.crypto.Hash -import com.daml.lf.data.Ref -import com.daml.lf.data.Time.Timestamp -import com.daml.lf.transaction.GlobalKey -import com.daml.lf.transaction.test.{TransactionBuilder => TxBuilder} -import com.daml.lf.value.Value.ValueText -import com.daml.platform.apiserver.execution.MissingContracts -import com.daml.platform.store.backend.{ContractStorageBackend, PartyStorageBackend} -import com.daml.platform.store.entries.PartyLedgerEntry -import com.daml.platform.store.interfaces.LedgerDaoContractsReader.KeyState -import org.scalatest.matchers.should.Matchers -import org.scalatest.wordspec.AnyWordSpec - -import java.sql.Connection -import java.time.Instant -import java.util.UUID -import scala.util.{Failure, Success, Try} - -final class PostCommitValidationSpec extends AnyWordSpec with Matchers { - import PostCommitValidation._ - import PostCommitValidationSpec._ - - "PostCommitValidation" when { - "run without prior history" should { - val fixture = noCommittedContract(parties = List.empty) - val store = new PostCommitValidation.BackedBy( - fixture, - fixture, - validatePartyAllocation = false, - ) - - "accept a create with a key" in { - val createWithKey = genTestCreate() - - val error = store.validate( - transaction = TxBuilder.justCommitted(createWithKey), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - - "accept a create without a key" in { - val createWithoutKey = genTestCreate().copy(key = None) - - val error = store.validate( - transaction = TxBuilder.justCommitted(createWithoutKey), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - - "accept an exercise of a contract created within the transaction" in { - val createContract = genTestCreate() - val exerciseContract = genTestExercise(createContract) - - val error = store.validate( - transaction = TxBuilder.justCommitted(createContract, exerciseContract), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - - "accept an exercise of a contract divulged in the current transaction" in { - val divulgedContract = genTestCreate() - val exerciseContract = genTestExercise(divulgedContract) - - val error = store.validate( - transaction = TxBuilder.justCommitted(exerciseContract), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set(divulgedContract.coid), - ) - - error shouldBe None - } - - "reject an exercise of a contract not created in this transaction" in { - val missingCreate = genTestCreate() - val exerciseContract = genTestExercise(missingCreate) - - val error = store.validate( - transaction = TxBuilder.justCommitted(exerciseContract), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe Some(Rejection.UnknownContracts(Set(missingCreate.coid.coid))) - } - - "accept a fetch of a contract created within the transaction" in { - val createContract = genTestCreate() - - val error = store.validate( - transaction = TxBuilder.justCommitted(createContract, txBuilder.fetch(createContract)), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - - "accept a fetch of a contract divulged in the current transaction" in { - val divulgedContract = genTestCreate() - - val error = store.validate( - transaction = TxBuilder.justCommitted(txBuilder.fetch(divulgedContract)), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set(divulgedContract.coid), - ) - - error shouldBe None - } - - "reject a fetch of a contract not created in this transaction" in { - val missingCreate = genTestCreate() - - val error = store.validate( - transaction = TxBuilder.justCommitted(txBuilder.fetch(missingCreate)), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe Some(Rejection.UnknownContracts(Set(missingCreate.coid.coid))) - } - - "accept a successful lookup of a contract created in this transaction" in { - val createContract = genTestCreate() - - val error = store.validate( - transaction = TxBuilder - .justCommitted(createContract, txBuilder.lookupByKey(createContract, found = true)), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - - "reject a successful lookup of a missing contract" in { - val missingCreate = genTestCreate() - - val error = store.validate( - transaction = TxBuilder.justCommitted(txBuilder.lookupByKey(missingCreate, found = true)), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe Some( - Rejection.MismatchingLookup(expectation = Some(missingCreate.coid), result = None) - ) - } - - "accept a failed lookup of a missing contract" in { - val missingContract = genTestCreate() - - val error = store.validate( - transaction = - TxBuilder.justCommitted(txBuilder.lookupByKey(missingContract, found = false)), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - - "accept a create in a rollback node" in { - val createContract = genTestCreate() - val builder = TxBuilder() - val rollback = builder.add(builder.rollback()) - builder.add(createContract, rollback) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - - "accept a create after a rolled back create with the same key" in { - val createContract = genTestCreate() - val builder = TxBuilder() - val rollback = builder.add(builder.rollback()) - builder.add(createContract, rollback) - builder.add(createContract) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - - "reject a create in a rollback after a create with the same key" in { - val createContract = genTestCreate() - val builder = TxBuilder() - builder.add(createContract) - val rollback = builder.add(builder.rollback()) - builder.add(createContract, rollback) - - val duplicateKey = - GlobalKey.assertBuild(createContract.templateId, createContract.key.get.key) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe Some(Rejection.DuplicateKey(duplicateKey)) - } - - "reject a create after a rolled back archive of a contract with the same key" in { - val createContract = genTestCreate() - val builder = TxBuilder() - builder.add(createContract) - val rollback = builder.add(builder.rollback()) - builder.add(genTestExercise(createContract), rollback) - builder.add(createContract) - - val duplicateKey = - GlobalKey.assertBuild(createContract.templateId, createContract.key.get.key) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe Some(Rejection.DuplicateKey(duplicateKey)) - } - - "accept a failed lookup in a rollback" in { - val createContract = genTestCreate() - val builder = TxBuilder() - val rollback = builder.add(builder.rollback()) - builder.add(builder.lookupByKey(createContract, found = false), rollback) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - } - - "run with one committed contract with a key" should { - val committedContract = genTestCreate() - val exerciseOnCommittedContract = genTestExercise(committedContract) - val committedContractLedgerEffectiveTime = - Timestamp.assertFromInstant(Instant.ofEpochMilli(1000)) - - val fixture = committedContracts( - parties = List.empty, - contractFixture = committed( - id = committedContract.coid.coid, - ledgerEffectiveTime = committedContractLedgerEffectiveTime, - key = committedContract.key.map(x => - GlobalKey.assertBuild(committedContract.templateId, x.key) - ), - ), - ) - val store = new PostCommitValidation.BackedBy( - fixture, - fixture, - validatePartyAllocation = false, - ) - - "reject a create that would introduce a duplicate key" in { - val error = store.validate( - transaction = TxBuilder.justCommitted(committedContract), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime, - divulged = Set.empty, - ) - - val duplicateKey = - GlobalKey.assertBuild(committedContract.templateId, committedContract.key.get.key) - - error shouldBe Some(Rejection.DuplicateKey(duplicateKey)) - } - - "accept an exercise on the committed contract" in { - val error = store.validate( - transaction = TxBuilder.justCommitted(exerciseOnCommittedContract), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime, - divulged = Set.empty, - ) - - error shouldBe None - } - - "reject an exercise pre-dating the committed contract" in { - val error = store.validate( - transaction = TxBuilder.justCommitted(exerciseOnCommittedContract), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime.addMicros(-1), - divulged = Set.empty, - ) - - error shouldBe Some( - Rejection.CausalMonotonicityViolation( - contractLedgerEffectiveTime = committedContractLedgerEffectiveTime, - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime.addMicros(-1), - ) - ) - } - - "accept a fetch on the committed contract" in { - val error = store.validate( - transaction = TxBuilder.justCommitted(txBuilder.fetch(committedContract)), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime, - divulged = Set.empty, - ) - - error shouldBe None - } - - "reject a fetch pre-dating the committed contract" in { - val error = store.validate( - transaction = TxBuilder.justCommitted(txBuilder.fetch(committedContract)), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime.addMicros(-1), - divulged = Set.empty, - ) - - error shouldBe Some( - Rejection.CausalMonotonicityViolation( - contractLedgerEffectiveTime = committedContractLedgerEffectiveTime, - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime.addMicros(-1), - ) - ) - } - - "accept a successful lookup of the committed contract" in { - val error = store.validate( - transaction = - TxBuilder.justCommitted(txBuilder.lookupByKey(committedContract, found = true)), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime, - divulged = Set.empty, - ) - - error shouldBe None - } - - "reject a failed lookup of the committed contract" in { - val error = store.validate( - transaction = - TxBuilder.justCommitted(txBuilder.lookupByKey(committedContract, found = false)), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime, - divulged = Set.empty, - ) - - error shouldBe Some( - Rejection.MismatchingLookup( - expectation = None, - result = Some(committedContract.coid), - ) - ) - } - - "reject a create in a rollback" in { - val builder = TxBuilder() - val rollback = builder.add(builder.rollback()) - builder.add(committedContract, rollback) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime, - divulged = Set.empty, - ) - - val duplicateKey = - GlobalKey.assertBuild(committedContract.templateId, committedContract.key.get.key) - - error shouldBe Some(Rejection.DuplicateKey(duplicateKey)) - } - - "reject a failed lookup in a rollback" in { - val builder = TxBuilder() - val rollback = builder.add(builder.rollback()) - builder.add(builder.lookupByKey(committedContract, found = false), rollback) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime, - divulged = Set.empty, - ) - - error shouldBe Some( - Rejection.MismatchingLookup( - result = Some(committedContract.coid), - expectation = None, - ) - ) - } - - "accept a successful lookup in a rollback" in { - val builder = TxBuilder() - val rollback = builder.add(builder.rollback()) - builder.add(builder.lookupByKey(committedContract, found = true), rollback) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime, - divulged = Set.empty, - ) - - error shouldBe None - } - - "reject a create after a rolled back archive" in { - val builder = TxBuilder() - val rollback = builder.add(builder.rollback()) - builder.add(genTestExercise(committedContract), rollback) - builder.add(committedContract) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = committedContractLedgerEffectiveTime, - divulged = Set.empty, - ) - - val duplicateKey = - GlobalKey.assertBuild(committedContract.templateId, committedContract.key.get.key) - - error shouldBe Some(Rejection.DuplicateKey(duplicateKey)) - } - } - - "run with one divulged contract" should { - val divulgedContract = genTestCreate() - val exerciseOnDivulgedContract = genTestExercise(divulgedContract) - - val fixture = committedContracts( - parties = List.empty, - contractFixture = divulged(divulgedContract.coid.coid), - ) - val store = new PostCommitValidation.BackedBy( - fixture, - fixture, - validatePartyAllocation = false, - ) - - "accept an exercise on the divulged contract" in { - val error = store.validate( - transaction = TxBuilder.justCommitted(exerciseOnDivulgedContract), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - - "accept a fetch on the divulged contract" in { - val error = store.validate( - transaction = TxBuilder.justCommitted(txBuilder.fetch(divulgedContract)), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe None - } - } - - "run with unallocated parties" should { - val store = new PostCommitValidation.BackedBy( - noCommittedContract(List.empty), - noCommittedContract(List.empty), - validatePartyAllocation = true, - ) - - "reject" in { - val createWithKey = genTestCreate() - val error = store.validate( - transaction = TxBuilder.justCommitted(createWithKey), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe Some(Rejection.UnallocatedParties(Set("Alice"))) - } - - "reject if party is used in rollback" in { - val createWithKey = genTestCreate() - val builder = TxBuilder() - val rollback = builder.add(builder.rollback()) - builder.add(createWithKey, rollback) - - val error = store.validate( - transaction = builder.buildCommitted(), - transactionLedgerEffectiveTime = Timestamp.now(), - divulged = Set.empty, - ) - - error shouldBe Some(Rejection.UnallocatedParties(Set("Alice"))) - } - } - } -} - -object PostCommitValidationSpec { - - import TxBuilder.Implicits._ - - // Very dirty hack to have a contract store fixture without persistence - private implicit val connection: Connection = null - - private val txBuilder = TxBuilder() - - private def genTestCreate(): Create = - txBuilder.create( - id = ContractId.V1(Hash.hashPrivateKey(UUID.randomUUID.toString)), - templateId = "bar:baz", - argument = TxBuilder.record("field" -> "value"), - signatories = Set("Alice"), - observers = Set.empty, - key = Some(ValueText("key")), - ) - - private def genTestExercise(create: Create): Exercise = - txBuilder.exercise( - contract = create, - choice = "SomeChoice", - consuming = true, - actingParties = Set("Alice"), - argument = TxBuilder.record("field" -> "value"), - ) - - private final case class ContractFixture private ( - id: ContractId, - ledgerEffectiveTime: Option[Timestamp], - key: Option[Key], - ) - - private final case class ContractStoreFixture private ( - contracts: Set[ContractFixture], - parties: List[PartyDetails], - ) extends PartyStorageBackend - with ContractStorageBackend { - override def contractKeyGlobally(key: Key)(connection: Connection): Option[ContractId] = - contracts.find(c => c.key.contains(key)).map(_.id) - override def maximumLedgerTime( - ids: Set[ContractId] - )(connection: Connection): Try[Option[Timestamp]] = { - val lookup = contracts.collect { - case c if ids.contains(c.id) => c.ledgerEffectiveTime - } - if (lookup.isEmpty) Failure(notFound(ids)) - else Success(lookup.fold[Option[Timestamp]](None)(pickTheGreatest)) - } - override def keyState(key: Key, validAt: Long)(connection: Connection): KeyState = - notImplemented() - override def contractState(contractId: ContractId, before: Long)( - connection: Connection - ): Option[ContractStorageBackend.RawContractState] = notImplemented() - override def activeContractWithArgument(readers: Set[Ref.Party], contractId: ContractId)( - connection: Connection - ): Option[ContractStorageBackend.RawContract] = notImplemented() - override def activeContractWithoutArgument(readers: Set[Ref.Party], contractId: ContractId)( - connection: Connection - ): Option[String] = notImplemented() - override def contractKey(readers: Set[Ref.Party], key: Key)( - connection: Connection - ): Option[ContractId] = notImplemented() - override def contractStateEvents(startExclusive: Long, endInclusive: Long)( - connection: Connection - ): Vector[ContractStorageBackend.RawContractStateEvent] = notImplemented() - - override def partyEntries( - startExclusive: Offset, - endInclusive: Offset, - pageSize: Int, - queryOffset: Long, - )(connection: Connection): Vector[(Offset, PartyLedgerEntry)] = notImplemented() - override def parties(parties: Seq[Party])(connection: Connection): List[PartyDetails] = - this.parties.filter { party => - parties.contains(party.party) - } - override def knownParties(connection: Connection): List[PartyDetails] = notImplemented() - - // PostCommitValidation only uses a small subset of (PartyStorageBackend with ContractStorageBackend) - private def notImplemented() = - throw new RuntimeException( - "This method is not implemented because it's not used by PostCommitValidation" - ) - } - - private def pickTheGreatest(l: Option[Timestamp], r: Option[Timestamp]): Option[Timestamp] = - l.fold(r)(left => r.fold(l)(right => if (left > right) l else r)) - - private def notFound(contractIds: Set[ContractId]): Throwable = - MissingContracts(contractIds) - - private def noCommittedContract(parties: List[PartyDetails]): ContractStoreFixture = - ContractStoreFixture( - contracts = Set.empty, - parties = parties, - ) - - private def committedContracts( - parties: List[PartyDetails], - contractFixture: ContractFixture, - contractFixtures: ContractFixture* - ): ContractStoreFixture = - ContractStoreFixture( - contracts = (contractFixture +: contractFixtures).toSet, - parties = parties, - ) - - private def committed( - id: String, - ledgerEffectiveTime: Timestamp, - key: Option[Key], - ): ContractFixture = - ContractFixture( - id = ContractId.assertFromString(id), - ledgerEffectiveTime = Some(ledgerEffectiveTime), - key = key, - ) - - private def divulged(id: String): ContractFixture = - ContractFixture( - id = ContractId.assertFromString(id), - ledgerEffectiveTime = None, - key = None, - ) -} diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/dao/JdbcLedgerDaoValidatedH2DatabaseSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/dao/JdbcLedgerDaoValidatedH2DatabaseSpec.scala deleted file mode 100644 index dece2d84addf..000000000000 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/dao/JdbcLedgerDaoValidatedH2DatabaseSpec.scala +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.daml.platform.store.dao - -import org.scalatest.flatspec.AsyncFlatSpec -import org.scalatest.matchers.should.Matchers - -// Aggregate all specs in a single run to not start a new database fixture for each one -final class JdbcLedgerDaoValidatedH2DatabaseSpec - extends AsyncFlatSpec - with Matchers - with JdbcLedgerDaoSuite - with JdbcLedgerDaoBackendH2Database - with JdbcLedgerDaoPostCommitValidationSpec - with JdbcAppendOnlyTransactionInsertion diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/dao/JdbcLedgerDaoValidatedPostgresqlSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/dao/JdbcLedgerDaoValidatedPostgresqlSpec.scala deleted file mode 100644 index 5c8d021de1a7..000000000000 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/dao/JdbcLedgerDaoValidatedPostgresqlSpec.scala +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2022 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -// SPDX-License-Identifier: Apache-2.0 - -package com.daml.platform.store.dao - -import org.scalatest.flatspec.AsyncFlatSpec -import org.scalatest.matchers.should.Matchers - -// Aggregate all specs in a single run to not start a new database fixture for each one -final class JdbcLedgerDaoValidatedPostgresqlSpec - extends AsyncFlatSpec - with Matchers - with JdbcLedgerDaoSuite - with JdbcLedgerDaoBackendPostgresql - with JdbcLedgerDaoPostCommitValidationSpec - with JdbcAppendOnlyTransactionInsertion