Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support rollback nodes in KeyValueCommitting.submissionOutputs #9512

Merged
merged 7 commits into from
Apr 27, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -225,8 +225,12 @@ object KeyValueCommitting {
node.getNodeTypeCase match {

case TransactionOuterClass.Node.NodeTypeCase.ROLLBACK =>
// TODO https://github.com/digital-asset/daml/issues/8020
sys.error("rollback nodes are not supported")
// Rollback nodes itself do not produce any outputs. However, children
cocreature marked this conversation as resolved.
Show resolved Hide resolved
// of rollback nodes potentially will, e.g., divulgence.
// At the moment, we overapproximate and treat
// a node the same regardless of whether it was under a rollback node.
// This matches the treatment of transient contracts which could also
// be trimmed from the output.

case TransactionOuterClass.Node.NodeTypeCase.CREATE =>
val protoCreate = node.getCreate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
// Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
miklos-da marked this conversation as resolved.
Show resolved Hide resolved
// SPDX-License-Identifier: Apache-2.0

package com.daml.ledger.participant.state.kvutils

import com.codahale.metrics.MetricRegistry
import com.daml.ledger.participant.state.v1.{
SubmitterInfo,
ApplicationId,
CommandId,
TransactionMeta,
}
import com.daml.ledger.participant.state.kvutils.DamlKvutils.{DamlStateKey, DamlCommandDedupKey}
import com.daml.lf.crypto
import com.daml.lf.data.Time
import com.daml.lf.data.Ref.{Party, Identifier}
import com.daml.lf.data.ImmArray
import com.daml.lf.transaction.SubmittedTransaction
import com.daml.lf.transaction.test.TransactionBuilder
import com.daml.lf.value.Value
import com.daml.metrics.Metrics
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import java.time.Instant

class KeyValueCommitingSpec extends AnyWordSpec with Matchers {
cocreature marked this conversation as resolved.
Show resolved Hide resolved
private val metrics: Metrics = new Metrics(new MetricRegistry)
private val keyValueSubmission = new KeyValueSubmission(metrics)

private val alice = Party.assertFromString("Alice")
private val commandId = CommandId.assertFromString("cmdid")

private def toSubmission(tx: SubmittedTransaction) = {
val timestamp = Time.Timestamp.assertFromInstant(Instant.EPOCH)
cocreature marked this conversation as resolved.
Show resolved Hide resolved
val meta = TransactionMeta(
ledgerEffectiveTime = timestamp,
workflowId = None,
submissionTime = timestamp,
submissionSeed = crypto.Hash.hashPrivateKey(this.getClass.getName),
optUsedPackages = Some(Set.empty),
optNodeSeeds = None,
optByKeyNodes = None,
)
val submitterInfo = SubmitterInfo(
actAs = List(alice),
applicationId = ApplicationId.assertFromString("appid"),
commandId = commandId,
deduplicateUntil = Instant.EPOCH,
)
keyValueSubmission.transactionToSubmission(
submitterInfo = submitterInfo,
meta = meta,
tx = tx,
)
}

def getOutputs(builder: TransactionBuilder) =
KeyValueCommitting.submissionOutputs(toSubmission(builder.buildSubmitted()))
This conversation was marked as resolved.
Show resolved Hide resolved

val dedupKey = DamlStateKey.newBuilder
.setCommandDedup(
DamlCommandDedupKey.newBuilder.addSubmitters(alice).setCommandId(commandId).build
)
.build

private val keyValue = Value.ValueUnit
private val templateId = Identifier.assertFromString("pkg:M:T")

private def create(builder: TransactionBuilder, id: String, hasKey: Boolean = false) =
builder.create(
id = id,
template = templateId.toString,
argument = Value.ValueRecord(None, ImmArray.empty),
signatories = Seq(),
observers = Seq(),
key = if (hasKey) Some(keyValue) else None,
)

private def exercise(
builder: TransactionBuilder,
id: String,
hasKey: Boolean = false,
consuming: Boolean = true,
) =
builder.exercise(
contract = create(builder, id, hasKey),
choice = "Choice",
argument = Value.ValueRecord(None, ImmArray.empty),
actingParties = Set(),
consuming = consuming,
result = Some(Value.ValueUnit),
)

private def fetch(builder: TransactionBuilder, id: String, byKey: Boolean) =
builder.fetch(contract = create(builder, id, hasKey = true), byKey = byKey)

private def lookup(builder: TransactionBuilder, id: String, found: Boolean) =
builder.lookupByKey(contract = create(builder, id, hasKey = true), found = found)

import Conversions.{contractIdToStateKey, contractKeyToStateKey}

"submissionOutputs" should {
"return a single output for a create without a key" in {
val builder = TransactionBuilder()
val c = create(builder, "#1")
This conversation was marked as resolved.
Show resolved Hide resolved
builder.add(c)
val key = contractIdToStateKey(c.coid)
getOutputs(builder) shouldBe Set(
dedupKey,
key,
)
}
This conversation was marked as resolved.
Show resolved Hide resolved
"return two outputs for a create with a key" in {
val builder = TransactionBuilder()
val c = create(builder, "#1", true)
builder.add(c)
val contractIdKey = contractIdToStateKey(c.coid)
val contractKeyKey = contractKeyToStateKey(templateId, keyValue)
getOutputs(builder) shouldBe Set(
dedupKey,
contractIdKey,
contractKeyKey,
)
}
"return a single output for a transient contract" in {
val builder = TransactionBuilder()
val c = create(builder, "#1", true)
builder.add(c)
builder.add(exercise(builder, "#1"))
val contractIdKey = contractIdToStateKey(c.coid)
val contractKeyKey = contractKeyToStateKey(templateId, keyValue)
getOutputs(builder) shouldBe Set(
dedupKey,
contractIdKey,
contractKeyKey,
)
}
"return a single output for an exercise without a key" in {
val builder = TransactionBuilder()
val e = exercise(builder, "#1")
builder.add(e)
val contractIdKey = contractIdToStateKey(e.targetCoid)
getOutputs(builder) shouldBe Set(
dedupKey,
contractIdKey,
)
}
"return two outputs for an exercise with a key" in {
val builder = TransactionBuilder()
val e = exercise(builder, "#1", hasKey = true)
builder.add(e)
val contractIdKey = contractIdToStateKey(e.targetCoid)
val contractKeyKey = contractKeyToStateKey(templateId, keyValue)
getOutputs(builder) shouldBe Set(
dedupKey,
contractIdKey,
contractKeyKey,
)
}
"return one output per fetch and fetch-by-key" in {
val builder = TransactionBuilder()
val f1 = fetch(builder, "#1", byKey = true)
val f2 = fetch(builder, "#2", byKey = false)
builder.add(f1)
builder.add(f2)
getOutputs(builder) shouldBe Set(
dedupKey,
contractIdToStateKey(f1.coid),
contractIdToStateKey(f2.coid),
)
}
"return no output for a failing lookup-by-key" in {
val builder = TransactionBuilder()
builder.add(lookup(builder, "#1", found = false))
getOutputs(builder) shouldBe Set(dedupKey)

cocreature marked this conversation as resolved.
Show resolved Hide resolved
}
"return no output for a successful lookup-by-key" in {
val builder = TransactionBuilder()
builder.add(lookup(builder, "#1", found = true))
getOutputs(builder) shouldBe Set(dedupKey)
}
"return outputs for nodes under a rollback node" in {
val builder = TransactionBuilder()
val rollback = builder.add(builder.rollback())
val c = create(builder, "#1", hasKey = true)
builder.add(c, rollback)
val e = exercise(builder, "#2", hasKey = true)
builder.add(e, rollback)
val f = fetch(builder, "#3", byKey = true)
builder.add(f, rollback)
getOutputs(builder) shouldBe Set(
dedupKey,
contractIdToStateKey(c.coid),
contractKeyToStateKey(templateId, keyValue),
contractIdToStateKey(e.targetCoid),
contractIdToStateKey(f.coid),
)
}
}
}