remove scala bindings and ledger-api-client (#18301)

* remove scala bindings from daml-script

* remove bindings-pekko

* clean test-common

* clean rx-java

* clean the rest

* remove ledger and scala-bindings

* format

* correcting from code-review
This commit is contained in:
mziolekda 2024-01-29 18:28:14 +01:00 committed by GitHub
parent c6e1ace2b7
commit 90d90ea759
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
299 changed files with 432 additions and 22843 deletions

View File

@ -168,6 +168,7 @@ da_scala_library(
unused_dependency_checker_mode = "error",
visibility = [
"//daml-script:__subpackages__",
"//language-support/java/bindings-rxjava:__subpackages__",
"//test-common/canton:__subpackages__",
],
deps = [
@ -264,6 +265,7 @@ da_scala_library(
"//compiler/repl-service:__subpackages__",
"//daml-script:__subpackages__",
"//language-support/java:__subpackages__",
"//ledger-service/utils:__subpackages__",
"//test-common/canton:__subpackages__",
],
deps = [

View File

@ -343,7 +343,6 @@ maven_install(
name = "maven",
artifacts = [
"com.daml:bindings-akka_2.13:{}".format("2.7.6"),
"com.daml:bindings-pekko_2.13:{}".format(latest_stable_version),
"com.daml:daml-lf-archive-reader_2.13:{}".format(latest_stable_version),
"com.daml:daml-lf-transaction_2.13:{}".format(latest_stable_version),
"com.daml:ledger-api-common_2.13:{}".format(latest_stable_version),

File diff suppressed because it is too large Load Diff

View File

@ -22,7 +22,10 @@ da_scala_binary(
resources = ["src/main/resources/logback.xml"],
scala_deps = [
"@maven//:com_github_scopt_scopt",
"@maven//:com_typesafe_scala_logging_scala_logging",
"@maven//:io_spray_spray_json",
"@maven//:org_apache_pekko_pekko_actor",
"@maven//:org_apache_pekko_pekko_stream",
"@maven//:org_scalaz_scalaz_core",
],
scalacopts = lf_scalacopts_stricter,
@ -38,13 +41,15 @@ da_scala_binary(
"//daml-lf/language",
"//daml-lf/transaction",
"//daml-script/runner:script-runner-lib",
"//language-support/scala/bindings-pekko",
"//ledger-service/lf-value-json",
"//libs-scala/auth-utils",
"//libs-scala/rs-grpc-bridge",
"//libs-scala/rs-grpc-pekko",
"//libs-scala/scala-utils",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:io_grpc_grpc_api",
"@maven//:io_grpc_grpc_netty",
"@maven//:io_grpc_grpc_stub",
"@maven//:io_netty_netty_handler",
],
)

View File

@ -14,8 +14,6 @@ genrule(
"//canton:bindings-java_pom.xml",
"//language-support/java/bindings-rxjava:libbindings-rxjava.jar",
"//language-support/java/bindings-rxjava:bindings-rxjava_pom.xml",
"//ledger/ledger-api-auth-client:libledger-api-auth-client.jar",
"//ledger/ledger-api-auth-client:ledger-api-auth-client_pom.xml",
"//language-support/java/codegen:shaded_binary.jar",
"//language-support/java/codegen:shaded_binary_pom.xml",
"//libs-scala/rs-grpc-bridge:librs-grpc-bridge.jar",
@ -60,10 +58,6 @@ genrule(
"com.daml" "rs-grpc-bridge" \\
$(location //libs-scala/rs-grpc-bridge:librs-grpc-bridge.jar) \\
$(location //libs-scala/rs-grpc-bridge:rs-grpc-bridge_pom.xml)
install_mvn \\
"com.daml" "ledger-api-auth-client" \\
$(location //ledger/ledger-api-auth-client:libledger-api-auth-client.jar) \\
$(location //ledger/ledger-api-auth-client:ledger-api-auth-client_pom.xml)
"$$MVN" -q -Dmaven.repo.local=$$MVN_DB -f "$$TMP_DIR/quickstart-java/pom.xml" dependency:resolve dependency:resolve-plugins
$(execpath //bazel_tools/sh:mktar) $@ -C $$(dirname $$MVN_DB) $$(basename $$MVN_DB)
""".format(mvn = mvn_version),

View File

@ -26,6 +26,7 @@ da_scala_binary(
resources = glob(["src/main/resources/**/*"]),
scala_deps = [
"@maven//:com_github_scopt_scopt",
"@maven//:org_apache_pekko_pekko_actor",
"@maven//:org_apache_pekko_pekko_stream",
"@maven//:io_spray_spray_json",
"@maven//:org_scalaz_scalaz_core",
@ -39,15 +40,16 @@ da_scala_binary(
deps = [
"//canton:community_ledger_ledger-common",
"//canton:community_util-logging",
"//canton:ledger_api_proto_scala",
"//daml-lf/archive:daml_lf_archive_reader",
"//daml-lf/archive:daml_lf_dev_archive_proto_java",
"//daml-lf/data",
"//daml-lf/language",
"//language-support/scala/bindings",
"//language-support/scala/bindings-pekko",
"//libs-scala/auth-utils",
"//libs-scala/rs-grpc-bridge",
"//libs-scala/rs-grpc-pekko",
"//libs-scala/scala-utils",
"@maven//:io_netty_netty_handler",
"@maven//:org_apache_commons_commons_text",
],
)
@ -63,6 +65,7 @@ da_scala_test(
"@maven//:org_scalatest_scalatest_freespec",
"@maven//:org_scalatest_scalatest_matchers_core",
"@maven//:org_scalatest_scalatest_shouldmatchers",
"@maven//:org_scalaz_scalaz_core",
"@maven//:org_typelevel_paiges_core",
],
visibility = ["//visibility:public"],
@ -70,9 +73,9 @@ da_scala_test(
":export",
"//bazel_tools/runfiles:scala_runfiles",
"//canton:community_ledger_ledger-common",
"//canton:ledger_api_proto_scala",
"//daml-lf/data",
"//daml-lf/language",
"//language-support/scala/bindings",
"//libs-scala/auth-utils",
],
)

View File

@ -13,6 +13,8 @@ da_scala_binary(
main_class = "com.daml.script.export.ExampleExportClient",
scala_deps = [
"@maven//:com_github_scopt_scopt",
"@maven//:org_apache_pekko_pekko_actor",
"@maven//:org_scalaz_scalaz_core",
],
scalacopts = lf_scalacopts_stricter,
visibility = ["//visibility:public"],
@ -20,13 +22,15 @@ da_scala_binary(
"//:sdk-version-scala-lib",
"//canton:community_ledger_ledger-common",
"//canton:community_util-logging",
"//canton:ledger_api_proto_scala",
"//daml-lf/data",
"//daml-script/export",
"//daml-script/runner:script-runner-lib",
"//language-support/scala/bindings",
"//language-support/scala/bindings-pekko",
"//libs-scala/auth-utils",
"//libs-scala/fs-utils",
"//libs-scala/rs-grpc-bridge",
"//libs-scala/rs-grpc-pekko",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:io_netty_netty_handler",
],
)

View File

@ -11,7 +11,7 @@ import com.daml.SdkVersion
import com.daml.fs.Utils.deleteRecursively
import com.daml.grpc.adapter.{ExecutionSequencerFactory, PekkoExecutionSequencerPool}
import com.digitalasset.canton.ledger.client.LedgerClient
import com.daml.ledger.api.refinements.ApiTypes.Party
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.Party
import com.digitalasset.canton.ledger.api.tls.TlsConfiguration
import com.daml.lf.engine.script.{ParticipantMode, RunnerMain, RunnerMainConfig}
import com.digitalasset.canton.ledger.client.configuration.{

View File

@ -36,13 +36,14 @@ da_scala_test(
"//bazel_tools/runfiles:scala_runfiles",
"//canton:community_ledger_ledger-api-core",
"//canton:community_ledger_ledger-common",
"//canton:ledger_api_proto_scala",
"//daml-lf/archive:daml_lf_archive_reader",
"//daml-lf/data",
"//daml-lf/interpreter",
"//daml-lf/language",
"//daml-script/export",
"//daml-script/export/transaction-eq",
"//daml-script/runner:script-runner-lib",
"//language-support/scala/bindings",
"//libs-scala/fs-utils",
"//libs-scala/ledger-resources",
"//libs-scala/ports",

View File

@ -9,7 +9,7 @@ import org.apache.pekko.stream.scaladsl.Sink
import com.daml.SdkVersion
import com.daml.bazeltools.BazelRunfiles
import com.daml.integrationtest.CantonFixture
import com.daml.ledger.api.refinements.ApiTypes
import com.digitalasset.canton.ledger.api.refinements.ApiTypes
import com.digitalasset.canton.ledger.api.tls.TlsConfiguration
import com.daml.ledger.api.v2.command_service.SubmitAndWaitRequest
import com.daml.ledger.api.v2.commands.Commands

View File

@ -7,7 +7,7 @@ import java.nio.file.{Path, Paths}
import java.io.File
import com.daml.auth.TokenHolder
import com.daml.ledger.api.refinements.ApiTypes.Party
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.Party
import com.digitalasset.canton.ledger.api.tls.{TlsConfiguration, TlsConfigurationCli}
import com.daml.ledger.api.v2.participant_offset.ParticipantOffset

View File

@ -6,7 +6,7 @@ package com.daml.script.export
import java.io.FileOutputStream
import java.nio.file.Path
import com.daml.daml_lf_dev.DamlLf
import com.daml.ledger.api.refinements.ApiTypes
import com.digitalasset.canton.ledger.api.refinements.ApiTypes
import com.daml.ledger.api.v1.value
import com.digitalasset.canton.ledger.client.LedgerClient
import com.daml.lf.{VersionRange, archive}

View File

@ -6,7 +6,13 @@ package com.daml.script.export
import java.time.format.DateTimeFormatter
import java.time.{LocalDate, ZoneId, ZonedDateTime}
import com.daml.ledger.api.refinements.ApiTypes.{Choice, ContractId, InterfaceId, Party, TemplateId}
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.{
Choice,
ContractId,
InterfaceId,
Party,
TemplateId,
}
import com.daml.ledger.api.v1.event.{CreatedEvent, ExercisedEvent}
import com.daml.ledger.api.v1.value.Value.Sum
import com.daml.ledger.api.v1.value.{Identifier, Record, RecordField, Value, Variant}

View File

@ -5,7 +5,7 @@ package com.daml.script.export
import java.nio.charset.StandardCharsets
import java.nio.file.{Files, Path}
import com.daml.ledger.api.refinements.ApiTypes.{ContractId, Party, TemplateId}
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.{ContractId, Party, TemplateId}
import com.daml.ledger.api.v1.event.CreatedEvent
import com.daml.ledger.api.v2.transaction.TransactionTree
import com.daml.ledger.api.v1.value.Value.Sum

View File

@ -7,14 +7,14 @@ import org.apache.pekko.stream.Materializer
import org.apache.pekko.stream.scaladsl.Sink
import com.daml.auth.TokenHolder
import com.digitalasset.canton.ledger.api.domain
import com.daml.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.daml.ledger.api.v2.update_service.{GetUpdatesResponse, GetUpdateTreesResponse}
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.daml.ledger.api.v1.event.CreatedEvent
import com.daml.ledger.api.v1.event.Event.Event
import com.daml.ledger.api.v1.transaction_filter.Filters
import com.daml.ledger.api.v2.participant_offset.ParticipantOffset
import com.daml.ledger.api.v2.transaction.TransactionTree
import com.daml.ledger.api.v2.transaction_filter.TransactionFilter
import com.daml.ledger.api.v1.transaction_filter.Filters
import com.daml.ledger.api.v2.update_service.{GetUpdatesResponse, GetUpdateTreesResponse}
import com.digitalasset.canton.ledger.client.LedgerClient
import scala.concurrent.{ExecutionContext, Future}

View File

@ -4,7 +4,13 @@
package com.daml.script.export
import java.time.Instant
import com.daml.ledger.api.refinements.ApiTypes.{Choice, ContractId, InterfaceId, Party, TemplateId}
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.{
Choice,
ContractId,
InterfaceId,
Party,
TemplateId,
}
import com.daml.ledger.api.v1.event.{CreatedEvent, ExercisedEvent}
import com.daml.ledger.api.v2.transaction.TransactionTree
import com.daml.ledger.api.v1.transaction.TreeEvent

View File

@ -3,7 +3,7 @@
package com.daml.script.export
import com.daml.ledger.api.refinements.ApiTypes.ContractId
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.ContractId
import com.daml.lf.data.Time.Timestamp
import com.daml.script.export.TreeUtils.{SetTime, SubmitSimpleSingle, Action}
import org.scalatest.freespec.AnyFreeSpec

View File

@ -3,7 +3,7 @@
package com.daml.script.export
import com.daml.ledger.api.refinements.ApiTypes.ContractId
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.ContractId
import com.daml.ledger.api.v1.{value => v}
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers

View File

@ -3,7 +3,7 @@
package com.daml.script.export
import com.daml.ledger.api.refinements.ApiTypes
import com.digitalasset.canton.ledger.api.refinements.ApiTypes
import com.daml.ledger.api.v1.{value => V}
import com.daml.lf.data.Ref
import com.daml.lf.language.Ast

View File

@ -3,8 +3,8 @@
package com.daml.script.export
import com.daml.ledger.api.refinements.ApiTypes
import com.daml.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.digitalasset.canton.ledger.api.refinements.ApiTypes
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.daml.ledger.api.v1.value.Value
import com.daml.script.export.TreeUtils.SubmitSimpleMulti
import org.scalatest.freespec.AnyFreeSpec

View File

@ -7,7 +7,7 @@ import com.daml.ledger.api.v1.{value => v}
import java.time.{Instant, LocalDate, OffsetDateTime, ZoneOffset}
import java.util.concurrent.TimeUnit
import com.daml.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.google.protobuf.empty.Empty
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers

View File

@ -3,7 +3,7 @@
package com.daml.script.export
import com.daml.ledger.api.refinements.ApiTypes.ContractId
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.ContractId
import com.daml.ledger.api.v1.{value => v}
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers

View File

@ -3,7 +3,7 @@
package com.daml.script.export
import com.daml.ledger.api.refinements.ApiTypes.ContractId
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.ContractId
import com.daml.ledger.api.v1.value.{Identifier, Record, RecordField, Value}
import com.daml.script.export.TreeUtils.{
Command,

View File

@ -3,7 +3,7 @@
package com.daml.script.export
import com.daml.ledger.api.refinements.ApiTypes.ContractId
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.ContractId
import com.daml.ledger.api.v1.value.Value
import com.daml.script.export.TreeUtils.{Command, ExerciseByKeyCommand, SimpleCommand}
import org.scalatest.freespec.AnyFreeSpec

View File

@ -7,7 +7,7 @@ import com.daml.ledger.api.v1.{value => v}
import java.time.{Instant, LocalDate, OffsetDateTime, ZoneOffset}
import java.util.concurrent.TimeUnit
import com.daml.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.daml.script.export.TreeUtils.{contractsReferences, treesReferences, valueRefs}
import com.google.protobuf.empty.Empty
import org.scalatest.freespec.AnyFreeSpec

View File

@ -3,7 +3,7 @@
package com.daml.script.export
import com.daml.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.digitalasset.canton.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.daml.ledger.api.v1.event.{CreatedEvent, ExercisedEvent}
import com.daml.ledger.api.v2.transaction.TransactionTree
import com.daml.ledger.api.v1.transaction.TreeEvent

View File

@ -20,7 +20,8 @@ da_scala_library(
scalacopts = lf_scalacopts_stricter,
visibility = ["//visibility:public"],
deps = [
"//language-support/scala/bindings",
"//canton:community_ledger_ledger-common",
"//canton:ledger_api_proto_scala",
],
)
@ -32,11 +33,13 @@ da_scala_test(
"@maven//:org_scalatest_scalatest_freespec",
"@maven//:org_scalatest_scalatest_matchers_core",
"@maven//:org_scalatest_scalatest_shouldmatchers",
"@maven//:org_scalaz_scalaz_core",
],
visibility = ["//visibility:public"],
deps = [
":transaction-eq",
"//language-support/scala/bindings",
"//canton:community_ledger_ledger-common",
"//canton:ledger_api_proto_scala",
"@maven//:org_scalatest_scalatest_compatible",
],
)

View File

@ -11,7 +11,7 @@ import com.daml.ledger.api.v1.event.{CreatedEvent, ExercisedEvent}
import com.daml.ledger.api.v1.transaction.TreeEvent
import com.daml.ledger.api.v2.transaction.TransactionTree
import com.daml.ledger.api.v1.{value => v}
import com.daml.ledger.api.refinements.ApiTypes._
import com.digitalasset.canton.ledger.api.refinements.ApiTypes._
object TransactionEq {
// Helper which constructs a temporary

View File

@ -3,7 +3,7 @@
package com.daml.ledger.testing.utils
import com.daml.ledger.api.refinements.ApiTypes._
import com.digitalasset.canton.ledger.api.refinements.ApiTypes._
import com.daml.ledger.api.v1.{value => v}
import org.scalatest.freespec.AnyFreeSpec

View File

@ -36,7 +36,6 @@ da_java_library(
],
deps = [
"//canton:bindings-java",
"//ledger/ledger-api-auth-client",
"//libs-scala/rs-grpc-bridge",
"@maven//:com_google_api_grpc_proto_google_common_protos",
"@maven//:com_google_protobuf_protobuf_java",
@ -63,6 +62,7 @@ da_scala_library(
],
),
scala_deps = [
"@maven//:com_typesafe_scala_logging_scala_logging",
"@maven//:org_scalacheck_scalacheck",
"@maven//:org_scalactic_scalactic",
"@maven//:org_scalatest_scalatest_core",
@ -73,13 +73,14 @@ da_scala_library(
deps = [
":bindings-rxjava",
"//canton:bindings-java",
"//canton:community_ledger_ledger-api-core",
"//canton:community_ledger_ledger-common",
"//canton:community_util-logging",
"//canton:ledger_api_proto_scala",
"//daml-lf/data",
"//ledger/ledger-api-auth",
"//ledger/ledger-api-domain",
"//ledger/participant-local-store",
"//libs-scala/contextualized-logging",
"//libs-scala/rs-grpc-bridge",
"//observability/tracing",
"@maven//:com_google_protobuf_protobuf_java",
"@maven//:io_grpc_grpc_netty",
"@maven//:io_reactivex_rxjava2_rxjava",
@ -115,9 +116,9 @@ da_scala_test_suite(
":bindings-java-tests-lib",
":bindings-rxjava",
"//canton:bindings-java",
"//canton:community_ledger_ledger-api-core",
"//canton:community_util-logging",
"//canton:ledger_api_proto_scala",
"//ledger/ledger-api-auth",
"//ledger/ledger-api-common",
"//libs-scala/contextualized-logging",
"@maven//:com_google_api_grpc_proto_google_common_protos",
"@maven//:com_google_protobuf_protobuf_java",

View File

@ -1,7 +1,7 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.auth.client;
package com.daml.ledger.rxjava.grpc.helpers;
import io.grpc.CallCredentials;
import io.grpc.Metadata;
@ -32,9 +32,4 @@ public final class LedgerCallCredentials extends CallCredentials {
LedgerCallCredentials.header, token.startsWith("Bearer ") ? token : "Bearer " + token);
applier.apply(metadata);
}
@Override
public void thisUsesUnstableApi() {
// No need to implement this, it's used as a warning from upstream
}
}

View File

@ -3,7 +3,6 @@
package com.daml.ledger.rxjava.grpc.helpers;
import com.daml.ledger.api.auth.client.LedgerCallCredentials;
import io.grpc.stub.AbstractStub;
import java.util.Optional;

View File

@ -14,7 +14,7 @@ import com.daml.ledger.javaapi.data.{
Identifier,
}
import com.daml.ledger.rxjava.grpc.helpers._
import com.daml.ledger.api.auth.{AuthService, AuthServiceWildcard}
import com.digitalasset.canton.ledger.api.auth.{AuthService, AuthServiceWildcard}
import com.daml.ledger.api.v1.command_completion_service.CompletionStreamResponse
import com.daml.ledger.api.v1.command_service.{
SubmitAndWaitForTransactionIdResponse,

View File

@ -3,7 +3,7 @@
package com.daml.ledger.rxjava.grpc
import com.daml.ledger.api.auth.{AuthService, AuthServiceWildcard}
import com.digitalasset.canton.ledger.api.auth.{AuthService, AuthServiceWildcard}
import com.daml.ledger.api.v1.command_service.{
SubmitAndWaitForTransactionIdResponse,
SubmitAndWaitForTransactionResponse,

View File

@ -3,7 +3,7 @@
package com.daml.ledger.rxjava.grpc
import com.daml.ledger.api.auth.{AuthService, AuthServiceWildcard}
import com.digitalasset.canton.ledger.api.auth.{AuthService, AuthServiceWildcard}
import com.daml.ledger.rxjava.grpc.helpers.TestConfiguration
import com.daml.ledger.api.v1.event_query_service.{
GetEventsByContractIdResponse,

View File

@ -27,7 +27,8 @@ final class TimeClientImplTest
behavior of "[9.1] TimeClientImpl.setTime"
it should "send requests with the correct ledger ID, current time and new time" in {
val (timeService, timeServiceImpl) = TimeServiceImpl.createWithRef(Seq.empty, authorizer)
val (timeService, timeServiceImpl) =
TimeServiceImpl.createWithRef(Seq.empty, ledgerServices.authorizer)
ledgerServices.withTimeClient(Seq(timeService)) { timeClient =>
val currentLedgerTimeSeconds = 1L
val currentLedgerTimeNanos = 2L
@ -53,7 +54,7 @@ final class TimeClientImplTest
it should "return the responses received" in {
val getTimeResponse = genGetTimeResponse
ledgerServices.withTimeClient(
Seq(TimeServiceImpl.createWithRef(Seq(getTimeResponse), authorizer)._1)
Seq(TimeServiceImpl.createWithRef(Seq(getTimeResponse), ledgerServices.authorizer)._1)
) { timeClient =>
val response = timeClient.getTime
.timeout(TestConfiguration.timeoutInSeconds, TimeUnit.SECONDS)
@ -68,7 +69,8 @@ final class TimeClientImplTest
it should "send requests with the correct ledger ID" in {
val getTimeResponse =
genGetTimeResponse // we use the first element to block on the first element
val (service, impl) = TimeServiceImpl.createWithRef(Seq(getTimeResponse), authorizer)
val (service, impl) =
TimeServiceImpl.createWithRef(Seq(getTimeResponse), ledgerServices.authorizer)
ledgerServices.withTimeClient(Seq(service)) { timeClient =>
val _ = timeClient
.getTime()
@ -81,15 +83,16 @@ final class TimeClientImplTest
behavior of "[9.4] TimeClientImpl.setTime"
it should "return an error without sending a request when the time to set if bigger than the current time" in {
ledgerServices.withTimeClient(Seq(TimeServiceImpl.createWithRef(Seq.empty, authorizer)._1)) {
timeClient =>
val currentTime = Instant.ofEpochSecond(1L, 2L)
intercept[RuntimeException](
timeClient
.setTime(currentTime, currentTime)
.timeout(TestConfiguration.timeoutInSeconds, TimeUnit.SECONDS)
.blockingGet()
)
ledgerServices.withTimeClient(
Seq(TimeServiceImpl.createWithRef(Seq.empty, ledgerServices.authorizer)._1)
) { timeClient =>
val currentTime = Instant.ofEpochSecond(1L, 2L)
intercept[RuntimeException](
timeClient
.setTime(currentTime, currentTime)
.timeout(TestConfiguration.timeoutInSeconds, TimeUnit.SECONDS)
.blockingGet()
)
}
}
@ -97,7 +100,7 @@ final class TimeClientImplTest
def toAuthenticatedServer(fn: TimeClient => Any): Any =
ledgerServices.withTimeClient(
Seq(TimeServiceImpl.createWithRef(Seq(genGetTimeResponse), authorizer)._1),
Seq(TimeServiceImpl.createWithRef(Seq(genGetTimeResponse), ledgerServices.authorizer)._1),
mockedAuthService,
)(fn)

View File

@ -3,8 +3,8 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.ActiveContractsServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.ActiveContractsServiceAuthorization
import com.daml.ledger.api.v1.active_contracts_service.ActiveContractsServiceGrpc.ActiveContractsService
import com.daml.ledger.api.v1.active_contracts_service.{
ActiveContractsServiceGrpc,

View File

@ -1,11 +1,13 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.auth
import com.daml.ledger.api.auth.AuthService.AUTHORIZATION_KEY
package com.daml.ledger.rxjava.grpc.helpers
import java.util.concurrent.{CompletableFuture, CompletionStage}
import com.digitalasset.canton.ledger.api.auth.AuthService.AUTHORIZATION_KEY
import com.digitalasset.canton.ledger.api.auth.{AuthService, ClaimSet}
import com.digitalasset.canton.tracing.TraceContext
import io.grpc.Metadata
/** An AuthService that matches the value of the `Authorization` HTTP header against
@ -14,7 +16,9 @@ import io.grpc.Metadata
* Note: This AuthService is meant to be used for testing purposes only.
*/
final class AuthServiceStatic(claims: PartialFunction[String, ClaimSet]) extends AuthService {
override def decodeMetadata(headers: Metadata): CompletionStage[ClaimSet] = {
override def decodeMetadata(headers: Metadata)(implicit
traceContext: TraceContext
): CompletionStage[ClaimSet] = {
if (headers.containsKey(AUTHORIZATION_KEY)) {
val authorizationValue = headers.get(AUTHORIZATION_KEY).stripPrefix("Bearer ")
CompletableFuture.completedFuture(

View File

@ -3,8 +3,8 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.CommandCompletionServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.CommandCompletionServiceAuthorization
import com.daml.ledger.api.v1.command_completion_service.CommandCompletionServiceGrpc.CommandCompletionService
import com.daml.ledger.api.v1.command_completion_service._
import io.grpc.ServerServiceDefinition

View File

@ -3,8 +3,8 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.CommandServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.CommandServiceAuthorization
import com.daml.ledger.api.v1.command_service.CommandServiceGrpc.CommandService
import com.daml.ledger.api.v1.command_service._
import com.google.protobuf.empty.Empty

View File

@ -3,8 +3,8 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.CommandSubmissionServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.CommandSubmissionServiceAuthorization
import com.daml.ledger.api.v1.command_submission_service.CommandSubmissionServiceGrpc.CommandSubmissionService
import com.daml.ledger.api.v1.command_submission_service.{
CommandSubmissionServiceGrpc,

View File

@ -3,8 +3,8 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.EventQueryServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.EventQueryServiceAuthorization
import com.daml.ledger.api.v1.event_query_service.EventQueryServiceGrpc.EventQueryService
import com.daml.ledger.api.v1.event_query_service._
import io.grpc.ServerServiceDefinition

View File

@ -3,8 +3,8 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.LedgerConfigurationServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.LedgerConfigurationServiceAuthorization
import com.daml.ledger.api.v1.ledger_configuration_service.LedgerConfigurationServiceGrpc.LedgerConfigurationService
import com.daml.ledger.api.v1.ledger_configuration_service.{
GetLedgerConfigurationRequest,

View File

@ -3,8 +3,8 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.LedgerIdentityServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.LedgerIdentityServiceAuthorization
import com.daml.ledger.api.v1.ledger_identity_service.LedgerIdentityServiceGrpc.LedgerIdentityService
import com.daml.ledger.api.v1.ledger_identity_service.{
GetLedgerIdentityRequest,

View File

@ -6,6 +6,7 @@ package com.daml.ledger.rxjava.grpc.helpers
import java.net.{InetSocketAddress, SocketAddress}
import java.time.{Clock, Duration}
import java.util.concurrent.TimeUnit
import org.apache.pekko.actor.ActorSystem
import com.daml.ledger.rxjava.grpc._
import com.daml.ledger.rxjava.grpc.helpers.TransactionsServiceImpl.LedgerItem
@ -16,8 +17,16 @@ import com.daml.ledger.rxjava.{
PackageClient,
}
import com.daml.grpc.adapter.{ExecutionSequencerFactory, SingleThreadExecutionSequencerPool}
import com.daml.ledger.api.auth.interceptor.AuthorizationInterceptor
import com.daml.ledger.api.auth.{AuthService, AuthServiceWildcard, Authorizer, ClaimSet}
import com.digitalasset.canton.ledger.api.auth.interceptor.{
AuthorizationInterceptor,
IdentityProviderAwareAuthService,
}
import com.digitalasset.canton.ledger.api.auth.{
AuthService,
AuthServiceWildcard,
Authorizer,
ClaimSet,
}
import com.daml.ledger.api.v1.active_contracts_service.GetActiveContractsResponse
import com.daml.ledger.api.v1.command_completion_service.{
CompletionEndResponse,
@ -39,8 +48,9 @@ import com.daml.ledger.api.v1.package_service.{
ListPackagesResponse,
}
import com.daml.ledger.api.v1.testing.time_service.GetTimeResponse
import com.daml.logging.LoggingContext
import com.daml.platform.localstore.InMemoryUserManagementStore
import com.daml.tracing.NoOpTelemetry
import com.digitalasset.canton.logging.{LoggingContextWithTrace, NamedLoggerFactory}
import com.digitalasset.canton.platform.localstore.InMemoryUserManagementStore
import com.google.protobuf.empty.Empty
import io.grpc._
import io.grpc.netty.NettyServerBuilder
@ -57,16 +67,20 @@ final class LedgerServices(val ledgerId: String) {
private val esf: ExecutionSequencerFactory = new SingleThreadExecutionSequencerPool(ledgerId)
private val pekkoSystem = ActorSystem("LedgerServicesParticipant")
private val participantId = "LedgerServicesParticipant"
private val authorizer =
private val loggerFactory = NamedLoggerFactory.root
val authorizer: Authorizer =
new Authorizer(
now = () => Clock.systemUTC().instant(),
ledgerId = ledgerId,
participantId = participantId,
userManagementStore = new InMemoryUserManagementStore(),
userManagementStore = new InMemoryUserManagementStore(createAdmin = false, loggerFactory),
ec = executionContext,
userRightsCheckIntervalInSeconds = 1,
pekkoScheduler = pekkoSystem.scheduler,
)(LoggingContext.ForTesting)
jwtTimestampLeeway = None,
telemetry = NoOpTelemetry,
loggerFactory = loggerFactory,
)
def newServerBuilder(): NettyServerBuilder = NettyServerBuilder.forAddress(nextAddress())
@ -101,14 +115,23 @@ final class LedgerServices(val ledgerId: String) {
}
}
private class IDPAuthService extends IdentityProviderAwareAuthService {
override def decodeMetadata(headers: Metadata)(implicit
loggingContext: LoggingContextWithTrace
): Future[ClaimSet] =
Future.successful(ClaimSet.Unauthenticated)
}
private def createServer(
authService: AuthService,
services: Seq[ServerServiceDefinition],
): Server = {
val authorizationInterceptor = AuthorizationInterceptor(
authService,
Some(new InMemoryUserManagementStore()),
{ _ => Future.successful(ClaimSet.Unauthenticated) },
Some(new InMemoryUserManagementStore(false, loggerFactory)),
new IDPAuthService,
NoOpTelemetry,
loggerFactory,
executionContext,
)
services
@ -272,7 +295,7 @@ final class LedgerServices(val ledgerId: String) {
accessToken: java.util.Optional[String] = java.util.Optional.empty[String],
)(f: (UserManagementClientImpl, UserManagementServiceImpl) => Any): Any = {
val (service, serviceImpl) =
UserManagementServiceImpl.createWithRef(authorizer)(executionContext)
UserManagementServiceImpl.createWithRef(authorizer, loggerFactory)(executionContext)
withServerAndChannel(authService, Seq(service)) { channel =>
f(new UserManagementClientImpl(channel, accessToken), serviceImpl)
}

View File

@ -3,7 +3,7 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.daml.ledger.api.v1.active_contracts_service.GetActiveContractsResponse
import com.daml.ledger.api.v1.command_completion_service.{
CompletionEndResponse,

View File

@ -3,8 +3,8 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.PackageServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.PackageServiceAuthorization
import com.daml.ledger.api.v1.package_service.PackageServiceGrpc.PackageService
import com.daml.ledger.api.v1.package_service._
import io.grpc.ServerServiceDefinition

View File

@ -3,8 +3,8 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.TimeServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.TimeServiceAuthorization
import com.daml.ledger.api.v1.testing.time_service.TimeServiceGrpc.TimeService
import com.daml.ledger.api.v1.testing.time_service.{
GetTimeRequest,

View File

@ -9,8 +9,8 @@ import com.daml.ledger.rxjava.grpc.helpers.TransactionsServiceImpl.{
LedgerItem,
ledgerOffsetOrdering,
}
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.TransactionServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.TransactionServiceAuthorization
import com.daml.ledger.api.v1.event.Event
import com.daml.ledger.api.v1.event.Event.Event.{Archived, Created, Empty}
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset

View File

@ -3,11 +3,11 @@
package com.daml.ledger.rxjava.grpc.helpers
import com.daml.ledger.api.auth.Authorizer
import com.daml.ledger.api.auth.services.UserManagementServiceAuthorization
import com.digitalasset.canton.ledger.api.auth.Authorizer
import com.digitalasset.canton.ledger.api.auth.services.UserManagementServiceAuthorization
import com.daml.ledger.api.v1.admin.user_management_service.UserManagementServiceGrpc.UserManagementService
import com.daml.ledger.api.v1.admin.user_management_service._
import com.daml.logging.LoggingContext
import com.digitalasset.canton.logging.NamedLoggerFactory
import io.grpc.ServerServiceDefinition
import scala.collection.mutable
@ -60,11 +60,11 @@ object UserManagementServiceImpl {
// for testing only
private[helpers] def createWithRef(
authorizer: Authorizer
authorizer: Authorizer,
loggerFactory: NamedLoggerFactory,
)(implicit ec: ExecutionContext): (ServerServiceDefinition, UserManagementServiceImpl) = {
implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
val impl = new UserManagementServiceImpl
val authImpl = new UserManagementServiceAuthorization(impl, authorizer)
val authImpl = new UserManagementServiceAuthorization(impl, authorizer, loggerFactory)
(UserManagementServiceGrpc.bindService(authImpl, ec), impl)
}

View File

@ -3,16 +3,12 @@
package com.daml.ledger
import java.time.Clock
import java.util.UUID
import org.apache.pekko.actor.ActorSystem
import scala.concurrent.ExecutionContext
import com.daml.lf.data.Ref
import com.daml.ledger.api.auth.{
AuthServiceStatic,
Authorizer,
import com.digitalasset.canton.ledger.api.auth.{
Claim,
ClaimActAsParty,
ClaimAdmin,
@ -20,27 +16,12 @@ import com.daml.ledger.api.auth.{
ClaimReadAsParty,
ClaimSet,
}
import com.daml.logging.LoggingContext
import com.daml.platform.localstore.InMemoryUserManagementStore
package object rxjava {
private[rxjava] def untestedEndpoint: Nothing =
throw new UnsupportedOperationException("Untested endpoint, implement if needed")
private val pekkoSystem = ActorSystem("testActorSystem")
sys.addShutdownHook(pekkoSystem.terminate(): Unit)
private[rxjava] val authorizer =
new Authorizer(
() => Clock.systemUTC().instant(),
"testLedgerId",
"testParticipantId",
new InMemoryUserManagementStore(),
ExecutionContext.parasitic,
userRightsCheckIntervalInSeconds = 1,
pekkoScheduler = pekkoSystem.scheduler,
)(LoggingContext.ForTesting)
private[rxjava] val emptyToken = "empty"
private[rxjava] val publicToken = "public"
private[rxjava] val adminToken = "admin"
@ -54,7 +35,7 @@ package object rxjava {
private[rxjava] val someOtherPartyReadWriteToken = UUID.randomUUID.toString
private[rxjava] val mockedAuthService =
AuthServiceStatic {
grpc.helpers.AuthServiceStatic {
case `emptyToken` => ClaimSet.Unauthenticated
case `publicToken` => ClaimSet.Claims.Empty.copy(claims = Seq[Claim](ClaimPublic))
case `adminToken` => ClaimSet.Claims.Empty.copy(claims = Seq[Claim](ClaimAdmin))

View File

@ -413,7 +413,6 @@ da_scala_test(
"//canton:community_ledger_ledger-api-core",
"//canton:community_ledger_ledger-common",
"//daml-lf/data",
"//language-support/scala/bindings-pekko",
"//libs-scala/ledger-resources",
"//libs-scala/ledger-resources:ledger-resources-test-lib",
"//libs-scala/ports",

View File

@ -1,35 +0,0 @@
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
load(
"//bazel_tools:scala.bzl",
"da_scala_library",
)
da_scala_library(
name = "bindings-pekko-testing",
srcs = glob(["src/main/scala/**/*.scala"]),
resources = glob(["src/main/resources/**/*"]),
scala_deps = [
"@maven//:org_apache_pekko_pekko_actor",
"@maven//:org_apache_pekko_pekko_stream",
"@maven//:com_typesafe_scala_logging_scala_logging",
"@maven//:org_scalactic_scalactic",
"@maven//:org_scalatest_scalatest_core",
],
scala_runtime_deps = [
"@maven//:org_apache_pekko_pekko_slf4j",
],
visibility = [
"//visibility:public",
],
exports = [],
runtime_deps = [
"@maven//:ch_qos_logback_logback_classic",
],
deps = [
"//libs-scala/rs-grpc-bridge",
"@maven//:com_typesafe_config",
"@maven//:org_scalatest_scalatest_compatible",
],
)

View File

@ -1,16 +0,0 @@
test-dispatcher {
# Dispatcher is the name of the event-based dispatcher
type = Dispatcher
# What kind of ExecutionService to use
executor = "thread-pool-executor"
# Configuration for the fork join pool
thread-pool-executor {
# Define a fixed thread pool size with this property. The corePoolSize
# and the maximumPoolSize of the ThreadPoolExecutor will be set to this
# value, if it is defined. Then the other pool-size properties will not
# be used.
#
# Valid values are: `off` or a positive integer.
fixed-pool-size = 1
}
}

View File

@ -1,72 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.testing
import java.util
import java.util.concurrent.{Executors, ScheduledExecutorService}
import org.apache.pekko.NotUsed
import org.apache.pekko.actor.{ActorSystem, Scheduler}
import org.apache.pekko.stream.scaladsl.{Sink, Source}
import org.apache.pekko.stream.Materializer
import org.apache.pekko.util.ByteString
import com.daml.grpc.adapter.{ExecutionSequencerFactory, SingleThreadExecutionSequencerPool}
import com.typesafe.config.{Config, ConfigFactory, ConfigValueFactory}
import com.typesafe.scalalogging.LazyLogging
import org.scalatest.{BeforeAndAfterAll, Suite}
import scala.concurrent.duration._
import scala.concurrent.{Await, ExecutionContextExecutor, Future}
import scala.util.control.NonFatal
trait PekkoTest extends BeforeAndAfterAll with LazyLogging { self: Suite =>
// TestEventListener is needed for log testing
private val loggers =
util.Arrays.asList(
"org.apache.pekko.event.slf4j.Slf4jLogger",
"org.apache.pekko.testkit.TestEventListener",
)
protected implicit val sysConfig: Config = ConfigFactory
.load()
.withValue("pekko.loggers", ConfigValueFactory.fromIterable(loggers))
.withValue("pekko.logger-startup-timeout", ConfigValueFactory.fromAnyRef("30s"))
.withValue("pekko.stdout-loglevel", ConfigValueFactory.fromAnyRef("INFO"))
protected implicit val system: ActorSystem = ActorSystem("test", sysConfig)
protected implicit val ec: ExecutionContextExecutor =
system.dispatchers.lookup("test-dispatcher")
protected implicit val scheduler: Scheduler = system.scheduler
protected implicit val schedulerService: ScheduledExecutorService =
Executors.newSingleThreadScheduledExecutor()
protected implicit val materializer: Materializer = Materializer(system)
protected implicit val esf: ExecutionSequencerFactory =
new SingleThreadExecutionSequencerPool("testSequencerPool")
protected val timeout: FiniteDuration = 2.minutes
protected val shortTimeout: FiniteDuration = 5.seconds
protected def await[T](fun: => Future[T]): T = Await.result(fun, timeout)
protected def awaitShort[T](fun: => Future[T]): T = Await.result(fun, shortTimeout)
protected def drain(source: Source[ByteString, NotUsed]): ByteString = {
val futureResult: Future[ByteString] = source.runFold(ByteString.empty) { (a, b) =>
a.concat(b)
}
awaitShort(futureResult)
}
protected def drain[A, B](source: Source[A, B]): Seq[A] = {
val futureResult: Future[Seq[A]] = source.runWith(Sink.seq)
awaitShort(futureResult)
}
override protected def afterAll(): Unit = {
try {
val _ = await(system.terminate())
} catch {
case NonFatal(_) => ()
}
schedulerService.shutdownNow()
super.afterAll()
}
}

View File

@ -1,106 +0,0 @@
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
load(
"//bazel_tools:scala.bzl",
"da_scala_library",
"da_scala_test_suite",
"kind_projector_plugin",
)
da_scala_library(
name = "bindings-pekko",
srcs = glob(["src/main/**/*.scala"]),
plugins = [
kind_projector_plugin,
],
resources = glob(["src/main/resources/**/*"]),
scala_deps = [
"@maven//:com_chuusai_shapeless",
"@maven//:com_github_pureconfig_pureconfig_core",
"@maven//:org_apache_pekko_pekko_actor",
"@maven//:org_apache_pekko_pekko_stream",
"@maven//:com_typesafe_scala_logging_scala_logging",
"@maven//:org_scalaz_scalaz_core",
],
scala_exports = [
"@maven//:com_chuusai_shapeless",
"@maven//:com_github_pureconfig_pureconfig_core",
"@maven//:org_apache_pekko_pekko_actor",
"@maven//:org_apache_pekko_pekko_stream",
"@maven//:com_typesafe_scala_logging_scala_logging",
"@maven//:org_scalaz_scalaz_core",
],
tags = ["maven_coordinates=com.daml:bindings-pekko:__VERSION__"],
visibility = [
"//visibility:public",
],
exports = [
"//canton:bindings-java",
"//language-support/scala/bindings",
"//ledger/ledger-api-client",
"//ledger/ledger-api-domain",
"//libs-scala/rs-grpc-pekko",
"@maven//:ch_qos_logback_logback_classic",
"@maven//:ch_qos_logback_logback_core",
"@maven//:com_google_api_grpc_proto_google_common_protos",
"@maven//:com_typesafe_config",
"@maven//:io_grpc_grpc_netty",
"@maven//:io_netty_netty_handler",
"@maven//:io_netty_netty_tcnative_boringssl_static",
"@maven//:org_slf4j_slf4j_api",
],
deps = [
"//canton:bindings-java",
"//language-support/scala/bindings",
"//ledger/ledger-api-client",
"//ledger/ledger-api-common",
"//ledger/ledger-api-domain",
"//libs-scala/rs-grpc-pekko",
"//observability/tracing",
"@maven//:ch_qos_logback_logback_classic",
"@maven//:ch_qos_logback_logback_core",
"@maven//:com_google_api_grpc_proto_google_common_protos",
"@maven//:com_typesafe_config",
"@maven//:io_grpc_grpc_netty",
"@maven//:io_netty_netty_handler",
"@maven//:io_netty_netty_tcnative_boringssl_static",
"@maven//:org_slf4j_slf4j_api",
],
)
da_scala_test_suite(
name = "tests",
srcs = glob(
[
"src/test/**/*.scala",
],
),
scala_deps = [
"@maven//:org_apache_pekko_pekko_actor",
"@maven//:org_apache_pekko_pekko_stream",
"@maven//:com_typesafe_scala_logging_scala_logging",
"@maven//:org_scalatest_scalatest_core",
"@maven//:org_scalatest_scalatest_matchers_core",
"@maven//:org_scalatest_scalatest_shouldmatchers",
"@maven//:org_scalatest_scalatest_wordspec",
"@maven//:org_scalaz_scalaz_core",
],
scala_runtime_deps = [
"@maven//:org_apache_pekko_pekko_stream_testkit",
],
visibility = [
"//visibility:public",
],
deps = [
":bindings-pekko",
"//language-support/scala/bindings",
"//language-support/scala/bindings-pekko-testing",
"//ledger/ledger-api-client",
"//libs-scala/rs-grpc-bridge",
"//observability/tracing",
"@maven//:com_google_api_grpc_proto_google_common_protos",
"@maven//:com_typesafe_config",
"@maven//:org_scalatest_scalatest_compatible",
],
)

View File

@ -1,18 +0,0 @@
# Ledger Client Binding
This module contains the glue code between `nanobot-framework`, `scala-codegen`
and `prototype-client` (Scala binding for the ledger API). The gRPC API provided
by `prototype-client` as it is is not type-safe. The incomming `Transaction`s
from `LedgerClient` is routed to `DomainTransactionMapper`, which will:
* convert the data in `Transaction` to type-safe types coming from `api-refinements`
* call the `EventDecoder` provided by `scala-codegen` for created events
* verify that the messages contain all the neccessary fields, and remove `Optional` wrappers
The result of this will be the `Domain*` classes.
In the other directions (for the commands coming out from the nanobots) the
'CompositeCommandAdapter' will be used to transform back to the gRPC interface
(`SubmitRequest`). After the command submission, the outcoming `Completion` will
be used to check the result, and based on that the `CommandRetryFlow` can decide
wether the command should be retried or an error should be reported.

View File

@ -1,9 +0,0 @@
ledger-client {
command-client {
max-commands-in-flight = 256
max-parallel-submissions = 32
default-deduplication-time = PT30S
}
max-retry-time = PT1M
}

View File

@ -1,39 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.refinements.ApiTypes._
import scala.collection.immutable
sealed trait DomainEvent {
/** The id of the event */
def eventId: EventId
/** The id of the target contract */
def contractId: ContractId
/** The template ID of the target contract */
def templateId: TemplateId
/** Which parties are notified of the events */
def witnessParties: immutable.Seq[Party]
}
final case class DomainCreatedEvent(
eventId: EventId,
contractId: ContractId,
templateId: TemplateId,
witnessParties: immutable.Seq[Party],
createArguments: CreateArguments,
contractData: Contract.OfAny,
) extends DomainEvent
final case class DomainArchivedEvent(
eventId: EventId,
contractId: ContractId,
templateId: TemplateId,
witnessParties: immutable.Seq[Party],
) extends DomainEvent

View File

@ -1,17 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.refinements.ApiTypes.{CommandId, TransactionId, WorkflowId}
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.google.protobuf.timestamp.Timestamp
case class DomainTransaction(
transactionId: TransactionId,
workflowId: WorkflowId,
offset: LedgerOffset,
commandId: CommandId,
effectiveAt: Timestamp,
events: Seq[DomainEvent],
)

View File

@ -1,147 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import org.apache.pekko.NotUsed
import org.apache.pekko.stream.scaladsl.Flow
import com.daml.ledger.api.refinements.ApiTypes._
import com.daml.ledger.api.v1.event.Event.Event.{Archived, Created, Empty}
import com.daml.ledger.api.v1.event._
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset.Value.Absolute
import com.daml.ledger.api.v1.transaction.Transaction
import com.daml.ledger.client.binding.DomainTransactionMapper.DecoderType
import com.typesafe.scalalogging.LazyLogging
import scalaz.std.either._
import scalaz.std.list._
import scalaz.syntax.traverse._
import scala.collection.immutable
object DomainTransactionMapper {
type DecoderType = CreatedEvent => Either[EventDecoderError, Contract.OfAny]
def apply(decoder: DecoderType): Flow[Transaction, DomainTransaction, NotUsed] =
new DomainTransactionMapper(decoder).transactionsMapper
private sealed trait InputValidationError extends Product with Serializable
private final case class RequiredFieldDoesNotExistError(field: String)
extends InputValidationError
private final case object EmptyEvent extends InputValidationError
}
class DomainTransactionMapper(decoder: DecoderType) extends LazyLogging {
import DomainTransactionMapper._
def transactionsMapper: Flow[Transaction, DomainTransaction, NotUsed] =
Flow[Transaction].mapConcat { transaction =>
domainTransaction(transaction) match {
case Left(error) =>
logger.warn(
s"Input validation error when converting to domain transaction: $error. Transaction is discarded."
)
List.empty
case Right(t) =>
List(t)
}
}
private def domainTransaction(t: Transaction): Either[InputValidationError, DomainTransaction] =
for {
effectiveAt <- checkExists("effectiveAt", t.effectiveAt)
events <- domainEvents(t.events)
transactionId = TransactionId(t.transactionId)
workflowId = WorkflowId(t.workflowId)
offset = LedgerOffset(Absolute(t.offset))
commandId = CommandId(t.commandId)
} yield DomainTransaction(
transactionId,
workflowId,
offset,
commandId,
effectiveAt,
events,
)
private def checkExists[T](
fieldName: String,
maybeElement: Option[T],
): Either[InputValidationError, T] =
maybeElement match {
case Some(element) => Right(element)
case None => Left(RequiredFieldDoesNotExistError(fieldName))
}
private def domainEvents(events: Seq[Event]): Either[InputValidationError, Seq[DomainEvent]] =
events.toList
.traverse { event =>
for {
domainEvent <- mapEvent(event)
} yield domainEvent.toList
}
.map(_.flatten)
private def mapEvent(event: Event): Either[InputValidationError, Option[DomainEvent]] =
event.event match {
case Created(createdEvent) =>
decoder(createdEvent)
.fold(logAndDiscard(createdEvent), mapCreatedEvent(createdEvent, _).map(Some.apply))
case Archived(archivedEvent) =>
mapArchivedEvent(archivedEvent).map(Some.apply)
case Empty =>
Left(EmptyEvent)
}
private def logAndDiscard(
event: CreatedEvent
)(err: EventDecoderError): Either[InputValidationError, Option[DomainEvent]] = {
// TODO: improve error handling (make discarding error log message configurable)
logger.warn(s"Unhandled create event ${event.toString}. Error: ${err.toString}")
Right(None)
}
private def mapCreatedEvent(
createdEvent: CreatedEvent,
contract: Contract.OfAny,
): Either[InputValidationError, DomainCreatedEvent] =
for {
tid <- checkExists("events.witnessedEvents.event.created.templateId", createdEvent.templateId)
arguments <- checkExists(
"events.witnessedEvents.event.created.createArguments",
createdEvent.createArguments,
)
eventId = EventId(createdEvent.eventId)
contractId = ContractId(createdEvent.contractId)
templateId = TemplateId(tid)
witnessParties = createdEvent.witnessParties.map(Party.apply).to(immutable.Seq)
createArguments = CreateArguments(arguments)
} yield DomainCreatedEvent(
eventId,
contractId,
templateId,
witnessParties,
createArguments,
contract,
)
private def mapArchivedEvent(
archivedEvent: ArchivedEvent
): Either[InputValidationError, DomainArchivedEvent] =
for {
tid <- checkExists(
"events.witnessedEvents.event.archived.templateId",
archivedEvent.templateId,
)
eventId = EventId(archivedEvent.eventId)
contractId = ContractId(archivedEvent.contractId)
templateId = TemplateId(tid)
witnessParties = archivedEvent.witnessParties.map(Party.apply).to(immutable.Seq)
} yield DomainArchivedEvent(eventId, contractId, templateId, witnessParties)
}

View File

@ -1,176 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import java.time.Duration
import java.util.concurrent.TimeUnit.MINUTES
import org.apache.pekko.NotUsed
import org.apache.pekko.stream.scaladsl.{Flow, Source}
import com.daml.api.util.TimeProvider
import com.daml.ledger.api.refinements.ApiTypes.{ApplicationId, LedgerId, Party}
import com.daml.ledger.api.refinements.{CompositeCommand, CompositeCommandAdapter}
import com.daml.ledger.api.v1.event.Event
import com.daml.ledger.api.v1.ledger_identity_service.{
GetLedgerIdentityRequest,
LedgerIdentityServiceGrpc,
}
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.ledger.api.v1.transaction_filter.TransactionFilter
import com.daml.ledger.client.LedgerClient
import com.daml.ledger.client.binding.DomainTransactionMapper.DecoderType
import com.daml.ledger.client.binding.retrying.CommandRetryFlow
import com.daml.ledger.client.binding.util.Slf4JLogger
import com.daml.ledger.client.configuration.LedgerClientConfiguration
import com.daml.ledger.client.services.commands.CommandSubmission
import com.daml.ledger.client.services.commands.tracker.CompletionResponse.{
CompletionFailure,
CompletionSuccess,
}
import com.daml.util.Ctx
import io.grpc.ManagedChannel
import io.grpc.netty.NegotiationType.TLS
import io.grpc.netty.NettyChannelBuilder
import io.netty.handler.ssl.SslContext
import org.slf4j.LoggerFactory
import scalaz.syntax.tag._
import scala.annotation.nowarn
import scala.concurrent.{ExecutionContext, Future}
class LedgerClientBinding(
val ledgerClient: LedgerClient,
val ledgerClientConfig: LedgerClientConfiguration,
val channel: ManagedChannel,
retryTimeout: Duration,
timeProvider: TimeProvider,
decoder: DecoderType,
) {
private val logger = LoggerFactory.getLogger(this.getClass)
import LedgerClientBinding._
def transactionSource(
party: Party,
templateSelector: TemplateSelector,
startOffset: LedgerOffset,
endOffset: Option[LedgerOffset],
token: Option[String] = None,
): Source[DomainTransaction, NotUsed] = {
logger.debug(
"[tx {}] subscription start with offset template selector {}, start {}, end {}",
Party.unwrap(party),
templateSelector,
startOffset,
endOffset,
)
ledgerClient.transactionClient
.getTransactions(
startOffset,
endOffset,
transactionFilter(party, templateSelector),
token = token,
)
.via(
Slf4JLogger(
logger,
s"tx $party",
tx =>
s"CID ${tx.commandId} TX ${tx.transactionId} CONTAINS ${tx.events
.map {
case Event(Event.Event.Created(value)) => s"C ${value.contractId}"
case Event(Event.Event.Archived(value)) => s"A ${value.contractId}"
case other => sys.error(s"Expected Created or Archived, got $other"): String
}
.mkString("[", ",", "]")}",
)
)
.via(DomainTransactionMapper(decoder))
}
type CommandTrackingFlow[C] =
Flow[Ctx[C, CompositeCommand], Ctx[C, Either[CompletionFailure, CompletionSuccess]], NotUsed]
private val compositeCommandAdapter = new CompositeCommandAdapter(
LedgerId(ledgerClient.ledgerId.unwrap),
ApplicationId(ledgerClientConfig.applicationId),
)
def retryingConfirmedCommands[C](
party: Party
)(implicit ec: ExecutionContext): Future[CommandTrackingFlow[C]] =
for {
tracking <- CommandRetryFlow[C](
party,
ledgerClient.commandClient,
timeProvider,
retryTimeout,
)
} yield Flow[Ctx[C, CompositeCommand]]
.map(
_.map(compositeCommand =>
CommandSubmission(compositeCommandAdapter.transform(compositeCommand))
)
)
.via(tracking)
type CommandsFlow[C] =
Flow[Ctx[C, CompositeCommand], Ctx[C, Either[CompletionFailure, CompletionSuccess]], NotUsed]
def commands[C](party: Party)(implicit ec: ExecutionContext): Future[CommandsFlow[C]] = {
for {
trackCommandsFlow <- ledgerClient.commandClient
.trackCommands[C](List(party.unwrap), token = None)
} yield Flow[Ctx[C, CompositeCommand]]
.map(
_.map(compositeCommand =>
CommandSubmission(compositeCommandAdapter.transform(compositeCommand))
)
)
.via(trackCommandsFlow)
.mapMaterializedValue(_ => NotUsed)
}
def shutdown()(implicit ec: ExecutionContext): Future[Unit] = Future {
channel.shutdown()
channel.awaitTermination(1, MINUTES)
()
}
}
object LedgerClientBinding {
def createChannel(host: String, port: Int, sslContext: Option[SslContext]): ManagedChannel = {
val builder = NettyChannelBuilder.forAddress(host, port)
sslContext match {
case Some(context) => builder.sslContext(context).negotiationType(TLS)
case None => builder.usePlaintext()
}
builder.build()
}
@nowarn(
"msg=parameter config .* is never used"
) // public function, unsure whether arg needed
def askLedgerId(
channel: ManagedChannel,
config: LedgerClientConfiguration,
)(implicit ec: ExecutionContext): Future[String] =
LedgerIdentityServiceGrpc
.stub(channel)
.getLedgerIdentity(GetLedgerIdentityRequest())
.map(_.ledgerId): @nowarn(
"cat=deprecation&origin=com\\.daml\\.ledger\\.api\\.v1\\.ledger_identity_service\\..*"
)
def transactionFilter(party: Party, templateSelector: TemplateSelector): TransactionFilter =
TransactionFilter(Map(party.unwrap -> templateSelector.toApi))
}

View File

@ -1,22 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.refinements.ApiTypes.TemplateId
import com.daml.ledger.api.v1.transaction_filter.{Filters, InclusiveFilters}
import scalaz.Tag
sealed trait TemplateSelector extends Product with Serializable {
def toApi: Filters
}
object TemplateSelector {
case object All extends TemplateSelector {
val toApi = Filters.defaultInstance
}
final case class Templates(templates: Set[TemplateId]) extends TemplateSelector {
val toApi = Filters(Some(InclusiveFilters(Tag.unsubst(templates).toSeq)))
}
}

View File

@ -1,23 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding.log
object Labels {
val CREATED = "created"
val ARCHIVED = "archived"
val EXERCISED = "exercised"
val WORKFLOW_ID = "workflowId"
val LET = "let"
val RECORDTIME = "recordedAt"
val NANOBOT = "nanobot"
val BLOCK_HEIGHT = "blockHeight"
val EVENT_COUNT = "eventCount"
val COMMAND_COUNT = "commandCount"
val RESULT = "result"
val EVENTS = "events"
val APPID = "applicationid"
val ERROR_CODE = "error-code"
val ERROR_MESSAGE = "error-message"
val ERROR_DETAILS = "error-details"
}

View File

@ -1,32 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding.log
import ch.qos.logback.classic.filter.ThresholdFilter
import ch.qos.logback.classic.spi.ILoggingEvent
import ch.qos.logback.core.spi.FilterReply
/** Logs at specific level and above, excluded logger logs everything.
* If you don't have exclusions, use regular ThresholdFilter. Without provided excludeLogger this
* implementation logs every event with no filtering at all.
*/
class LogbackThresholdFilterWithExclusion extends ThresholdFilter {
@volatile private var excludeLogger: String = _
def setExcludeLogger(a: String): Unit = this.excludeLogger = a
def getExcludeLogger: String = this.excludeLogger
override def decide(event: ILoggingEvent): FilterReply = {
if (!this.isStarted) FilterReply.NEUTRAL
else if (event.getLoggerName.startsWith(excludeLogger)) FilterReply.NEUTRAL
else super.decide(event)
}
override def start(): Unit = {
if (this.excludeLogger != null) {
super.start()
}
}
}

View File

@ -1,125 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding.retrying
import java.time.temporal.TemporalAmount
import org.apache.pekko.NotUsed
import org.apache.pekko.stream.FlowShape
import org.apache.pekko.stream.scaladsl.{Flow, GraphDSL, Merge, Partition}
import com.daml.api.util.TimeProvider
import com.daml.ledger.api.refinements.ApiTypes.Party
import com.daml.ledger.client.services.commands.tracker.CompletionResponse
import com.daml.ledger.client.services.commands.tracker.CompletionResponse.{
CompletionFailure,
CompletionSuccess,
}
import com.daml.ledger.client.services.commands.{CommandClient, CommandSubmission}
import com.daml.util.Ctx
import com.google.rpc.Code
import scalaz.syntax.tag._
import scala.concurrent.{ExecutionContext, Future}
object CommandRetryFlow {
type In[C] = Ctx[C, CommandSubmission]
type Out[C] = Ctx[C, Either[CompletionFailure, CompletionSuccess]]
type SubmissionFlowType[C] = Flow[In[C], Out[C], NotUsed]
private val RETRY_PORT = 0
private val PROPAGATE_PORT = 1
def apply[C](
party: Party,
commandClient: CommandClient,
timeProvider: TimeProvider,
maxRetryTime: TemporalAmount,
)(implicit ec: ExecutionContext): Future[SubmissionFlowType[C]] =
for {
submissionFlow <- commandClient
.trackCommands[RetryInfo[C, CommandSubmission]](List(party.unwrap))
submissionFlowWithoutMat = submissionFlow.mapMaterializedValue(_ => NotUsed)
graph = createGraph(submissionFlowWithoutMat, timeProvider, maxRetryTime)
} yield wrapGraph(graph, timeProvider)
def wrapGraph[C](
graph: SubmissionFlowType[RetryInfo[C, CommandSubmission]],
timeProvider: TimeProvider,
): SubmissionFlowType[C] =
Flow[In[C]]
.map(RetryInfo.wrap(timeProvider))
.via(graph)
.map(RetryInfo.unwrap)
def createGraph[C](
commandSubmissionFlow: SubmissionFlowType[RetryInfo[C, CommandSubmission]],
timeProvider: TimeProvider,
maxRetryTime: TemporalAmount,
): SubmissionFlowType[RetryInfo[C, CommandSubmission]] =
Flow
.fromGraph(GraphDSL.createGraph(commandSubmissionFlow) { implicit b => commandSubmission =>
import GraphDSL.Implicits._
val merge =
b.add(Merge[In[RetryInfo[C, CommandSubmission]]](inputPorts = 2, eagerComplete = true))
val retryDecider = b.add(
Partition[Out[RetryInfo[C, CommandSubmission]]](
outputPorts = 2,
{
case Ctx(
RetryInfo(value, nrOfRetries, firstSubmissionTime, _),
Left(notOk: CompletionResponse.NotOkResponse),
_,
) if RETRYABLE_ERROR_CODES.contains(notOk.grpcStatus.code) =>
if ((firstSubmissionTime plus maxRetryTime) isBefore timeProvider.getCurrentTime) {
RetryLogger.logStopRetrying(
value,
notOk.grpcStatus,
nrOfRetries,
firstSubmissionTime,
)
PROPAGATE_PORT
} else {
RetryLogger.logNonFatal(value, notOk.grpcStatus, nrOfRetries)
RETRY_PORT
}
case Ctx(
RetryInfo(value, nrOfRetries, _, _),
Left(notOk: CompletionResponse.NotOkResponse),
_,
) =>
RetryLogger.logFatal(value, notOk.grpcStatus, nrOfRetries)
PROPAGATE_PORT
case Ctx(_, Left(CompletionResponse.TimeoutResponse(_)), _) =>
PROPAGATE_PORT
case Ctx(_, Left(statusNotFound: CompletionResponse.NoStatusInResponse), _) =>
statusNotFoundError(statusNotFound.commandId)
case Ctx(_, Right(_), _) =>
PROPAGATE_PORT
},
)
)
val convertToRetry = b.add(Flow[Out[RetryInfo[C, CommandSubmission]]].map {
case Ctx(retryInfo, _, telemetryContext) =>
Ctx(retryInfo.newRetry, retryInfo.value, telemetryContext)
})
// format: off
merge.out ~> commandSubmission ~> retryDecider.in
merge.in(RETRY_PORT) <~ convertToRetry <~ retryDecider.out(RETRY_PORT)
// format: on
FlowShape(merge.in(PROPAGATE_PORT), retryDecider.out(PROPAGATE_PORT))
})
private[retrying] val RETRYABLE_ERROR_CODES =
Set(Code.RESOURCE_EXHAUSTED_VALUE, Code.UNAVAILABLE_VALUE)
private def statusNotFoundError(commandId: String): Int =
throw new RuntimeException(s"Status for command $commandId is missing.")
}

View File

@ -1,38 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding.retrying
import java.time.Instant
import com.daml.api.util.TimeProvider
import com.daml.ledger.client.binding.retrying.CommandRetryFlow.Out
import com.daml.util.Ctx
final case class RetryInfo[C, T](
value: T,
nrOfRetries: Int,
firstSubmissionTime: Instant,
ctx: C,
) {
def newRetry: RetryInfo[C, T] = copy(nrOfRetries = nrOfRetries + 1)
}
object RetryInfo {
def wrap[C, T](timeProvider: TimeProvider)(request: Ctx[C, T]): Ctx[RetryInfo[C, T], T] =
request match {
case Ctx(context, value, telemetryContext) =>
Ctx(
RetryInfo(value, 0, timeProvider.getCurrentTime, context),
value,
telemetryContext,
)
}
def unwrap[C, T](request: Out[RetryInfo[C, T]]): Out[C] = request match {
case Ctx(RetryInfo(_, _, _, context), completion, telemetryContext) =>
Ctx(context, completion, telemetryContext)
}
}

View File

@ -1,63 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding.retrying
import java.time.Instant
import com.daml.ledger.api.v1.commands.Commands
import com.daml.ledger.api.validation.CommandsValidator
import com.daml.ledger.client.binding.log.Labels.{
ERROR_CODE,
ERROR_DETAILS,
ERROR_MESSAGE,
WORKFLOW_ID,
}
import com.daml.ledger.client.services.commands.CommandSubmission
import com.google.rpc.status.Status
import com.typesafe.scalalogging.LazyLogging
object RetryLogger extends LazyLogging {
def logFatal(submission: CommandSubmission, status: Status, nrOfRetries: Int): Unit = {
logger.warn(
s"Encountered fatal error when submitting command after $nrOfRetries retries, therefore retry halted: " +
format(submission.commands, status)
)
}
def logStopRetrying(
submission: CommandSubmission,
status: Status,
nrOfRetries: Int,
firstSubmissionTime: Instant,
): Unit = {
logger.warn(
s"Retrying of command stopped after $nrOfRetries retries. Attempting since $firstSubmissionTime: " +
format(submission.commands, status)
)
}
def logNonFatal(submission: CommandSubmission, status: Status, nrOfRetries: Int): Unit = {
logger.warn(
s"Encountered non-fatal error when submitting command after $nrOfRetries retries, therefore will retry: " +
format(submission.commands, status)
)
}
private def format(commands: Commands, status: Status): String =
format(
(BIM, commands.commandId),
(ACT_AS, CommandsValidator.effectiveSubmitters(commands).actAs),
(WORKFLOW_ID, commands.workflowId),
(ERROR_CODE, status.code),
(ERROR_MESSAGE, status.message),
(ERROR_DETAILS, status.details.mkString(",")),
)
@SuppressWarnings(Array("org.wartremover.warts.JavaSerializable", "org.wartremover.warts.Any"))
private def format(fs: (String, Any)*): String = fs.map(f => s"${f._1} = ${f._2}").mkString(", ")
private val ACT_AS = "act-as"
private val BIM = "bim"
}

View File

@ -1,63 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding.util
import java.util.concurrent.{ScheduledExecutorService, ScheduledFuture, TimeUnit}
import com.typesafe.scalalogging.LazyLogging
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future, Promise, TimeoutException}
object ScalaUtil {
implicit class FutureOps[T](val future: Future[T]) extends LazyLogging {
def timeout(
name: String,
failTimeout: FiniteDuration = 1.minute,
warnTimeout: FiniteDuration = 30.seconds,
)(implicit ec: ExecutionContext, scheduler: ScheduledExecutorService): Future[T] = {
val promise = Promise[T]()
@SuppressWarnings(Array("org.wartremover.warts.JavaSerializable"))
val warningTask = schedule(warnTimeout) {
logger.warn("Function {} takes more than {}", name, warnTimeout: Any)
}
val errorTask = schedule(failTimeout) {
val error = new TimeoutException(s"Function call $name took more than $failTimeout")
promise.tryFailure(error)
()
}
future.onComplete { outcome =>
warningTask.cancel(false)
errorTask.cancel(false)
promise.tryComplete(outcome)
}
promise.future
}
private def schedule(
timeout: FiniteDuration
)(f: => Unit)(implicit scheduler: ScheduledExecutorService): ScheduledFuture[_] = {
val runnable = new Runnable {
override def run(): Unit = f
}
scheduler.schedule(runnable, timeout.toMillis, TimeUnit.MILLISECONDS)
}
def timeoutWithDefaultWarn(name: String, failTimeout: FiniteDuration)(implicit
ec: ExecutionContext,
scheduler: ScheduledExecutorService,
): Future[T] = timeout(name, failTimeout, 10.seconds)
}
}

View File

@ -1,64 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding.util
import org.apache.pekko.stream._
import org.apache.pekko.stream.stage.{GraphStage, GraphStageLogic, InHandler, OutHandler}
import org.slf4j.Logger
final case class Slf4JLogger[T, U](
logger: Logger,
prefix: String,
project: T => U,
logDemand: Boolean = false,
) extends GraphStage[FlowShape[T, T]] {
override def toString = "Slf4JLog"
val in: Inlet[T] = Inlet[T]("in")
val out: Outlet[T] = Outlet[T]("out")
override def shape: FlowShape[T, T] = FlowShape(in, out)
override def createLogic(inheritedAttributes: Attributes): GraphStageLogic =
new GraphStageLogic(shape) with OutHandler with InHandler {
override def onPush(): Unit = {
val elem = grab(in)
if (logger.isDebugEnabled) logger.debug("[{}] Element: {}", prefix, project(elem))
push(out, elem)
}
override def onPull(): Unit = {
if (logDemand) logger.debug("[{}] Demand", prefix)
pull(in)
}
override def onUpstreamFailure(cause: Throwable): Unit = {
logger.warn(s"[$prefix] Upstream failed", cause)
super.onUpstreamFailure(cause)
}
override def onUpstreamFinish(): Unit = {
logger.debug("[{}] Upstream finished.", prefix)
super.onUpstreamFinish()
}
override def onDownstreamFinish(cause: Throwable): Unit = {
logger.debug("[{}] Downstream finished.", prefix)
super.onDownstreamFinish(cause)
}
setHandlers(in, out, this)
}
}
object Slf4JLogger {
def apply[T](logger: Logger, prefix: String): Slf4JLogger[T, T] =
new Slf4JLogger(logger, prefix, identity)
}

View File

@ -1,120 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import java.time.Instant
import org.apache.pekko.stream.scaladsl.Source
import com.daml.ledger.api.refinements.ApiTypes._
import com.daml.ledger.api.v1.event.Event.Event.{Archived, Created}
import com.daml.ledger.api.v1.event._
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset.Value.Absolute
import com.daml.ledger.api.v1.transaction.Transaction
import com.daml.ledger.api.v1.value.{Identifier, Record}
import com.daml.ledger.client.testing.PekkoTest
import com.google.protobuf.timestamp.Timestamp
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import scala.collection.immutable
class DomainTransactionMapperUT extends AnyWordSpec with Matchers with PekkoTest {
private val mockContract =
Contract(Primitive.ContractId("contractId"), MockTemplate(), Seq.empty, Seq.empty, None)
private val transactionMapper = new DomainTransactionMapper(_ => Right(mockContract))
private def getResult(source: immutable.Iterable[Transaction]): Seq[DomainTransaction] =
drain(
Source(source)
.via(transactionMapper.transactionsMapper)
)
def createdEvent(contractId: String) =
Event(
Created(
CreatedEvent(
"createdEventId",
contractId,
Some(Identifier("pkgId", "modName", "createdTemplateId")),
None,
Some(Record()),
)
)
)
def archivedEvent(contractId: String) =
Event(
Archived(
ArchivedEvent(
"archivedEventId",
contractId,
Some(Identifier("pkgId", "modName", "archivedTemplateId")),
)
)
)
def domainCreatedEvent(contractId: String) =
DomainCreatedEvent(
EventId("createdEventId"),
ContractId(contractId),
TemplateId(Identifier("pkgId", "modName", "createdTemplateId")),
List.empty,
CreateArguments(Record()),
mockContract,
)
def domainArchivedEvent(contractId: String) =
DomainArchivedEvent(
EventId("archivedEventId"),
ContractId(contractId),
TemplateId(Identifier("pkgId", "modName", "archivedTemplateId")),
List.empty,
)
case class MockTemplate() extends Template[MockTemplate] {
override protected[this] def templateCompanion(implicit
d: DummyImplicit
): TemplateCompanion[MockTemplate] =
new TemplateCompanion.Empty[MockTemplate] {
override val onlyInstance = MockTemplate()
override val id: Primitive.TemplateId[MockTemplate] =
` templateId`("packageId", "modName", "templateId")
override val consumingChoices: Set[Choice] = Set.empty
}
}
private val now = Instant.now()
private val time = Timestamp(now.getEpochSecond, now.getNano)
private def transaction(events: Event*) =
Transaction("tid", "cid", "wid", Some(time), events, "0")
private def domainTransaction(events: DomainEvent*) =
DomainTransaction(
TransactionId("tid"),
WorkflowId("wid"),
LedgerOffset(Absolute("0")),
CommandId("cid"),
time,
events,
)
"DomainTransactionMapper" should {
"should map events to domain events" in {
val result = getResult(
List(
transaction(createdEvent("1")),
transaction(createdEvent("2")),
transaction(archivedEvent("2")),
)
)
result should contain theSameElementsInOrderAs List(
domainTransaction(domainCreatedEvent("1")),
domainTransaction(domainCreatedEvent("2")),
domainTransaction(domainArchivedEvent("2")),
)
}
}
}

View File

@ -1,152 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding.retrying
import java.time.{Duration, Instant}
import org.apache.pekko.stream.scaladsl.{Flow, Sink, Source}
import com.daml.api.util.TimeProvider
import com.daml.ledger.api.v1.commands.Commands
import com.daml.ledger.api.v1.completion.Completion
import com.daml.ledger.client.binding.retrying.CommandRetryFlow.{In, Out, SubmissionFlowType}
import com.daml.ledger.client.services.commands.CommandSubmission
import com.daml.ledger.client.services.commands.tracker.CompletionResponse
import com.daml.ledger.client.services.commands.tracker.CompletionResponse.NotOkResponse
import com.daml.ledger.client.testing.PekkoTest
import com.daml.util.Ctx
import com.google.rpc.Code
import com.google.rpc.status.Status
import org.scalatest.Inside
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec
import scala.concurrent.Future
class CommandRetryFlowUT extends AsyncWordSpec with Matchers with PekkoTest with Inside {
/** Uses the status received in the context for the first time,
* then replies OK status as the ledger effective time is stepped.
*/
val mockCommandSubmission: SubmissionFlowType[RetryInfo[Status, CommandSubmission]] =
Flow[In[RetryInfo[Status, CommandSubmission]]]
.map {
case Ctx(
context @ RetryInfo(_, nrOfRetries, _, status),
CommandSubmission(commands, _),
_,
) =>
// Return a completion based on the input status code only on the first submission.
if (nrOfRetries == 0) {
Ctx(
context,
CompletionResponse(
completion = Completion(commands.commandId, Some(status)),
checkpoint = None,
),
)
} else {
Ctx(
context,
Right(
CompletionResponse.CompletionSuccess(
completion = Completion(commands.commandId, Some(status)),
checkpoint = None,
)
),
)
}
case x =>
throw new RuntimeException(s"Unexpected input: '$x'")
}
private val timeProvider = TimeProvider.Constant(Instant.ofEpochSecond(60))
private val maxRetryTime = Duration.ofSeconds(30)
val retryFlow: SubmissionFlowType[RetryInfo[Status, CommandSubmission]] =
CommandRetryFlow.createGraph(mockCommandSubmission, timeProvider, maxRetryTime)
private def submitRequest(
statusCode: Int,
time: Instant,
): Future[Seq[Out[RetryInfo[Status, CommandSubmission]]]] = {
val request = CommandSubmission(
Commands(
ledgerId = "ledgerId",
workflowId = "workflowId",
applicationId = "applicationId",
commandId = "commandId",
party = "party",
commands = Seq.empty,
)
)
val input = Ctx(
context = RetryInfo(
value = request,
nrOfRetries = 0,
firstSubmissionTime = time,
ctx = Status(statusCode, "message", Seq.empty),
),
value = request,
)
Source.single(input).via(retryFlow).runWith(Sink.seq)
}
"command retry flow" should {
"propagate OK status" in {
submitRequest(Code.OK_VALUE, Instant.ofEpochSecond(45)) map { result =>
inside(result) { case Seq(Ctx(context, Right(_), _)) =>
context.nrOfRetries shouldBe 0
}
}
}
"fail on all codes but RESOURCE_EXHAUSTED and UNAVAILABLE status" in {
val codesToFail =
Code
.values()
.toList
.filterNot(c =>
c == Code.UNRECOGNIZED || CommandRetryFlow.RETRYABLE_ERROR_CODES
.contains(c.getNumber) || c == Code.OK
)
val failedSubmissions = codesToFail.map { code =>
submitRequest(code.getNumber, Instant.ofEpochSecond(45)) map { result =>
inside(result) { case Seq(Ctx(context, Left(notOk: NotOkResponse), _)) =>
context.nrOfRetries shouldBe 0
notOk.grpcStatus.code shouldBe code.getNumber
}
}
}
Future.sequence(failedSubmissions).map(_ => succeed)
}
"retry RESOURCE_EXHAUSTED status" in {
submitRequest(Code.RESOURCE_EXHAUSTED_VALUE, Instant.ofEpochSecond(45)) map { result =>
inside(result) { case Seq(Ctx(context, Right(_), _)) =>
context.nrOfRetries shouldBe 1
}
}
}
"retry UNAVAILABLE status" in {
submitRequest(Code.UNAVAILABLE_VALUE, Instant.ofEpochSecond(45)) map { result =>
inside(result) { case Seq(Ctx(context, Right(_), _)) =>
context.nrOfRetries shouldBe 1
}
}
}
"stop retrying after maxRetryTime" in {
submitRequest(Code.RESOURCE_EXHAUSTED_VALUE, Instant.ofEpochSecond(15)) map { result =>
inside(result) { case Seq(Ctx(context, Left(notOk: NotOkResponse), _)) =>
context.nrOfRetries shouldBe 0
notOk.grpcStatus.code shouldBe Code.RESOURCE_EXHAUSTED_VALUE.intValue
}
}
}
}
}

View File

@ -1,53 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding.util
import java.util.concurrent.{Executors, ScheduledExecutorService}
import com.daml.ledger.client.binding.util.ScalaUtil.FutureOps
import org.scalatest.concurrent.AsyncTimeLimitedTests
import org.scalatest.time.Span
import org.scalatest.time.SpanSugar._
import org.scalatest.BeforeAndAfterAll
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AsyncWordSpec
import scala.concurrent.{Future, Promise, TimeoutException}
class ScalaUtilIT
extends AsyncWordSpec
with AsyncTimeLimitedTests
with Matchers
with BeforeAndAfterAll {
implicit val scheduler: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor()
override def afterAll(): Unit = {
scheduler.shutdownNow()
super.afterAll()
}
"FutureOps" can {
"future with timeout" should {
"fail Future with TimoutException after specified duration" in {
val promise = Promise[Unit]() // never completes
val future = promise.future.timeout("name", 1000.millis, 100.millis)
recoverToSucceededIf[TimeoutException](future)
}
"be able to complete within specified duration" in {
val future = Future {
"result"
}.timeoutWithDefaultWarn("name", 1.second)
future.map(_ shouldBe "result")
}
}
}
override lazy val timeLimit: Span = 10.seconds
}

View File

@ -1,65 +0,0 @@
# Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
load(
"//bazel_tools:scala.bzl",
"da_scala_library",
"da_scala_test_suite",
"kind_projector_plugin",
)
load("@scala_version//:index.bzl", "scala_major_version")
da_scala_library(
name = "bindings",
srcs =
glob(["src/main/scala/**/*.scala"]) + glob([
"src/main/{}/**/*.scala".format(scala_major_version),
]),
plugins = [
kind_projector_plugin,
],
scala_deps = [
"@maven//:org_scalaz_scalaz_core",
],
scala_exports = [
"@maven//:org_scalaz_scalaz_core",
],
tags = ["maven_coordinates=com.daml:bindings-scala:__VERSION__"],
visibility = [
"//visibility:public",
],
exports = [
"//canton:ledger_api_proto_java",
"//canton:ledger_api_proto_scala",
"//daml-lf/data",
"@maven//:io_grpc_grpc_core",
],
deps = [
"//canton:ledger_api_proto_scala",
"//daml-lf/data",
"@maven//:io_grpc_grpc_core",
],
)
da_scala_test_suite(
name = "tests",
size = "small",
srcs = glob(["src/test/**/*.scala"]),
plugins = [
kind_projector_plugin,
],
scala_deps = [
"@maven//:com_chuusai_shapeless",
"@maven//:org_scalacheck_scalacheck",
"@maven//:org_scalatest_scalatest_core",
"@maven//:org_scalatest_scalatest_matchers_core",
"@maven//:org_scalatest_scalatest_shouldmatchers",
"@maven//:org_scalatest_scalatest_wordspec",
"@maven//:org_scalatestplus_scalacheck_1_15",
"@maven//:org_scalaz_scalaz_core",
],
deps = [
":bindings",
"@maven//:org_scalatest_scalatest_compatible",
],
)

View File

@ -1,20 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import scala.collection.immutable
object Compat {
private[binding] type MapLike[
K,
+V,
+CC[X, +Y] <: immutable.MapOps[X, Y, CC, _],
+C <: immutable.MapOps[K, V, CC, C],
] =
immutable.MapOps[K, V, CC, C]
private[binding] type MapFactory[CC[K, V]] = scala.collection.MapFactory[CC]
type DummyImplicit = scala.DummyImplicit
}

View File

@ -1,16 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.api.util
import java.time.{Duration => JDuration}
import com.google.protobuf.duration.{Duration => PDuration}
object DurationConversion {
def toProto(jDuration: JDuration): PDuration = PDuration(jDuration.getSeconds, jDuration.getNano)
def fromProto(pDuration: PDuration): JDuration =
JDuration.ofSeconds(pDuration.seconds, pDuration.nanos.toLong)
}

View File

@ -1,15 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.api.util
import java.time.Duration
final case class LedgerEffectiveTimeTolerance(transactionLatency: Duration, skew: Duration)
extends ToleranceWindow {
override val toleranceInPast: Duration = transactionLatency.plus(skew)
override val toleranceInFuture: Duration = skew
}

View File

@ -1,35 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.api.util
import java.time.{Clock, Instant}
import com.daml.api.util.TimeProvider.MappedTimeProvider
import com.daml.lf.data.Time.Timestamp
trait TimeProvider { self =>
def getCurrentTime: Instant
def getCurrentTimestamp: Timestamp = Timestamp.assertFromInstant(getCurrentTime)
def map(transform: Instant => Instant): TimeProvider = MappedTimeProvider(this, transform)
}
object TimeProvider {
final case class MappedTimeProvider(timeProvider: TimeProvider, transform: Instant => Instant)
extends TimeProvider {
override def getCurrentTime: Instant = transform(timeProvider.getCurrentTime)
}
final case class Constant(getCurrentTime: Instant) extends TimeProvider
case object UTC extends TimeProvider {
private val utcClock = Clock.systemUTC()
override def getCurrentTime: Instant = utcClock.instant()
}
}

View File

@ -1,82 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.api.util
import java.time.Instant
import java.util.concurrent.TimeUnit
import com.daml.ledger.api.v1.value.Value
import com.google.protobuf.timestamp.{Timestamp => ProtoTimestamp}
import com.daml.lf.data.Time.{Timestamp => LfTimestamp}
object TimestampConversion {
val MIN = Instant parse "0001-01-01T00:00:00Z"
val MAX = Instant parse "9999-12-31T23:59:59.999999Z"
def microsToInstant(micros: Value.Sum.Timestamp): Instant = {
val seconds = TimeUnit.MICROSECONDS.toSeconds(micros.value)
val deltaMicros = micros.value - TimeUnit.SECONDS.toMicros(seconds)
Instant.ofEpochSecond(seconds, TimeUnit.MICROSECONDS.toNanos(deltaMicros))
}
def instantToMicros(t: Instant): Value.Sum.Timestamp = {
if (t.getNano % 1000 != 0)
throw new IllegalArgumentException(
s"Conversion of Instant $t to microsecond granularity would result in loss of precision."
)
else
Value.Sum.Timestamp(
TimeUnit.SECONDS.toMicros(t.getEpochSecond) + TimeUnit.NANOSECONDS
.toMicros(t.getNano.toLong)
)
}
def roundInstantToMicros(t: Instant): Value.Sum.Timestamp = {
instantToMicros(roundToMicros(t, ConversionMode.HalfUp))
}
def toInstant(protoTimestamp: ProtoTimestamp): Instant = {
Instant.ofEpochSecond(protoTimestamp.seconds, protoTimestamp.nanos.toLong)
}
def fromInstant(instant: Instant): ProtoTimestamp = {
new ProtoTimestamp().withSeconds(instant.getEpochSecond).withNanos(instant.getNano)
}
def toLf(protoTimestamp: ProtoTimestamp, mode: ConversionMode): LfTimestamp = {
val instant = roundToMicros(toInstant(protoTimestamp), mode)
LfTimestamp.assertFromInstant(instant)
}
def fromLf(timestamp: LfTimestamp): ProtoTimestamp = {
fromInstant(timestamp.toInstant)
}
private def roundToMicros(t: Instant, mode: ConversionMode): Instant = {
val fractionNanos = t.getNano % 1000L
if (fractionNanos != 0) {
mode match {
case ConversionMode.Exact =>
throw new IllegalArgumentException(
s"Conversion of $t to microsecond granularity would result in loss of precision."
)
case ConversionMode.HalfUp =>
t.plusNanos(if (fractionNanos >= 500L) 1000L - fractionNanos else -fractionNanos)
}
} else {
t
}
}
sealed trait ConversionMode
object ConversionMode {
/** Throw an exception if the input can not be represented in microsecond resolution */
case object Exact extends ConversionMode
/** Round to the nearest microsecond */
case object HalfUp extends ConversionMode
}
}

View File

@ -1,12 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.api.util
import java.time.Duration
trait ToleranceWindow {
def toleranceInPast: Duration
def toleranceInFuture: Duration
}

View File

@ -1,63 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.refinements
import com.daml.ledger.api.v1.value.{Identifier, Record, Value}
import scalaz.{@@, Tag}
object ApiTypes {
sealed trait TransactionIdTag
type TransactionId = String @@ TransactionIdTag
val TransactionId = Tag.of[TransactionIdTag]
sealed trait CommandIdTag
type CommandId = String @@ CommandIdTag
val CommandId = Tag.of[CommandIdTag]
sealed trait WorkflowIdTag
type WorkflowId = String @@ WorkflowIdTag
val WorkflowId = Tag.of[WorkflowIdTag]
sealed trait EventIdTag
type EventId = String @@ EventIdTag
val EventId = Tag.of[EventIdTag]
sealed trait TemplateIdTag
type TemplateId = Identifier @@ TemplateIdTag
val TemplateId = Tag.of[TemplateIdTag]
sealed trait InterfaceIdTag
type InterfaceId = Identifier @@ InterfaceIdTag
val InterfaceId = Tag.of[InterfaceIdTag]
sealed trait ApplicationIdTag
type ApplicationId = String @@ ApplicationIdTag
val ApplicationId = Tag.of[ApplicationIdTag]
sealed trait LedgerIdTag
type LedgerId = String @@ LedgerIdTag
val LedgerId = Tag.of[LedgerIdTag]
sealed trait ContractIdTag
type ContractId = String @@ ContractIdTag
val ContractId = Tag.of[ContractIdTag]
sealed trait ChoiceTag
type Choice = String @@ ChoiceTag
val Choice = Tag.of[ChoiceTag]
sealed trait CreateArgumentsTag
type CreateArguments = Record @@ CreateArgumentsTag
val CreateArguments = Tag.of[CreateArgumentsTag]
sealed trait ChoiceArgumentTag
type ChoiceArgument = Value @@ ChoiceArgumentTag
val ChoiceArgument = Tag.of[ChoiceArgumentTag]
sealed trait PartyTag
type Party = String @@ PartyTag
val Party = Tag.of[PartyTag]
}

View File

@ -1,14 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.refinements
import com.daml.ledger.api.refinements.ApiTypes.{CommandId, Party, WorkflowId}
import com.daml.ledger.api.v1.commands.Command
final case class CompositeCommand(
commands: Seq[Command],
party: Party,
commandId: CommandId,
workflowId: WorkflowId,
)

View File

@ -1,30 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.refinements
import com.daml.ledger.api.refinements.ApiTypes.{ApplicationId, LedgerId}
import com.daml.ledger.api.v1.commands.Commands
import scalaz.syntax.tag._
class CompositeCommandAdapter(
ledgerId: LedgerId,
applicationId: ApplicationId,
) {
def transform(c: CompositeCommand): Commands =
Commands(
ledgerId = ledgerId.unwrap,
workflowId = c.workflowId.unwrap,
applicationId = applicationId.unwrap,
commandId = c.commandId.unwrap,
party = c.party.unwrap,
commands = c.commands,
)
}
object CompositeCommandAdapter {
def apply(
ledgerId: LedgerId,
applicationId: ApplicationId,
) = new CompositeCommandAdapter(ledgerId, applicationId)
}

View File

@ -1,16 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.refinements
import scalaz.{@@, Tag}
import scala.util.Random
class IdGenerator[TypeTag](seed: Long) {
private val rand = new Random(seed)
def generateRandom: String @@ TypeTag = Tag.apply(rand.nextLong().toHexString)
}

View File

@ -1,31 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.v1.{value => rpcvalue}
/** A class representing a Daml contract of specific type (Daml template) with assigned contract ID.
*
* @param contractId Contract ID.
* @param value Contract instance as defined in Daml template (without `contractId`).
* @param signatories Signatories of the contract as defined in the Daml template
* @param observers Observers of the contract, both explicitly as defined in the Daml template and implicitly as
* choice controllers.
* @param key The value of the key of this contract, if defined by the template.
*
* @tparam T Contract template type parameter.
*/
final case class Contract[+T](
contractId: Primitive.ContractId[T],
value: T with Template[T],
signatories: Seq[String],
observers: Seq[String],
key: Option[rpcvalue.Value],
) {
def arguments: rpcvalue.Record = value.arguments
}
object Contract {
type OfAny = Contract[Any]
}

View File

@ -1,35 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.refinements.ApiTypes.TemplateId
import com.daml.ledger.api.v1.{value => rpcvalue}
import encoding.ExerciseOn
/** Common superclass of template and interface companions objects. */
abstract class ContractTypeCompanion[T] extends ValueRefCompanion {
/** Alias for contract IDs for this template or interface. Can be used
* interchangeably with its expansion.
*/
type ContractId = Primitive.ContractId[T]
val id: Primitive.TemplateId[T]
override protected lazy val ` dataTypeId` = TemplateId.unwrap(id)
protected final def ` templateId`(
packageId: String,
moduleName: String,
entityName: String,
): Primitive.TemplateId[T] =
Primitive.TemplateId(packageId, moduleName, entityName)
protected final def ` exercise`[ExOn, Out](
receiver: ExOn,
choiceId: String,
arguments: Option[rpcvalue.Value],
)(implicit exon: ExerciseOn[ExOn, T]): Primitive.Update[Out] =
Primitive.exercise(this, receiver, choiceId, arguments getOrElse Value.encode(()))
}

View File

@ -1,8 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.v1.commands.Command
case class DomainCommand(command: Command, template: ContractTypeCompanion[_])

View File

@ -1,41 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.v1.{value => rpcvalue}
import com.daml.ledger.client.binding.encoding.{LfEncodable, LfTypeEncoding}
import scalaz.Liskov.<~<
import scalaz.OneAnd
import scalaz.syntax.functor._
import scalaz.std.vector._
abstract class EnumCompanion[T](implicit isEnum: T <~< EnumRef) extends ValueRefCompanion {
val firstValue: T
val otherValues: Vector[T]
final def values: Vector[T] = firstValue +: otherValues
implicit final lazy val `the enum Value`: Value[T] = new `Value ValueRef`[T] {
private[this] val readers = values.map(e => (isEnum(e).constructor: String) -> e).toMap
override def read(argValue: rpcvalue.Value.Sum): Option[T] =
argValue.enum flatMap (e => readers.get(e.constructor))
private[this] val rpcValues = values.map(e => ` enum`(isEnum(e).constructor))
override def write(enumeration: T): rpcvalue.Value.Sum =
rpcValues(isEnum(enumeration).index)
}
implicit final lazy val `the enum LfEncodable`: LfEncodable[T] = new LfEncodable[T] {
private[this] val cases = OneAnd(firstValue, otherValues).map(x => isEnum(x).constructor -> x)
override def encoding(lte: LfTypeEncoding): lte.Out[T] =
lte.enumAll(` dataTypeId`, isEnum(_).index, cases)
}
}

View File

@ -1,9 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
abstract class EnumRef extends ValueRef {
val constructor: String
val index: Int
}

View File

@ -1,40 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.refinements.ApiTypes
import scala.collection.immutable.{Map, Seq}
import scalaz.Id.Id
import com.daml.ledger.api.v1.{event => rpcevent, value => rpcvalue}
abstract class EventDecoderApi(val templateTypes: Seq[TemplateCompanion[_]]) {
@SuppressWarnings(Array("org.wartremover.warts.Any"))
val decoderTable: Map[ApiTypes.TemplateId, rpcevent.CreatedEvent => Option[Template[_]]] =
templateTypes.map(_.decoderEntry).toMap
private[this] val dtl = {
type F[A] = A => Option[rpcevent.CreatedEvent => Option[Template[_]]]
ApiTypes.TemplateId.unsubst[F, rpcvalue.Identifier](decoderTable.lift)
}
@SuppressWarnings(Array("org.wartremover.warts.Any"))
final def createdEventToContractRef(
createdEvent: rpcevent.CreatedEvent
): Either[EventDecoderError, Contract.OfAny] = {
for {
templateToContract <- createdEvent.templateId flatMap dtl toRight DecoderTableLookupFailure
tadt <- templateToContract(createdEvent).toRight(
CreateEventToContractMappingError: EventDecoderError
)
} yield Contract(
Primitive.substContractId[Id, Nothing](ApiTypes.ContractId(createdEvent.contractId)),
tadt,
createdEvent.signatories,
createdEvent.observers,
createdEvent.contractKey,
)
}
}

View File

@ -1,8 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
sealed trait EventDecoderError extends Product with Serializable
case object DecoderTableLookupFailure extends EventDecoderError
case object CreateEventToContractMappingError extends EventDecoderError

View File

@ -1,39 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.refinements.ApiTypes
import scala.annotation.nowarn
/** Common superclass of interface marker types. There are no instances of
* subclasses of this class; it is strictly a marker type to aid in implicit
* resolution, and only occurs within contract IDs.
*/
abstract class Interface extends VoidValueRef
object Interface {
import Primitive.ContractId, ContractId.subst
implicit final class `interface ContractId syntax`[I](private val self: ContractId[I])
extends AnyVal {
/** Convert an interface contract ID to a template contract ID. Sometimes
* this is needed if you got an interface contract ID from a choice, but
* you need to assert that the contract ID is of a particular template
* so that you can exercise contracts on it.
*
* This checks at compile-time that `T` is in fact a template that
* implements interface `I`, but it does not check that the specific
* contract ID is actually associated with `T` on the ledger, hence the
* `unsafe` in the name.
*/
@nowarn("cat=unused&msg=parameter ev in method")
def unsafeToTemplate[T](implicit ev: Template.Implements[T, I]): ContractId[T] = {
type K[C] = C => ApiTypes.ContractId
type K2[C] = ContractId[I] => C
subst[K2, T](subst[K, I](identity))(self)
}
}
}

View File

@ -1,7 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
/** Common superclass of interface companion objects. */
abstract class InterfaceCompanion[T] extends ContractTypeCompanion[T]

View File

@ -1,337 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import java.time.{Instant, LocalDate, LocalDateTime}
import java.util.TimeZone
import com.daml.ledger.api.refinements.ApiTypes
import com.daml.ledger.api.v1.{commands => rpccmd, value => rpcvalue}
import com.daml.ledger.client.binding.encoding.ExerciseOn
import scalaz.syntax.std.boolean._
import scalaz.syntax.tag._
import scala.annotation.nowarn
import scala.collection.{Factory, mutable, immutable => imm}
sealed abstract class Primitive extends PrimitiveInstances {
type Int64 = Long
type Numeric = BigDecimal
type Party = ApiTypes.Party
val Party: ApiTypes.Party.type = ApiTypes.Party
type Text = String
/** A [[LocalDate]] in the range [0001-01-01, 9999-12-31] (`[Date.MIN,
* Date.MAX]`). This is the range that can be stored as primitive
* `Date`s on a ledger, and matches the set of representable dates in
* the [RFC-3339](https://www.ietf.org/rfc/rfc3339.txt) date-time
* format. Any [[LocalDate]] in that range can be converted to a
* [[Date]] by calling `Date.fromLocalDate`.
*/
type Date <: LocalDate
val Date: DateApi
/** An [[Instant]] with only microsecond resolution in the range
* [0001-01-01T00:00:00Z, 9999-12-31T23:59:59.999999Z] (`[Timestamp.MIN,
* Timestamp.MAX]`). Only such times can be stored as primitive `Time`s
* on the ledger. Any [[Instant]] in that range can be converted to
* a [[Timestamp]] by calling `Time.discardNanos`.
*/
type Timestamp <: Instant
val Timestamp: TimeApi
type Unit = scala.Unit
type Bool = scala.Boolean
type List[+A] = imm.Seq[A]
val List: imm.Seq.type = imm.Seq
type Optional[+A] = scala.Option[A]
val Optional: scala.Option.type = scala.Option
type TextMap[+V] <: imm.Map[String, V] with Compat.MapLike[String, V, imm.Map, TextMap[V]]
val TextMap: TextMapApi
@deprecated("Use TextMap", since = "0.13.40")
type Map[+V] = TextMap[V]
@deprecated("Use TextMap", since = "0.13.40")
val Map: TextMap.type
type GenMap[K, +V] <: imm.Map[K, V] with Compat.MapLike[K, V, GenMap, GenMap[K, V]]
val GenMap: Compat.MapFactory[GenMap]
private[binding] def substGenMap[F[_[_, _]]](tc: F[imm.Map]): F[GenMap]
type ChoiceId = ApiTypes.Choice
val ChoiceId: ApiTypes.Choice.type = ApiTypes.Choice
// abstract primitives
type ContractId[+Tpl] <: ApiTypes.ContractId
val ContractId: ContractIdApi
type TemplateId[+Tpl] <: ApiTypes.TemplateId
val TemplateId: TemplateIdApi
type Update[+A] <: DomainCommand
sealed abstract class TextMapApi {
def empty[V]: TextMap[V]
def apply[V](elems: (String, V)*): TextMap[V]
def newBuilder[V]: mutable.Builder[(String, V), TextMap[V]]
implicit def factory[V]: Factory[(String, V), TextMap[V]]
final def fromMap[V](map: imm.Map[String, V]): TextMap[V] = leibniz[V](map)
def subst[F[_[_]]](fa: F[imm.Map[String, *]]): F[TextMap]
final def leibniz[V]: imm.Map[String, V] =:= TextMap[V] =
subst[Lambda[g[_] => imm.Map[String, V] =:= g[V]]](
implicitly[imm.Map[String, V] =:= imm.Map[String, V]]
)
}
sealed abstract class DateApi {
val MIN: Date
val MAX: Date
/** Narrow `ld` if it's in the `[MIN, MAX]` range, `None` otherwise. */
def fromLocalDate(ld: LocalDate): Option[Date]
// bypass the value test
private[binding] def subst[F[_]](tc: F[LocalDate]): F[Date]
}
sealed abstract class TimeApi {
val MIN: Timestamp
val MAX: Timestamp
/** Reduce `t`'s resolution to exclude nanoseconds; return `None` if outside
* the `[MIN, MAX]` range, the reduced-resolution value
* otherwise.
*/
def discardNanos(t: Instant): Option[Timestamp]
// bypass the value test
private[binding] def subst[F[_]](tc: F[Instant]): F[Timestamp]
}
sealed abstract class ContractIdApi {
def apply[Tpl](contractId: String): ContractId[Tpl]
def subst[F[_], Tpl](tc: F[ApiTypes.ContractId]): F[ContractId[Tpl]]
}
sealed abstract class TemplateIdApi {
// the sole source of valid Template-associated template IDs is
// their codegenned companions
def apply[Tpl <: Template[Tpl]](
packageId: String,
moduleName: String,
entityName: String,
): TemplateId[Tpl]
private[binding] def substEx[F[_]](fa: F[rpcvalue.Identifier]): F[TemplateId[_]]
/** Package ID, module name, and entity name.
*/
def unapply[Tpl](t: TemplateId[Tpl]): Option[(String, String, String)]
}
private[binding] def substContractId[F[_], Tpl](tc: F[ApiTypes.ContractId]): F[ContractId[Tpl]]
private[binding] def createFromArgs[Tpl](
companion: TemplateCompanion[_ <: Tpl],
na: rpcvalue.Record,
): Update[ContractId[Tpl]]
private[binding] def exercise[ExOn, Tpl, Out](
templateCompanion: ContractTypeCompanion[Tpl],
receiver: ExOn,
choiceId: String,
argument: rpcvalue.Value,
)(implicit ev: ExerciseOn[ExOn, Tpl]): Update[Out]
private[binding] def arguments(
recordId: rpcvalue.Identifier,
args: Seq[(String, rpcvalue.Value)],
): rpcvalue.Record
}
private[client] object OnlyPrimitive extends Primitive {
type Date = LocalDate
type Timestamp = Instant
type ContractId[+Tpl] = ApiTypes.ContractId
type TemplateId[+Tpl] = ApiTypes.TemplateId
type Update[+A] = DomainCommand
type TextMap[+V] = imm.Map[String, V]
object TextMap extends TextMapApi {
override def empty[V]: TextMap[V] = imm.Map.empty
override def apply[V](elems: (String, V)*): TextMap[V] = imm.Map(elems: _*)
override def newBuilder[V]: mutable.Builder[(String, V), TextMap[V]] = imm.Map.newBuilder
override def factory[V]: Factory[(String, V), TextMap[V]] =
implicitly[Factory[(String, V), imm.Map[String, V]]]
override def subst[F[_[_]]](fa: F[TextMap]): F[TextMap] = fa
}
override val Map = TextMap
type GenMap[K, +V] = imm.Map[K, V]
override val GenMap = imm.Map
private[binding] override def substGenMap[F[_[_, _]]](tc: F[GenMap]) = tc
object Date extends DateApi {
import com.daml.api.util.TimestampConversion
private val UTC = TimeZone.getTimeZone("UTC")
override val MIN = LocalDateTime.ofInstant(TimestampConversion.MIN, UTC.toZoneId).toLocalDate
override val MAX = LocalDateTime.ofInstant(TimestampConversion.MAX, UTC.toZoneId).toLocalDate
override def fromLocalDate(ld: LocalDate) = {
import scala.math.Ordering.Implicits._
val ldc: java.time.chrono.ChronoLocalDate = ld
(ldc >= MIN && ldc <= MAX) option ld
}
private[binding] override def subst[F[_]](tc: F[LocalDate]) = tc
}
object Timestamp extends TimeApi {
import com.daml.api.util.TimestampConversion
override val MIN = TimestampConversion.MIN
override val MAX = TimestampConversion.MAX
override def discardNanos(t: Instant) = {
import scala.math.Ordering.Implicits._
(t >= MIN && t <= MAX) option (t truncatedTo java.time.temporal.ChronoUnit.MICROS)
}
private[binding] override def subst[F[_]](tc: F[Instant]) = tc
}
object TemplateId extends TemplateIdApi {
// the ledger api still uses names with only dots in them, while QualifiedName.toString
// separates the module and the name in the module with colon.
override def apply[Tpl <: Template[Tpl]](
packageId: String,
moduleName: String,
entityName: String,
): TemplateId[Tpl] =
ApiTypes.TemplateId(
rpcvalue
.Identifier(packageId = packageId, moduleName = moduleName, entityName = entityName)
)
private[binding] override def substEx[F[_]](fa: F[rpcvalue.Identifier]) =
ApiTypes.TemplateId subst fa
override def unapply[Tpl](t: TemplateId[Tpl]): Some[(String, String, String)] = {
val rpcvalue.Identifier(packageId, moduleName, entityName) = t.unwrap
Some((packageId, moduleName, entityName))
}
}
object ContractId extends ContractIdApi {
override def apply[Tpl](contractId: String) =
ApiTypes.ContractId(contractId)
override def subst[F[_], Tpl](tc: F[ApiTypes.ContractId]): F[ContractId[Tpl]] = tc
}
private[binding] override def substContractId[F[_], Tpl](
tc: F[ApiTypes.ContractId]
): F[ContractId[Tpl]] = tc
private[binding] override def createFromArgs[Tpl](
companion: TemplateCompanion[_ <: Tpl],
na: rpcvalue.Record,
): Update[ContractId[Tpl]] =
DomainCommand(
rpccmd.Command(
rpccmd.Command.Command
.Create(rpccmd.CreateCommand(templateId = Some(companion.id.unwrap), Some(na)))
),
companion,
)
private[binding] override def exercise[ExOn, Tpl, Out](
exerciseTarget: ContractTypeCompanion[Tpl],
receiver: ExOn,
choiceId: String,
argument: rpcvalue.Value,
)(implicit ev: ExerciseOn[ExOn, Tpl]): Update[Out] =
DomainCommand(
rpccmd.Command {
ev match {
case _: ExerciseOn.OnId[Tpl] =>
rpccmd.Command.Command.Exercise(
rpccmd.ExerciseCommand(
templateId = Some(exerciseTarget.id.unwrap),
contractId = (receiver: ContractId[Tpl]).unwrap,
choice = choiceId,
choiceArgument = Some(argument),
)
)
case _: ExerciseOn.CreateAndOnTemplate[Tpl] =>
val cfe: Template.CreateForExercise[Tpl] = receiver
rpccmd.Command.Command.CreateAndExercise(
// TODO #13993 pass exerciseTarget.id.unwrap as interface ID
rpccmd.CreateAndExerciseCommand(
templateId = Some(cfe.value.templateId.unwrap),
createArguments = Some(cfe.value.arguments),
choice = choiceId,
choiceArgument = Some(argument),
)
)
case _: ExerciseOn.OnKey[Tpl] =>
val k: Template.Key[Tpl] = receiver
// TODO #13993 pass exerciseTarget.id.unwrap as interface ID
rpccmd.Command.Command.ExerciseByKey(
rpccmd.ExerciseByKeyCommand(
templateId = Some(k.origin.id.unwrap),
contractKey = Some(k.encodedKey),
choice = choiceId,
choiceArgument = Some(argument),
)
)
}
},
exerciseTarget,
)
private[binding] override def arguments(
recordId: rpcvalue.Identifier,
args: Seq[(String, rpcvalue.Value)],
): rpcvalue.Record =
rpcvalue.Record(
recordId = Some(recordId),
args.map { case (k, v) =>
rpcvalue.RecordField(k, Some(v))
},
)
}
sealed abstract class PrimitiveInstances
// do not import this._, use -Xsource:2.13 scalac option instead
object PrimitiveInstances {
import Primitive.{GenMap, TextMap}
import language.implicitConversions
implicit def textMapFactory[V]: Factory[(String, V), TextMap[V]] =
TextMap.factory
implicit def genMapFactory[K, V]: Factory[(K, V), GenMap[K, V]] = {
type CBF[M[_, _]] = Factory[(K, V), M[K, V]]
@nowarn("msg=local val genMapFactory in method genMapFactory is never used")
val genMapFactory = () // prevent recursion
GenMap.subst[CBF](implicitly[CBF[imm.Map]])
}
/** Applied in contexts that ''expect'' a `TextMap`, iff -Xsource:2.13. */
implicit def textMapFromMap[V](m: imm.Map[String, V]): TextMap[V] = TextMap fromMap m
/** Applied in contexts that ''expect'' a `GenMap`, iff -Xsource:2.13. */
implicit def genMapFromMap[K, V](m: imm.Map[K, V]): GenMap[K, V] = {
type Id2[M[_, _]] = M[K, V]
GenMap.subst[Id2](m)
}
implicit final class GenMapCompanionMethods(private val self: GenMap.type) extends AnyVal {
private[binding] def subst[F[_[_, _]]](tc: F[imm.Map]): F[GenMap] =
Primitive substGenMap tc
}
}

View File

@ -1,108 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.refinements.ApiTypes
import ApiTypes.Choice
import com.daml.ledger.api.v1.{value => rpcvalue}
import scala.annotation.{nowarn, implicitNotFound}
abstract class Template[+T] extends ValueRef { self: T =>
final def create(implicit d: DummyImplicit): Primitive.Update[Primitive.ContractId[T]] =
Primitive.createFromArgs(templateCompanion, templateCompanion.toNamedArguments(self))
/** Part of a `CreateAndExercise` command.
*
* {{{
* Iou(foo, bar).createAnd.exerciseTransfer(controller, ...)
* }}}
*/
final def createAnd(implicit d: DummyImplicit): Template.CreateForExercise[T] =
Template.CreateForExercise(self)
final def arguments(implicit d: DummyImplicit): rpcvalue.Record =
templateCompanion.toNamedArguments(self)
final def templateId(implicit d: DummyImplicit): Primitive.TemplateId[T] =
templateCompanion.id
final def consumingChoices(implicit d: DummyImplicit): Set[Choice] =
templateCompanion.consumingChoices
// arguments and templateId are provided in lieu of making templateCompanion
// public, though the latter might be more "powerful"
protected[this] def templateCompanion(implicit
d: DummyImplicit
): TemplateCompanion[_ >: self.type <: T]
}
object Template {
/** Part of a `CreateAndExercise` command.
*
* {{{
* Iou(foo, bar).createAnd.exerciseTransfer(controller, ...)
* }}}
*/
final case class CreateForExercise[+T](
private[binding] val value: Template[_]
) {
/** Get access to interface choices.
*
* {{{
* MyTemplate(foo, bar).createAnd
* .toInterface[MyInterface]
* .exerciseInterfaceChoice(controller, ...)
* }}}
*/
@nowarn("cat=unused&msg=parameter ev in method")
def toInterface[I](implicit ev: ToInterface[T, I]): CreateForExercise[I] =
CreateForExercise(value)
}
/** Part of an `ExerciseByKey` command.
*
* {{{
* Iou.key(foo).exerciseTransfer(controller, ...)
* }}}
*/
final case class Key[+T](
private[binding] val encodedKey: rpcvalue.Value,
private[binding] val origin: TemplateCompanion[_],
)
/** Evidence that coercing from template-IDed to interface-IDed is sound,
* i.e. `toInterface`. Not safe at all for the opposite coercion.
*
* This weaker marker is defined so that [[Key]] and [[CreateForExercise]] can
* have simple `toInterface` methods.
*/
@implicitNotFound("${T} is not a template that implements interface ${I}")
sealed abstract class ToInterface[-T, I]
/** As with [[ToInterface]], but also proves that `T` implements `I`.
* This is a subtle distinction, but [[ToInterface]] allows a universe of
* subtypes for which this would not be true. So this can be used for
* `unsafeToTemplate` as well as `toInterface`.
*/
@implicitNotFound("${T} is not a template that implements interface ${I}")
final class Implements[T, I] extends ToInterface[T, I]
import Primitive.ContractId, ContractId.subst
implicit final class `template ContractId syntax`[T](private val self: ContractId[T])
extends AnyVal {
/** Widen a contract ID to the same contract ID for one of `T`'s implemented
* interfaces. Do this to get access to the interface choices.
*/
@nowarn("cat=unused&msg=parameter ev in method")
def toInterface[I](implicit ev: ToInterface[T, I]): ContractId[I] = {
type K[C] = C => ApiTypes.ContractId
type K2[C] = ContractId[T] => C
subst[K2, I](subst[K, T](identity))(self)
}
}
}

View File

@ -1,79 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.refinements.ApiTypes.Choice
import com.daml.ledger.api.v1.{event => rpcevent, value => rpcvalue}
import rpcvalue.Value.{Sum => VSum}
import encoding.{LfEncodable, LfTypeEncoding, RecordView}
/** Common superclass of template classes' companions.
*
* @tparam T The companion class's type. We can get away with this here, but
* not for [[ValueRefCompanion]], because templates' associated
* types are guaranteed to have zero tparams.
*/
abstract class TemplateCompanion[T](implicit isTemplate: T <:< Template[T])
extends ContractTypeCompanion[T]
with LfEncodable.ViaFields[T] {
val consumingChoices: Set[Choice]
/** Permits TemplateCompanion to be optionally treated as a typeclass for
* template types.
*/
implicit final def `the TemplateCompanion`: this.type = this
/** The template's key type, or [[Nothing]] if there is no key type. */
type key
/** Prepare an exercise-by-key Update. */
final def key(k: key)(implicit enc: ValueEncoder[key]): Template.Key[T] =
Template.Key(Value encode k, this)
/** Proof that T <: Template[T]. Expressed here instead of as a type parameter
* bound because the latter is much more inconvenient in practice.
*/
val describesTemplate: T <:< Template[T] = isTemplate
def toNamedArguments(associatedType: T): rpcvalue.Record
def fromNamedArguments(namedArguments: rpcvalue.Record): Option[T]
implicit val `the template Value`: Value[T] = new `Value ValueRef`[T] {
override def read(argumentValue: VSum): Option[T] =
argumentValue.record flatMap fromNamedArguments
override def write(obj: T): VSum = VSum.Record(toNamedArguments(obj))
}
implicit val `the template LfEncodable`: LfEncodable[T] = new LfEncodable[T] {
override def encoding(lte: LfTypeEncoding): lte.Out[T] =
TemplateCompanion.this.encoding(lte)(TemplateCompanion.this.fieldEncoding(lte))
}
/** Helper for EventDecoderApi. */
private[binding] final def decoderEntry
: (Primitive.TemplateId[T], rpcevent.CreatedEvent => Option[Template[T]]) = {
type K[+A] = (Primitive.TemplateId[T], rpcevent.CreatedEvent => Option[A])
describesTemplate.substituteCo[K](
(id, _.createArguments flatMap fromNamedArguments)
)
}
protected final def ` arguments`(elems: (String, rpcvalue.Value)*): rpcvalue.Record =
Primitive.arguments(` dataTypeId`, elems)
}
object TemplateCompanion {
abstract class Empty[T](implicit isTemplate: T <:< Template[T]) extends TemplateCompanion[T] {
protected def onlyInstance: T
override type key = Nothing
type view[C[_]] = RecordView.Empty[C]
override def toNamedArguments(associatedType: T) = ` arguments`()
override def fromNamedArguments(namedArguments: rpcvalue.Record): Option[T] = Some(onlyInstance)
override def fieldEncoding(lte: LfTypeEncoding): view[lte.Field] = RecordView.Empty
override def encoding(lte: LfTypeEncoding)(view: view[lte.Field]): lte.Out[T] =
lte.emptyRecord(` dataTypeId`, () => onlyInstance)
}
}

View File

@ -1,232 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.refinements.ApiTypes.{ContractId, Party}
import com.daml.ledger.api.v1.value.Value.{Sum => VSum}
import com.daml.ledger.api.v1.{value => rpcvalue}
import com.daml.ledger.client.binding.{Primitive => P}
import com.daml.lf.{data => lf}
import scalaz.std.option._
import scalaz.syntax.traverse._
import scala.annotation.tailrec
import scala.{specialized => sp}
sealed trait DamlCodecs // always include `object DamlCodecs` in implicit search
// Should be covariant, but that prevents P.List resolution from happening (see
// test "be found for lists" in ValueSpec)
sealed trait ValueDecoder[@sp(Long) A] extends DamlCodecs {
def read(argumentValue: VSum): Option[A]
}
sealed trait ValueEncoder[@sp(Long) -A] extends DamlCodecs {
def write(obj: A): VSum
}
/** Typeclass of "serializable" types as defined by the LF specification.
*
* @tparam A Scala representation for some Daml ''serializable''
* type. Specialized to match [[Primitive]].
*/
sealed trait Value[@sp(Long) A] extends ValueDecoder[A] with ValueEncoder[A]
object Value {
// Replace all three with imp if available.
@inline def apply[@sp(Long) A](implicit A: Value[A]): Value[A] = A
@inline def Decoder[@sp(Long) A](implicit A: ValueDecoder[A]): ValueDecoder[A] = A
@inline def Encoder[@sp(Long) A](implicit A: ValueEncoder[A]): ValueEncoder[A] = A
@inline def decode[@sp(Long) A](a: rpcvalue.Value)(implicit A: ValueDecoder[A]): Option[A] =
A read a.sum
@inline def encode[@sp(Long) A](a: A)(implicit A: ValueEncoder[A]): rpcvalue.Value =
rpcvalue.Value(A write a)
// TODO remove this suppression, sadly now we do inherit from `Value ValueRef`
@SuppressWarnings(Array("org.wartremover.warts.LeakingSealed"))
private[binding] trait InternalImpl[A] extends Value[A]
private[binding] def splattedVariantId(
baseVariantId: rpcvalue.Identifier,
caseName: String,
): rpcvalue.Identifier =
baseVariantId copy (entityName = s"${baseVariantId.entityName}.$caseName")
}
object DamlCodecs extends encoding.ValuePrimitiveEncoding[Value] {
@inline private[this] def fromArgumentValueFuns[@sp(Long) A](
_read: VSum => Option[A],
_write: A => VSum,
): Value[A] =
new Value[A] {
override def read(argumentValue: VSum): Option[A] = _read(argumentValue)
override def write(obj: A): VSum = _write(obj)
}
implicit override val valueInt64: Value[P.Int64] = new Value[P.Int64] {
override def read(argumentValue: VSum): Option[P.Int64] =
argumentValue.int64
override def write(obj: P.Int64): VSum = VSum.Int64(obj)
}
implicit override val valueNumeric: Value[P.Numeric] =
fromArgumentValueFuns(
_.numeric flatMap (lf.Numeric.fromString(_).toOption.map(BigDecimal(_))),
bd => VSum.Numeric(lf.Numeric.toString(bd.bigDecimal)),
)
implicit override val valueParty: Value[P.Party] =
Party.subst(fromArgumentValueFuns(_.party, VSum.Party))
implicit override val valueText: Value[P.Text] =
fromArgumentValueFuns(_.text, VSum.Text)
implicit val valueDate: Value[P.Date] = P.Date.subst {
import java.time.LocalDate
fromArgumentValueFuns(
_.date flatMap (i => P.Date.fromLocalDate(LocalDate ofEpochDay i.toLong)),
ld => VSum.Date(ld.toEpochDay.toInt),
)
}
implicit override val valueTimestamp: Value[P.Timestamp] = P.Timestamp.subst {
import com.daml.api.util.TimestampConversion.{instantToMicros, microsToInstant}
fromArgumentValueFuns(
{
case ts @ VSum.Timestamp(_) => Some(microsToInstant(ts))
case _ => None
},
instantToMicros,
)
}
implicit override val valueUnit: Value[P.Unit] = {
val decoded = Some(())
val encoded = VSum.Unit(com.google.protobuf.empty.Empty())
fromArgumentValueFuns(_ => decoded, _ => encoded)
}
implicit override val valueBool: Value[P.Bool] =
fromArgumentValueFuns(_.bool, VSum.Bool)
private[this] def seqAlterTraverse[A, B, That](
xs: Iterable[A]
)(f: A => Option[B])(implicit factory: collection.Factory[B, That]): Option[That] = {
val bs = factory.newBuilder
val i = xs.iterator
@tailrec def go(): Option[That] =
if (i.hasNext) f(i.next()) match {
case Some(b) =>
bs += b
go()
case None => None
}
else Some(bs.result())
go()
}
implicit override def valueList[A](implicit A: Value[A]): Value[P.List[A]] =
fromArgumentValueFuns(
_.list flatMap (gl => seqAlterTraverse(gl.elements)(Value.decode[A](_))),
as => VSum.List(rpcvalue.List(as map (Value.encode(_)))),
)
implicit override def valueContractId[A]: Value[P.ContractId[A]] =
Primitive.substContractId(
ContractId.subst(fromArgumentValueFuns(_.contractId, VSum.ContractId))
)
implicit override def valueOptional[A](implicit A: Value[A]): Value[P.Optional[A]] =
fromArgumentValueFuns(
_.optional flatMap (_.value traverse (Value.decode[A](_))),
oa => VSum.Optional(rpcvalue.Optional(oa map (Value.encode(_)))),
)
implicit override def valueTextMap[A](implicit A: Value[A]): Value[P.TextMap[A]] =
fromArgumentValueFuns(
_.map.flatMap(gm =>
seqAlterTraverse(gm.entries)(e => e.value.flatMap(Value.decode[A](_)).map(e.key -> _))
),
oa =>
VSum.Map(rpcvalue.Map(oa.map { case (key, value) =>
rpcvalue.Map.Entry(
key = key,
value = Some(Value.encode(value)),
)
}.toSeq)),
)
implicit override def valueGenMap[K, V](implicit
K: Value[K],
V: Value[V],
): Value[P.GenMap[K, V]] =
fromArgumentValueFuns(
_.genMap.flatMap(gm =>
seqAlterTraverse(gm.entries)(e =>
for {
optK <- e.key
k <- Value.decode[K](optK)
optV <- e.value
v <- Value.decode[V](optV)
} yield k -> v
)
),
oa =>
VSum.GenMap(rpcvalue.GenMap(oa.map { case (key, value) =>
rpcvalue.GenMap.Entry(
key = Some(Value.encode(key)),
value = Some(Value.encode(value)),
)
}.toSeq)),
)
}
/** Common superclass of record and variant classes' companions. */
abstract class ValueRefCompanion {
// This class is not generally safe for generic records/variants,
// so is hidden. It's the only externally visible opening in the
// Value typeclass.
//
// TODO remove this suppression, sadly now we do inherit from `Value ValueRef`
@SuppressWarnings(Array("org.wartremover.warts.LeakingSealed"))
protected abstract class `Value ValueRef`[A] extends Value[A]
protected val ` dataTypeId`: rpcvalue.Identifier
// recordId and variantId are optional when submitting commands, according to
// value.proto
protected final def ` mkDataTypeId`(
packageId: String,
moduleName: String,
entityName: String,
): rpcvalue.Identifier =
rpcvalue.Identifier(packageId = packageId, moduleName = moduleName, entityName = entityName)
protected final def ` record`(elements: (String, rpcvalue.Value)*): VSum.Record =
VSum.Record(Primitive.arguments(` dataTypeId`, elements))
protected final def ` variant`(constructor: String, value: rpcvalue.Value): VSum.Variant =
VSum.Variant(
rpcvalue
.Variant(variantId = Some(` dataTypeId`), constructor = constructor, Some(value))
)
protected final def ` enum`(constructor: String): VSum.Enum =
VSum.Enum(rpcvalue.Enum(enumId = Some(` dataTypeId`), constructor))
protected final def ` createVariantOfSynthRecord`(
k: String,
o: (String, rpcvalue.Value)*
): VSum.Variant =
` variant`(
k,
rpcvalue.Value(VSum.Record(Primitive.arguments(Value.splattedVariantId(` dataTypeId`, k), o))),
)
}

View File

@ -1,6 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
abstract class ValueRef extends Product with Serializable

View File

@ -1,23 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
import com.daml.ledger.api.v1.value.Value.{Sum => VSum}
abstract class VoidValueRef extends ValueRef {
val cannotExist: Nothing
}
object VoidValueRef {
/** Automatically provides the [[Value]] instance for all subclasses of
* [[VoidValueRef]], which are produced by codegen for zero-constructor
* variants.
*/
implicit def `VoidValueRef Value`[A <: VoidValueRef]: Value[A] =
new Value.InternalImpl[A] {
override def read(argumentValue: VSum): Option[A] = None
override def write(obj: A): VSum = obj.cannotExist
}
}

View File

@ -1,26 +0,0 @@
// Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.client.binding
package encoding
import Primitive.ContractId
import Template.CreateForExercise
import scala.annotation.implicitNotFound
@implicitNotFound(
"""Cannot decide how to exercise a choice on ${Self}; only well-typed contract IDs, Templates (.createAnd), and keys (TemplateType key k) are candidates for choice exercise"""
)
sealed abstract class ExerciseOn[-Self, Tpl]
object ExerciseOn {
implicit def OnId[T]: ExerciseOn[ContractId[T], T] = new OnId
implicit def CreateAndOnTemplate[T]: ExerciseOn[CreateForExercise[T], T] =
new CreateAndOnTemplate
implicit def OnKey[T]: ExerciseOn[Template.Key[T], T] = new OnKey
private[binding] final class OnId[T] extends ExerciseOn[ContractId[T], T]
private[binding] final class CreateAndOnTemplate[T] extends ExerciseOn[CreateForExercise[T], T]
private[binding] final class OnKey[T] extends ExerciseOn[Template.Key[T], T]
}

Some files were not shown because too many files have changed in this diff Show More