Skip to content

Commit

Permalink
DPP-432 Add exception tests to the JdbcLedgerDao suite (#10040)
Browse files Browse the repository at this point in the history
* Add exception tests

changelog_begin
changelog_end

* Add another test

This one tests a transaction that can only be
produced by a privacy-aware ledger.

* Address review comments
  • Loading branch information
rautenrieth-da authored Jun 23, 2021
1 parent 3d79cbf commit 01e329f
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

package com.daml.platform.store.dao

import com.daml.lf.transaction.TransactionVersion
import com.daml.lf.transaction.test.TransactionBuilder
import com.daml.lf.value.Value.{ContractId, ContractInst}
import org.scalatest.flatspec.AsyncFlatSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.{Inside, LoneElement, OptionValues}

/** There are two important parts to cover with Daml exceptions:
* - Create and exercise nodes under rollback nodes should not be indexed
* - Lookup and fetch nodes under rollback nodes may lead to divulgence
*/
private[dao] trait JdbcLedgerDaoExceptionSpec extends LoneElement with Inside with OptionValues {
this: AsyncFlatSpec with Matchers with JdbcLedgerDaoSuite =>

private def contractsReader = ledgerDao.contractsReader

behavior of "JdbcLedgerDao (exceptions)"

it should "not find contracts created under rollback nodes" in {
val builder = TransactionBuilder(TransactionVersion.VDev)
val rollback = builder.add(builder.rollback())
val cid1 = builder.newCid
val cid2 = builder.newCid
builder.add(
createNode(absCid = cid1, signatories = Set(alice), stakeholders = Set(alice)),
rollback,
)
builder.add(createNode(absCid = cid2, signatories = Set(alice), stakeholders = Set(alice)))
val offsetAndEntry = fromTransaction(builder.buildCommitted())

for {
_ <- store(offsetAndEntry)
result1 <- contractsReader.lookupActiveContractAndLoadArgument(Set(alice), cid1)
result2 <- contractsReader.lookupActiveContractAndLoadArgument(Set(alice), cid2)
} yield {
result1 shouldBe None
result2.value shouldBe a[ContractInst[_]]
}
}

it should "divulge contracts fetched under rollback nodes" in {
val stakeholders = Set(alice)
val divulgees = Set(bob)

// A transaction that fetches a contract under a rollback node
def rolledBackFetch(createCid: ContractId, fetcherCid: ContractId) = {
val builder = TransactionBuilder(TransactionVersion.VDev)
val exercise1 = exerciseNode(fetcherCid).copy(
consuming = false,
actingParties = stakeholders,
signatories = divulgees,
stakeholders = stakeholders.union(divulgees),
)
val rollback = builder.rollback()
val fetch1 = fetchNode(createCid).copy(
actingParties = stakeholders,
signatories = stakeholders,
stakeholders = stakeholders,
)

val exercise1Nid = builder.add(exercise1)
val rollbackNid = builder.add(rollback, exercise1Nid)
builder.add(fetch1, rollbackNid)
fromTransaction(builder.buildCommitted()).copy()
}

for {
// Create contract to be divulged
(_, tx1) <- createAndStoreContract(
submittingParties = stakeholders,
signatories = stakeholders,
stakeholders = stakeholders,
key = None,
)
create = nonTransient(tx1).loneElement
createCid = ContractId.assertFromString(create.coid)

// Create "Fetcher" contract
(_, tx2) <- createAndStoreContract(
submittingParties = divulgees,
signatories = divulgees,
stakeholders = stakeholders.union(divulgees),
key = None,
)
fetcher = nonTransient(tx2).loneElement
fetcherCid = ContractId.assertFromString(fetcher.coid)

// Exercise a choice on the "Fetcher" contract that divulges the first contract
_ <- store(
divulgedContracts = Map((create, someVersionedContractInstance) -> divulgees),
blindingInfo = None,
offsetAndTx = rolledBackFetch(createCid, fetcherCid),
)
resultAlice <- contractsReader.lookupActiveContractAndLoadArgument(Set(alice), createCid)
resultBob <- contractsReader.lookupActiveContractAndLoadArgument(Set(bob), createCid)
resultCharlie <- contractsReader.lookupActiveContractAndLoadArgument(Set(charlie), createCid)
} yield {
withClue("Alice is stakeholder") {
resultAlice.value shouldBe a[ContractInst[_]]
}
withClue("Contract was divulged to Bob under a rollback node") {
resultBob.value shouldBe a[ContractInst[_]]
}
withClue("Charlie is unrelated and must not see the contract") {
resultCharlie shouldBe None
}
}
}

it should "divulge contracts fetched under rollback nodes within a single transaction" in {
val stakeholders = Set(alice)
val divulgees = Set(bob)

val builder = TransactionBuilder(TransactionVersion.VDev)
val createCid = builder.newCid
val fetcherCid = builder.newCid
val create1 = createNode(createCid, stakeholders, stakeholders)
val create2 = createNode(fetcherCid, divulgees, stakeholders.union(divulgees))
val exercise1 = exerciseNode(fetcherCid).copy(
consuming = false,
actingParties = stakeholders,
signatories = divulgees,
stakeholders = stakeholders.union(divulgees),
)
val rollback = builder.rollback()
val fetch1 = fetchNode(createCid).copy(
actingParties = stakeholders,
signatories = stakeholders,
stakeholders = stakeholders,
)

builder.add(create1)
builder.add(create2)
val exercise1Nid = builder.add(exercise1)
val rollbackNid = builder.add(rollback, exercise1Nid)
builder.add(fetch1, rollbackNid)
val offsetAndEntry = fromTransaction(builder.buildCommitted()).copy()

for {
_ <- store(
divulgedContracts = Map.empty,
blindingInfo = None,
offsetAndTx = offsetAndEntry,
)
resultAlice <- contractsReader.lookupActiveContractAndLoadArgument(Set(alice), createCid)
resultBob <- contractsReader.lookupActiveContractAndLoadArgument(Set(bob), createCid)
resultCharlie <- contractsReader.lookupActiveContractAndLoadArgument(Set(charlie), createCid)
} yield {
withClue("Alice is stakeholder") {
resultAlice.value shouldBe a[ContractInst[_]]
}
withClue("Contract was divulged to Bob under a rollback node") {
resultBob.value shouldBe a[ContractInst[_]]
}
withClue("Charlie is unrelated and must not see the contract") {
resultCharlie shouldBe None
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
version = TransactionVersion.minVersion,
)

private def exercise(
protected final def exerciseNode(
targetCid: ContractId,
key: Option[KeyWithMaintainers[LfValue[ContractId]]] = None,
): NodeExercises[NodeId, ContractId] =
Expand All @@ -227,6 +227,22 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
version = TransactionVersion.minVersion,
)

protected final def fetchNode(
contractId: ContractId,
party: Party = alice,
): NodeFetch[ContractId] =
NodeFetch(
coid = contractId,
templateId = someTemplateId,
optLocation = None,
actingParties = Set(party),
signatories = Set(party),
stakeholders = Set(party),
None,
byKey = false,
version = TransactionVersion.minVersion,
)

// Ids of all contracts created in a transaction - both transient and non-transient
protected def created(tx: LedgerEntry.Transaction): Set[ContractId] =
tx.transaction.fold(Set.empty[ContractId]) {
Expand Down Expand Up @@ -277,6 +293,26 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
)
}

protected final def fromTransaction(
transaction: CommittedTransaction,
actAs: List[Party] = List(alice),
): (Offset, LedgerEntry.Transaction) = {
val offset = nextOffset()
val id = offset.toLong
val let = Instant.now
offset -> LedgerEntry.Transaction(
commandId = Some(s"commandId$id"),
transactionId = s"trId$id",
applicationId = Some("appID1"),
actAs = actAs,
workflowId = Some("workflowId"),
ledgerEffectiveTime = let,
recordedAt = let,
transaction = transaction,
explicitDisclosure = Map.empty,
)
}

protected final def createTestKey(
maintainers: Set[Party]
): (KeyWithMaintainers[ValueText], GlobalKey) = {
Expand Down Expand Up @@ -375,7 +411,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
key: Option[KeyWithMaintainers[LfValue[ContractId]]] = None,
): (Offset, LedgerEntry.Transaction) = {
val txBuilder = TransactionBuilder()
val nid = txBuilder.add(exercise(targetCid, key))
val nid = txBuilder.add(exerciseNode(targetCid, key))
val offset = nextOffset()
val id = offset.toLong
val let = Instant.now
Expand All @@ -396,7 +432,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
targetCid: ContractId
): (Offset, LedgerEntry.Transaction) = {
val txBuilder = TransactionBuilder()
val nid = txBuilder.add(exercise(targetCid))
val nid = txBuilder.add(exerciseNode(targetCid))
val offset = nextOffset()
val id = offset.toLong
val let = Instant.now
Expand All @@ -417,7 +453,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
targetCid: ContractId
): (Offset, LedgerEntry.Transaction) = {
val txBuilder = TransactionBuilder()
val nid = txBuilder.add(exercise(targetCid).copy(consuming = false))
val nid = txBuilder.add(exerciseNode(targetCid).copy(consuming = false))
val offset = nextOffset()
val id = offset.toLong
val let = Instant.now
Expand All @@ -438,7 +474,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
targetCid: ContractId
): (Offset, LedgerEntry.Transaction) = {
val txBuilder = TransactionBuilder()
val exerciseId = txBuilder.add(exercise(targetCid))
val exerciseId = txBuilder.add(exerciseNode(targetCid))
val childId = txBuilder.add(create(txBuilder.newCid), exerciseId)
val tx = CommittedTransaction(txBuilder.build())
val offset = nextOffset()
Expand All @@ -464,7 +500,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
val txBuilder = TransactionBuilder()
val cid = txBuilder.newCid
val createId = txBuilder.add(create(cid))
val exerciseId = txBuilder.add(exercise(cid))
val exerciseId = txBuilder.add(exerciseNode(cid))
val let = Instant.now
nextOffset() -> LedgerEntry.Transaction(
commandId = Some(UUID.randomUUID().toString),
Expand All @@ -489,9 +525,9 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
val root = txBuilder.newCid
val transient = txBuilder.newCid
val rootCreateId = txBuilder.add(create(root))
val rootExerciseId = txBuilder.add(exercise(root).copy(actingParties = Set(charlie)))
val rootExerciseId = txBuilder.add(exerciseNode(root).copy(actingParties = Set(charlie)))
val createTransientId = txBuilder.add(create(transient), rootExerciseId)
val consumeTransientId = txBuilder.add(exercise(transient), rootExerciseId)
val consumeTransientId = txBuilder.add(exerciseNode(transient), rootExerciseId)
val let = Instant.now
nextOffset() -> LedgerEntry.Transaction(
commandId = Some(UUID.randomUUID.toString),
Expand Down Expand Up @@ -532,7 +568,7 @@ private[dao] trait JdbcLedgerDaoSuite extends JdbcLedgerDaoBackend {
)
)
val exerciseId = txBuilder.add(
exercise(txBuilder.newCid).copy(
exerciseNode(txBuilder.newCid).copy(
actingParties = Set(charlie),
signatories = Set(charlie),
stakeholders = Set(charlie),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class JdbcLedgerDaoH2DatabaseSpec
with JdbcLedgerDaoConfigurationSpec
with JdbcLedgerDaoContractsSpec
with JdbcLedgerDaoDivulgenceSpec
with JdbcLedgerDaoExceptionSpec
with JdbcLedgerDaoPartiesSpec
with JdbcLedgerDaoTransactionsSpec
with JdbcLedgerDaoTransactionTreesSpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class JdbcLedgerDaoOracleDatabaseSpec
with JdbcLedgerDaoCompletionsSpec
with JdbcLedgerDaoContractsSpec
with JdbcLedgerDaoDivulgenceSpec
with JdbcLedgerDaoExceptionSpec
with JdbcLedgerDaoTransactionsSpec
with JdbcLedgerDaoTransactionTreesSpec
with JdbcLedgerDaoTransactionsWriterSpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class JdbcLedgerDaoPipelinedOracleSpec
with JdbcLedgerDaoCompletionsSpec
with JdbcLedgerDaoContractsSpec
with JdbcLedgerDaoDivulgenceSpec
with JdbcLedgerDaoExceptionSpec
with JdbcLedgerDaoTransactionsSpec
with JdbcLedgerDaoTransactionTreesSpec
with JdbcLedgerDaoTransactionsWriterSpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ final class JdbcLedgerDaoPipelinedPostgresqlSpec
with JdbcLedgerDaoCompletionsSpec
with JdbcLedgerDaoContractsSpec
with JdbcLedgerDaoDivulgenceSpec
with JdbcLedgerDaoExceptionSpec
with JdbcLedgerDaoTransactionsSpec
with JdbcLedgerDaoTransactionTreesSpec
with JdbcLedgerDaoTransactionsWriterSpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ final class JdbcLedgerDaoPostgresqlAppendOnlySpec
with JdbcLedgerDaoContractsSpec
with JdbcLedgerDaoContractsAppendOnlySpec
with JdbcLedgerDaoDivulgenceSpec
with JdbcLedgerDaoExceptionSpec
with JdbcLedgerDaoPartiesSpec
with JdbcLedgerDaoTransactionsSpec
with JdbcLedgerDaoTransactionTreesSpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ final class JdbcLedgerDaoPostgresqlSpec
with JdbcLedgerDaoConfigurationSpec
with JdbcLedgerDaoContractsSpec
with JdbcLedgerDaoDivulgenceSpec
with JdbcLedgerDaoExceptionSpec
with JdbcLedgerDaoPartiesSpec
with JdbcLedgerDaoTransactionsSpec
with JdbcLedgerDaoTransactionTreesSpec
Expand Down

0 comments on commit 01e329f

Please sign in to comment.