Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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 @@ -236,6 +236,18 @@ class TransactionCoder(allowNullCharacters: Boolean) {
} yield builder.build()
}

private[this] def encodeExternalCallResult(
result: ExternalCallResult
): proto.ExternalCallResult =
proto.ExternalCallResult
.newBuilder()
.setExtensionId(result.extensionId)
.setFunctionId(result.functionId)
.setConfig(result.config.toByteString)
.setInput(result.input.toByteString)
.setOutput(result.output.toByteString)
.build()

private[this] def encodeExercise(
node: Node.Exercise
): Either[EncodeError, proto.Node.Exercise] = {
Expand Down Expand Up @@ -280,11 +292,22 @@ class TransactionCoder(allowNullCharacters: Boolean) {
Right(())
}
_ <-
Either.cond(
node.externalCallResults.isEmpty,
(),
EncodeError("external call results are not supported by transaction encoding"),
)
if (node.externalCallResults.nonEmpty) {
if (node.version < SerializationVersion.minExternalCallResults)
Left(
EncodeError(
s"external call results are not supported by version ${node.version}"
)
)
else {
node.externalCallResults.foreach { result =>
discard(builder.addExternalCallResults(encodeExternalCallResult(result)))
}
Right(())
}
} else {
Right(())
}
} yield builder.build()
}

Expand Down Expand Up @@ -490,6 +513,17 @@ class TransactionCoder(allowNullCharacters: Boolean) {
)
}

private[this] def decodeExternalCallResult(
resultProto: proto.ExternalCallResult
): ExternalCallResult =
ExternalCallResult(
extensionId = resultProto.getExtensionId,
functionId = resultProto.getFunctionId,
config = data.Bytes.fromByteString(resultProto.getConfig),
input = data.Bytes.fromByteString(resultProto.getInput),
output = data.Bytes.fromByteString(resultProto.getOutput),
)

