mirror of
https://github.com/digital-asset/daml.git
synced 2024-09-17 15:57:21 +03:00
Up to now, the engine blindly assumed that contract ids pointed to contracts of the right type. However, this assumption is faulty: contract ids coming from the Ledger API cannot be type checked in command translation since we need access to the contract itself to do so. This caused some seriously surprising / broken behavior: one could send an exercise command with the wrong template id and still go through, or break internal invariants about the type of choices. This commit fixes this by checking that the type of the contract instances we fetch is correct at runtime. cc @hurryabit @dajmaki @remyhaemmerle-da @S11001001 @meiersi-da
This commit is contained in:
parent
66934f8a1e
commit
de54e8f60f
@ -122,6 +122,9 @@ case class Conversions(homePackageId: Ref.PackageId) {
|
||||
case SError.ScenarioErrorInvalidPartyName(party, _) =>
|
||||
builder.setScenarioInvalidPartyName(party)
|
||||
|
||||
case wtc: SError.DamlEWronglyTypedContract =>
|
||||
sys.error(
|
||||
s"Got unexpected DamlEWronglyTypedContract error in scenario service: $wtc. Note that in the scenario service this error should never surface since contract fetches are all type checked.")
|
||||
}
|
||||
builder.build
|
||||
}
|
||||
|
@ -65,6 +65,11 @@ object Pretty {
|
||||
(line + text("Recursive exercise of ") + prettyTypeConName(tid)).nested(4)
|
||||
case Some(nid) => (line + prettyTransactionNode(nid)).nested(4)
|
||||
})
|
||||
|
||||
case DamlEWronglyTypedContract(coid, expected, actual) =>
|
||||
text("Update failed due to wrongly typed contract id") & prettyContractId(coid) /
|
||||
text("Expected contract of type") & prettyTypeConName(expected) & text("but got") & prettyTypeConName(
|
||||
actual)
|
||||
}
|
||||
|
||||
// A minimal pretty-print of an update transaction node, without recursing into child nodes..
|
||||
|
@ -11,7 +11,12 @@ import com.digitalasset.daml.lf.data._
|
||||
import com.digitalasset.daml.lf.lfpackage.Ast._
|
||||
import com.digitalasset.daml.lf.speedy.SError._
|
||||
import com.digitalasset.daml.lf.speedy.SExpr._
|
||||
import com.digitalasset.daml.lf.speedy.Speedy.{CtrlValue, Machine, SpeedyHungry}
|
||||
import com.digitalasset.daml.lf.speedy.Speedy.{
|
||||
CtrlValue,
|
||||
CtrlWronglyTypeContractId,
|
||||
Machine,
|
||||
SpeedyHungry
|
||||
}
|
||||
import com.digitalasset.daml.lf.speedy.SResult._
|
||||
import com.digitalasset.daml.lf.speedy.SValue._
|
||||
import com.digitalasset.daml.lf.transaction.Transaction._
|
||||
@ -774,6 +779,17 @@ object SBuiltin {
|
||||
case Some((_, Some(consumedBy))) =>
|
||||
throw DamlELocalContractNotActive(coid, templateId, consumedBy)
|
||||
case Some((coinst, None)) =>
|
||||
// Here we crash hard rather than throwing a "nice" error
|
||||
// ([[DamlEWronglyTypedContract]]) since if _relative_ contract
|
||||
// id to be of the wrong template it means that the DAML-LF
|
||||
// program that generated it is ill-typed.
|
||||
//
|
||||
// On the other hand absolute contract ids can come from outside
|
||||
// (e.g. Ledger API) and thus we need to fail more gracefully
|
||||
// (see below).
|
||||
if (coinst.template != templateId) {
|
||||
crash(s"Relative contract $rcoid ($templateId) not found from partial transaction")
|
||||
}
|
||||
coinst.arg
|
||||
}
|
||||
case acoid: V.AbsoluteContractId =>
|
||||
@ -784,7 +800,14 @@ object SBuiltin {
|
||||
machine.committer,
|
||||
cbMissing = _ => machine.tryHandleException(),
|
||||
cbPresent = { coinst =>
|
||||
machine.ctrl = CtrlValue(SValue.fromValue(coinst.arg.value))
|
||||
// Note that we cannot throw in this continuation -- instead
|
||||
// set the control appropriately which will crash the machine
|
||||
// correctly later.
|
||||
if (coinst.template != templateId) {
|
||||
machine.ctrl = CtrlWronglyTypeContractId(acoid, templateId, coinst.template)
|
||||
} else {
|
||||
machine.ctrl = CtrlValue(SValue.fromValue(coinst.arg.value))
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
|
@ -76,6 +76,15 @@ object SError {
|
||||
consumedBy: Ledger.NodeId)
|
||||
extends SErrorScenario
|
||||
|
||||
/** We tried to fetch / exercise a contract of the wrong type --
|
||||
* see <https://github.com/digital-asset/daml/issues/1005>.
|
||||
*/
|
||||
final case class DamlEWronglyTypedContract(
|
||||
coid: ContractId,
|
||||
expected: TypeConName,
|
||||
actual: TypeConName)
|
||||
extends SErrorDamlException
|
||||
|
||||
/** A fetch or exercise was being made against a contract that has not
|
||||
* been disclosed to 'committer'. */
|
||||
final case class ScenarioErrorContractNotVisible(
|
||||
|
@ -16,6 +16,7 @@ import scala.collection.JavaConverters._
|
||||
import java.util.ArrayList
|
||||
|
||||
import com.digitalasset.daml.lf.CompiledPackages
|
||||
import com.digitalasset.daml.lf.value.Value.AbsoluteContractId
|
||||
|
||||
object Speedy {
|
||||
|
||||
@ -224,7 +225,10 @@ object Speedy {
|
||||
def execute(machine: Machine): Unit
|
||||
}
|
||||
|
||||
/** A special control object to guard against misbehaving operations */
|
||||
/** A special control object to guard against misbehaving operations.
|
||||
* It is set by default, so for example if an action forgets to set the
|
||||
* control we won't loop but rather we'll crash.
|
||||
*/
|
||||
final case class CtrlCrash(before: Ctrl) extends Ctrl {
|
||||
def execute(machine: Machine) =
|
||||
crash(s"CtrlCrash: control set to crash after evaluting: $before")
|
||||
@ -268,6 +272,20 @@ object Speedy {
|
||||
}
|
||||
}
|
||||
|
||||
/** When we fetch a contract id from upstream we cannot crash in the
|
||||
* that upstream calls. Rather, we set the control to this and then crash
|
||||
* when executing.
|
||||
*/
|
||||
final case class CtrlWronglyTypeContractId(
|
||||
acoid: AbsoluteContractId,
|
||||
expected: TypeConName,
|
||||
actual: TypeConName)
|
||||
extends Ctrl {
|
||||
override def execute(machine: Machine): Unit = {
|
||||
throw DamlEWronglyTypedContract(acoid, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
object Ctrl {
|
||||
def fromPrim(prim: Prim, arity: Int): Ctrl =
|
||||
CtrlValue(SPAP(prim, new ArrayList[SValue](), arity))
|
||||
|
@ -0,0 +1,76 @@
|
||||
// Copyright (c) 2019 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package com.digitalasset.platform.tests.integration.ledger.api
|
||||
|
||||
import org.scalatest.{AsyncFreeSpec, Matchers}
|
||||
import com.digitalasset.ledger.api.testing.utils.{
|
||||
AkkaBeforeAndAfterAll,
|
||||
SuiteResourceManagementAroundEach
|
||||
}
|
||||
import org.scalatest.concurrent.{AsyncTimeLimitedTests, ScalaFutures}
|
||||
import com.digitalasset.platform.apitesting.{LedgerContext, MultiLedgerFixture, TestTemplateIds}
|
||||
import com.digitalasset.platform.apitesting.LedgerContextExtensions._
|
||||
import com.digitalasset.ledger.api.v1.value.{Record, RecordField, Value}
|
||||
import com.digitalasset.platform.participant.util.ValueConversions._
|
||||
import com.google.rpc.code.Code
|
||||
|
||||
class WronglyTypedContractIdIT
|
||||
extends AsyncFreeSpec
|
||||
with AkkaBeforeAndAfterAll
|
||||
with MultiLedgerFixture
|
||||
with SuiteResourceManagementAroundEach
|
||||
with ScalaFutures
|
||||
with AsyncTimeLimitedTests
|
||||
with Matchers
|
||||
with TestTemplateIds {
|
||||
override protected def config: Config = Config.default
|
||||
|
||||
def createDummy(ctx: LedgerContext) = ctx.testingHelpers.simpleCreate(
|
||||
"create-dummy",
|
||||
"alice",
|
||||
templateIds.dummy,
|
||||
Record(fields = List(RecordField(value = "alice".asParty)))
|
||||
)
|
||||
|
||||
"exercising something of the wrong type fails" in allFixtures { ctx =>
|
||||
for {
|
||||
ce <- createDummy(ctx)
|
||||
_ <- ctx.testingHelpers.failingExercise(
|
||||
"exercise-wrong",
|
||||
"alice",
|
||||
templateIds.dummyWithParam,
|
||||
ce.contractId,
|
||||
"DummyChoice2",
|
||||
Value(Value.Sum.Record(Record(fields = List(RecordField(value = "txt".asText))))),
|
||||
Code.INVALID_ARGUMENT,
|
||||
"wrongly typed contract id"
|
||||
)
|
||||
} yield succeed
|
||||
}
|
||||
|
||||
"fetching something of the wrong type fails" in allFixtures { ctx =>
|
||||
for {
|
||||
dummyCreateEvt <- createDummy(ctx)
|
||||
delegationCreateEvt <- ctx.testingHelpers.simpleCreate(
|
||||
"create-delegation",
|
||||
"alice",
|
||||
templateIds.delegation,
|
||||
Record(
|
||||
fields = List(RecordField(value = "alice".asParty), RecordField(value = "bob".asParty)))
|
||||
)
|
||||
_ <- ctx.testingHelpers.failingExercise(
|
||||
"fetch-wrong",
|
||||
"alice",
|
||||
templateIds.delegation,
|
||||
delegationCreateEvt.contractId,
|
||||
"FetchDelegated",
|
||||
Value(
|
||||
Value.Sum.Record(
|
||||
Record(fields = List(RecordField(value = dummyCreateEvt.contractId.asContractId))))),
|
||||
Code.INVALID_ARGUMENT,
|
||||
"wrongly typed contract id"
|
||||
)
|
||||
} yield succeed
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user