Improve error reporting when running DAML Script over the JSON API (#6481)

It is pretty easy to hit this, e.g., when your templates haven’t been
uploaded to the ledger. Just printing the response doesn’t actually
include the response body which means that you don’t see the actual
error so it’s pretty useless. This PR changes that by printing the
status code and the response body.

All the rest is just test setup to be able to submit a script with a
template that has not been uploaded to the ledger.

changelog_begin
changelog_end
This commit is contained in:
Moritz Kiefer 2020-06-25 14:53:58 +02:00 committed by GitHub
parent 6ffdb8d6bc
commit b0eebba51b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 80 additions and 12 deletions

View File

@ -579,9 +579,13 @@ class JsonLedgerClient(
getResponseDataBytes(resp).map(description =>
Left(new StatusRuntimeException(Status.UNKNOWN.withDescription(description))))
} else {
// A non-500 failure is something like invalid JSON. In that case
// the script runner is just broken so fail hard.
Future.failed(new RuntimeException(s"Request failed: $resp"))
// A non-500 failure is something like invalid JSON or cannot resolve template ID.
// We dont want to treat that failures as ones that can be caught
// via `submitMustFail` so fail hard.
getResponseDataBytes(resp).flatMap(
description =>
Future.failed(
new RuntimeException(s"Request failed: $description, status code: ${resp.status}")))
}
}
}

View File

@ -47,6 +47,39 @@ EOF
visibility = ["//visibility:public"],
)
# A variant of script-test that has not been uploaded to the ledger
# to test missing template ids. We only care that this has a different package id.
genrule(
name = "script-test-no-ledger",
srcs =
glob(["**/*.daml"]) + [
"//daml-script/daml:daml-script.dar",
"//docs:source/daml-script/template-root/src/ScriptExample.daml",
],
outs = ["script-test-no-ledger.dar"],
cmd = """
set -eou pipefail
TMP_DIR=$$(mktemp -d)
mkdir -p $$TMP_DIR/daml
cp -L $(location :daml/ScriptTest.daml) $$TMP_DIR/daml
cp -L $(location //daml-script/daml:daml-script.dar) $$TMP_DIR/
cat << EOF > $$TMP_DIR/daml.yaml
sdk-version: {sdk}
name: script-test-no-ledger
source: daml
version: 0.0.2
dependencies:
- daml-stdlib
- daml-prim
- daml-script.dar
EOF
$(location //compiler/damlc) build --project-root=$$TMP_DIR -o $$PWD/$(location script-test-no-ledger.dar)
rm -rf $$TMP_DIR
""".format(sdk = sdk_version),
tools = ["//compiler/damlc"],
visibility = ["//visibility:public"],
)
da_scala_library(
name = "test-lib",
srcs = glob(
@ -95,7 +128,10 @@ da_scala_test_suite(
srcs = [
"src/com/digitalasset/daml/lf/engine/script/test/JsonApiIt.scala",
],
data = [":script-test.dar"],
data = [
":script-test.dar",
":script-test-no-ledger.dar",
],
resources = glob(["src/main/resources/**/*"]),
deps = [
":test-lib",

View File

@ -236,6 +236,11 @@ jsonMultiParty (alice, bob) = do
submit bob (exerciseCmd c Accept)
pure ()
jsonMissingTemplateId : Party -> Script Int
jsonMissingTemplateId p = do
cid <- submit p (createCmd $ TProposal p p)
snd <$> submit p (exerciseCmd cid Accept)
-- maxInboundMessageSize
template MessageSize

View File

@ -16,7 +16,7 @@ import scalaz.syntax.traverse._
import spray.json._
import com.daml.bazeltools.BazelRunfiles._
import com.daml.lf.archive.DarReader
import com.daml.lf.archive.{Dar, DarReader}
import com.daml.lf.archive.Decode
import com.daml.lf.data.Ref._
import com.daml.lf.engine.script.{
@ -30,6 +30,7 @@ import com.daml.lf.engine.script.{
}
import com.daml.lf.iface.EnvironmentInterface
import com.daml.lf.iface.reader.InterfaceReader
import com.daml.lf.language.Ast.Package
import com.daml.lf.speedy.SError._
import com.daml.lf.speedy.SValue
import com.daml.lf.speedy.SValue._
@ -61,6 +62,7 @@ trait JsonApiFixture
self: Suite =>
override protected def darFile = new File(rlocation("daml-script/test/script-test.dar"))
protected val darFileNoLedger = new File(rlocation("daml-script/test/script-test-no-ledger.dar"))
protected def server: SandboxServer = suiteResource.value._1
override protected def serverPort: Port = server.port
override protected def channel: Channel = suiteResource.value._2
@ -134,11 +136,17 @@ final class JsonApiIt
with SuiteResourceManagementAroundAll
with TryValues {
private val dar = DarReader().readArchiveFromFile(darFile).get.map {
case (pkgId, archive) => Decode.readArchivePayload(pkgId, archive)
private def readDar(file: File): (Dar[(PackageId, Package)], EnvironmentInterface) = {
val dar = DarReader().readArchiveFromFile(file).get.map {
case (pkgId, archive) => Decode.readArchivePayload(pkgId, archive)
}
val ifaceDar = dar.map(pkg => InterfaceReader.readInterface(() => \/-(pkg))._2)
val envIface = EnvironmentInterface.fromReaderInterfaces(ifaceDar)
(dar, envIface)
}
private val ifaceDar = dar.map(pkg => InterfaceReader.readInterface(() => \/-(pkg))._2)
private val envIface = EnvironmentInterface.fromReaderInterfaces(ifaceDar)
val (dar, envIface) = readDar(darFile)
val (darNoLedger, envIfaceNoLedger) = readDar(darFileNoLedger)
def getToken(parties: List[String], admin: Boolean): String = {
val payload = AuthServiceJWTPayload(
@ -161,7 +169,8 @@ final class JsonApiIt
private def getClients(
parties: List[String] = List(party),
defaultParty: Option[String] = None,
admin: Boolean = false) = {
admin: Boolean = false,
envIface: EnvironmentInterface = envIface) = {
// We give the default participant some nonsense party so the checks for party mismatch fail
// due to the mismatch and not because the token does not allow inferring a party
val defaultParticipant =
@ -183,8 +192,9 @@ final class JsonApiIt
private def run(
clients: Participants[ScriptLedgerClient],
name: QualifiedName,
inputValue: Option[JsValue] = Some(JsString(party))): Future[SValue] = {
val scriptId = Identifier(packageId, name)
inputValue: Option[JsValue] = Some(JsString(party)),
dar: Dar[(PackageId, Package)] = dar): Future[SValue] = {
val scriptId = Identifier(dar.main._1, name)
Runner.run(
dar,
scriptId,
@ -305,5 +315,18 @@ final class JsonApiIt
assert(result == SUnit)
}
}
"missing template id" in {
for {
clients <- getClients(envIface = envIfaceNoLedger)
ex <- recoverToExceptionIf[RuntimeException](
run(
clients,
QualifiedName.assertFromString("ScriptTest:jsonMissingTemplateId"),
dar = darNoLedger
))
} yield {
assert(ex.toString.contains("Cannot resolve template ID"))
}
}
}
}