private[this] def decodeExercise(
txVersion: SerializationVersion,
nodeVersionStr: String,
Expand All @@ -516,12 +550,21 @@ class TransactionCoder(allowNullCharacters: Boolean) {
Left(DecodeError(s"Exercise Authorizer not supported by version $nodeVersion"))
else
toPartySet(msg.getAuthorizersList).map(Some(_))
_ <-
Either.cond(
msg.getExternalCallResultsCount == 0,
(),
DecodeError("external call results are not supported by transaction decoding"),
)
externalCallResults <-
if (msg.getExternalCallResultsCount == 0)
Right(ImmArray.empty[ExternalCallResult])
else if (nodeVersion < SerializationVersion.minExternalCallResults)
Left(
DecodeError(
s"External call results not supported by version $nodeVersion"
)
)
else
Right(
ImmArray.from(
msg.getExternalCallResultsList.asScala.map(decodeExternalCallResult)
)
)
} yield Node.Exercise(
targetCoid = fetch.coid,
packageName = fetch.packageName,
Expand All @@ -539,7 +582,7 @@ class TransactionCoder(allowNullCharacters: Boolean) {
exerciseResult = result,
keyOpt = fetch.keyOpt,
byKey = fetch.byKey,
externalCallResults = ExternalCallResult.Empty,
externalCallResults = externalCallResults,
version = fetch.version,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.digitalasset.daml.lf.transaction.{GlobalKey, GlobalKeyWithMaintainers
import com.digitalasset.daml.lf.value.Value
import com.digitalasset.daml.lf.value.Value.ContractId
import com.digitalasset.daml.lf.value.ValueCoder.{DecodeError, EncodeError}
import com.google.protobuf.ByteString
import org.scalacheck.Gen
import org.scalatest.Inside
import org.scalatest.matchers.should.Matchers
Expand Down Expand Up @@ -119,19 +118,39 @@ final class TransactionCoderSpec
}
}

"reject Node.Exercise with external call results" in {
"do Node.Exercise with external call results" in {
forAll(danglingRefExerciseNodeGen) { exerciseNode =>
val normalizedNode = normalizeExe(exerciseNode).copy(
version = SerializationVersion.VDev,
externalCallResults = ImmArray(externalCallResult()),
)

val Right(encodedNode) =
TransactionCoder.internal.encodeNode(
enclosingVersion = SerializationVersion.VDev,
nodeId = NodeId(0),
node = normalizedNode,
)

TransactionCoder.internal.decodeNode(SerializationVersion.VDev, encodedNode) shouldBe Right(
(NodeId(0), normalizedNode)
)
}
}

"reject Node.Exercise with external call results before minExternalCallResults" in {
val nodeVersion = SerializationVersion.StableVersions.max
forAll(danglingRefExerciseNodeGen) { exerciseNode =>
val normalizedNode = normalizeExe(exerciseNode.updateVersion(nodeVersion)).copy(
externalCallResults = ImmArray(externalCallResult())
)

TransactionCoder.internal.encodeNode(
enclosingVersion = SerializationVersion.VDev,
enclosingVersion = nodeVersion,
nodeId = NodeId(0),
node = normalizedNode,
) shouldBe Left(
EncodeError("external call results are not supported by transaction encoding")
EncodeError(s"external call results are not supported by version $nodeVersion")
)
}
}
Expand Down Expand Up @@ -347,18 +366,20 @@ final class TransactionCoderSpec
}
}

"reject exercise nodes with external call results" in {
"decode exercise nodes with external call results" in {
forAll(danglingRefExerciseNodeGen) { exerciseNode =>
val normalizedNode = normalizeExe(exerciseNode).copy(
val result = externalCallResult()
val nodeWithoutExternalCallResults = normalizeExe(exerciseNode).copy(
version = SerializationVersion.VDev,
externalCallResults = ImmArray.Empty,
)
val expectedNode = nodeWithoutExternalCallResults.copy(externalCallResults = ImmArray(result))

val Right(encoded) = TransactionCoder.internal
.encodeNode(
enclosingVersion = SerializationVersion.VDev,
nodeId = NodeId(0),
node = normalizedNode,
node = nodeWithoutExternalCallResults,
)

val withExternalCallResultsBuilder = encoded.toBuilder
Expand All @@ -369,18 +390,55 @@ final class TransactionCoderSpec
.newBuilder()
.setExtensionId("ext")
.setFunctionId("fn")
.setConfig(ByteString.copyFromUtf8("cfg"))
.setInput(ByteString.copyFromUtf8("in"))
.setOutput(ByteString.copyFromUtf8("out"))
.setConfig(result.config.toByteString)
.setInput(result.input.toByteString)
.setOutput(result.output.toByteString)
.build()
)
val withExternalCallResults = withExternalCallResultsBuilder.build()

TransactionCoder.internal.decodeNode(
SerializationVersion.VDev,
withExternalCallResults,
) shouldBe Right(
(NodeId(0), expectedNode)
)
}
}

"reject exercise nodes with external call results before minExternalCallResults" in {
val nodeVersion = SerializationVersion.StableVersions.max
forAll(danglingRefExerciseNodeGen) { exerciseNode =>
val result = externalCallResult()
val nodeWithoutExternalCallResults = normalizeExe(exerciseNode.updateVersion(nodeVersion))

val Right(encoded) = TransactionCoder.internal
.encodeNode(
enclosingVersion = nodeVersion,
nodeId = NodeId(0),
node = nodeWithoutExternalCallResults,
)

val withExternalCallResultsBuilder = encoded.toBuilder
withExternalCallResultsBuilder
.getExerciseBuilder
.addExternalCallResults(
proto.ExternalCallResult
.newBuilder()
.setExtensionId(result.extensionId)
.setFunctionId(result.functionId)
.setConfig(result.config.toByteString)
.setInput(result.input.toByteString)
.setOutput(result.output.toByteString)
.build()
)
val withExternalCallResults = withExternalCallResultsBuilder.build()

TransactionCoder.internal.decodeNode(
nodeVersion,
withExternalCallResults,
) shouldBe Left(
DecodeError("external call results are not supported by transaction decoding")
DecodeError(s"External call results not supported by version $nodeVersion")
)
}
}
Expand Down Expand Up @@ -474,11 +532,14 @@ private object TransactionCoderSpec {
case otherwise => otherwise
},
keyOpt = exe.keyOpt.map(normalizeKey(_, exe.version)),
externalCallResults = ImmArray.Empty,
byKey =
if (exe.version >= SerializationVersion.minContractKeys)
exe.byKey
else false,
externalCallResults =
if (exe.version >= SerializationVersion.minExternalCallResults)
exe.externalCallResults
else ImmArray.empty,
)
}

Expand Down
Loading