Remove ledger api test tool (#18088)

* Remove ledger api test tool

* Force full compat

run-full-compat: true

* Force run all tests

run-all-tests: true

---------

Co-authored-by: Paul Brauner <paul.brauner@digitalasset.com>
This commit is contained in:
mziolekda 2024-01-10 17:48:12 +01:00 committed by GitHub
parent bef1938a21
commit 36fe0abb09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
156 changed files with 20 additions and 25724 deletions

View File

@ -46,10 +46,6 @@ NOTICES @garyverhaegen-da @dasormeter
# Owned by KV Participant with KV Committer added for notifications
/ledger/ledger-api-common/ @digital-asset/kv-participant @digital-asset/kv-committer
/ledger/ledger-api-domain/ @digital-asset/kv-participant @digital-asset/kv-committer
/ledger-test-tool/ @digital-asset/kv-participant @digital-asset/kv-committer
# Conformance test on canton
/ledger-test-tool/ledger-api-test-tool-on-canton @remyhaemmerle-da @rgugliel-da @digital-asset/kv-participant @digital-asset/kv-committer
# Ecosystems
/language-support/hs/ @remyhaemmerle-da

View File

@ -25,20 +25,6 @@ Since running all tests can be rather slow, we run them in a daily
cron job. On each PR we only include HEAD and the latest stable
release.
#### Cross-version compatibility between ledger-api-test-tool
We test that the `ledger-api-test-tool` of a given version passes
against Sandbox next and classic of another version. We test all
possible version combinations here to ensure forwards and backwards
compatibility. The `ledger-api-test-tool` includes a DAR built using a
compiler from the same SDK version so this also ensures that sandbox
can load a DAR from a different SDK version. We test both in-memory
backends and postgresql backends.
Since all our JVM ledger clients use the same client libraries we
consider the `ledger-api-test-tool` to be a good proxy and if things
are not covered it should be extended.
#### Data-continuity for Sandbox classic
We have migration tests that work as follows:
@ -86,5 +72,4 @@ We test that we can run the create-daml-app tests with JS client
libraries and codegen from one version against the JSON API and
Sandbox from another version. We test all version combinations
here. Currently we do not test different versions of the JSON API and
Sandbox. This should be covered by the `ledger-api-test-tool` tests
since the JSON API uses the same client libraries.
Sandbox.

View File

@ -295,7 +295,6 @@ daml_sdk_head(
daml_ledger_tarball = "@head_sdk//:daml-ledger-0.0.0.tgz",
daml_react_tarball = "@head_sdk//:daml-react-0.0.0.tgz",
daml_types_tarball = "@head_sdk//:daml-types-0.0.0.tgz",
ledger_api_test_tool = "@head_sdk//:ledger-api-test-tool_distribute.jar",
os_name = os_name,
sdk_tarball = "@head_sdk//:sdk-release-tarball-ce.tar.gz",
)
@ -310,7 +309,6 @@ daml_sdk_head(
daml_types_sha256 = version_sha256s.get(ver).get("daml_types"),
os_name = os_name,
sdk_sha256 = version_sha256s.get(ver),
test_tool_sha256 = version_sha256s.get(ver).get("test_tool"),
version = ver,
)
for ver in sdk_versions

View File

@ -55,41 +55,21 @@ def _daml_sdk_impl(ctx):
else:
fail("Must specify either sdk_tarball or sdk_sha256")
if ctx.attr.test_tool:
ctx.symlink(ctx.attr.test_tool, "ledger-api-test-tool.jar")
elif ctx.attr.test_tool_sha256:
ctx.download(
output = "ledger-api-test-tool.jar",
url = ["{mirror}/com/daml/ledger-api-test-tool/{version}/ledger-api-test-tool-{version}.jar".format(mirror = mirror, version = ctx.attr.version) for mirror in default_maven_server_urls()],
sha256 = ctx.attr.test_tool_sha256,
)
else:
fail("Must specify either test_tool or test_tool_sha256")
if versions.is_at_least("2.6.0", ctx.attr.version):
stripPrefix = "test-common"
else:
stripPrefix = "ledger/test-common"
ctx.extract(
"ledger-api-test-tool.jar",
output = "extracted-test-tool",
# We cannot fully extract the JAR because there are files
# that clash on case insensitive file systems. Luckily, we only
# need the DAR so this is not an issue.
stripPrefix = stripPrefix,
)
if ctx.attr.create_daml_app_patch:
ctx.symlink(ctx.attr.create_daml_app_patch, "create_daml_app.patch")
elif ctx.attr.test_tool_sha256:
elif ctx.attr.create_daml_app_patch_sha256:
ctx.download(
output = "create_daml_app.patch",
url = "https://raw.githubusercontent.com/digital-asset/daml/v{}/templates/create-daml-app-test-resources/messaging.patch".format(ctx.attr.version),
sha256 = ctx.attr.create_daml_app_patch_sha256,
)
else:
fail("Must specify either test_tool or test_tool_sha256")
fail("Must specify either create_daml_app_patch or create_daml_app_patch_sha256")
for lib in ["types", "ledger", "react"]:
tarball_name = "daml_{}_tarball".format(lib)
@ -114,14 +94,6 @@ auto-install: false
update-check: never
""",
)
ctx.file(
"ledger-api-test-tool.sh",
content =
"""#!/usr/bin/env bash
{runfiles_library}
$JAVA_HOME/bin/java -jar $(rlocation daml-sdk-{version}/ledger-api-test-tool.jar) $@
""".format(version = ctx.attr.version, runfiles_library = runfiles_library),
)
# Depending on all files as runfiles results in thousands of symlinks
# which eventually results in us running out of inodes on CI.
@ -156,24 +128,12 @@ $(rlocation daml-sdk-{version}/sdk/bin/daml) $@
content =
"""
package(default_visibility = ["//visibility:public"])
sh_binary(
name = "ledger-api-test-tool",
srcs = [":ledger-api-test-tool.sh"],
data = [":ledger-api-test-tool.jar"],
deps = ["@bazel_tools//tools/bash/runfiles"],
)
cc_binary(
name = "daml",
srcs = ["daml.cc"],
data = [":sdk/bin/daml", ":sdk/sdk/{version}/checksums"],
deps = ["@bazel_tools//tools/cpp/runfiles:runfiles"],
)
# Needed to provide the same set of DARs to the ledger that
# are used by the ledger API test tool.
filegroup(
name = "dar-files",
srcs = glob(["extracted-test-tool/**/*.dar"], exclude = ["**/*-dev.dar"]),
)
exports_files(["daml-types.tgz", "daml-ledger.tgz", "daml-react.tgz", "create_daml_app.patch"])
""".format(version = ctx.attr.version),
)
@ -186,8 +146,6 @@ _daml_sdk = repository_rule(
"os_name": attr.string(mandatory = False, default = os_name),
"sdk_sha256": attr.string_dict(mandatory = False),
"sdk_tarball": attr.label(allow_single_file = True, mandatory = False),
"test_tool_sha256": attr.string(mandatory = False),
"test_tool": attr.label(allow_single_file = True, mandatory = False),
"daml_types_tarball": attr.label(allow_single_file = True, mandatory = False),
"daml_ledger_tarball": attr.label(allow_single_file = True, mandatory = False),
"daml_react_tarball": attr.label(allow_single_file = True, mandatory = False),
@ -206,13 +164,12 @@ def daml_sdk(version, **kwargs):
**kwargs
)
def daml_sdk_head(sdk_tarball, ledger_api_test_tool, daml_types_tarball, daml_ledger_tarball, daml_react_tarball, create_daml_app_patch, **kwargs):
def daml_sdk_head(sdk_tarball, daml_types_tarball, daml_ledger_tarball, daml_react_tarball, create_daml_app_patch, **kwargs):
version = "0.0.0"
_daml_sdk(
name = "daml-sdk-{}".format(version),
version = version,
sdk_tarball = sdk_tarball,
test_tool = ledger_api_test_tool,
daml_types_tarball = daml_types_tarball,
daml_ledger_tarball = daml_ledger_tarball,
daml_react_tarball = daml_react_tarball,

View File

@ -9,728 +9,6 @@ load("@os_info//:os_info.bzl", "is_linux", "is_windows")
load("//bazel_tools:versions.bzl", "version_to_name", "versions")
load("//:versions.bzl", "latest_stable_version")
# Each exclusion in the list below is defined in terms of:
#
# - A range of ledger API test tool versions described by `start` and `end` (both inclusive).
# - A list of platform (ledger) ranges, each described by `start` and `end` (both inclusive),
# as well as the actual list of `--exclude` flags to be passed to the ledger API test tool.
#
# The "0.0.0" special version corresponds the current HEAD and is considered greater than
# all other versions. Also, HEAD corresponds to different commits in different CI runs:
#
# - In a PR, HEAD is the result of merging the latest PR commit with the tip of `main`
# at the time the build starts.
# - In a nightly run, HEAD is the tip of `main` at the time the build starts.
#
# Either or both `start` and `end` can be omitted and, if present, are always inclusive.
# An interval extreme can be excluded by setting it to a non-existing version that is
# guaranteed to be between two existing ones, according to version ordering. This is
# especially useful to denote the upcoming (yet unknown) release; for example, if the
# current release is `1.17.0-snapshot.20210811.7565.0.f1a55aa4`, then
# `1.17.0-snapshot.20210811.7565.1` will be greater than the current release but
# smaller than HEAD and the upcoming release.
#
# Here are some change types that require adding exclusions:
#
# 1. A platform feature is added and new tests for it are provided that make the new
# ledger API test tool incompatible with previous platforms, hence, the new tests
# should be excluded for ledger API test tool versions greater than the current
# release but less than HEAD and the upcoming release (i.e., start = last release
# excluded) on all platforms up to and including the last release (i.e., end = last
# release included).
# 2. An implementation-specific behavior is changed in a not backwards compatible
# way, together with its accompanying implementation-specific API-level tests,
# hence, the new ledger API test tool is incompatible with all released platforms
# and the new platform is incompatible with all released ledger API tests.
# This case requires, for the changed tests, both the exclusion above and its
# dual (i.e., excluding such tests on ledger API test tool versions up to and
# including the latest release, against platform versions greater than the
# current release but less than HEAD and the next release).
#
# Finally, note that before 1.3 the granularity for disabling tests
# was sadly quite coarse. See
# https://discuss.daml.com/t/can-i-disable-individual-tests-in-the-ledger-api-test-tool/226
# for details.
#
# PRs that resulted in exclusions:
# - ContractKeysIT:
# - https://github.com/digital-asset/daml/pull/5608
# - https://github.com/digital-asset/daml/pull/7829
# - https://github.com/digital-asset/daml/pull/9218
# - https://github.com/digital-asset/daml/pull/15131
# - ContractKeysSubmitterIsMaintainerIT:
# - https://github.com/digital-asset/daml/pull/5611
# - SemanticTests:
# - https://github.com/digital-asset/daml/pull/9218
# - DeeplyNestedValueIT
# - https://github.com/digital-asset/daml/pull/10393
# - https://github.com/digital-asset/daml/pull/17241
# - KVCommandDeduplicationIT (only some test cases):
# - https://github.com/digital-asset/daml/pull/11095
# - CommandDeduplicationIT:CDDeduplicateSubmitterBasic (fixed in https://github.com/digital-asset/daml/pull/11095):
# - https://github.com/digital-asset/daml/pull/11141
first_granular_test_tool = "1.3.0-snapshot.20200623.4546.0.4f68cfc4"
# Some of gRPC error codes changed from INVALID_ARGUMENT to ABORTED
# See https://github.com/digital-asset/daml/pull/9218
after_grpc_error_code_breaking_change = "1.12.0-snapshot.20210323.6567.1.90c5ce70"
grpc_error_code_breaking_change_exclusions = [
"SemanticTests:SemanticDoubleSpendShared",
"SemanticTests:SemanticPrivacyProjections",
"SemanticTests:SemanticDivulgence",
"ContractKeysIT:CKFetchOrLookup",
"ContractKeysIT:CKNoFetchUndisclosed",
"ContractKeysIT:CKMaintainerScoped",
]
before_removing_legacy_error_codes = "2.0.0-snapshot.20220127.9042.0.4038d0a7"
after_removing_legacy_error_codes = "2.0.0-snapshot.20220127.9042.0.4038d0a7.1"
first_canton_in_ledger_api_tests = "2.7.0-snapshot.20230504.11748.0.af51d660"
excluded_test_tool_tests = [
{
# We drop visibily restrictions about local contract key in
# https://github.com/digital-asset/daml/pull/15131
# Since this change does not raise any obvious security problems, it
# seems superfluous to continue checking that old SDKs enforce the
# restriction. So we disable completely the test.
"start": "1.13.0-snapshot.20210419.6730.1.8c3a8c04",
"end": "2.6.0-snapshot.20221226.11190.0.71548477",
"platform_ranges": [
{
"start": "1.0.0",
"exclusions": ["ContractKeysIT:CKLocalKeyVisibility"],
},
],
},
{
# Tests got renamed in
# https://github.com/digital-asset/daml/commit/f2707cc54f5b7da339bc565bc322be1e57db5edb
"start": "1.5.0-snapshot.20200907.5151.0.eb68e680",
"end": "1.17.0-snapshot.20210831.7702.0.f058c2f1",
"platform_ranges": [
{
"start": "1.17.0-snapshot.20210831.7702.1.f058c2f1",
"exclusions": [
"CommandDeduplicationIT:CDSimpleDeduplicationBasic",
"CommandDeduplicationIT:CDSimpleDeduplicationCommandClient",
],
},
],
},
{
"start": first_granular_test_tool,
"end": "1.17.0-snapshot.20210831.7702.0.f058c2f1",
"platform_ranges": [
{
"start": "1.17.0-snapshot.20210831.7702.1.f058c2f1",
"exclusions": [
"CommandServiceIT:CSRefuseBadParameter",
],
},
],
},
{
"start": "1.17.0-snapshot.20210910.7786.0.976ca400", # The first version these tests appeared
"end": "1.18.0-snapshot.20210928.7948.0.b4d00317",
"platform_ranges": [
{
"start": "1.18.0-snapshot.20210928.7948.1",
"exclusions": [
"KVCommandDeduplicationIT:KVCommandDeduplicationDeduplicateSubmitterBasic",
"KVCommandDeduplicationIT:KVCommandDeduplicationSimpleDeduplicationBasic",
],
},
],
},
{
"start": "1.17.0-snapshot.20210915.7841.0.b4328b3d", # The first version this test appeared
"end": "1.18.0-snapshot.20211026.8179.0.e474b2d1", # The version when this test was removed
"platform_ranges": [
{
"start": "1.18.0-snapshot.20210928.7948.1",
"exclusions": [
"KVCommandDeduplicationIT:KVCommandDeduplicationCommitterDeduplication",
],
},
],
},
{
"start": "1.5.0",
"end": "1.16.0",
"platform_ranges": [
{
"start": "1.18.0-snapshot.20210928.7948.1",
"exclusions": [
"CommandDeduplicationIT:CDDeduplicateSubmitterBasic", # Fixed in later ledger API test tools
],
},
],
},
{
"start": "1.18.0",
"platform_ranges": [
{
"end": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"exclusions": [
"CommandDeduplicationIT", # Latest version of the test is dependent on having the submission id populated
],
},
],
},
{
"start": "1.18.0",
"end": "2.0.0-snapshot.20220105.8777.1", # was removed in 2.0
"platform_ranges": [
{
"end": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"exclusions": [
# Exclude dedup tests due to large number of changes (removed participant deduplication, switch to append-only schema, changes in deduplication duration)
"KVCommandDeduplicationIT",
],
},
],
},
{
# Self-service error code assertions adapted
"end": "1.18.0-snapshot.20211102.8257.1",
"platform_ranges": [
{
"start": "1.18.0-snapshot.20211102.8257.1",
"exclusions": [
"PackageManagementServiceIT",
],
},
],
},
{
# Completion offset included in the CommandService responses
"start": "1.18.0",
"platform_ranges": [
{
"end": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"exclusions": [
"CommandServiceIT:CSsubmitAndWaitCompletionOffset",
],
},
],
},
{
"start": "2.0.0",
"platform_ranges": [
{
"end": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"exclusions": [
# Unexpected failure (StatusRuntimeException) ALREADY_EXISTS: DUPLICATE_COMMAND(10,KVComman):
"CommandDeduplicationIT:DeduplicationMixedClients",
# Unexpected failure (StatusRuntimeException) ALREADY_EXISTS: DUPLICATE_COMMAND(10,KVComman):
"CommandDeduplicationIT:SimpleDeduplicationCommandClient",
# Offsets are not supported for versions < 2.0.0
"CommandDeduplicationIT:DeduplicateUsingOffsets",
# Actual error id (INCONSISTENT) does not match expected error id (DUPLICATE_CONTRACT_KEY}
"ExceptionsIT:ExRollbackDuplicateKeyCreated",
"ExceptionsIT:ExRollbackDuplicateKeyArchived",
],
},
],
},
{
"start": "1.18.0",
"end": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"platform_ranges": [
{
"start": "2.0.0-snapshot.20211123.8463.0.bd2a6852",
"exclusions": [
# Actual error id (INCONSISTENT) does not match expected error id (DUPLICATE_CONTRACT_KEY}
"ExceptionsIT:ExRollbackDuplicateKeyCreated",
"ExceptionsIT:ExRollbackDuplicateKeyArchived",
],
},
],
},
{
# max deduplication duration is no longer enforced in the ledger API
"start": first_granular_test_tool,
"end": "2.0.0-snapshot.20211210.8653.0.35beb44c ",
"platform_ranges": [
{
"start": "2.0.0-snapshot.20211210.8653.0.35beb44c",
"exclusions": [
"LedgerConfigurationServiceIT:CSLSuccessIfMaxDeduplicationTimeExceeded",
],
},
],
},
{
"start": "1.3.0",
"end": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"platform_ranges": [
{
"start": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"exclusions": [
"CommandServiceIT:CSReturnStackTrace",
],
},
],
},
{
"start": "1.16.0",
"end": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"platform_ranges": [
{
"start": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"exclusions": [
"ExceptionsIT:ExUncaught",
],
},
],
},
{
"start": first_granular_test_tool,
"end": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"platform_ranges": [
{
"start": "2.0.0-snapshot.20220110.8812.0.3a08380b",
"exclusions": [
# Error message did not contain [\QParty not known on ledger\E], but was [Parties not known on ledger: [unallocated]].
"ClosedWorldIT:ClosedWorldObserver",
],
},
],
},
{
# Contract ID and participant pruning tests are no longer optional.
"start": "2.0.0-snapshot.20220110.8812.1",
"platform_ranges": [
{
"end": "2.0.0-snapshot.20220110.8812.1",
"exclusions": [
"ContractIdIT", # Contract ID tests are governed by feature descriptors.
"ParticipantPruningIT", # Now enabled by default, but some ledgers may need to disable certain tests.
],
},
],
},
{
# Some command deduplication tests are no longer optional, but fail on older releases.
"start": "2.0.0-snapshot.20220118.8919.1",
"platform_ranges": [
{
"end": "2.0.0-snapshot.20220118.8919.1",
"exclusions": [
"CommandDeduplicationParallelIT",
"CommandDeduplicationPeriodValidationIT",
"CompletionDeduplicationInfoITCommandService",
"CompletionDeduplicationInfoITCommandSubmissionService",
],
},
],
},
{
# Sandbox-on-X doesn't use participant-side command deduplication starting with next release,
# hence older tests will fail to assert it.
"start": "1.17.0",
"end": "1.18.3",
"platform_ranges": [
{
"start": "2.0.0-snapshot.20220126.9029.1",
"exclusions": [
"CommandDeduplicationIT:ParticipantCommandDeduplication",
],
},
],
},
{
# Sandbox-on-X doesn't use participant-side command deduplication starting with next release,
# hence older tests will fail to assert it.
"start": "1.18.0",
"end": "1.18.3",
"platform_ranges": [
{
"start": "2.0.0-snapshot.20220126.9029.1",
"exclusions": [
"CommandDeduplicationIT:ParticipantCommandDeduplicationSimpleDeduplicationMixedClients",
"CommandDeduplicationIT:ParticipantCommandDeduplicationDeduplicateSubmitterBasic",
"CommandDeduplicationIT:ParticipantCommandDeduplicationSimpleDeduplicationBasic",
],
},
],
},
{
"end": before_removing_legacy_error_codes,
"platform_ranges": [
{
"start": after_removing_legacy_error_codes,
"exclusions": [
"CommandSubmissionCompletionIT",
"ConfigManagementServiceIT",
"ContractKeysIT",
"SemanticTests",
"TransactionService",
],
},
],
},
{
"start": "1.16.0",
"end": before_removing_legacy_error_codes,
"platform_ranges": [
{
"start": after_removing_legacy_error_codes,
"exclusions": [
"ExceptionsIT",
],
},
],
},
{
# Sandbox-on-X starts forwarding rejections on duplicate party allocation starting with next release
"start": "2.0.0-snapshot.20220201.9108.1",
"platform_ranges": [
{
"end": "2.0.0-snapshot.20220201.9108.0.aa2494f1",
"exclusions": [
"PartyManagementServiceIT:PMRejectionDuplicateHint",
],
},
],
},
{
# Fixing childEventId ordering: this test is now checking conformance to ordering, so it needs to be excluded for conformance tests which come after, and for versions which are older
"start": "2.2.0-snapshot.20220425.9780.1",
"platform_ranges": [
{
"end": "2.2.0-snapshot.20220425.9780.0.f4d60375",
"exclusions": [
"TransactionServiceVisibilityIT:TXTreeChildOrder",
],
},
],
},
{
"start": "2.3.0-snapshot.20220606.10031.1",
"platform_ranges": [
{
"end": "2.3.0-snapshot.20220606.10031.0.ce98be86",
"exclusions": [
"ExceptionsIT:ExCKRollbackGlobalArchivedCreate",
"ExceptionsIT:ExCKRollbackGlobalArchivedLookup",
],
},
],
},
{
"start": "2.3.0-snapshot.20220611.10066.1",
"platform_ranges": [
{
"end": "2.3.0-snapshot.20220611.10066.0.458cfc43",
"exclusions": [
"ExceptionsIT:ExRollbackCreate",
"ExceptionsIT:ExRollbackExerciseCreateLookup",
],
},
],
},
{
"start": "2.6.0-snapshot.20221226.11190.1",
"platform_ranges": [
{
"end": "2.6.0-snapshot.20221226.11190.0.71548477",
"exclusions": [
"ContractKeysIT:CKLocalFetchByKeyVisibility",
"ContractKeysIT:CKLocalLookupByKeyVisibility",
],
},
],
},
{
"start": "2.0.0",
"end": "2.1.0",
"platform_ranges": [
{
"start": "2.6.0-snapshot.20230119.11284.1",
"exclusions": [
"UserManagementServiceIT:TestGrantUserRights",
"UserManagementServiceIT:TestRevokeUserRights",
"UserManagementServiceIT:TestListUserRights",
"UserManagementServiceIT:UserManagementUserRightsLimit",
"UserManagementServiceIT:GrantRightsRaceCondition",
],
},
],
},
{
"start": "2.1.1",
"end": "2.6.0-snapshot.20230119.11284.0.179b865a",
"platform_ranges": [
{
"start": "2.6.0-snapshot.20230119.11284.1",
"exclusions": [
"UserManagementServiceIT:TestGrantUserRights",
"UserManagementServiceIT:TestRevokeUserRights",
"UserManagementServiceIT:TestListUserRights",
"UserManagementServiceIT:UserManagementUserRightsLimit",
"UserManagementServiceIT:RaceConditionGrantRights",
"UserManagementServiceIT:RaceConditionRevokeRights",
],
},
],
},
{
"start": "2.6.0-snapshot.20230130.11335.0.a24439f0",
"platform_ranges": [
{
"end": "2.6.0-snapshot.20230130.11335.1",
"exclusions": [
"IdentityProviderConfigServiceIT",
],
},
],
},
{
"start": "2.6.0-snapshot.20230123.11292.1",
"platform_ranges": [
{
"end": "2.6.0-snapshot.20230123.11292.0.b3f84bfc",
"exclusions": [
# This test relies on a new Ledger API endpoint. Disable it for prior platforms
"ParticipantPruningIT:PRQueryLatestPrunedOffsets",
],
},
],
},
{
"start": "2.6.0-snapshot",
"platform_ranges": [
{
"end": "2.6.0-snapshot",
"exclusions": [
"ParticipantPruningIT:PREventsByContractIdPruned",
"ParticipantPruningIT:PREventsByContractKey",
"EventQueryServiceIT",
],
},
],
},
{
"start": "1.16.0",
"platform_ranges": [
{
"start": first_canton_in_ledger_api_tests,
"exclusions": [
"ClosedWorldIT",
"ConfigManagementServiceIT",
"LedgerConfigurationServiceIT",
"ParticipantPruningIT",
],
},
],
},
{
"start": "2.0.1",
"platform_ranges": [
{
"start": first_canton_in_ledger_api_tests,
"exclusions": [
"TLSOnePointThreeIT",
"TLSAtLeastOnePointTwoIT",
"CommandDeduplicationPeriodValidationIT:OffsetPruned",
],
},
],
},
{
"start": "2.6.3",
"platform_ranges": [
{
"start": first_canton_in_ledger_api_tests,
"exclusions": [
"ActiveContractsServiceIT:AcsBeforePruningOffsetIsDisallowed",
"ActiveContractsServiceIT:AcsAtPruningOffsetIsAllowed",
],
},
],
},
{
"start": "2.7.0-snapshot.20230529.11827.1",
"platform_ranges": [
{
"end": "2.7.0-snapshot.20230529.11827.0.v3fbe7d01",
"exclusions": [
"PartyManagementServiceIT:PMUpdatingPartyIdentityProviderNonDefaultIdps",
"PartyManagementServiceIT:PMUpdatingPartyIdentityProviderWithDefaultIdp",
"PartyManagementServiceIT:PMUpdatingPartyIdentityProviderNonExistentIdps",
"PartyManagementServiceIT:PMUpdatingPartyIdentityProviderMismatchedSourceIdp",
"PartyManagementServiceIT:PMUpdatingPartyIdentityProviderSourceAndTargetIdpTheSame",
"PartyManagementServiceIT:PMGetPartiesUsingDifferentIdps",
"UserManagementServiceIT:UserManagementUpdateUserIdpWithNonDefaultIdps",
"UserManagementServiceIT:UserManagementUpdateUserIdpWithDefaultIdp",
"UserManagementServiceIT:UserManagementUpdateUserIdpNonExistentIdps",
"UserManagementServiceIT:UserManagementUpdateUserIdpMismatchedSourceIdp",
"UserManagementServiceIT:UserManagementUpdateUserIdpSourceAndTargetIdpTheSame",
],
},
],
},
{
"start": "2.6.0",
"end": "2.7.0-snapshot.20230619.11890.0.vedd1a5f6",
"platform_ranges": [
{
"start": "2.7.0-snapshot.20230619.11890.0.vedd1a5f6.1",
"exclusions": [
"InterfaceSubscriptionsIT:ISTransactionsEquivalentFilters",
],
},
],
},
# Ledger api error structure change, all following tests make assertions on errors
# New error api tool cannot be used on old api platform
{
"start": "2.7.0-snapshot.20230703.11931.1",
"platform_ranges": [
{
"end": "2.7.0-snapshot.20230703.11931.0.vc04c7ac9",
"exclusions": [
"TransactionServiceExerciseIT:TXRejectOnFailingAssertion",
"DeeplyNestedValueIT",
"MultiPartySubmissionIT:MPSLookupOtherByKeyInvisible",
"CommandServiceIT:CSReturnStackTrace",
"TransactionServiceAuthorizationIT:TXRejectMultiActorExcessiveAuth",
"ContractKeysIT:CKGlocalKeyVisibility",
"WronglyTypedContractIdIT",
"ExplicitDisclosureIT:EDMalformedDisclosedContracts",
"InterfaceSubscriptionsIT:ISTransactionsEquivalentFilters",
"TimeServiceIT:TSFailWhenTimeNotAdvanced",
"ExceptionsIT:ExUncaught",
],
},
],
},
# Reverse of above, old api error tool cannot be used on new api platform
# Split up to account for tests not existing in older tools
{
"end": "2.7.0-snapshot.20230703.11931.0.vc04c7ac9",
"platform_ranges": [
{
"start": "2.7.0-snapshot.20230703.11931.1",
"exclusions": [
"CommandServiceIT:CSReturnStackTrace",
"DeeplyNestedValueIT",
"ExceptionsIT:ExUncaught",
"MultiPartySubmissionIT:MPSLookupOtherByKeyInvisible",
"WronglyTypedContractIdIT",
],
},
],
},
{
"start": "1.17.1",
"end": "2.7.0-snapshot.20230703.11931.0.vc04c7ac9",
"platform_ranges": [
{
"start": "2.7.0-snapshot.20230703.11931.1",
"exclusions": [
"TransactionServiceAuthorizationIT:TXRejectMultiActorExcessiveAuth",
"TransactionServiceExerciseIT:TXRejectOnFailingAssertion",
],
},
],
},
{
"start": "2.0.1",
"end": "2.7.0-snapshot.20230703.11931.0.vc04c7ac9",
"platform_ranges": [
{
"start": "2.7.0-snapshot.20230703.11931.1",
"exclusions": [
"TimeServiceIT:TSFailWhenTimeNotAdvanced",
],
},
],
},
{
"start": "2.3.14",
"end": "2.7.0-snapshot.20230703.11931.0.vc04c7ac9",
"platform_ranges": [
{
"start": "2.7.0-snapshot.20230703.11931.1",
"exclusions": [
"ContractKeysIT:CKGlocalKeyVisibility",
],
},
],
},
{
"start": "2.6.5",
"end": "2.7.0-snapshot.20230703.11931.0.vc04c7ac9",
"platform_ranges": [
{
"start": "2.7.0-snapshot.20230703.11931.1",
"exclusions": [
"ExplicitDisclosureIT:EDMalformedDisclosedContracts",
"InterfaceSubscriptionsIT:ISTransactionsEquivalentFilters",
],
},
],
},
# From 2.7.0snap - 2.7.x the DeeplyNestedValueIT test was misaligned with the real error
# After this, it had changed on both tests and ledger to be different from <2.7.1
# As such, Test tool <2.7.x cannot run with platform >= 2.7.1
# and test tool >= 2.7.1 cannot run with platform <2.7.x
{
"end": "2.7.9",
"platform_ranges": [
{
"start": "2.7.0-snapshot.20230703.11931.0.vc04c7ac9",
"exclusions": [
"DeeplyNestedValueIT",
],
},
],
},
{
"start": "2.7.0-snapshot.20230703.11931.0.vc04c7ac9",
"platform_ranges": [
{
"end": "2.7.9",
"exclusions": [
"DeeplyNestedValueIT",
],
},
],
},
# The next two exclusions pertain to changes to explicit disclosure Ledger API interface
# where DisclosedContract.create_arguments(_blob), DisclosedContract.metadata,
# InclusiveFilters.template_ids are deprecated and replaced by
# DisclosedContract.created_event_blob and InclusiveFilters.template_filters respectively
{
"start": "2.8.0-snapshot.20231025.1",
"platform_ranges": [
{
"end": "2.8.0-snapshot.20231025.0",
"exclusions": [
"ExplicitDisclosureIT",
],
},
],
},
{
"start": "2.6.0-snapshot.20230123.11292.0.b3f84bfc",
"end": "2.8.0-snapshot.20231025.0",
"platform_ranges": [
{
"start": "2.8.0-snapshot.20231025.1",
"exclusions": [
"ExplicitDisclosureIT",
"InterfaceSubscriptionsIT:ISTransactionsCreateArgumentsBlob",
],
},
],
},
]
def in_range(version, range):
start = range.get("start")
end = range.get("end")
@ -742,15 +20,6 @@ def in_range(version, range):
return False
return True
def get_excluded_tests(test_tool_version, sandbox_version):
exclusions = []
for test_tool_range in excluded_test_tool_tests:
if in_range(test_tool_version, test_tool_range):
for platform_range in test_tool_range["platform_ranges"]:
if in_range(sandbox_version, platform_range):
exclusions += platform_range["exclusions"]
return exclusions
def extra_tags(sdk_version, platform_version):
if sorted([sdk_version, platform_version]) == sorted(["0.0.0", latest_stable_version]):
# These tests are the ones that we check on each PR since they
@ -958,9 +227,6 @@ def sdk_platform_test(sdk_version, platform_version):
daml_assistant = "@daml-sdk-{sdk_version}//:daml".format(
sdk_version = sdk_version,
)
ledger_api_test_tool = "@daml-sdk-{sdk_version}//:ledger-api-test-tool".format(
sdk_version = sdk_version,
)
dar_files = "@daml-sdk-{sdk_version}//:dar-files".format(
sdk_version = sdk_version,
)
@ -975,46 +241,6 @@ def sdk_platform_test(sdk_version, platform_version):
platform_version = platform_version,
)
use_canton = versions.is_at_least(first_canton_in_ledger_api_tests, platform_version)
# ledger-api-test-tool test-cases
name = "ledger-api-test-tool-{sdk_version}-platform-{platform_version}".format(
sdk_version = version_to_name(sdk_version),
platform_version = version_to_name(platform_version),
)
exclusions = ["--exclude=" + test for test in get_excluded_tests(test_tool_version = sdk_version, sandbox_version = platform_version)]
if versions.is_at_least("1.16.0", sdk_version) and versions.is_stable(sdk_version) and versions.is_stable(platform_version):
if use_canton:
client_server_test(
name = name + "-canton",
client = ledger_api_test_tool,
client_args = [
"localhost:6865",
"--concurrent-test-runs=2", # lowered from default #procs to reduce flakes - details in https://github.com/digital-asset/daml/issues/7316
"--timeout-scale-factor=2",
] + exclusions,
data = [dar_files],
runner = "@//bazel_tools/client_server:runner",
runner_args = ["6865", "7000"],
server = canton_sandbox,
server_args = [
"sandbox",
"--canton-port-file",
"_port_file",
"--",
"-C",
"canton.monitoring.health.server.port=7000",
"-C",
"canton.monitoring.health.check.type=ping",
"-C",
"canton.monitoring.health.check.participant=sandbox",
"-C",
"canton.monitoring.health.check.interval=5s",
],
tags = ["exclusive", sdk_version, platform_version] + extra_tags(sdk_version, platform_version),
)
# daml-ledger test-cases
name = "daml-ledger-{sdk_version}-platform-{platform_version}".format(
sdk_version = version_to_name(sdk_version),

View File

@ -5,7 +5,6 @@ $ErrorActionPreference = 'Stop'
# Build the release artifacts required for running the compatibility
# tests against HEAD. At the moment this includes the SDK release tarball
# and the ledger-api-test-tool fat JAR.
# See https://github.com/lukesampson/scoop/issues/3859
Set-Strictmode -Off
@ -41,12 +40,10 @@ bazel fetch @nodejs_dev_env//...
bazel build `
`-`-experimental_execution_log_file ${ARTIFACT_DIRS}/build_execution_windows.log `
//release:sdk-release-tarball `
//ledger-test-tool/tool:ledger-api-test-tool_distribute.jar `
//daml-assistant:daml
git clean -fxd -e 'daml-*.tgz' compatibility/head_sdk
cp -Force bazel-bin\release\sdk-release-tarball-ce.tar.gz compatibility/head_sdk
cp -Force bazel-bin\ledger-test-tool\tool\ledger-api-test-tool_distribute.jar compatibility/head_sdk
cp -Force bazel-bin\daml-assistant\daml.exe compatibility/head_sdk
cp -Force templates\create-daml-app-test-resources\messaging.patch compatibility/head_sdk

View File

@ -4,8 +4,7 @@
# Build the release artifacts required for running the compatibility
# tests against HEAD. At the moment this includes the SDK release tarball
# and the ledger-api-test-tool fat JAR.
# tests against HEAD. At the moment this includes the SDK release tarball.
set -eou pipefail
@ -25,10 +24,8 @@ bazel build \
--tool_java_runtime_version=nixpkgs_java_11 \
--tool_java_language_version=11 \
//release:sdk-release-tarball \
//ledger-test-tool/tool:ledger-api-test-tool_distribute.jar \
//daml-assistant:daml
cp -f bazel-bin/release/sdk-release-tarball-ce.tar.gz "$HEAD_TARGET_DIR"
cp -f bazel-bin/ledger-test-tool/tool/ledger-api-test-tool_distribute.jar "$HEAD_TARGET_DIR"
cp -f bazel-bin/daml-assistant/daml "$HEAD_TARGET_DIR"
cp -f templates/create-daml-app-test-resources/messaging.patch "$HEAD_TARGET_DIR"

View File

@ -1,3 +1,2 @@
sdk-release-tarball.tar.gz
ledger-api-test-tool_deploy.jar
messaging.patch

View File

@ -3,5 +3,4 @@ load("@os_info//:os_info.bzl", "is_windows")
exports_files([
"daml" if not is_windows else "daml.exe",
"sdk-release-tarball-ce.tar.gz",
"ledger-api-test-tool_deploy.jar",
])

View File

@ -4,9 +4,7 @@ $ErrorActionPreference = 'Stop'
# SPDX-License-Identifier: Apache-2.0
# Build the release artifacts required for running the compatibility
# tests against HEAD. At the moment this includes the SDK release tarball
# and the ledger-api-test-tool fat JAR.
# tests against HEAD. At the moment this includes the SDK release tarball.
$test_args = ""
if (($args.length -ge 1) -and ($args[0] -eq "--quick")) {
$test_args = "--test_tag_filters=+head-quick"

View File

@ -4,8 +4,7 @@
# Build the release artifacts required for running the compatibility
# tests against HEAD. At the moment this includes the SDK release tarball
# and the ledger-api-test-tool fat JAR.
# tests against HEAD. At the moment this includes the SDK release tarball.
set -eou pipefail

View File

@ -47,7 +47,6 @@ version_sha256s = {
"linux": "35493fe48c3ccb3af0879e72215022e33536babde9e2e56a051e497c28817782",
"macos": "742e600a40d32fa43c646748ebce96f4e5c38288f0db946b181661f2c6be0184",
"windows": "4fa3570f4dbadd6889a00f5c0ff61c6520ce93f777f0b0664054aa0d97555e12",
"test_tool": "9bf907ef11d7fc96e1843ca7066125cd43b3556370be127d0fd28ccdfa9fa760",
"daml_types": "4f9799527f11a913eccd4e320f247636d1b031a942ea7cece20efb7f867cd44f",
"daml_ledger": "c4178f198834a5a8ffc49bfb97294d6d84416ca5ed45000f981902f72e5dee9e",
"daml_react": "9568afc25474a19c6d00644fbde6ad6472914c82fb6d32a3d0a118b08b81f6d5",
@ -57,7 +56,6 @@ version_sha256s = {
"linux": "373ac6345b00f91ac85a8b64c632d1a97c055beb142709f564399f306f1800d9",
"macos": "45b3abe42d8aa74bbff6f3a59999d5e77694fc849540cbf03861a0a3ae85602c",
"windows": "2a5cb325d88c594b7f36ff42fa01a4098a90a922a2aa0dbeb7a9bed0fa303f45",
"test_tool": "7ddbfa10532934bee2b634c7c501b13dd9660c519ac175ca2bfa1f90caba4d75",
"daml_types": "d99ad1eeb146d4d548005af13dfcfc92b4d1006c72ff565fa0537eb4564d8804",
"daml_ledger": "173db6fca7a4108c765ff301c21453c8b51974db07637c137cbfd21aa1fe991e",
"daml_react": "6c07955e8f9ec31cbb079ae819bd0bca6410bdd137e8a7be4b82d0220e2992fe",
@ -67,7 +65,6 @@ version_sha256s = {
"linux": "f5a999105f4eaac94363dddeb00827e3ccd2cc6a00fed667334ab0b1ed75c24c",
"macos": "1c467d9a7429d54414d14f509cfebb68e700dfe19527f67502a1a1249adfcdb2",
"windows": "e59fe10487e6a0b63e591c719729322b9d25e57a3933ff084b865fb2cc48a381",
"test_tool": "4509e9da694530e22704334dd596081956747e01e52866edffed90a415b1e075",
"daml_types": "39b9acb91a6a56175b429a3925f176dbb80ee8b5cc2a80752b2c7b0e13c44584",
"daml_ledger": "0a3c3581c69fd5f0c33bc8ce2ab85c95764bd79f7be570b1bc76b6300a3fd352",
"daml_react": "e6f81c27387711156bcd3f1239f515c6f6250b780aa6e279043d1c40202f682e",
@ -77,7 +74,6 @@ version_sha256s = {
"linux": "6def46b83d601d0892186a9ee06852a3040c2e1e0c40de50dc382976b316c130",
"macos": "2704aae32fa4f22002692513f5a2e3de25761b8e86b185e5abb2640d870ccb48",
"windows": "be9e6603d7e5579b697cecb5dc07c07df84d806e29bc798c58180dbc4c3a86d4",
"test_tool": "857f33e911ef6a4dd52522ef327ccee1cca80fcda121a410a7bc850d1e0b61e2",
"daml_types": "4595ff1dad4d7a9f92aa8e17561011a27b1e020d9d435eca43986917e14acb9a",
"daml_ledger": "4b48fcff784fd9a0aa68bb212a120eae81cfd41565c523d1b6dd09fa9d9ca5ba",
"daml_react": "6f76758a7a98dd29abb9b241633d9e970ff97663a5dea8893291ea1501581005",
@ -87,7 +83,6 @@ version_sha256s = {
"linux": "85e1d46ea6e172451ce22a74fed1732fbf68958acfe57b843180a0579ab3ab42",
"macos": "01c8f69d38b1f2ee1dd5b17c1402c1d20bccf121eef1f0fdc021a5983080c03f",
"windows": "0b81affdfdab1d3375a6444c06ec9ae926a091c3bd033bd88fb33feeef650943",
"test_tool": "89918eb7709de64ed9bcea3f3fc55370ad4c9869ecfe23846250b4347f6af7a1",
"daml_types": "6b586094bf71d6a416c6db14cd4a3af19d19ba00a7bc575223efe892676adf63",
"daml_ledger": "d2c6f20edbfeeaccdbc4882c5a67d46cce414054b23f8072064edd9a746e9195",
"daml_react": "9191206233867aaf1d8138b5dbdd49d346d76e6bdfaa57e2630a5b8edd40221f",
@ -97,7 +92,6 @@ version_sha256s = {
"linux": "90589d400b8e0dfc5ac2d3d68405fa09e44e530b717c06d396a875b696603d0d",
"macos": "d31fa2bcbb5cb8e569a1e423826134c55d84e5f1c43c44d68b297ad953763a5d",
"windows": "a9ce79e02285c4612cbc2d169ac1dad316676c6291c80da86331703bc85c94cf",
"test_tool": "b6f83471107d062dd504e9d4b8fe186d312b30dee4fdb42e770f566ba1731d48",
"daml_types": "ea4dd30f4917bb38e3a9d00417957f343835cbbbba2e7db8a423b19c84aff205",
"daml_ledger": "b75c3128beca8fd4c610f46ec01082fb6193c132422e9c3cfb263f004b798fce",
"daml_react": "c1a9456ead0564514c56ed2da44ba49888aaf51fdec2327c71f5b4fea80f292b",
@ -107,7 +101,6 @@ version_sha256s = {
"linux": "ade1f4687d558dcf62e0d4ae45ba23319990ab0097952ab5f8123f0c9224419a",
"macos": "68fd1482fd0cc2b35697abd1fb2a542a6f1cdc74a684cb0078b8b329fea18f61",
"windows": "739e74e08f410ce240072aeef6202ad2b7341cba1a98c1e45611e8f174694aaa",
"test_tool": "ea809e79fefcc3e9ce8b19dfb93d1e696f76e5bca310911a275bf7362b1a0369",
"daml_types": "7384ba867735f73c48c868af93c627401e7d4c8926854b4b01806fb0a8670f61",
"daml_ledger": "44812c2f86282b37156471034925b91d81f5a8bdec2b7bccf93c463e6ae5053c",
"daml_react": "55e509ca5fc6b545c142de4e0f57408626a6e5167a71bc75ba4909ab4ea49122",
@ -117,7 +110,6 @@ version_sha256s = {
"linux": "3c2d3e518f22a3620a0dbbc7edf3ef3e1a565d1e766b5fb0a7667a1f688962a4",
"macos": "d9bf32ef6256fd18618afea37fb645a954c7a4f5692480312bfc04f2016df68b",
"windows": "2333fad6150dc5dc12c9125b8141b3ff3957a0311642dbfcc3c364ccdfefba51",
"test_tool": "4c35997a98c1b1925a6001b4551430505f3f3ba2ef37d96213a4ea4f3570b34f",
"daml_types": "9c11635c917a1651be1165bab5c91471e818e9872993cf4a95b1297b8dc09d7e",
"daml_ledger": "683bb48e99d228d09716d95026b5fc2ed6e0cc28d0b40cbae88f480e156b745f",
"daml_react": "36ad551102e95281b78b4b66f5250fc12730f145529089eb0160730287610b00",
@ -127,7 +119,6 @@ version_sha256s = {
"linux": "17e314df841b19edf54e5b83b809c3deafb13ae6bc86819281e6f92b9cc48f51",
"macos": "419ddf99efaeaa65274dba889e4e229714668bcb1b02f8ae58fc69f374db0c59",
"windows": "ef4b93cf4120299de8104e2202f0b0bc49c1cfbaffdcaf16ddf8b7fe892cbc8b",
"test_tool": "adabd0f87b4d82964922177a2ca49907177a7c2487189f15e8263b0f8ab7d8b5",
"daml_types": "65b30eed230a71e16bfae8134150353164b6e6b6e153c1603a875e7cc8ab24a4",
"daml_ledger": "6ac2c554d4cfb578cd3cd387766958267138b5d4cfc89c3ad393fea358b79f6b",
"daml_react": "5a729a15c635bb1dcebc1b4068f31201edbfa2c0ea5b8c243fd4c6461b41d3db",
@ -137,7 +128,6 @@ version_sha256s = {
"linux": "d72f808b1c02ebec8b064eccda280fb03cb781ff85d59b6f7ac145c8b43e57ce",
"macos": "9076ef00e6e06d5f2a10c4b497e32fcd921401d803543409fe1b0b976de3bd7a",
"windows": "de1101e3a60f3d48f95534cc6d957f16b19e6a55bbd5cbe78e5451d2fbab27c9",
"test_tool": "80a823289848f04d883118457a34e04fd3b5cc9f7180db68aa81c64676ce3da7",
"daml_types": "199b38284c560631c01887595df4a59f25310052ac2a278e64af91da66eeaed5",
"daml_ledger": "46aeaf994a432f5bc106761aa9fe1800e6136d1f13a0749e4bf2121e78c22ef1",
"daml_react": "30cd65f7411f93822645fcdec77251e99c35ac635e70256291265a59c832667d",

View File

@ -74,7 +74,6 @@ renderVersionsFile (Versions (Set.toAscList -> versions)) checksums =
, " \"linux\": " <> renderDigest linuxHash <> ","
, " \"macos\": " <> renderDigest macosHash <> ","
, " \"windows\": " <> renderDigest windowsHash <> ","
, " \"test_tool\": " <> renderDigest testToolHash <> ","
, " \"daml_types\": " <> renderDigest damlTypesHash <> ","
, " \"daml_ledger\": " <> renderDigest damlLedgerHash <> ","
, " \"daml_react\": " <> renderDigest damlReactHash <> ","
@ -129,8 +128,7 @@ getChecksums ver = do
digestFromByteString @SHA256 @ByteString byteHash
[ testToolHash, damlTypesHash, damlLedgerHash, damlReactHash] <-
forConcurrently
[ testToolUrl
, tsLib "types"
[ tsLib "types"
, tsLib "ledger"
, tsLib "react"
] getHash
@ -141,9 +139,6 @@ getChecksums ver = do
sha256Url =
"https://github.com/digital-asset/daml/releases/download/v" <>
SemVer.toString ver <> "/sha256sums"
testToolUrl =
"https://repo1.maven.org/maven2/com/daml/ledger-api-test-tool/" <>
SemVer.toString ver <> "/ledger-api-test-tool-" <> SemVer.toString ver <> ".jar"
tsLib name =
"https://registry.npmjs.org/@daml/" <> name <>
"/-/" <> name <> "-" <> SemVer.toString ver <> ".tgz"

View File

@ -177,7 +177,6 @@ rst_prolog = """
.. _installer: https://github.com/digital-asset/daml/releases/download/v{release}/daml-sdk-{release}-windows.exe
.. _Artifactory: https://digitalasset.jfrog.io/ui/repos/tree/General/sdk-ee
.. _protobufs: https://github.com/digital-asset/daml/releases/download/v{release}/protobufs-{release}.zip
.. _api-test-tool: https://repo1.maven.org/maven2/com/daml/ledger-api-test-tool/{release}/ledger-api-test-tool-{release}.jar
.. _ts-daml-react: daml-react
.. _ts-daml-ledger: daml-ledger
.. _ts-daml-types: daml-types

View File

@ -332,7 +332,6 @@ rst_prolog = """
.. _installer: https://github.com/digital-asset/daml/releases/download/v{release}/daml-sdk-{release}-windows.exe
.. _Artifactory: https://digitalasset.jfrog.io/ui/repos/tree/General/sdk-ee
.. _protobufs: https://github.com/digital-asset/daml/releases/download/v{release}/protobufs-{release}.zip
.. _api-test-tool: https://repo1.maven.org/maven2/com/daml/ledger-api-test-tool/{release}/ledger-api-test-tool-{release}.jar
.. _ts-daml-react: https://docs.daml.com/{release}/app-dev/bindings-ts/daml-react
.. _ts-daml-ledger: https://docs.daml.com/{release}/app-dev/bindings-ts/daml-ledger
.. _ts-daml-types: https://docs.daml.com/{release}/app-dev/bindings-ts/daml-types

View File

@ -1,2 +0,0 @@
# Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

View File

@ -1,77 +0,0 @@
# Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
load(
"//bazel_tools/client_server:client_server_test.bzl",
"client_server_test",
)
load("@os_info//:os_info.bzl", "is_windows")
load("@scala_version//:index.bzl", "scala_major_version")
load("//daml-lf/language:daml-lf.bzl", "lf_version_configuration", "lf_version_is_dev", "lf_versions_aggregate")
def conformance_test(
name,
server,
server_args = [],
extra_data = [],
ports = [6865],
test_tool_args = [],
tags = [],
runner = "@//bazel_tools/client_server/runner_with_port_check",
extra_runner_args = [],
lf_versions = ["default"],
dev_mod_flag = "--daml-lf-dev-mode-unsafe",
preview_mod_flag = "--early-access",
flaky = False,
hocon = False,
server_hocon_config = None):
for lf_version in lf_versions_aggregate(lf_versions):
daml_lf_dev_mode_args = ["-C ledger.engine.allowed-language-versions=daml-lf-dev-mode-unsafe"] if hocon else [dev_mod_flag]
daml_lf_preview_mode_args = ["-C ledger.engine.allowed-language-versions=early-access"] if hocon else [preview_mod_flag]
extra_server_args = daml_lf_preview_mode_args if lf_version == lf_version_configuration.get("preview") else daml_lf_dev_mode_args if lf_version_is_dev(lf_version) else []
if not is_windows:
test_name = "-".join([name, lf_version])
hocon_conf_file_name = test_name + ".conf"
if server_hocon_config:
generate_conf("generate-" + test_name, hocon_conf_file_name, content = server_hocon_config, data = extra_data)
hocon_server_args = ["-c $(rootpath :" + hocon_conf_file_name + ")"] if server_hocon_config else []
hocon_data = [":" + hocon_conf_file_name] if server_hocon_config else []
client_server_test(
name = test_name,
runner = runner,
runner_args = ["%s" % port for port in ports] + extra_runner_args,
timeout = "long",
client = "//ledger-test-tool/tool:tool-%s" % lf_version,
client_args = test_tool_args + ["localhost:%s" % port for port in ports],
data = extra_data + hocon_data,
server = server,
server_args = server_args + extra_server_args + hocon_server_args,
tags = [
"dont-run-on-darwin",
"exclusive",
] + tags,
flaky = flaky,
)
if lf_version == lf_version_configuration.get("default"):
native.test_suite(
name = name,
tests = [test_name],
tags = tags,
)
def generate_conf(name, conf_file_name, content, data = []):
native.genrule(
name = name,
srcs = data,
outs = [conf_file_name],
cmd = """
set -eou pipefail
cat << 'EOF' > $@
{content}
EOF
""".format(content = content),
visibility = ["//visibility:public"],
)
# versions for which we build a test-tool
testtool_lf_versions = ["1.8", "1.14", "1.15", "1.dev", "2.1", "2.dev"]

View File

@ -1,61 +0,0 @@
# Copyright (c) 2023 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")
load(
"//daml-lf/language:daml-lf.bzl",
"lf_version_configuration",
)
load("//ledger-test-tool:conformance.bzl", "testtool_lf_versions")
[
da_scala_library(
name = "infrastructure-%s" % lf_version,
srcs = glob(["src/main/scala/**/*.scala"]),
scala_deps = [
"@maven//:org_apache_pekko_pekko_actor",
"@maven//:org_apache_pekko_pekko_stream",
"@maven//:org_scalameta_munit",
],
tags = ["maven_coordinates=com.daml:ledger-api-tests-infrastructure-%s:__VERSION__" % lf_version],
visibility = ["//:__subpackages__"],
deps = [
"//canton:bindings-java",
"//canton:ledger_api_proto_scala",
"//daml-lf/data",
"//ledger/error",
"//ledger/ledger-api-errors",
"//libs-scala/test-evidence/tag:test-evidence-tag",
"//test-common:dar-files-%s-lib" % lf_version,
"//libs-scala/build-info",
"//libs-scala/contextualized-logging",
"//libs-scala/grpc-test-utils",
"//libs-scala/resources",
"//libs-scala/resources-grpc",
"//libs-scala/resources-pekko",
"//libs-scala/timer-utils",
"@maven//:com_google_api_grpc_proto_google_common_protos",
"@maven//:com_typesafe_config",
"@maven//:io_netty_netty_common",
"@maven//:io_netty_netty_transport",
"@maven//:junit_junit",
"@maven//:org_slf4j_slf4j_api",
],
)
for lf_version in testtool_lf_versions
]
[
alias(
name = "infrastructure-%s" % name,
actual = ":infrastructure-%s" % lf_target,
visibility = ["//visibility:public"],
)
for (name, lf_target) in lf_version_configuration.items()
]
alias(
name = "infrastructure",
actual = ":infrastructure-default",
visibility = ["//visibility:public"],
)

View File

@ -1,67 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.javaapi.data.Party
object Allocation {
/** Specifies a sequence of party counts to be allocated on a sequence of participants.
*
* Number of party counts does not need to match the number of participants.
* If there are fewer participants than party counts,
* the participants will be reused in a circular fashion.
*/
def allocate(firstPartyCount: PartyCount, partyCounts: PartyCount*): PartyAllocation =
PartyAllocation(firstPartyCount +: partyCounts, minimumParticipantCount = 1)
final case class PartyAllocation private (
partyCounts: Seq[PartyCount],
minimumParticipantCount: Int,
) {
def expectingMinimumActualParticipantCount(
minimumParticipantCount: Int
): PartyAllocation =
copy(minimumParticipantCount = minimumParticipantCount)
}
/** Specifies the number of parties to allocate in a participant.
*
* NOTE: A single participant can be allocated parties from multiple party counts.
*/
sealed trait PartyCount {
val count: Int
}
case object NoParties extends PartyCount {
override val count = 0
}
case object SingleParty extends PartyCount {
override val count = 1
}
case object TwoParties extends PartyCount {
override val count = 2
}
final case class Parties(override val count: Int) extends PartyCount
/** Exposes information about configured participants and allocated parties to a test case.
*
* When using multiple participants, keep in mind that they do not update their view of the ledger synchronously.
* E.g., after you create a contract through the command service of one participant,
* other participants might not know about the contract yet.
* Use `com.daml.ledger.api.testtool.infrastructure.Eventually` if you want to wait for a participant to catch up,
* or `com.daml.ledger.api.testtool.infrastructure.Synchronize` to add a synchronization point between participants.
*/
final case class Participants private[infrastructure] (participants: Participant*)
final case class Participant private[infrastructure] (
context: ParticipantTestContext,
parties: Party*
)
}

View File

@ -1,9 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
final case class AssertionErrorWithPreformattedMessage[T](
preformattedMessage: String,
message: String,
) extends AssertionError(message)

View File

@ -1,219 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import java.util.regex.Pattern
import com.daml.error.ErrorCode
import com.daml.error.utils.ErrorDetails
import com.daml.timer.RetryStrategy
import com.google.rpc.ErrorInfo
import io.grpc.protobuf.StatusProto
import io.grpc.StatusRuntimeException
import munit.{ComparisonFailException, Assertions => MUnit}
import scala.annotation.tailrec
import scala.concurrent.Future
import scala.jdk.CollectionConverters._
import scala.language.implicitConversions
import scala.util.Try
object Assertions {
def fail(message: String): Nothing =
throw new AssertionError(message)
def fail(message: String, cause: Throwable): Nothing =
throw new AssertionError(message, cause)
def assertLength[A, F[_] <: Seq[_]](context: String, length: Int, as: F[A]): F[A] = {
assert(as.length == length, s"$context: expected $length item(s), got ${as.length}")
as
}
def assertSingleton[A](context: String, as: Seq[A]): A =
assertLength(context, 1, as).head
def assertEquals[T](context: String, actual: T, expected: T): Unit = {
try {
MUnit.assertEquals(actual, expected, context)
} catch {
case e: ComparisonFailException =>
throw AssertionErrorWithPreformattedMessage(
e.message,
s"$context: two objects are supposed to be equal but they are not",
)
}
}
def assertEquals[T](actual: T, expected: T): Unit = {
try {
MUnit.assertEquals(actual, expected)
} catch {
case e: ComparisonFailException =>
throw AssertionErrorWithPreformattedMessage(
e.message,
s"two objects are supposed to be equal but they are not",
)
}
}
def assertSameElements[T](actual: Iterable[T], expected: Iterable[T]): Unit = {
assert(
actual.toSet == expected.toSet,
s"Actual |${actual.mkString(", ")}| should have the same elements as (expected): |${expected.mkString(", ")}|",
)
}
def assertIsEmpty(actual: Iterable[_]): Unit = {
assertSameElements(actual, Seq.empty)
}
def assertGrpcErrorOneOf(t: Throwable, errors: ErrorCode*): Unit = {
val hasErrorCode =
errors.map(errorCode => Try(assertGrpcError(t, errorCode, None))).exists(_.isSuccess)
if (!hasErrorCode)
fail(s"gRPC failure did not contain one of the expected error codes $errors.", t)
}
def assertGrpcError(
t: Throwable,
errorCode: ErrorCode,
exceptionMessageSubstring: Option[String],
checkDefiniteAnswerMetadata: Boolean = false,
additionalErrorAssertions: Throwable => Unit = _ => (),
): Unit =
assertGrpcErrorRegex(
t,
errorCode,
exceptionMessageSubstring
.map(msgSubstring => Pattern.compile(Pattern.quote(msgSubstring))),
checkDefiniteAnswerMetadata,
additionalErrorAssertions,
)
/** Match the given exception against a status code and a regex for the expected message.
* Succeeds if the exception is a GrpcException with the expected code and
* the regex matches some part of the message or there is no message and the pattern is
* None.
*/
@tailrec
def assertGrpcErrorRegex(
t: Throwable,
errorCode: ErrorCode,
optPattern: Option[Pattern],
checkDefiniteAnswerMetadata: Boolean = false,
additionalErrorAssertions: Throwable => Unit = _ => (),
): Unit =
t match {
case RetryStrategy.FailedRetryException(cause) =>
assertGrpcErrorRegex(
cause,
errorCode,
optPattern,
checkDefiniteAnswerMetadata,
additionalErrorAssertions,
)
case exception: StatusRuntimeException =>
optPattern.foreach(assertMatches(exception.getMessage, _))
assertErrorCode(exception, errorCode)
if (checkDefiniteAnswerMetadata) assertDefiniteAnswer(exception)
additionalErrorAssertions(exception)
case _ =>
fail("Exception is not a StatusRuntimeException", t)
}
private def assertMatches(message: String, pattern: Pattern): Unit =
if (pattern.matcher(message).find()) {
()
} else {
fail(s"Error message did not contain [$pattern], but was [$message].")
}
private def assertDefiniteAnswer(exception: Exception): Unit = {
val definitiveAnswer = extractErrorInfoMetadataValue(exception, "definite_answer")
if (!Set("true", "false").contains(definitiveAnswer.toLowerCase)) {
fail(s"The error contained an invalid definite answer: [$definitiveAnswer]")
}
}
def extractErrorInfoMetadataValue(exception: Throwable, key: String): String = {
val metadata = extractErrorInfoMetadata(exception)
metadata.get(key) match {
case Some(value) =>
value
case None =>
fail(
s"The error metadata did not contain the key $key. Metadata was: [$metadata]",
exception,
)
}
}
def extractErrorInfoMetadata(exception: Throwable): Map[String, String] =
extractErrorInfoMetadata(StatusProto.fromThrowable(exception))
def extractErrorInfoMetadata(status: com.google.rpc.Status): Map[String, String] = {
val details = status.getDetailsList.asScala
details
.find(_.is(classOf[ErrorInfo]))
.map { any =>
val errorInfo = any.unpack(classOf[ErrorInfo])
errorInfo.getMetadataMap.asScala.toMap
}
.getOrElse {
Map.empty
}
}
def assertErrorCode(
statusRuntimeException: StatusRuntimeException,
expectedErrorCode: ErrorCode,
): Unit = {
val status = StatusProto.fromThrowable(statusRuntimeException)
val expectedStatusCode = expectedErrorCode.category.grpcCode
.map(_.value())
.getOrElse(
throw new RuntimeException(
s"Errors without grpc code cannot be asserted on the Ledger API. Expected error: $expectedErrorCode"
)
)
val expectedErrorId = expectedErrorCode.id
val expectedRetryability = expectedErrorCode.category.retryable.map(_.duration)
val actualStatusCode = status.getCode
val actualErrorDetails = ErrorDetails.from(status.getDetailsList.asScala.toSeq)
val actualErrorId = actualErrorDetails
.collectFirst { case err: ErrorDetails.ErrorInfoDetail => err.errorCodeId }
.getOrElse(fail(s"Actual error id is not defined. Actual error: $statusRuntimeException"))
val actualRetryability = actualErrorDetails
.collectFirst { case err: ErrorDetails.RetryInfoDetail => err.duration }
if (actualErrorId != expectedErrorId)
fail(
s"Actual error id ($actualErrorId) does not match expected error id ($expectedErrorId}. Actual error: $statusRuntimeException"
)
Assertions.assertEquals(
"gRPC error code mismatch",
actualStatusCode,
expectedStatusCode,
)
Assertions.assertEquals(
s"Error retryability details mismatch",
actualRetryability,
expectedRetryability,
)
}
def assertDefined[T](option: Option[T], errorMessage: String): T = {
assert(option.isDefined, errorMessage)
option.get
}
/** Allows for assertions with more information in the error messages. */
implicit def futureAssertions[T](future: Future[T]): FutureAssertions[T] =
new FutureAssertions[T](future)
}

View File

@ -1,27 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import io.grpc.Channel
sealed trait Endpoint
object Endpoint {
final case object InProcess extends Endpoint
final case class Remote(hostname: String, port: Int) extends Endpoint
}
final case class ChannelEndpoint(channel: Channel, endpoint: Endpoint)
object ChannelEndpoint {
def forRemote(channel: Channel, hostname: String, port: Int): ChannelEndpoint =
ChannelEndpoint(channel, Endpoint.Remote(hostname, port))
def forInProcess(channel: Channel): ChannelEndpoint = ChannelEndpoint(channel, Endpoint.InProcess)
}

View File

@ -1,18 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.test.TestDar
import com.google.protobuf.ByteString
object Dars {
// The list of all DAR packages that are bundled with this binary.
// The TestDar object is generated by Bazel.
val resources: List[String] = TestDar.paths
def read(name: String): ByteString =
ByteString.readFrom(getClass.getClassLoader.getResourceAsStream(name))
}

View File

@ -1,20 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
object Errors {
sealed abstract class FrameworkException(message: String, cause: Throwable)
extends RuntimeException(message, cause)
final class ParticipantConnectionException(cause: Throwable)
extends FrameworkException(
s"Could not connect to the participant: ${cause.getMessage}",
cause,
)
final class DarUploadException(name: String, cause: Throwable)
extends FrameworkException(s"""Failed to upload DAR "$name": ${cause.getMessage}""", cause)
}

View File

@ -1,114 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.v1.event.Event.Event.{Archived, Created, Empty}
import com.daml.ledger.api.v1.event.{CreatedEvent, Event, ExercisedEvent}
import com.daml.ledger.api.v1.transaction.TreeEvent
import com.daml.ledger.api.v1.transaction.TreeEvent.Kind.{
Created => TreeCreated,
Exercised => TreeExercised,
}
import com.daml.ledger.api.v1.value.Identifier
object EventOps {
implicit class EventOps(val event: Event) extends AnyVal {
def eventId: String = event.event.eventId
def witnessParties: Seq[String] = event.event.witnessParties
def updateWitnessParties(set: Seq[String]): Event =
event.copy(event = event.event.updateWitnessParties(set))
def modifyWitnessParties(f: Seq[String] => Seq[String]): Event =
event.copy(event = event.event.modifyWitnessParties(f))
def contractId: String = event.event.contractId
def templateId: Identifier = event.event.templateId
def isCreated: Boolean = event.event.isCreated
def isArchived: Boolean = event.event.isArchived
}
implicit class EventEventOps(val event: Event.Event) extends AnyVal {
def eventId: String = event match {
case Archived(value) => value.eventId
case Created(value) => value.eventId
case Empty => throw new IllegalArgumentException("Cannot extract Event ID from Empty event.")
}
def witnessParties: Seq[String] = event match {
case Archived(value) => value.witnessParties
case Created(value) => value.witnessParties
case Empty => Seq.empty
}
def updateWitnessParties(set: Seq[String]): Event.Event = event match {
case Archived(value) => Archived(value.copy(witnessParties = set))
case Created(value) => Created(value.copy(witnessParties = set))
case Empty => Empty
}
def modifyWitnessParties(f: Seq[String] => Seq[String]): Event.Event = event match {
case Archived(value) => Archived(value.copy(witnessParties = f(value.witnessParties)))
case Created(value) => Created(value.copy(witnessParties = f(value.witnessParties)))
case Empty => Empty
}
def templateId: Identifier = event match {
case Archived(value) => value.templateId.get
case Created(value) => value.templateId.get
case Empty =>
throw new IllegalArgumentException("Cannot extract Template ID from Empty event.")
}
def contractId: String = event match {
case Archived(value) => value.contractId
case Created(value) => value.contractId
case Empty =>
throw new IllegalArgumentException("Cannot extract contractId from Empty event.")
}
}
implicit final class TreeEventKindOps(val kind: TreeEvent.Kind) extends AnyVal {
def fold[T](exercise: ExercisedEvent => T, create: CreatedEvent => T): T =
kind match {
case TreeExercised(value) => exercise(value)
case TreeCreated(value) => create(value)
case tk => throw new IllegalArgumentException(s"Unknown TreeEvent type: $tk")
}
}
implicit final class TreeEventOps(val event: TreeEvent) extends AnyVal {
def eventId: String = event.kind.fold(_.eventId, _.eventId)
def childEventIds: Seq[String] = event.kind.fold(_.childEventIds, _ => Nil)
def filterChildEventIds(f: String => Boolean): TreeEvent =
event.kind.fold(
exercise =>
TreeEvent(TreeExercised(exercise.copy(childEventIds = exercise.childEventIds.filter(f)))),
create => TreeEvent(TreeCreated(create)),
)
def sortChildEventIdsBy(order: Map[String, Int]): TreeEvent =
event.kind.fold(
exercise =>
TreeEvent(
TreeExercised(exercise.copy(childEventIds = exercise.childEventIds.sortBy(order)))
),
create => TreeEvent(TreeCreated(create)),
)
def witnessParties: Seq[String] = event.kind.fold(_.witnessParties, _.witnessParties)
def modifyWitnessParties(f: Seq[String] => Seq[String]): TreeEvent =
event.kind.fold(
exercise =>
TreeEvent(TreeExercised(exercise.copy(witnessParties = f(exercise.witnessParties)))),
create => TreeEvent(TreeCreated(create.copy(witnessParties = f(create.witnessParties)))),
)
def templateId: Option[Identifier] = event.kind.fold(_.templateId, _.templateId)
}
}

View File

@ -1,34 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.timer.RetryStrategy
import com.daml.timer.RetryStrategy.{TooManyAttemptsException, UnhandledFailureException}
import scala.concurrent.duration.{Duration, DurationInt}
import scala.concurrent.{ExecutionContext, Future}
object Eventually {
/*
Runs provided closure with the exponential back-off retry strategy for a number of `attempts`.
*/
def eventually[A](assertionName: String, attempts: Int = 10, firstWaitTime: Duration = 10.millis)(
runAssertion: => Future[A]
)(implicit ec: ExecutionContext): Future[A] =
RetryStrategy
.exponentialBackoff(attempts, firstWaitTime) { (_, _) =>
runAssertion
}
.recoverWith {
case tooManyAttempts: TooManyAttemptsException =>
Future.failed(
tooManyAttempts.copy(message = s"$assertionName: ${tooManyAttempts.message}")
)
case unhandledFailure: UnhandledFailureException =>
Future.failed(
unhandledFailure.copy(message = s"$assertionName: ${unhandledFailure.message}")
)
}
}

View File

@ -1,147 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.error.ErrorCode
import com.daml.ledger.api.testtool.infrastructure.FutureAssertions.ExpectedFailureException
import com.daml.ledger.api.testtool.infrastructure.time.DelayMechanism
import com.daml.logging.{ContextualizedLogger, LoggingContext}
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal
import scala.util.{Failure, Success}
final class FutureAssertions[T](future: Future[T]) {
/** Checks that the future failed, and returns the throwable.
* We use this instead of `Future#failed` because the error message that delivers is unhelpful.
* It doesn't tell us what the value actually was.
*/
def mustFail(context: String)(implicit executionContext: ExecutionContext): Future[Throwable] =
handle(_ => true, context)
/** Checks that the future failed satisfying the predicate and returns the throwable.
* We use this instead of `Future#failed` because the error message that delivers is unhelpful.
* It doesn't tell us what the value actually was.
*/
def mustFailWith(context: String)(
predicate: Throwable => Boolean
)(implicit executionContext: ExecutionContext): Future[Throwable] =
handle(predicate, context)
def mustFailWith(
context: String,
errorCode: ErrorCode,
exceptionMessageSubstring: Option[String] = None,
)(implicit executionContext: ExecutionContext): Future[Unit] = {
for {
error <- mustFail(context)
} yield {
Assertions.assertGrpcError(
t = error,
errorCode = errorCode,
exceptionMessageSubstring = exceptionMessageSubstring,
)
}
}
private def handle(predicate: Throwable => Boolean, context: String)(implicit
executionContext: ExecutionContext
): Future[Throwable] =
future.transform {
case Failure(throwable) if predicate(throwable) => Success(throwable)
case Success(value) => Failure(new ExpectedFailureException(context, value))
case Failure(other) => Failure(other)
}
}
object FutureAssertions {
private val logger = ContextualizedLogger.get(getClass)
/** Runs the test case after the specified delay
*/
def assertAfter[V](
delay: FiniteDuration,
delayMechanism: DelayMechanism,
)(test: => Future[V])(implicit executionContext: ExecutionContext): Future[V] =
delayMechanism.delayBy(delay).flatMap(_ => test)
/** Run the test every `retryDelay` up to `maxRetryDuration`.
* The test case will run up to `ceil(maxRetryDuration / retryDelay)` times.
* The assertion will succeed as soon as any of the test case runs are successful.
* The assertion will fail if no test case runs are successful and the `maxRetryDuration` is exceeded.
*/
def succeedsEventually[V](
retryDelay: FiniteDuration = 100.millis,
maxRetryDuration: FiniteDuration,
delayMechanism: DelayMechanism,
description: String,
)(
test: => Future[V]
)(implicit ec: ExecutionContext, loggingContext: LoggingContext): Future[V] = {
def internalSucceedsEventually(remainingDuration: FiniteDuration): Future[V] = {
val nextRetryRemainingDuration = remainingDuration - retryDelay
if (nextRetryRemainingDuration < Duration.Zero) test.andThen { case Failure(exception) =>
logger.error(
s"Assertion never succeeded after $maxRetryDuration with a delay of $retryDelay. Description: $description",
exception,
)
}
else
assertAfter(retryDelay, delayMechanism)(test).recoverWith { case NonFatal(ex) =>
logger.debug(
s"Failed assertion: $description. Running again with new max duration $nextRetryRemainingDuration",
ex,
)
internalSucceedsEventually(nextRetryRemainingDuration)
}
}
internalSucceedsEventually(maxRetryDuration)
}
def forAllParallel[T](
data: Seq[T]
)(
testCase: T => Future[Unit]
)(implicit ec: ExecutionContext, loggingContext: LoggingContext): Future[Unit] = Future
.traverse(data)(input =>
testCase(input).map(Right(_)).recover { case NonFatal(ex) =>
Left(input -> ex)
}
)
.map { results =>
val (failures, successes) = results.partitionMap(identity)
if (failures.nonEmpty) {
failures
.foreach(res => logger.error(s"Failed parallel test case for input ${res._1}", res._2))
throw ParallelTestFailureException(
s"Failed parallel test case. Failures: ${failures.length}. Success: ${successes.length}\nFailed inputs: ${failures
.map(_._1)
.mkString("[", ",", "]")}",
failures.last._2,
)
}
}
def optionalAssertion(runs: Boolean, description: String)(
assertions: => Future[_]
)(implicit loggingContext: LoggingContext): Future[_] = if (runs) assertions
else {
logger.warn(s"Not running optional assertions: $description")
Future.unit
}
final class ExpectedFailureException[T](context: String, value: T)
extends NoSuchElementException(
s"Expected a failure when $context, but got a successful result of: $value"
)
}
final case class ParallelTestFailureException(message: String, failure: Throwable)
extends RuntimeException(message, failure)

View File

@ -1,33 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import java.math.BigInteger
import com.daml.lf.data.Ref
object HexOffset {
/** Computes the first lexicographical string before [[value]], if it exists.
* If there's no such a string (i.e., the input is 00000000...) then it returns `None`.
* The string uses the base16 lowercase format as per [[Ref.HexString]] definition.
*
* Examples:
*
* - for 000000000... it returns None
* - for 000001000 it returns Some(000000fff)
* - for 00000ab00 it returns Some(00000aaff)
* - for 00007a900 it returns Some(00007a8ff)
*/
def previous(value: Ref.HexString): Option[Ref.HexString] = {
val offsetInteger = new BigInteger(value, 16)
if (offsetInteger == BigInteger.ZERO) {
None
} else {
val hexString = offsetInteger.subtract(BigInteger.ONE).toString(16)
val padding = Seq.fill(value.length - hexString.length)('0')
Some(Ref.HexString.assertFromString(hexString.prependedAll(padding.mkString)))
}
}
}

View File

@ -1,68 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
object Identification {
val greekAlphabet = Vector(
"alpha",
"beta",
"gamma",
"delta",
"epsilon",
"zeta",
"eta",
"theta",
"iota",
"kappa",
"lambda",
"mu",
"nu",
"xi",
"omicron",
"pi",
"rho",
"sigma",
"tau",
"upsilon",
"phi",
"chi",
"psi",
"omega",
)
/** E.g.
*
* val ids = circularWithIndex(Vector("a", "b", "c"))
*
* assert(ids() == "a")
* assert(ids() == "b")
* assert(ids() == "c")
* assert(ids() == "a0")
* assert(ids() == "b0")
* assert(ids() == "c0")
* assert(ids() == "a1")
*/
def circularWithIndex(base: Vector[String]): () => String =
synchronizedProvider(base.iterator ++ Iterator.continually(base).zipWithIndex.flatMap {
case (alphabet, index) => alphabet.map(letter => s"$letter$index")
})
/** E.g.
*
* val ids = indexSuffix("prefix")
*
* assert(ids() == "prefix-0")
* assert(ids() == "prefix-1")
* assert(ids() == "prefix-2")
*/
def indexSuffix(template: String): () => String =
synchronizedProvider(Iterator.from(0).map(n => s"$template-$n"))
/** Rules out race conditions when accessing an iterator
*/
private def synchronizedProvider[A](it: Iterator[A]): () => A =
() => it.synchronized(it.next())
}

View File

@ -1,111 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.v1.active_contracts_service.ActiveContractsServiceGrpc
import com.daml.ledger.api.v1.active_contracts_service.ActiveContractsServiceGrpc.ActiveContractsService
import com.daml.ledger.api.v1.admin.config_management_service.ConfigManagementServiceGrpc
import com.daml.ledger.api.v1.admin.config_management_service.ConfigManagementServiceGrpc.ConfigManagementService
import com.daml.ledger.api.v1.admin.identity_provider_config_service.IdentityProviderConfigServiceGrpc
import com.daml.ledger.api.v1.admin.identity_provider_config_service.IdentityProviderConfigServiceGrpc.IdentityProviderConfigService
import com.daml.ledger.api.v1.admin.metering_report_service.MeteringReportServiceGrpc
import com.daml.ledger.api.v1.admin.metering_report_service.MeteringReportServiceGrpc.MeteringReportService
import com.daml.ledger.api.v1.admin.package_management_service.PackageManagementServiceGrpc
import com.daml.ledger.api.v1.admin.package_management_service.PackageManagementServiceGrpc.PackageManagementService
import com.daml.ledger.api.v1.admin.participant_pruning_service.ParticipantPruningServiceGrpc
import com.daml.ledger.api.v1.admin.participant_pruning_service.ParticipantPruningServiceGrpc.ParticipantPruningService
import com.daml.ledger.api.v1.admin.party_management_service.PartyManagementServiceGrpc
import com.daml.ledger.api.v1.admin.party_management_service.PartyManagementServiceGrpc.PartyManagementService
import com.daml.ledger.api.v1.admin.user_management_service.UserManagementServiceGrpc
import com.daml.ledger.api.v1.admin.user_management_service.UserManagementServiceGrpc.UserManagementService
import com.daml.ledger.api.v1.command_completion_service.CommandCompletionServiceGrpc
import com.daml.ledger.api.v1.command_completion_service.CommandCompletionServiceGrpc.CommandCompletionService
import com.daml.ledger.api.v1.command_service.CommandServiceGrpc
import com.daml.ledger.api.v1.command_service.CommandServiceGrpc.CommandService
import com.daml.ledger.api.v1.command_submission_service.CommandSubmissionServiceGrpc
import com.daml.ledger.api.v1.command_submission_service.CommandSubmissionServiceGrpc.CommandSubmissionService
import com.daml.ledger.api.v1.event_query_service.EventQueryServiceGrpc
import com.daml.ledger.api.v1.event_query_service.EventQueryServiceGrpc.EventQueryService
import com.daml.ledger.api.v1.ledger_configuration_service.LedgerConfigurationServiceGrpc
import com.daml.ledger.api.v1.ledger_configuration_service.LedgerConfigurationServiceGrpc.LedgerConfigurationService
import com.daml.ledger.api.v1.ledger_identity_service.LedgerIdentityServiceGrpc
import com.daml.ledger.api.v1.ledger_identity_service.LedgerIdentityServiceGrpc.LedgerIdentityService
import com.daml.ledger.api.v1.package_service.PackageServiceGrpc
import com.daml.ledger.api.v1.package_service.PackageServiceGrpc.PackageService
import com.daml.ledger.api.v1.testing.time_service.TimeServiceGrpc
import com.daml.ledger.api.v1.testing.time_service.TimeServiceGrpc.TimeService
import com.daml.ledger.api.v1.transaction_service.TransactionServiceGrpc
import com.daml.ledger.api.v1.transaction_service.TransactionServiceGrpc.TransactionService
import com.daml.ledger.api.v1.version_service.VersionServiceGrpc
import com.daml.ledger.api.v1.version_service.VersionServiceGrpc.VersionService
import io.grpc.{Channel, ClientInterceptor}
import io.grpc.health.v1.health.HealthGrpc
import io.grpc.health.v1.health.HealthGrpc.Health
import scala.annotation.nowarn
private[infrastructure] final class LedgerServices(
channel: Channel,
commandInterceptors: Seq[ClientInterceptor],
) {
val activeContracts: ActiveContractsService =
ActiveContractsServiceGrpc.stub(channel)
val command: CommandService =
CommandServiceGrpc.stub(channel).withInterceptors(commandInterceptors: _*)
val commandCompletion: CommandCompletionService =
CommandCompletionServiceGrpc.stub(channel)
val commandSubmission: CommandSubmissionService =
CommandSubmissionServiceGrpc.stub(channel).withInterceptors(commandInterceptors: _*)
val configuration: LedgerConfigurationService =
LedgerConfigurationServiceGrpc.stub(channel)
val health: Health =
HealthGrpc.stub(channel)
val identity: LedgerIdentityService =
LedgerIdentityServiceGrpc.stub(channel): @nowarn(
"cat=deprecation&origin=com\\.daml\\.ledger\\.api\\.v1\\.ledger_identity_service\\..*"
)
val partyManagement: PartyManagementService =
PartyManagementServiceGrpc.stub(channel)
val meteringReport: MeteringReportService =
MeteringReportServiceGrpc.stub(channel)
val packageManagement: PackageManagementService =
PackageManagementServiceGrpc.stub(channel)
val configManagement: ConfigManagementService =
ConfigManagementServiceGrpc.stub(channel)
val participantPruning: ParticipantPruningService =
ParticipantPruningServiceGrpc.stub(channel)
val packages: PackageService =
PackageServiceGrpc.stub(channel)
val transaction: TransactionService =
TransactionServiceGrpc.stub(channel)
val eventQuery: EventQueryService =
EventQueryServiceGrpc.stub(channel)
val time: TimeService =
TimeServiceGrpc.stub(channel)
val version: VersionService =
VersionServiceGrpc.stub(channel)
val userManagement: UserManagementService =
UserManagementServiceGrpc.stub(channel)
val identityProviderConfig: IdentityProviderConfigService =
IdentityProviderConfigServiceGrpc.stub(channel)
}

View File

@ -1,52 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantSession
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Random
private[infrastructure] final class LedgerSession private (
participantSessions: Vector[(String, ParticipantSession)],
shuffleParticipants: Boolean,
)(implicit val executionContext: ExecutionContext) {
private[infrastructure] def createTestContext(
applicationId: String,
identifierSuffix: String,
): Future[LedgerTestContext] = {
val sessions =
if (shuffleParticipants) Random.shuffle(participantSessions)
else participantSessions
Future
.traverse(sessions) { case (endpointId, session) =>
session.createTestContext(
endpointId,
applicationId,
identifierSuffix,
session.features,
)
}
.map(new LedgerTestContext(_))
}
}
object LedgerSession {
def apply(
participantSessions: Vector[ParticipantSession],
shuffleParticipants: Boolean,
)(implicit executionContext: ExecutionContext): LedgerSession = {
val endpointIdProvider =
Identification.circularWithIndex(Identification.greekAlphabet)
val sessions = participantSessions.map(endpointIdProvider() -> _)
new LedgerSession(
sessions,
shuffleParticipants,
)
}
}

View File

@ -1,17 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantSessionConfiguration
import io.grpc.ManagedChannelBuilder
private[testtool] final class LedgerSessionConfiguration(
participantChannelBuilders: Vector[ManagedChannelBuilder[_]],
partyAllocation: PartyAllocationConfiguration,
val shuffleParticipants: Boolean,
) {
val participants: Vector[ParticipantSessionConfiguration] =
for (participantChannelBuilder <- participantChannelBuilders)
yield ParticipantSessionConfiguration(participantChannelBuilder, partyAllocation)
}

View File

@ -1,115 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.Allocation.{Participants, PartyAllocation}
import com.daml.ledger.api.testtool.infrastructure.participant.{Features, ParticipantTestContext}
import com.daml.lf.data.Ref
import com.daml.test.evidence.tag.EvidenceTag
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}
/** @param suite To which collection of tests this case belongs to
* @param shortIdentifier A unique identifier used to generate party names, command identifiers, etc.
* @param description A human-readable description of what this case tests
* @param timeoutScale The factor applied to the default
* @param runConcurrently True if the test is safe be ran concurrently with other tests without affecting their results
* @param partyAllocation What parties need to be allocated on what participants as a setup for the test case
* @param runTestCase The body of the test to be executed
*/
sealed class LedgerTestCase(
val suite: LedgerTestSuite,
val shortIdentifier: Ref.LedgerString,
val description: String,
val timeoutScale: Double,
val runConcurrently: Boolean,
val repeated: Int = 1,
val tags: List[EvidenceTag] = List.empty,
enabled: Features => Boolean,
disabledReason: String,
partyAllocation: PartyAllocation,
runTestCase: ExecutionContext => Seq[ParticipantTestContext] => Participants => Future[Unit],
) {
val name: String = s"${suite.name}:$shortIdentifier"
private def allocatePartiesAndRun(
context: LedgerTestContext
)(implicit ec: ExecutionContext): Future[Unit] = {
for {
participants: Participants <- context.allocateParties(partyAllocation)
result <- runTestCase(ec)(context.configuredParticipants)(participants)
.transformWith { result =>
cleanUpCreatedUsers(context, result)
}
} yield {
result
}
}
def repetitions: Vector[LedgerTestCase.Repetition] =
if (repeated == 1)
Vector(new LedgerTestCase.Repetition(this, repetition = None))
else
(1 to repeated)
.map(i => new LedgerTestCase.Repetition(this, repetition = Some(i -> repeated)))
.toVector
def isEnabled(features: Features, participantCount: Int): Either[String, Unit] =
for {
_ <- Either.cond(enabled(features), (), disabledReason)
_ <- Either.cond(
partyAllocation.minimumParticipantCount <= participantCount,
(),
"Not enough participants to run this test case.",
)
} yield ()
/** Deletes users created during this test case execution.
*/
private def cleanUpCreatedUsers(context: LedgerTestContext, testCaseRunResult: Try[Unit])(implicit
ec: ExecutionContext
): Future[Unit] = {
lazy val deleteCreatedUsersF =
Future.sequence(context.configuredParticipants.map(_.deleteCreatedUsers()))
lazy val deleteIdentityProvidersUsersF =
Future.sequence(context.configuredParticipants.map(_.deleteCreateIdentityProviders()))
testCaseRunResult match {
case Success(v) =>
for {
_ <- deleteCreatedUsersF
_ <- deleteIdentityProvidersUsersF
} yield v
case Failure(exception) =>
// Prioritizing a failure of users' clean-up over the original failure of the test case
// since clean-up failures can affect other test cases.
{
for {
_ <- deleteCreatedUsersF
_ <- deleteIdentityProvidersUsersF
} yield ()
}.flatMap(_ => Future.failed(exception))
}
}
}
object LedgerTestCase {
final class Repetition(val testCase: LedgerTestCase, val repetition: Option[(Int, Int)]) {
def suite: LedgerTestSuite = testCase.suite
def shortIdentifier: Ref.LedgerString = testCase.shortIdentifier
def description: String = testCase.description
def timeoutScale: Double = testCase.timeoutScale
def allocatePartiesAndRun(context: LedgerTestContext)(implicit
ec: ExecutionContext
): Future[Unit] =
testCase.allocatePartiesAndRun(context)
}
}

View File

@ -1,287 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import java.util.concurrent.{ExecutionException, TimeoutException}
import java.util.{Timer, TimerTask}
import org.apache.pekko.actor.ActorSystem
import org.apache.pekko.stream.Materializer
import org.apache.pekko.stream.scaladsl.{Sink, Source}
import com.daml.ledger.api.testtool.infrastructure.LedgerTestCasesRunner._
import com.daml.ledger.api.testtool.infrastructure.PartyAllocationConfiguration.ClosedWorldWaitingForAllParticipants
import com.daml.ledger.api.testtool.infrastructure.future.FutureUtil
import com.daml.ledger.api.testtool.infrastructure.participant.{
ParticipantSession,
ParticipantTestContext,
}
import io.grpc.ClientInterceptor
import org.slf4j.LoggerFactory
import scala.concurrent.duration.{Duration, DurationInt}
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.Try
import scala.util.control.NonFatal
object LedgerTestCasesRunner {
private val DefaultTimeout = 30.seconds
private val timer = new Timer("ledger-test-suite-runner-timer", true)
private val logger = LoggerFactory.getLogger(classOf[LedgerTestCasesRunner])
private[this] val uncaughtExceptionErrorMessage =
"UNEXPECTED UNCAUGHT EXCEPTION, GATHER THE STACKTRACE AND OPEN A _DETAILED_ TICKET DESCRIBING THE ISSUE HERE: https://github.com/digital-asset/daml/issues/new"
private final class UncaughtExceptionError(cause: Throwable)
extends RuntimeException(uncaughtExceptionErrorMessage, cause)
}
final class LedgerTestCasesRunner(
testCases: Vector[LedgerTestCase],
participantChannels: Vector[ChannelEndpoint],
maxConnectionAttempts: Int = 10,
partyAllocation: PartyAllocationConfiguration = ClosedWorldWaitingForAllParticipants,
shuffleParticipants: Boolean = false,
timeoutScaleFactor: Double = 1.0,
concurrentTestRuns: Int = 8,
uploadDars: Boolean = true,
identifierSuffix: String = "test",
commandInterceptors: Seq[ClientInterceptor] = Seq.empty,
) {
private[this] val verifyRequirements: Try[Unit] =
Try {
require(
maxConnectionAttempts > 0,
"The number of connection attempts must be strictly positive",
)
require(timeoutScaleFactor > 0, "The timeout scale factor must be strictly positive")
require(identifierSuffix.nonEmpty, "The identifier suffix cannot be an empty string")
}
def runTests(implicit executionContext: ExecutionContext): Future[Vector[LedgerTestSummary]] =
verifyRequirements.fold(
Future.failed,
_ => prepareResourcesAndRun,
)
private def createTestContextAndStart(
test: LedgerTestCase.Repetition,
session: LedgerSession,
)(implicit executionContext: ExecutionContext): Future[Duration] = {
val execution = Promise[Duration]()
val scaledTimeout = DefaultTimeout * timeoutScaleFactor * test.timeoutScale
val testName =
test.repetition.fold[String](test.shortIdentifier)(r => s"${test.shortIdentifier}_${r._1}")
val startedTest =
session
.createTestContext(testName, identifierSuffix)
.flatMap { context =>
val start = System.nanoTime()
val result = test
.allocatePartiesAndRun(context)
.map(_ => Duration.fromNanos(System.nanoTime() - start))
logger.info(
s"Started '${test.description}'${test.repetition.fold("")(r => s" (${r._1}/${r._2})")} with a timeout of $scaledTimeout."
)
result
}
val testTimeout = new TimerTask {
override def run(): Unit = {
val message = s"Timeout of $scaledTimeout for '${test.description}' hit."
if (execution.tryFailure(new TimeoutException(message))) {
logger.error(message)
}
}
}
timer.schedule(testTimeout, scaledTimeout.toMillis)
startedTest.onComplete { _ =>
testTimeout.cancel()
logger.info(s"Finished '${test.description}'.")
}
execution.completeWith(startedTest).future
}
private def result(
startedTest: Future[Duration]
)(implicit executionContext: ExecutionContext): Future[Either[Result.Failure, Result.Success]] =
startedTest
.map[Either[Result.Failure, Result.Success]](duration => Right(Result.Succeeded(duration)))
.recover[Either[Result.Failure, Result.Success]] {
case Result.Retired =>
Right(Result.Retired)
case Result.Excluded(reason) =>
Right(Result.Excluded(reason))
case _: TimeoutException =>
Left(Result.TimedOut)
case failure: AssertionError =>
Left(Result.Failed(failure))
case NonFatal(box: ExecutionException) =>
box.getCause match {
case failure: AssertionError =>
Left(Result.Failed(failure))
case NonFatal(exception) =>
Left(Result.FailedUnexpectedly(exception))
}
case NonFatal(exception) =>
Left(Result.FailedUnexpectedly(exception))
}
private def summarize(
suite: LedgerTestSuite,
test: LedgerTestCase,
result: Either[Result.Failure, Result.Success],
): LedgerTestSummary =
LedgerTestSummary(suite.name, test.name, test.description, result)
private def run(
test: LedgerTestCase.Repetition,
session: LedgerSession,
)(implicit executionContext: ExecutionContext): Future[Either[Result.Failure, Result.Success]] =
result(createTestContextAndStart(test, session))
private def uploadDarsIfRequired(
sessions: Vector[ParticipantSession]
)(implicit executionContext: ExecutionContext): Future[Unit] =
if (uploadDars) {
FutureUtil
.sequential(sessions) { session =>
logger.info(s"Uploading DAR files for session $session")
for {
context <- session.createInitContext(
applicationId = "upload-dars",
identifierSuffix = identifierSuffix,
features = session.features,
)
// upload the dars sequentially to avoid conflicts
_ <- FutureUtil.sequential(Dars.resources)(uploadDar(context, _))
} yield ()
}
.map(_ => ())
} else {
Future.successful(logger.info("DAR files upload skipped."))
}
private def uploadDar(
context: ParticipantTestContext,
name: String,
)(implicit executionContext: ExecutionContext): Future[Unit] = {
logger.info(s"""Uploading DAR "$name"...""")
context
.uploadDarFile(Dars.read(name))
.map { _ =>
logger.info(s"""Uploaded DAR "$name".""")
}
.recover { case NonFatal(exception) =>
throw new Errors.DarUploadException(name, exception)
}
}
private def createActorSystem: ActorSystem =
ActorSystem(classOf[LedgerTestCasesRunner].getSimpleName)
private def runTestCases(
ledgerSession: LedgerSession,
testCases: Vector[LedgerTestCase],
concurrency: Int,
)(implicit
materializer: Materializer,
executionContext: ExecutionContext,
): Future[Vector[LedgerTestSummary]] = {
val testCaseRepetitions = testCases.flatMap(_.repetitions)
val testCount = testCaseRepetitions.size
logger.info(s"Running $testCount tests with concurrency of $concurrency.")
Source(testCaseRepetitions.zipWithIndex)
.mapAsyncUnordered(concurrency) { case (test, index) =>
run(test, ledgerSession).map(summarize(test.suite, test.testCase, _) -> index)
}
.runWith(Sink.seq)
.map(_.toVector.sortBy(_._2).map(_._1))
}
private def run(
participantChannels: Vector[ChannelEndpoint]
)(implicit
materializer: Materializer,
executionContext: ExecutionContext,
): Future[Vector[LedgerTestSummary]] = {
val sessions: Future[Vector[ParticipantSession]] = ParticipantSession.createSessions(
partyAllocationConfig = partyAllocation,
participantChannels = participantChannels,
maxConnectionAttempts = maxConnectionAttempts,
commandInterceptors = commandInterceptors,
timeoutScaleFactor = timeoutScaleFactor,
)
sessions
.flatMap { sessions: Vector[ParticipantSession] =>
// All the participants should support the same features (for testing at least)
val ledgerFeatures = sessions.head.features
val (disabledTestCases, enabledTestCases) =
testCases.partitionMap(testCase =>
testCase
.isEnabled(ledgerFeatures, sessions.size)
.fold(disabledReason => Left(testCase -> disabledReason), _ => Right(testCase))
)
val excludedTestResults = disabledTestCases
.map { case (testCase, disabledReason) =>
LedgerTestSummary(
testCase.suite.name,
testCase.name,
testCase.description,
Right(Result.Excluded(disabledReason)),
)
}
val (concurrentTestCases, sequentialTestCases) =
enabledTestCases.partition(_.runConcurrently)
val ledgerSession = LedgerSession(
sessions,
shuffleParticipants,
)
val testResults =
for {
_ <- uploadDarsIfRequired(sessions)
concurrentTestResults <- runTestCases(
ledgerSession,
concurrentTestCases,
concurrentTestRuns,
)(materializer, executionContext)
sequentialTestResults <- runTestCases(
ledgerSession,
sequentialTestCases,
concurrency = 1,
)(materializer, executionContext)
} yield concurrentTestResults ++ sequentialTestResults ++ excludedTestResults
testResults.recover {
case NonFatal(e) if !e.isInstanceOf[Errors.FrameworkException] =>
throw new LedgerTestCasesRunner.UncaughtExceptionError(e)
}
}
}
private def prepareResourcesAndRun(implicit
executionContext: ExecutionContext
): Future[Vector[LedgerTestSummary]] = {
val materializerResources =
ResourceOwner.forMaterializerDirectly(() => createActorSystem).acquire()
// When running the tests, explicitly use the materializer's execution context
// The tests will then be executed on it instead of the implicit one -- which
// should only be used to manage resources' lifecycle
val results =
for {
materializer <- materializerResources.asFuture
results <- run(participantChannels)(materializer, executionContext)
} yield results
results.onComplete(_ => materializerResources.release())
results
}
}

View File

@ -1,60 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.Allocation.{
Participant,
PartyAllocation,
Participants,
}
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import scala.collection.immutable
import scala.concurrent.{ExecutionContext, Future}
private[testtool] final class LedgerTestContext private[infrastructure] (
val configuredParticipants: immutable.Seq[ParticipantTestContext]
)(implicit ec: ExecutionContext) {
require(configuredParticipants.nonEmpty, "At least one participant must be provided.")
private[this] val participantsRing = Iterator.continually(configuredParticipants).flatten
/** This allocates participants and a specified number of parties for each participant.
*
* e.g. `allocate(ParticipantAllocation(SingleParty, Parties(3), NoParties, TwoParties))`
* will eventually return:
*
* {{{
* Participants(
* Participant(alpha: ParticipantTestContext, alice: Party),
* Participant(beta: ParticipantTestContext, bob: Party, barbara: Party, bernard: Party),
* Participant(gamma: ParticipantTestContext),
* Participant(delta: ParticipantTestContext, doreen: Party, dan: Party),
* )
* }}}
*
* Each execution of a test case allocates parties on participants,
* then deconstructs the result and uses the various participants
* and parties throughout the test.
*/
def allocateParties(allocation: PartyAllocation): Future[Participants] = {
val participantAllocations: Seq[(ParticipantTestContext, Allocation.PartyCount)] =
allocation.partyCounts.map(nextParticipant() -> _)
val participantsUnderTest: Seq[ParticipantTestContext] = participantAllocations.map(_._1)
Future
.sequence(participantAllocations.map {
case (participant: ParticipantTestContext, partyCount: Allocation.PartyCount) =>
participant
.preallocateParties(partyCount.count, participantsUnderTest)
.map(parties => Participant(participant, parties: _*))
})
.map(participants => Participants(participants: _*))
}
private[this] def nextParticipant(): ParticipantTestContext =
participantsRing.synchronized {
participantsRing.next()
}
}

View File

@ -1,158 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.Allocation.{Participants, PartyAllocation}
import com.daml.ledger.api.testtool.infrastructure.participant.{Features, ParticipantTestContext}
import com.daml.ledger.javaapi.data.{Command, Party}
import com.daml.ledger.javaapi.data.{Identifier => JavaIdentifier}
import com.daml.lf.data.Ref
import com.daml.test.evidence.tag.EvidenceTag
import com.daml.ledger.api.v1.commands.Command.toJavaProto
import com.daml.ledger.api.v1.commands.{Command => CommandV1}
import com.daml.ledger.api.v1.value.Value.Sum
import com.daml.ledger.api.v1.value.{GenMap, Identifier, Optional, Value}
import com.daml.ledger.api.v1.value.{List => ListV1}
import com.daml.ledger.api.v1.value.{Map => MapV1}
import java.math.BigDecimal
import java.time.Instant
import java.util.{List => JList}
import scala.collection.mutable.ListBuffer
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.CollectionConverters._
import scala.language.implicitConversions
abstract class LedgerTestSuite {
private val testCaseBuffer: ListBuffer[LedgerTestCase] = ListBuffer()
final lazy val tests: Vector[LedgerTestCase] = testCaseBuffer.toVector
protected final def test(
shortIdentifier: String,
description: String,
partyAllocation: PartyAllocation,
timeoutScale: Double = 1.0,
runConcurrently: Boolean = true,
repeated: Int = 1,
enabled: Features => Boolean = _ => true,
disabledReason: String = "No reason",
tags: List[EvidenceTag] = List.empty,
)(testCase: ExecutionContext => PartialFunction[Participants, Future[Unit]]): Unit = {
testGivenAllParticipants(
shortIdentifier,
description,
partyAllocation,
timeoutScale,
runConcurrently,
repeated,
enabled,
disabledReason,
tags,
)((ec: ExecutionContext) => (_: Seq[ParticipantTestContext]) => testCase(ec))
}
protected final def testGivenAllParticipants(
shortIdentifier: String,
description: String,
partyAllocation: PartyAllocation,
timeoutScale: Double = 1.0,
runConcurrently: Boolean = true,
repeated: Int = 1,
enabled: Features => Boolean = _ => true,
disabledReason: String = "No reason",
tags: List[EvidenceTag] = List.empty,
)(
testCase: ExecutionContext => Seq[ParticipantTestContext] => PartialFunction[
Participants,
Future[Unit],
]
): Unit = {
val shortIdentifierRef = Ref.LedgerString.assertFromString(shortIdentifier)
testCaseBuffer.append(
new LedgerTestCase(
this,
shortIdentifierRef,
description,
timeoutScale,
runConcurrently,
repeated,
tags,
enabled,
disabledReason,
partyAllocation,
testCase,
)
)
}
def name: String = getClass.getSimpleName
def updateCommands(commands: JList[Command], f: CommandV1 => CommandV1): JList[Command] =
commands.asScala
.map(c => CommandV1.fromJavaProto(c.toProtoCommand))
.map(f)
.map(c => Command.fromProtoCommand(toJavaProto(c)))
.asJava
implicit class IdentifierConverter(id: JavaIdentifier) {
def toV1: Identifier = Identifier.fromJavaProto(id.toProto)
}
implicit def partyToString(party: Party): String = party.getValue
// TODO: when merged in canton (get it from ledger-common)
object TimestampConversion {
val MIN = Instant parse "0001-01-01T00:00:00Z"
val MAX = Instant parse "9999-12-31T23:59:59.999999Z"
}
object ClearIdsImplicits {
def clearIds(value: Value): Value = {
val sum = value.sum match {
case Sum.Record(record) =>
Sum.Record(
record.clearRecordId.copy(fields =
record.fields.map(f => f.copy(value = f.value.map(clearIds)))
)
)
case Sum.Variant(variant) =>
Sum.Variant(variant.clearVariantId.copy(value = variant.value.map(clearIds)))
case Sum.List(list) => Sum.List(ListV1(list.elements.map(clearIds)))
case Sum.Optional(optional) =>
Sum.Optional(Optional(optional.value.map(clearIds)))
case Sum.GenMap(genmap) =>
Sum.GenMap(GenMap(genmap.entries.map({ case GenMap.Entry(k, v) =>
GenMap.Entry(k.map(clearIds), v.map(clearIds))
})))
case Sum.Map(simplemap) =>
Sum.Map(MapV1(simplemap.entries.map({ case MapV1.Entry(k, v) =>
MapV1.Entry(k, v.map(clearIds))
})))
case _ => value.sum
}
Value(sum)
}
implicit class ClearValueIdsImplicits(record: com.daml.ledger.api.v1.value.Record) {
// TODO: remove when java bindings toValue produce an enriched Record w.r.t. type and field name
def clearValueIds: com.daml.ledger.api.v1.value.Record =
clearIds(Value(Sum.Record(record))).getRecord
}
}
object BigDecimalImplicits {
// TODO: when merged in canton (merge it w/ the corresponding BigDecimalImplicits)
implicit class IntToBigDecimal(value: Int) {
def toBigDecimal: BigDecimal = BigDecimal.valueOf(value.toLong)
}
implicit class DoubleToBigDecimal(value: Double) {
def toBigDecimal: BigDecimal = BigDecimal.valueOf(value)
}
}
}

View File

@ -1,11 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
final case class LedgerTestSummary(
suite: String,
name: String,
description: String,
result: Either[Result.Failure, Result.Success],
)

View File

@ -1,32 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
sealed trait PartyAllocationConfiguration {
def allocateParties: Boolean
def waitForAllParticipants: Boolean
}
object PartyAllocationConfiguration {
case object OpenWorld extends PartyAllocationConfiguration {
override val allocateParties: Boolean = false
override val waitForAllParticipants: Boolean = false
}
case object ClosedWorld extends PartyAllocationConfiguration {
override val allocateParties: Boolean = true
override val waitForAllParticipants: Boolean = false
}
case object ClosedWorldWaitingForAllParticipants extends PartyAllocationConfiguration {
override val allocateParties: Boolean = true
override val waitForAllParticipants: Boolean = true
}
}

View File

@ -1,39 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.google.protobuf
import scala.jdk.DurationConverters.{JavaDurationOps, ScalaDurationOps}
object ProtobufConverters {
implicit class JavaDurationConverter(duration: java.time.Duration) {
def asProtobuf: protobuf.duration.Duration =
new protobuf.duration.Duration(duration.getSeconds, duration.getNano)
}
implicit class JavaInstantConverter(instant: java.time.Instant) {
def asProtobuf: protobuf.timestamp.Timestamp =
new protobuf.timestamp.Timestamp(instant.getEpochSecond, instant.getNano)
}
implicit class ScalaDurationConverter(duration: scala.concurrent.duration.FiniteDuration) {
def asProtobuf: protobuf.duration.Duration =
duration.toJava.asProtobuf
}
implicit class ProtobufDurationConverter(duration: protobuf.duration.Duration) {
def asJava: java.time.Duration =
java.time.Duration.ofSeconds(duration.seconds, duration.nanos.toLong)
def asScala: scala.concurrent.duration.Duration =
asJava.toScala
}
implicit class ProtobufTimestampConverter(timestamp: protobuf.timestamp.Timestamp) {
def asJava: java.time.Instant =
java.time.Instant.ofEpochSecond(timestamp.seconds, timestamp.nanos.toLong)
}
}

View File

@ -1,77 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.v1.transaction.TransactionTree
import com.daml.ledger.javaapi.data.Party
import com.daml.lf.data.{Bytes, Ref}
import com.daml.timer.Delayed
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Success, Try}
private[testtool] object RaceConditionTests {
val DefaultRepetitionsNumber: Int = 3
private val WaitBeforeGettingTransactions: FiniteDuration = 500.millis
def executeRepeatedlyWithRandomDelay[T](
numberOfAttempts: Int,
once: => Future[T],
repeated: => Future[T],
)(implicit ec: ExecutionContext): Future[Vector[Try[T]]] = {
val attempts = (1 to numberOfAttempts).toVector
Future.traverse(attempts) { attempt =>
scheduleWithRandomDelay(upTo = 20.millis) { _ =>
(if (attempt == numberOfAttempts) {
once
} else {
repeated
}).transform(Success(_))
}
}
}
private def randomDurationUpTo(limit: FiniteDuration): FiniteDuration =
scala.util.Random.nextInt(limit.toMillis.toInt).millis
private def scheduleWithRandomDelay[T](upTo: FiniteDuration)(f: Unit => Future[T])(implicit
ec: ExecutionContext
): Future[T] =
Delayed.by(randomDurationUpTo(upTo))(()).flatMap(f)
def transactions(
ledger: ParticipantTestContext,
party: Party,
waitBefore: FiniteDuration = WaitBeforeGettingTransactions,
)(implicit ec: ExecutionContext): Future[Vector[TransactionTree]] =
Delayed.by(waitBefore)(()).flatMap(_ => ledger.transactionTrees(party))
def assertTransactionOrder(
expectedFirst: TransactionTree,
expectedSecond: TransactionTree,
): Unit = {
if (offsetLessThan(expectedFirst.offset, expectedSecond.offset)) ()
else fail(s"""Offset ${expectedFirst.offset} is not before ${expectedSecond.offset}
|
|Expected first: ${printTransaction(expectedFirst)}
|Expected second: ${printTransaction(expectedSecond)}
|""".stripMargin)
}
def printTransaction(transactionTree: TransactionTree): String = {
s"""Offset: ${transactionTree.offset}, number of events: ${transactionTree.eventsById.size}
|${transactionTree.eventsById.values.map(e => s" -> $e").mkString("\n")}
|""".stripMargin
}
private def offsetLessThan(a: String, b: String): Boolean =
Bytes.ordering.lt(offsetBytes(a), offsetBytes(b))
private def offsetBytes(offset: String): Bytes = {
Bytes.fromHexString(Ref.HexString.assertFromString(offset))
}
}

View File

@ -1,151 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import java.io.{PrintStream, PrintWriter, StringWriter}
import com.daml.buildinfo.BuildInfo
import scala.util.Try
trait Reporter[A] {
def report(
results: Vector[LedgerTestSummary],
skippedTests: Vector[LedgerTestSummary],
runInfo: Seq[(String, String)],
): A
}
object Reporter {
object ColorizedPrintStreamReporter {
private val reset = "\u001b[0m"
private def red(s: String): String = s"\u001b[31m$s$reset"
private def green(s: String): String = s"\u001b[32m$s$reset"
private def yellow(s: String): String = s"\u001b[33m$s$reset"
private def blue(s: String): String = s"\u001b[34m$s$reset"
private def cyan(s: String): String = s"\u001b[36m$s$reset"
private def render(t: Throwable): Iterator[String] = {
val stringWriter = new StringWriter
val writer = new PrintWriter(stringWriter)
t.printStackTrace(writer)
stringWriter.toString.linesIterator
}
private def extractRelevantLineNumber(t: Throwable): Option[Int] =
t.getStackTrace
.find(stackTraceElement =>
Try(Class.forName(stackTraceElement.getClassName))
.filter(_ != classOf[LedgerTestSuite])
.filter(classOf[LedgerTestSuite].isAssignableFrom)
.isSuccess
)
.map(_.getLineNumber)
}
final class ColorizedPrintStreamReporter(
s: PrintStream,
printStackTraces: Boolean,
) extends Reporter[Unit] {
import ColorizedPrintStreamReporter._
private def indented(msg: String, n: Int = 2): String = {
val indent = " " * n
if (msg != null) msg.linesIterator.map(l => s"$indent$l").mkString("\n")
else ""
}
private def printReport(results: Vector[LedgerTestSummary]): Unit =
results.groupBy(_.suite).foreach { case (suite, summaries) =>
s.println()
s.println(cyan(suite))
for (LedgerTestSummary(_, name, description, result) <- summaries) {
s.print(cyan(s"- [$name] $description ... "))
result match {
case Right(Result.Succeeded(duration)) =>
s.println(green(s"Success (${duration.toMillis} ms)"))
case Right(Result.Retired) =>
s.println(yellow(s"Skipped (retired test)"))
case Right(Result.Excluded(reason)) =>
s.println(yellow(s"Skipped ($reason)"))
case Left(Result.TimedOut) => s.println(red(s"Timeout"))
case Left(Result.Failed(cause)) =>
val message =
extractRelevantLineNumber(cause).fold("Assertion failed") { lineHint =>
s"Assertion failed at line $lineHint"
}
s.println(red(message))
s.println(red(indented(cause.getMessage)))
cause match {
case AssertionErrorWithPreformattedMessage(preformattedMessage, _) =>
preformattedMessage.split("\n").map(indented(_)).foreach(s.println)
case _ => // ignore
}
if (printStackTraces) {
for (renderedStackTraceLine <- render(cause))
s.println(red(indented(renderedStackTraceLine)))
}
case Left(Result.FailedUnexpectedly(cause)) =>
val prefix =
s"Unexpected failure (${cause.getClass.getSimpleName})"
val message =
extractRelevantLineNumber(cause).fold(prefix) { lineHint =>
s"$prefix at line $lineHint"
}
s.println(red(message))
s.println(red(indented(cause.getMessage)))
if (printStackTraces) {
for (renderedStackTraceLine <- render(cause))
s.println(red(indented(renderedStackTraceLine)))
}
}
}
}
override def report(
results: Vector[LedgerTestSummary],
excludedTests: Vector[LedgerTestSummary],
runInfo: Seq[(String, String)],
): Unit = {
s.println()
s.println(blue("#" * 80))
s.println(blue("#"))
s.println(blue(s"# TEST REPORT, version: ${BuildInfo.Version}"))
s.println(blue("#"))
s.println(blue("#" * 80))
s.println()
s.println(yellow("### RUN INFORMATION"))
s.println()
runInfo.foreach { case (label, value) => s.println(cyan(s"$label = $value")) }
val (successes, failures) = results.partition(_.result.isRight)
if (successes.nonEmpty) {
s.println()
s.println(green("### SUCCESSES"))
printReport(successes)
}
if (excludedTests.nonEmpty) {
s.println()
s.println(yellow("### EXCLUDED TESTS"))
printReport(excludedTests)
}
if (failures.nonEmpty) {
s.println()
s.println(red("### FAILURES"))
printReport(failures)
}
}
}
}

View File

@ -1,19 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.resources.{HasExecutionContext, ResourceOwnerFactories}
import com.daml.resources.pekko.PekkoResourceOwnerFactories
import com.daml.resources.grpc.GrpcResourceOwnerFactories
import scala.concurrent.ExecutionContext
import HasExecutionContext.`ExecutionContext has itself`
object ResourceOwner
extends ResourceOwnerFactories[ExecutionContext]
with PekkoResourceOwnerFactories[ExecutionContext]
with GrpcResourceOwnerFactories[ExecutionContext] {
override protected implicit val hasExecutionContext: HasExecutionContext[ExecutionContext] =
implicitly[HasExecutionContext[ExecutionContext]]
}

View File

@ -1,27 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import scala.concurrent.duration.Duration
import scala.util.control.NoStackTrace
private[daml] object Result {
sealed trait Success
final case class Succeeded(duration: Duration) extends Success
case object Retired extends RuntimeException with NoStackTrace with Success
final case class Excluded(reason: String) extends RuntimeException with NoStackTrace with Success
sealed trait Failure
case object TimedOut extends Failure
final case class Failed(cause: AssertionError) extends Failure
final case class FailedUnexpectedly(cause: Throwable) extends Failure
}

View File

@ -1,53 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.testtool.infrastructure.Eventually.eventually
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.javaapi.data.Party
import com.daml.ledger.javaapi.data.codegen.ContractId
import scala.concurrent.{ExecutionContext, Future}
object Synchronize {
/** Create a synchronization point between two participants by ensuring that a
* party created on one participant is visible on the other one.
*
* Useful to ensure two parties distributed across participants both view the
* updates happened _BEFORE_ the call to this method.
*
* This allows us to check that an earlier update which is not to be seen on either
* participant by parties distributed across them is actually not visible and not
* a byproduct of interleaved distributed calls.
*/
final def synchronize(alpha: ParticipantTestContext, beta: ParticipantTestContext)(implicit
ec: ExecutionContext
): Future[Unit] =
for {
alice <- alpha.allocateParty()
bob <- beta.allocateParty()
_ <- alpha.waitForParties(Set(beta), Set(alice, bob))
_ <- beta.waitForParties(Set(alpha), Set(alice, bob))
} yield {
// Nothing to do, by flatmapping over this we know
// the two participants are synchronized up to the
// point before invoking this method
}
final def waitForContract[TCid <: ContractId[?]](
participant: ParticipantTestContext,
party: Party,
contractId: TCid,
)(implicit ec: ExecutionContext): Future[Unit] =
eventually("Wait for contract to become active") {
participant.activeContracts(party).map { events =>
assert(
events.exists(_.contractId == contractId.contractId),
s"Could not find contract $contractId",
)
}
}
}

View File

@ -1,10 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import scala.util.control.NoStackTrace
case object TimeoutException extends RuntimeException with NoStackTrace {
override val getMessage: String = s"Future could not be completed before timeout"
}

View File

@ -1,14 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import java.util.TimerTask
import scala.concurrent.Promise
final class TimeoutTask[A](p: Promise[A]) extends TimerTask {
override def run(): Unit = {
val _ = p.tryFailure(TimeoutException)
}
}

View File

@ -1,27 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import com.daml.ledger.api.v1.event.{ArchivedEvent, CreatedEvent, Event, ExercisedEvent}
import com.daml.ledger.api.v1.transaction.{Transaction, TransactionTree, TreeEvent}
object TransactionHelpers {
def archivedEvents(transaction: Transaction): Vector[ArchivedEvent] =
events(transaction).flatMap(_.event.archived.toList).toVector
def createdEvents(tree: TransactionTree): Vector[CreatedEvent] =
events(tree).flatMap(_.kind.created.toList).toVector
def createdEvents(transaction: Transaction): Vector[CreatedEvent] =
events(transaction).flatMap(_.event.created.toList).toVector
def exercisedEvents(tree: TransactionTree): Vector[ExercisedEvent] =
events(tree).flatMap(_.kind.exercised.toList).toVector
private def events(transaction: Transaction): Iterator[Event] =
transaction.events.iterator
private def events(tree: TransactionTree): Iterator[TreeEvent] =
tree.eventsById.valuesIterator
}

View File

@ -1,21 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure
import java.util.Timer
import scala.concurrent.duration.Duration
import scala.concurrent.{Future, Promise}
object WithTimeout {
private[this] val timer = new Timer("timeout-timer", true)
def apply[A](t: Duration)(f: => Future[A]): Future[A] = {
val p = Promise[A]()
timer.schedule(new TimeoutTask(p), t.toMillis)
p.completeWith(f).future
}
}

View File

@ -1,175 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.assertions
import java.time.Duration
import com.daml.ledger.api.testtool.infrastructure.Assertions.{assertDefined, fail}
import com.daml.ledger.api.testtool.infrastructure.WithTimeout
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext.CompletionResponse
import com.daml.ledger.api.v1.experimental_features.CommandDeduplicationPeriodSupport.{
DurationSupport,
OffsetSupport,
}
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.ledger.javaapi.data.Party
import com.daml.lf.data.Ref
import com.google.protobuf.duration.{Duration => DurationProto}
import io.grpc.Status.Code
import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future}
import scala.math.Ordering.Implicits._
object CommandDeduplicationAssertions {
def assertDeduplicationDuration(
requestedDeduplicationDuration: DurationProto,
completionResponse: CompletionResponse,
submittingParty: Party,
ledger: ParticipantTestContext,
)(implicit executionContext: ExecutionContext): Future[Unit] = {
val requestedDuration = DurationConversion.fromProto(requestedDeduplicationDuration)
ledger.features.commandDeduplicationFeatures.getDeduplicationPeriodSupport.durationSupport match {
case DurationSupport.DURATION_NATIVE_SUPPORT =>
Future {
val reportedDurationProto = assertDefined(
completionResponse.completion.deduplicationPeriod.deduplicationDuration,
"No deduplication duration has been reported",
)
val reportedDuration = DurationConversion.fromProto(reportedDurationProto)
assert(
reportedDuration >= requestedDuration,
s"The reported deduplication duration $reportedDuration was smaller than the requested deduplication duration $requestedDuration.",
)
}
case DurationSupport.DURATION_CONVERT_TO_OFFSET =>
val reportedOffset = assertDefined(
completionResponse.completion.deduplicationPeriod.deduplicationOffset,
"No deduplication offset has been reported",
)
val reportedHexOffset = Ref.HexString.assertFromString(reportedOffset)
if (completionResponse.completion.getStatus.code == Code.ALREADY_EXISTS.value) {
assertReportedOffsetForDuplicateSubmission(
reportedHexOffset,
completionResponse,
submittingParty,
ledger,
)
} else {
assertReportedOffsetForAcceptedSubmission(
reportedHexOffset,
requestedDuration,
completionResponse,
submittingParty,
ledger,
)
}
case DurationSupport.Unrecognized(_) =>
fail("Unrecognized deduplication duration support")
}
}
private def assertReportedOffsetForDuplicateSubmission(
reportedOffset: Ref.HexString,
completionResponse: CompletionResponse,
submittingParty: Party,
ledger: ParticipantTestContext,
)(implicit executionContext: ExecutionContext) =
WithTimeout(5.seconds)(
ledger.findCompletionAtOffset(
reportedOffset,
c => c.commandId == completionResponse.completion.commandId && c.getStatus.code == Code.OK.value,
)(submittingParty)
).map { optAcceptedCompletionResponse =>
val acceptedCompletionResponse = assertDefined(
optAcceptedCompletionResponse,
s"No accepted completion with the command ID '${completionResponse.completion.commandId}' since the reported offset $reportedOffset has been found",
)
assert(
acceptedCompletionResponse.offset.getAbsolute < completionResponse.offset.getAbsolute,
s"An accepted completion with the command ID '${completionResponse.completion.commandId}' at the offset ${acceptedCompletionResponse.offset} that is not before the completion's offset ${completionResponse.offset} has been found",
)
}
private def assertReportedOffsetForAcceptedSubmission(
reportedOffset: Ref.HexString,
requestedDuration: Duration,
completionResponse: CompletionResponse,
submittingParty: Party,
ledger: ParticipantTestContext,
)(implicit executionContext: ExecutionContext) =
WithTimeout(5.seconds)(
ledger.findCompletionAtOffset(
reportedOffset,
_.commandId == completionResponse.completion.commandId,
)(submittingParty)
).map { optReportedOffsetCompletionResponse =>
val reportedOffsetCompletionResponse = assertDefined(
optReportedOffsetCompletionResponse,
s"No completion with the command ID '${completionResponse.completion.commandId}' since the reported offset $reportedOffset has been found",
)
assert(
reportedOffsetCompletionResponse.offset == LedgerOffset.of(
LedgerOffset.Value.Absolute(reportedOffset)
),
s"No completion with the reported offset $reportedOffset has been found, the ${reportedOffsetCompletionResponse.offset} offset has been found instead",
)
val durationBetweenReportedDeduplicationOffsetAndCompletionRecordTimes = Duration
.between(
reportedOffsetCompletionResponse.recordTime,
completionResponse.recordTime,
)
assert(
durationBetweenReportedDeduplicationOffsetAndCompletionRecordTimes >= requestedDuration,
s"The requested deduplication duration $requestedDuration was greater than the duration between the reported deduplication offset and completion record times ($durationBetweenReportedDeduplicationOffsetAndCompletionRecordTimes).",
)
}
def assertDeduplicationOffset(
requestedDeduplicationOffsetCompletionResponse: CompletionResponse,
completionResponse: CompletionResponse,
offsetSupport: OffsetSupport,
): Unit =
offsetSupport match {
case OffsetSupport.OFFSET_NATIVE_SUPPORT =>
val reportedOffset = assertDefined(
completionResponse.completion.deduplicationPeriod.deduplicationOffset,
"No deduplication offset has been reported",
)
val requestedDeduplicationOffset =
requestedDeduplicationOffsetCompletionResponse.offset.getAbsolute
assert(
reportedOffset <= requestedDeduplicationOffset,
s"The reported deduplication offset $reportedOffset was more recent than the requested deduplication offset $requestedDeduplicationOffset.",
)
case OffsetSupport.OFFSET_CONVERT_TO_DURATION =>
val reportedDurationProto = assertDefined(
completionResponse.completion.deduplicationPeriod.deduplicationDuration,
"No deduplication duration has been reported",
)
val reportedDuration = DurationConversion.fromProto(reportedDurationProto)
val durationBetweenDeduplicationOffsetAndCompletionRecordTimes = Duration
.between(
requestedDeduplicationOffsetCompletionResponse.recordTime,
completionResponse.recordTime,
)
assert(
reportedDuration >= durationBetweenDeduplicationOffsetAndCompletionRecordTimes,
s"The reported duration $reportedDuration was smaller than the duration between deduplication offset and completion record times ($durationBetweenDeduplicationOffsetAndCompletionRecordTimes).",
)
case OffsetSupport.Unrecognized(_) | OffsetSupport.OFFSET_NOT_SUPPORTED =>
fail("Deduplication offsets are not supported")
}
object DurationConversion {
def toProto(jDuration: Duration): DurationProto =
DurationProto(jDuration.getSeconds, jDuration.getNano)
def fromProto(pDuration: DurationProto): Duration =
Duration.ofSeconds(pDuration.seconds, pDuration.nanos.toLong)
}
}

View File

@ -1,17 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.future
import scala.concurrent.{ExecutionContext, Future}
object FutureUtil {
def sequential[T, R](elements: Seq[T])(f: T => Future[R])(implicit
ec: ExecutionContext
): Future[Seq[R]] =
elements.foldLeft(Future.successful(Seq.empty[R])) { case (results, newElement) =>
results.flatMap(resultsSoFar => f(newElement).map(resultsSoFar :+ _))
}
}

View File

@ -1,54 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.participant
import com.daml.ledger.api.v1.experimental_features.ExperimentalCommitterEventLog.CommitterEventLogType.CENTRALIZED
import com.daml.ledger.api.v1.experimental_features.{
CommandDeduplicationFeatures,
ExperimentalCommitterEventLog,
ExperimentalContractIds,
}
import com.daml.ledger.api.v1.version_service.{GetLedgerApiVersionResponse, UserManagementFeature}
final case class Features(
userManagement: UserManagementFeature,
staticTime: Boolean,
commandDeduplicationFeatures: CommandDeduplicationFeatures,
optionalLedgerId: Boolean = false,
contractIds: ExperimentalContractIds,
committerEventLog: ExperimentalCommitterEventLog,
explicitDisclosure: Boolean = false,
userAndPartyLocalMetadataExtensions: Boolean = false,
acsActiveAtOffsetFeature: Boolean = false,
templateFilters: Boolean = false,
)
object Features {
val defaultFeatures: Features = Features(
userManagement = UserManagementFeature.defaultInstance,
staticTime = false,
commandDeduplicationFeatures = CommandDeduplicationFeatures.defaultInstance,
contractIds = ExperimentalContractIds.defaultInstance,
committerEventLog = ExperimentalCommitterEventLog.of(eventLogType = CENTRALIZED),
)
def fromApiVersionResponse(response: GetLedgerApiVersionResponse): Features = {
val features = response.getFeatures
val experimental = features.getExperimental
Features(
userManagement = features.getUserManagement,
staticTime = experimental.getStaticTime.supported,
commandDeduplicationFeatures = experimental.getCommandDeduplication,
optionalLedgerId = experimental.optionalLedgerId.isDefined,
contractIds = experimental.getContractIds,
committerEventLog = experimental.getCommitterEventLog,
explicitDisclosure = experimental.getExplicitDisclosure.supported,
userAndPartyLocalMetadataExtensions =
experimental.getUserAndPartyLocalMetadataExtensions.supported,
acsActiveAtOffsetFeature = experimental.getAcsActiveAtOffset.supported,
templateFilters = experimental.getTemplateFilters.supported,
)
}
}

View File

@ -1,127 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.participant
import com.daml.ledger.api.testtool.infrastructure.{
ChannelEndpoint,
Endpoint,
Errors,
LedgerServices,
PartyAllocationConfiguration,
}
import com.daml.ledger.api.v1.ledger_identity_service.GetLedgerIdentityRequest
import com.daml.ledger.api.v1.transaction_service.GetLedgerEndRequest
import com.daml.ledger.api.v1.version_service.GetLedgerApiVersionRequest
import com.daml.timer.RetryStrategy
import io.grpc.ClientInterceptor
import org.slf4j.LoggerFactory
import scala.annotation.nowarn
import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Failure
import scala.util.control.NonFatal
/** Represents a running participant server exposing a set of services.
*/
private[infrastructure] final class ParticipantSession private (
partyAllocationConfig: PartyAllocationConfiguration,
services: LedgerServices,
// The ledger ID is retrieved only once when the participant session is created.
// Changing the ledger ID during a session can result in unexpected consequences.
// The test tool is designed to run tests in an isolated environment but changing the
// global state of the ledger breaks this assumption, no matter what.
ledgerId: String,
ledgerEndpoint: Endpoint,
val features: Features,
timeoutScaleFactor: Double,
)(implicit val executionContext: ExecutionContext) {
private[testtool] def createInitContext(
applicationId: String,
identifierSuffix: String,
features: Features,
): Future[ParticipantTestContext] =
createTestContext(
"init",
applicationId,
identifierSuffix,
features = features,
)
private[testtool] def createTestContext(
endpointId: String,
applicationId: String,
identifierSuffix: String,
features: Features,
): Future[ParticipantTestContext] =
for {
end <- services.transaction.getLedgerEnd(new GetLedgerEndRequest(ledgerId)).map(_.getOffset)
} yield new TimeoutParticipantTestContext(
timeoutScaleFactor,
new SingleParticipantTestContext(
ledgerId = ledgerId,
endpointId = endpointId,
applicationId = applicationId,
identifierSuffix = identifierSuffix,
referenceOffset = end,
services = services,
partyAllocationConfig = partyAllocationConfig,
ledgerEndpoint = ledgerEndpoint,
features = features,
),
)
}
object ParticipantSession {
private val logger = LoggerFactory.getLogger(classOf[ParticipantSession])
def createSessions(
partyAllocationConfig: PartyAllocationConfiguration,
participantChannels: Vector[ChannelEndpoint],
maxConnectionAttempts: Int,
commandInterceptors: Seq[ClientInterceptor],
timeoutScaleFactor: Double,
)(implicit
executionContext: ExecutionContext
): Future[Vector[ParticipantSession]] =
Future.traverse(participantChannels) { participant: ChannelEndpoint =>
val services = new LedgerServices(participant.channel, commandInterceptors)
for {
ledgerId <- RetryStrategy
.exponentialBackoff(attempts = maxConnectionAttempts, 100.millis) { (attempt, wait) =>
services.identity
.getLedgerIdentity(new GetLedgerIdentityRequest)
.map(_.ledgerId)
.andThen { case Failure(_) =>
logger.info(
s"Could not connect to the participant (attempt #$attempt). Trying again in $wait..."
)
}: @nowarn(
"cat=deprecation&origin=com\\.daml\\.ledger\\.api\\.v1\\.ledger_identity_service\\..*"
)
}
.recoverWith { case NonFatal(exception) =>
Future.failed(new Errors.ParticipantConnectionException(exception))
}
features <-
services.version
.getLedgerApiVersion(new GetLedgerApiVersionRequest(ledgerId))
.map(Features.fromApiVersionResponse)
.recover { case failure =>
logger.warn(
s"Could not retrieve feature descriptors from the version service: $failure"
)
Features.defaultFeatures
}
} yield new ParticipantSession(
partyAllocationConfig = partyAllocationConfig,
services = services,
ledgerId = ledgerId,
ledgerEndpoint = participant.endpoint,
features = features,
timeoutScaleFactor = timeoutScaleFactor,
)
}
}

View File

@ -1,14 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.participant
import com.daml.ledger.api.testtool.infrastructure.PartyAllocationConfiguration
import io.grpc.ManagedChannelBuilder
import scala.language.existentials
private[testtool] final case class ParticipantSessionConfiguration(
participant: ManagedChannelBuilder[_],
partyAllocation: PartyAllocationConfiguration,
)

View File

@ -1,475 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.participant
import com.daml.error.ErrorCode
import java.time.Instant
import com.daml.ledger.api.testtool.infrastructure.Endpoint
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext.{
CompletionResponse,
IncludeInterfaceView,
}
import com.daml.ledger.api.testtool.infrastructure.time.DelayMechanism
import com.daml.ledger.api.v1.active_contracts_service.GetActiveContractsRequest
import com.daml.ledger.api.v1.admin.config_management_service.{
GetTimeModelResponse,
SetTimeModelRequest,
SetTimeModelResponse,
TimeModel,
}
import com.daml.ledger.api.v1.admin.object_meta.ObjectMeta
import com.daml.ledger.api.v1.admin.package_management_service.{
PackageDetails,
UploadDarFileRequest,
}
import com.daml.ledger.api.v1.admin.participant_pruning_service.PruneResponse
import com.daml.ledger.api.v1.admin.party_management_service.{
AllocatePartyRequest,
AllocatePartyResponse,
GetPartiesRequest,
GetPartiesResponse,
ListKnownPartiesResponse,
PartyDetails,
UpdatePartyDetailsRequest,
UpdatePartyDetailsResponse,
UpdatePartyIdentityProviderRequest,
UpdatePartyIdentityProviderResponse,
}
import com.daml.ledger.api.v1.command_completion_service.{
Checkpoint,
CompletionEndRequest,
CompletionEndResponse,
CompletionStreamRequest,
CompletionStreamResponse,
}
import com.daml.ledger.api.v1.command_service.{
SubmitAndWaitForTransactionIdResponse,
SubmitAndWaitForTransactionResponse,
SubmitAndWaitForTransactionTreeResponse,
SubmitAndWaitRequest,
}
import com.daml.ledger.api.v1.command_submission_service.SubmitRequest
import com.daml.ledger.api.v1.completion.Completion
import com.daml.ledger.api.v1.event.CreatedEvent
import com.daml.ledger.api.v1.ledger_configuration_service.LedgerConfiguration
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.ledger.api.v1.package_service.{GetPackageResponse, PackageStatus}
import com.daml.ledger.api.v1.transaction.{Transaction, TransactionTree}
import com.daml.ledger.api.v1.transaction_filter.{Filters, TransactionFilter}
import com.daml.ledger.api.v1.event_query_service.{
GetEventsByContractIdRequest,
GetEventsByContractIdResponse,
GetEventsByContractKeyRequest,
GetEventsByContractKeyResponse,
}
import com.daml.ledger.api.v1.transaction_service.{
GetTransactionByEventIdRequest,
GetTransactionByIdRequest,
GetTransactionsRequest,
GetTransactionsResponse,
}
import com.daml.ledger.javaapi.data.{Command, Identifier, Party, Template, Value, Unit => UnitData}
import com.daml.ledger.javaapi.data.codegen.{ContractCompanion, ContractId, Exercised, Update}
import com.daml.lf.data.Ref.HexString
import com.google.protobuf.ByteString
import io.grpc.health.v1.health.HealthCheckResponse
import io.grpc.stub.StreamObserver
import java.util.{List => JList}
import scala.concurrent.{ExecutionContext, Future}
trait ParticipantTestContext extends UserManagementTestContext {
val begin: LedgerOffset =
LedgerOffset(LedgerOffset.Value.Boundary(LedgerOffset.LedgerBoundary.LEDGER_BEGIN))
/** A reference to the moving ledger end. If you want a fixed reference to the offset at
* a given point in time, use [[currentEnd]]
*/
val end: LedgerOffset =
LedgerOffset(LedgerOffset.Value.Boundary(LedgerOffset.LedgerBoundary.LEDGER_END))
val ledgerId: String
val applicationId: String
val endpointId: String
def ledgerEndpoint: Endpoint
def features: Features
def referenceOffset: LedgerOffset
def nextKeyId: () => String
def nextUserId: () => String
def nextIdentityProviderId: () => String
def nextPartyId: () => String
def delayMechanism: DelayMechanism
/** Gets the absolute offset of the ledger end at a point in time. Use [[end]] if you need
* a reference to the moving end of the ledger.
*/
def currentEnd(): Future[LedgerOffset]
/** Works just like [[currentEnd]] but allows to override the ledger identifier.
*
* Used only for low-level testing. Please use the other method unless you want to test the
* behavior of the ledger end endpoint with a wrong ledger identifier.
*/
def currentEnd(overrideLedgerId: String): Future[LedgerOffset]
/** Returns an absolute offset that is beyond the current ledger end.
*
* Note: offsets are opaque byte strings, but they are lexicographically sortable.
* Prepending the current absolute ledger end with non-zero bytes creates an offset that
* is be beyond the current ledger end for the ledger API server.
* The offset might however not be valid for the underlying ledger.
* This method can therefore only be used for offsets that are only interpreted by the
* ledger API server and not sent to the ledger.
*/
def offsetBeyondLedgerEnd(): Future[LedgerOffset]
def time(): Future[Instant]
def setTime(currentTime: Instant, newTime: Instant): Future[Unit]
def listKnownPackages(): Future[Seq[PackageDetails]]
def uploadDarFile(bytes: ByteString): Future[Unit] =
uploadDarFile(new UploadDarFileRequest(bytes))
def uploadDarRequest(bytes: ByteString): UploadDarFileRequest
def uploadDarFile(request: UploadDarFileRequest): Future[Unit]
def participantId(): Future[String]
def listPackages(): Future[Seq[String]]
def getPackage(packageId: String): Future[GetPackageResponse]
def getPackageStatus(packageId: String): Future[PackageStatus]
/** Managed version of party allocation, should be used anywhere a party has
* to be allocated unless the party management service itself is under test
*/
def allocateParty(): Future[Party]
/** Non managed version of party allocation. Use exclusively when testing the party management service.
*/
def allocateParty(
partyIdHint: Option[String] = None,
displayName: Option[String] = None,
localMetadata: Option[ObjectMeta] = None,
identityProviderId: Option[String] = None,
): Future[Party]
def allocateParty(req: AllocatePartyRequest): Future[AllocatePartyResponse]
def updatePartyDetails(req: UpdatePartyDetailsRequest): Future[UpdatePartyDetailsResponse]
def updatePartyIdentityProviderId(
request: UpdatePartyIdentityProviderRequest
): Future[UpdatePartyIdentityProviderResponse]
def allocateParties(n: Int): Future[Vector[Party]]
def getParties(req: GetPartiesRequest): Future[GetPartiesResponse]
def getParties(parties: Seq[Party]): Future[Seq[PartyDetails]]
def listKnownParties(): Future[Set[Party]]
def listKnownPartiesResp(): Future[ListKnownPartiesResponse]
/** @return a future that completes when all the participants can list all the expected parties
*/
def waitForParties(
otherParticipants: Iterable[ParticipantTestContext],
expectedParties: Set[Party],
): Future[Unit]
def activeContracts(
request: GetActiveContractsRequest
): Future[(Option[LedgerOffset], Vector[CreatedEvent])]
def activeContractsIds(
request: GetActiveContractsRequest
): Future[(Option[LedgerOffset], Vector[ContractId[Any]])] = {
activeContracts(request).map { case (offset, createEvents: Seq[CreatedEvent]) =>
(offset, createEvents.map(c => new ContractId[Any](c.contractId)))
}
}
def activeContractsRequest(
parties: Seq[Party],
templateIds: Seq[Identifier] = Seq.empty,
interfaceFilters: Seq[(Identifier, IncludeInterfaceView)] = Seq.empty,
activeAtOffset: String = "",
useTemplateIdBasedLegacyFormat: Boolean = true,
): GetActiveContractsRequest
def activeContracts(parties: Party*): Future[Vector[CreatedEvent]]
def activeContractsByTemplateId(
templateIds: Seq[Identifier],
parties: Party*
): Future[Vector[CreatedEvent]]
/** Create a [[TransactionFilter]] with a set of [[Party]] objects.
* You should use this only when you need to tweak the request of [[flatTransactions]]
* or [[transactionTrees]], otherwise use the shortcut override that allows you to
* directly pass a set of [[Party]]
*/
def transactionFilter(
parties: Seq[Party],
templateIds: Seq[Identifier] = Seq.empty,
interfaceFilters: Seq[(Identifier, IncludeInterfaceView)] = Seq.empty,
useTemplateIdBasedLegacyFormat: Boolean = true,
): TransactionFilter
def filters(
templateIds: Seq[Identifier] = Seq.empty,
interfaceFilters: Seq[(Identifier, IncludeInterfaceView)] = Seq.empty,
useTemplateIdBasedLegacyFormat: Boolean = true,
): Filters
def getTransactionsRequest(
transactionFilter: TransactionFilter,
begin: LedgerOffset = referenceOffset,
): GetTransactionsRequest
def transactionStream(
request: GetTransactionsRequest,
responseObserver: StreamObserver[GetTransactionsResponse],
): Unit
def flatTransactionsByTemplateId(
templateId: Identifier,
parties: Party*
): Future[Vector[Transaction]]
/** Non-managed version of [[flatTransactions]], use this only if you need to tweak the request (i.e. to test low-level details)
*/
def flatTransactions(request: GetTransactionsRequest): Future[Vector[Transaction]]
/** Managed version of [[flatTransactions]], use this unless you need to tweak the request (i.e. to test low-level details)
*/
def flatTransactions(parties: Party*): Future[Vector[Transaction]]
/** Non-managed version of [[flatTransactions]], use this only if you need to tweak the request (i.e. to test low-level details)
*/
def flatTransactions(take: Int, request: GetTransactionsRequest): Future[Vector[Transaction]]
/** Managed version of [[flatTransactions]], use this unless you need to tweak the request (i.e. to test low-level details)
*/
def flatTransactions(take: Int, parties: Party*): Future[Vector[Transaction]]
def transactionTreesByTemplateId(
templateId: Identifier,
parties: Party*
): Future[Vector[TransactionTree]]
/** Non-managed version of [[transactionTrees]], use this only if you need to tweak the request (i.e. to test low-level details)
*/
def transactionTrees(request: GetTransactionsRequest): Future[Vector[TransactionTree]]
/** Managed version of [[transactionTrees]], use this unless you need to tweak the request (i.e. to test low-level details)
*/
def transactionTrees(parties: Party*): Future[Vector[TransactionTree]]
/** Non-managed version of [[transactionTrees]], use this only if you need to tweak the request (i.e. to test low-level details)
*/
def transactionTrees(
take: Int,
request: GetTransactionsRequest,
): Future[Vector[TransactionTree]]
/** Managed version of [[transactionTrees]], use this unless you need to tweak the request (i.e. to test low-level details)
*/
def transactionTrees(take: Int, parties: Party*): Future[Vector[TransactionTree]]
/** Create a [[GetTransactionByIdRequest]] with an identifier and a set of [[Party]] objects.
* You should use this only when you need to tweak the request of [[transactionTreeById]] or
* [[flatTransactionById]], otherwise use the shortcut override that allows you to directly
* pass the identifier and parties.
*/
def getTransactionByIdRequest(
transactionId: String,
parties: Seq[Party],
): GetTransactionByIdRequest
/** Non-managed version of [[transactionTreeById]], use this only if you need to tweak the request (i.e. to test low-level details)
*/
def transactionTreeById(request: GetTransactionByIdRequest): Future[TransactionTree]
/** Managed version of [[transactionTrees]], use this unless you need to tweak the request (i.e. to test low-level details)
*/
def transactionTreeById(transactionId: String, parties: Party*): Future[TransactionTree]
/** Non-managed version of [[flatTransactionById]], use this only if you need to tweak the request (i.e. to test low-level details)
*/
def flatTransactionById(request: GetTransactionByIdRequest): Future[Transaction]
/** Managed version of [[flatTransactionById]], use this unless you need to tweak the request (i.e. to test low-level details)
*/
def flatTransactionById(transactionId: String, parties: Party*): Future[Transaction]
/** Create a [[GetTransactionByEventIdRequest]] with an identifier and a set of [[Party]] objects.
* You should use this only when you need to tweak the request of [[transactionTreeByEventId]] or
* [[flatTransactionByEventId]], otherwise use the shortcut override that allows you to directly
* pass the identifier and parties.
*/
def getTransactionByEventIdRequest(
eventId: String,
parties: Seq[Party],
): GetTransactionByEventIdRequest
/** Non-managed version of [[transactionTreeByEventId]], use this only if you need to tweak the request (i.e. to test low-level details)
*/
def transactionTreeByEventId(request: GetTransactionByEventIdRequest): Future[TransactionTree]
/** Managed version of [[transactionTreeByEventId]], use this unless you need to tweak the request (i.e. to test low-level details)
*/
def transactionTreeByEventId(eventId: String, parties: Party*): Future[TransactionTree]
/** Non-managed version of [[flatTransactionByEventId]], use this only if you need to tweak the request (i.e. to test low-level details)
*/
def flatTransactionByEventId(request: GetTransactionByEventIdRequest): Future[Transaction]
/** Managed version of [[flatTransactionByEventId]], use this unless you need to tweak the request (i.e. to test low-level details)
*/
def flatTransactionByEventId(eventId: String, parties: Party*): Future[Transaction]
def getEventsByContractId(
request: GetEventsByContractIdRequest
): Future[GetEventsByContractIdResponse]
def getEventsByContractKey(
request: GetEventsByContractKeyRequest
): Future[GetEventsByContractKeyResponse]
def create[
TCid <: ContractId[T],
T <: Template,
](
party: Party,
template: T,
)(implicit companion: ContractCompanion[?, TCid, T]): Future[TCid]
def create[TCid <: ContractId[T], T <: Template](
actAs: List[Party],
readAs: List[Party],
template: T,
)(implicit companion: ContractCompanion[?, TCid, T]): Future[TCid]
def createAndGetTransactionId[TCid <: ContractId[T], T <: Template](
party: Party,
template: T,
)(implicit companion: ContractCompanion[?, TCid, T]): Future[(String, TCid)]
def exercise[T](
party: Party,
exercise: Update[T],
): Future[TransactionTree]
def exercise[T](
actAs: List[Party],
readAs: List[Party],
exercise: Update[T],
): Future[TransactionTree]
def exerciseForFlatTransaction[T](
party: Party,
exercise: Update[T],
): Future[Transaction]
def exerciseAndGetContract[TCid <: ContractId[T], T](
party: Party,
exercise: Update[Exercised[TCid]],
)(implicit companion: ContractCompanion[?, TCid, T]): Future[TCid]
def exerciseAndGetContractNoDisclose[TCid <: ContractId[?]](
party: Party,
exercise: Update[Exercised[UnitData]],
)(implicit companion: ContractCompanion[?, TCid, ?]): Future[TCid]
def exerciseByKey(
party: Party,
template: Identifier,
key: Value,
choice: String,
argument: Value,
): Future[TransactionTree]
def submitRequest(
actAs: List[Party],
readAs: List[Party],
commands: JList[Command],
): SubmitRequest
def submitRequest(party: Party, commands: JList[Command] = JList.of()): SubmitRequest
def submitAndWaitRequest(
actAs: List[Party],
readAs: List[Party],
commands: JList[Command],
): SubmitAndWaitRequest
def submitAndWaitRequest(party: Party, commands: JList[Command]): SubmitAndWaitRequest
def submit(request: SubmitRequest): Future[Unit]
def submitAndWait(request: SubmitAndWaitRequest): Future[Unit]
def submitAndWaitForTransactionId(
request: SubmitAndWaitRequest
): Future[SubmitAndWaitForTransactionIdResponse]
def submitAndWaitForTransaction(
request: SubmitAndWaitRequest
): Future[SubmitAndWaitForTransactionResponse]
def submitAndWaitForTransactionTree(
request: SubmitAndWaitRequest
): Future[SubmitAndWaitForTransactionTreeResponse]
def submitRequestAndTolerateGrpcError[T](
errorCode: ErrorCode,
submitAndWaitGeneric: ParticipantTestContext => Future[T],
): Future[T]
def completionStreamRequest(from: LedgerOffset = referenceOffset)(
parties: Party*
): CompletionStreamRequest
def completionEnd(request: CompletionEndRequest): Future[CompletionEndResponse]
def completionStream(
request: CompletionStreamRequest,
streamObserver: StreamObserver[CompletionStreamResponse],
): Unit
def firstCompletions(request: CompletionStreamRequest): Future[Vector[Completion]]
def firstCompletions(parties: Party*): Future[Vector[Completion]]
def findCompletionAtOffset(
offset: HexString,
p: Completion => Boolean,
)(parties: Party*): Future[Option[CompletionResponse]]
def findCompletion(
request: CompletionStreamRequest
)(p: Completion => Boolean): Future[Option[CompletionResponse]]
def findCompletion(parties: Party*)(
p: Completion => Boolean
): Future[Option[CompletionResponse]]
def checkpoints(n: Int, request: CompletionStreamRequest): Future[Vector[Checkpoint]]
def checkpoints(n: Int, from: LedgerOffset = referenceOffset)(
parties: Party*
): Future[Vector[Checkpoint]]
def firstCheckpoint(request: CompletionStreamRequest): Future[Checkpoint]
def firstCheckpoint(parties: Party*): Future[Checkpoint]
def nextCheckpoint(request: CompletionStreamRequest): Future[Checkpoint]
def nextCheckpoint(from: LedgerOffset, parties: Party*): Future[Checkpoint]
def configuration(overrideLedgerId: Option[String] = None): Future[LedgerConfiguration]
def checkHealth(): Future[HealthCheckResponse]
def watchHealth(): Future[Seq[HealthCheckResponse]]
def getTimeModel(): Future[GetTimeModelResponse]
def setTimeModel(
mrt: Instant,
generation: Long,
newTimeModel: TimeModel,
): Future[SetTimeModelResponse]
def setTimeModelRequest(
mrt: Instant,
generation: Long,
newTimeModel: TimeModel,
): SetTimeModelRequest
def setTimeModel(
request: SetTimeModelRequest
): Future[SetTimeModelResponse]
private[infrastructure] def preallocateParties(
n: Int,
participants: Iterable[ParticipantTestContext],
): Future[Vector[Party]]
def prune(
pruneUpTo: LedgerOffset,
attempts: Int = 10,
pruneAllDivulgedContracts: Boolean = false,
): Future[PruneResponse]
/** We are retrying a command submission + pruning to get a safe-to-prune offset for Canton.
* That's because in Canton pruning will fail unless ACS commitments have been exchanged between participants.
* To this end, repeatedly submitting commands is prompting Canton to exchange ACS commitments
* and allows the pruning call to eventually succeed.
*/
def pruneCantonSafe(
pruneUpTo: LedgerOffset,
party: Party,
dummyCommand: Party => JList[Command],
pruneAllDivulgedContracts: Boolean = false,
)(implicit ec: ExecutionContext): Future[Unit]
def latestPrunedOffsets(): Future[(LedgerOffset, LedgerOffset)]
}
object ParticipantTestContext {
type IncludeInterfaceView = Boolean
case class CompletionResponse(completion: Completion, offset: LedgerOffset, recordTime: Instant)
}

View File

@ -1,959 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.participant
import com.daml.error.ErrorCode
import java.time.{Clock, Instant}
import com.daml.grpc.test.StreamConsumer
import com.daml.ledger.api.testtool.infrastructure.Eventually.eventually
import com.daml.ledger.api.testtool.infrastructure.ProtobufConverters._
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext.{
CompletionResponse,
IncludeInterfaceView,
}
import com.daml.ledger.api.testtool.infrastructure.time.{
DelayMechanism,
StaticTimeDelayMechanism,
TimeDelayMechanism,
}
import com.daml.ledger.api.testtool.infrastructure.{
Endpoint,
FutureAssertions,
HexOffset,
Identification,
LedgerServices,
PartyAllocationConfiguration,
}
import com.daml.ledger.api.v1.{value => v1}
import com.daml.ledger.api.v1.active_contracts_service.{
GetActiveContractsRequest,
GetActiveContractsResponse,
}
import com.daml.ledger.api.v1.admin.config_management_service.{
GetTimeModelRequest,
GetTimeModelResponse,
SetTimeModelRequest,
SetTimeModelResponse,
TimeModel,
}
import com.daml.ledger.api.v1.admin.object_meta.ObjectMeta
import com.daml.ledger.api.v1.admin.package_management_service.{
ListKnownPackagesRequest,
PackageDetails,
UploadDarFileRequest,
}
import com.daml.ledger.api.v1.admin.participant_pruning_service.{PruneRequest, PruneResponse}
import com.daml.ledger.api.v1.admin.party_management_service.{
AllocatePartyRequest,
AllocatePartyResponse,
GetParticipantIdRequest,
GetPartiesRequest,
GetPartiesResponse,
ListKnownPartiesRequest,
ListKnownPartiesResponse,
PartyDetails,
UpdatePartyDetailsRequest,
UpdatePartyDetailsResponse,
UpdatePartyIdentityProviderRequest,
UpdatePartyIdentityProviderResponse,
}
import com.daml.ledger.api.v1.command_completion_service.{
Checkpoint,
CompletionEndRequest,
CompletionEndResponse,
CompletionStreamRequest,
CompletionStreamResponse,
}
import com.daml.ledger.api.v1.command_service.{
SubmitAndWaitForTransactionIdResponse,
SubmitAndWaitForTransactionResponse,
SubmitAndWaitForTransactionTreeResponse,
SubmitAndWaitRequest,
}
import com.daml.ledger.api.v1.command_submission_service.SubmitRequest
import com.daml.ledger.api.v1.commands.{Commands, Command => ApiCommand}
import com.daml.ledger.api.v1.completion.Completion
import com.daml.ledger.api.v1.event.Event.Event.Created
import com.daml.ledger.api.v1.event.{CreatedEvent, Event}
import com.daml.ledger.api.v1.ledger_configuration_service.{
GetLedgerConfigurationRequest,
GetLedgerConfigurationResponse,
LedgerConfiguration,
}
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.ledger.api.v1.package_service._
import com.daml.ledger.api.v1.testing.time_service.{GetTimeRequest, GetTimeResponse, SetTimeRequest}
import com.daml.ledger.api.v1.transaction.{Transaction, TransactionTree}
import com.daml.ledger.api.v1.transaction_filter.{
Filters,
InclusiveFilters,
InterfaceFilter,
TemplateFilter,
TransactionFilter,
}
import com.daml.ledger.api.v1.event_query_service.{
GetEventsByContractIdRequest,
GetEventsByContractIdResponse,
GetEventsByContractKeyRequest,
GetEventsByContractKeyResponse,
}
import com.daml.ledger.api.v1.transaction_service.{
GetLatestPrunedOffsetsRequest,
GetLedgerEndRequest,
GetTransactionByEventIdRequest,
GetTransactionByIdRequest,
GetTransactionsRequest,
GetTransactionsResponse,
}
import com.daml.ledger.javaapi.data.{
Command,
ExerciseByKeyCommand,
Identifier,
Party,
Template,
Value,
Unit => UnitData,
}
import com.daml.ledger.javaapi.data.codegen.{ContractCompanion, ContractId, Exercised, Update}
import com.daml.lf.data.Ref
import com.daml.logging.{ContextualizedLogger, LoggingContext}
import com.daml.timer.Delayed
import com.google.protobuf.ByteString
import io.grpc.StatusRuntimeException
import io.grpc.health.v1.health.{HealthCheckRequest, HealthCheckResponse}
import io.grpc.protobuf.StatusProto
import io.grpc.stub.StreamObserver
import java.util.{List => JList}
import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.CollectionConverters._
import scala.util.{Failure, Success}
import scala.util.control.NonFatal
/** Exposes services running on some participant server in a test case.
*
* Each time a test case is run it receives a fresh instance of [[SingleParticipantTestContext]]
* (one for every used participant server).
*/
final class SingleParticipantTestContext private[participant] (
val ledgerId: String,
val endpointId: String,
val applicationId: String,
identifierSuffix: String,
val referenceOffset: LedgerOffset,
protected[participant] val services: LedgerServices,
partyAllocationConfig: PartyAllocationConfiguration,
val ledgerEndpoint: Endpoint,
val features: Features,
)(protected[participant] implicit val ec: ExecutionContext)
extends ParticipantTestContext {
private val logger = ContextualizedLogger.get(getClass)
private[this] val identifierPrefix =
s"$applicationId-$endpointId-$identifierSuffix"
private[this] def nextIdGenerator(name: String, lowerCase: Boolean = false): () => String = {
val f = Identification.indexSuffix(s"$identifierPrefix-$name")
if (lowerCase)
() => f().toLowerCase
else
f
}
private[this] val nextPartyHintId: () => String = nextIdGenerator("party")
private[this] val nextCommandId: () => String = nextIdGenerator("command")
private[this] val nextSubmissionId: () => String = nextIdGenerator("submission")
private[this] val workflowId: String = s"$applicationId-$identifierSuffix"
override val nextKeyId: () => String = nextIdGenerator("key")
override val nextUserId: () => String = nextIdGenerator("user", lowerCase = true)
override val nextPartyId: () => String = nextIdGenerator("party", lowerCase = true)
override val nextIdentityProviderId: () => String = nextIdGenerator("idp", lowerCase = true)
override lazy val delayMechanism: DelayMechanism = if (features.staticTime) {
new StaticTimeDelayMechanism(this)
} else
new TimeDelayMechanism()
override def toString: String = s"participant $endpointId"
override def currentEnd(): Future[LedgerOffset] =
services.transaction
.getLedgerEnd(new GetLedgerEndRequest(ledgerId))
.map(_.getOffset)
override def currentEnd(overrideLedgerId: String): Future[LedgerOffset] =
services.transaction
.getLedgerEnd(new GetLedgerEndRequest(overrideLedgerId))
.map(_.getOffset)
override def latestPrunedOffsets(): Future[(LedgerOffset, LedgerOffset)] =
services.transaction
.getLatestPrunedOffsets(GetLatestPrunedOffsetsRequest())
.map(response =>
response.getParticipantPrunedUpToInclusive -> response.getAllDivulgedContractsPrunedUpToInclusive
)
override def offsetBeyondLedgerEnd(): Future[LedgerOffset] =
currentEnd().map(end => LedgerOffset(LedgerOffset.Value.Absolute("ffff" + end.getAbsolute)))
override def time(): Future[Instant] =
new StreamConsumer[GetTimeResponse](services.time.getTime(new GetTimeRequest(ledgerId), _))
.first()
.map(_.map(r => r.getCurrentTime.asJava).get)
.recover { case NonFatal(_) =>
Clock.systemUTC().instant()
}
override def setTime(currentTime: Instant, newTime: Instant): Future[Unit] =
services.time
.setTime(
SetTimeRequest(
ledgerId = ledgerId,
currentTime = Some(currentTime.asProtobuf),
newTime = Some(newTime.asProtobuf),
)
)
.map(_ => ())
override def listKnownPackages(): Future[Seq[PackageDetails]] =
services.packageManagement
.listKnownPackages(new ListKnownPackagesRequest)
.map(_.packageDetails)
override def uploadDarRequest(bytes: ByteString): UploadDarFileRequest =
new UploadDarFileRequest(bytes, nextSubmissionId())
override def uploadDarFile(request: UploadDarFileRequest): Future[Unit] =
services.packageManagement
.uploadDarFile(request)
.map(_ => ())
override def participantId(): Future[String] =
services.partyManagement
.getParticipantId(new GetParticipantIdRequest)
.map(_.participantId)
override def listPackages(): Future[Seq[String]] =
services.packages
.listPackages(new ListPackagesRequest(ledgerId))
.map(_.packageIds)
override def getPackage(packageId: String): Future[GetPackageResponse] =
services.packages.getPackage(new GetPackageRequest(ledgerId, packageId))
override def getPackageStatus(packageId: String): Future[PackageStatus] =
services.packages
.getPackageStatus(new GetPackageStatusRequest(ledgerId, packageId))
.map(_.packageStatus)
override def allocateParty(): Future[Party] =
services.partyManagement
.allocateParty(new AllocatePartyRequest(partyIdHint = nextPartyHintId()))
.map(r => new Party(r.partyDetails.get.party))
override def allocateParty(
partyIdHint: Option[String] = None,
displayName: Option[String] = None,
localMetadata: Option[ObjectMeta] = None,
identityProviderId: Option[String] = None,
): Future[Party] =
services.partyManagement
.allocateParty(
new AllocatePartyRequest(
partyIdHint = partyIdHint.getOrElse(""),
displayName = displayName.getOrElse(""),
localMetadata = localMetadata,
identityProviderId = identityProviderId.getOrElse(""),
)
)
.map(r => new Party(r.partyDetails.get.party))
override def allocateParty(req: AllocatePartyRequest): Future[AllocatePartyResponse] =
services.partyManagement
.allocateParty(req)
override def updatePartyDetails(
req: UpdatePartyDetailsRequest
): Future[UpdatePartyDetailsResponse] = {
services.partyManagement.updatePartyDetails(req)
}
def updatePartyIdentityProviderId(
request: UpdatePartyIdentityProviderRequest
): Future[UpdatePartyIdentityProviderResponse] =
services.partyManagement.updatePartyIdentityProviderId(request)
override def allocateParties(n: Int): Future[Vector[Party]] =
Future.sequence(Vector.fill(n)(allocateParty()))
override def getParties(req: GetPartiesRequest): Future[GetPartiesResponse] =
services.partyManagement.getParties(req)
override def getParties(parties: Seq[Party]): Future[Seq[PartyDetails]] =
services.partyManagement
.getParties(GetPartiesRequest(parties.map(_.getValue)))
.map(_.partyDetails)
override def listKnownParties(): Future[Set[Party]] =
services.partyManagement
.listKnownParties(new ListKnownPartiesRequest())
.map(_.partyDetails.map(partyDetails => new Party(partyDetails.party)).toSet)
override def listKnownPartiesResp(): Future[ListKnownPartiesResponse] =
services.partyManagement
.listKnownParties(new ListKnownPartiesRequest())
override def waitForParties(
otherParticipants: Iterable[ParticipantTestContext],
expectedParties: Set[Party],
): Future[Unit] =
if (partyAllocationConfig.waitForAllParticipants) {
eventually("Wait for parties") {
val participants = otherParticipants.toSet + this
Future
.sequence(participants.map(participant => {
participant
.listKnownParties()
.map { actualParties =>
assert(
expectedParties.subsetOf(actualParties),
s"Parties from $this never appeared on $participant.",
)
}
}))
.map(_ => ())
}
} else {
Future.unit
}
override def activeContracts(
request: GetActiveContractsRequest
): Future[(Option[LedgerOffset], Vector[CreatedEvent])] =
for {
contracts <- new StreamConsumer[GetActiveContractsResponse](
services.activeContracts.getActiveContracts(request, _)
).all()
} yield contracts.lastOption
.map(c => LedgerOffset(LedgerOffset.Value.Absolute(c.offset))) -> contracts
.flatMap(_.activeContracts)
override def activeContractsRequest(
parties: Seq[Party],
templateIds: Seq[Identifier] = Seq.empty,
interfaceFilters: Seq[(Identifier, IncludeInterfaceView)] = Seq.empty,
activeAtOffset: String = "",
useTemplateIdBasedLegacyFormat: Boolean = true,
): GetActiveContractsRequest =
new GetActiveContractsRequest(
ledgerId = ledgerId,
filter = Some(
transactionFilter(parties, templateIds, interfaceFilters, useTemplateIdBasedLegacyFormat)
),
verbose = true,
activeAtOffset,
)
override def activeContracts(parties: Party*): Future[Vector[CreatedEvent]] =
activeContractsByTemplateId(Seq.empty, parties: _*)
override def activeContractsByTemplateId(
templateIds: Seq[Identifier],
parties: Party*
): Future[Vector[CreatedEvent]] =
activeContracts(
activeContractsRequest(
parties,
templateIds,
useTemplateIdBasedLegacyFormat = !features.templateFilters,
)
).map(_._2)
def transactionFilter(
parties: Seq[Party],
templateIds: Seq[Identifier] = Seq.empty,
interfaceFilters: Seq[(Identifier, IncludeInterfaceView)] = Seq.empty,
useTemplateIdBasedLegacyFormat: Boolean = true,
): TransactionFilter =
new TransactionFilter(
parties
.map(party =>
party.getValue -> filters(templateIds, interfaceFilters, useTemplateIdBasedLegacyFormat)
)
.toMap
)
override def filters(
templateIds: Seq[Identifier] = Seq.empty,
interfaceFilters: Seq[(Identifier, IncludeInterfaceView)] = Seq.empty,
useTemplateIdBasedLegacyFormat: Boolean = true,
): Filters = new Filters(
if (templateIds.isEmpty && interfaceFilters.isEmpty) None
else
Some(
new InclusiveFilters(
templateIds =
if (useTemplateIdBasedLegacyFormat)
templateIds
.map(id => v1.Identifier.fromJavaProto(id.toProto))
.toSeq
else scala.Seq.empty,
interfaceFilters = interfaceFilters.map { case (id, includeInterfaceView) =>
new InterfaceFilter(
Some(v1.Identifier.fromJavaProto(id.toProto)),
includeInterfaceView = includeInterfaceView,
)
}.toSeq,
templateFilters =
if (!useTemplateIdBasedLegacyFormat)
templateIds
.map(tid =>
new TemplateFilter(
Some(v1.Identifier.fromJavaProto(tid.toProto)),
false,
)
)
.toSeq
else
scala.Seq.empty,
)
)
)
def getTransactionsRequest(
transactionFilter: TransactionFilter,
begin: LedgerOffset = referenceOffset,
): GetTransactionsRequest = new GetTransactionsRequest(
ledgerId = ledgerId,
begin = Some(begin),
end = Some(end),
filter = Some(transactionFilter),
verbose = true,
)
private def transactions[Res](
n: Int,
request: GetTransactionsRequest,
service: (GetTransactionsRequest, StreamObserver[Res]) => Unit,
): Future[Vector[Res]] =
new StreamConsumer[Res](service(request, _)).take(n)
private def transactions[Res](
request: GetTransactionsRequest,
service: (GetTransactionsRequest, StreamObserver[Res]) => Unit,
): Future[Vector[Res]] =
new StreamConsumer[Res](service(request, _)).all()
override def transactionStream(
request: GetTransactionsRequest,
responseObserver: StreamObserver[GetTransactionsResponse],
): Unit =
services.transaction.getTransactions(request, responseObserver)
override def flatTransactionsByTemplateId(
templateId: Identifier,
parties: Party*
): Future[Vector[Transaction]] =
flatTransactions(getTransactionsRequest(transactionFilter(parties, Seq(templateId))))
override def flatTransactions(request: GetTransactionsRequest): Future[Vector[Transaction]] =
transactions(request, services.transaction.getTransactions)
.map(_.flatMap(_.transactions))
override def flatTransactions(parties: Party*): Future[Vector[Transaction]] =
flatTransactions(getTransactionsRequest(transactionFilter(parties)))
override def flatTransactions(
take: Int,
request: GetTransactionsRequest,
): Future[Vector[Transaction]] =
transactions(take, request, services.transaction.getTransactions)
.map(_.flatMap(_.transactions))
override def flatTransactions(take: Int, parties: Party*): Future[Vector[Transaction]] =
flatTransactions(take, getTransactionsRequest(transactionFilter(parties)))
override def transactionTreesByTemplateId(
templateId: Identifier,
parties: Party*
): Future[Vector[TransactionTree]] =
transactionTrees(getTransactionsRequest(transactionFilter(parties, Seq(templateId))))
override def transactionTrees(request: GetTransactionsRequest): Future[Vector[TransactionTree]] =
transactions(request, services.transaction.getTransactionTrees)
.map(_.flatMap(_.transactions))
override def transactionTrees(parties: Party*): Future[Vector[TransactionTree]] =
transactionTrees(getTransactionsRequest(transactionFilter(parties)))
override def transactionTrees(
take: Int,
request: GetTransactionsRequest,
): Future[Vector[TransactionTree]] =
transactions(take, request, services.transaction.getTransactionTrees)
.map(_.flatMap(_.transactions))
override def transactionTrees(take: Int, parties: Party*): Future[Vector[TransactionTree]] =
transactionTrees(take, getTransactionsRequest(transactionFilter(parties)))
override def getTransactionByIdRequest(
transactionId: String,
parties: Seq[Party],
): GetTransactionByIdRequest =
new GetTransactionByIdRequest(ledgerId, transactionId, parties.map(_.getValue))
override def transactionTreeById(request: GetTransactionByIdRequest): Future[TransactionTree] =
services.transaction.getTransactionById(request).map(_.getTransaction)
override def transactionTreeById(
transactionId: String,
parties: Party*
): Future[TransactionTree] =
transactionTreeById(getTransactionByIdRequest(transactionId, parties))
override def flatTransactionById(request: GetTransactionByIdRequest): Future[Transaction] =
services.transaction.getFlatTransactionById(request).map(_.getTransaction)
override def flatTransactionById(transactionId: String, parties: Party*): Future[Transaction] =
flatTransactionById(getTransactionByIdRequest(transactionId, parties))
override def getTransactionByEventIdRequest(
eventId: String,
parties: Seq[Party],
): GetTransactionByEventIdRequest =
new GetTransactionByEventIdRequest(ledgerId, eventId, parties.map(_.getValue))
override def transactionTreeByEventId(
request: GetTransactionByEventIdRequest
): Future[TransactionTree] =
services.transaction.getTransactionByEventId(request).map(_.getTransaction)
override def transactionTreeByEventId(eventId: String, parties: Party*): Future[TransactionTree] =
transactionTreeByEventId(getTransactionByEventIdRequest(eventId, parties))
override def flatTransactionByEventId(
request: GetTransactionByEventIdRequest
): Future[Transaction] =
services.transaction
.getFlatTransactionByEventId(request)
.map(_.getTransaction)
override def flatTransactionByEventId(eventId: String, parties: Party*): Future[Transaction] =
flatTransactionByEventId(getTransactionByEventIdRequest(eventId, parties))
private def extractContracts[TCid <: ContractId[?]](transaction: Transaction)(implicit
companion: ContractCompanion[?, TCid, ?]
): Seq[TCid] =
transaction.events.collect { case Event(Created(e)) =>
companion.toContractId(new ContractId(e.contractId))
}
override def getEventsByContractId(
request: GetEventsByContractIdRequest
): Future[GetEventsByContractIdResponse] =
services.eventQuery.getEventsByContractId(request)
override def getEventsByContractKey(
request: GetEventsByContractKeyRequest
): Future[GetEventsByContractKeyResponse] =
services.eventQuery.getEventsByContractKey(request)
override def create[
TCid <: ContractId[T],
T <: Template,
](
party: Party,
template: T,
)(implicit companion: ContractCompanion[?, TCid, T]): Future[TCid] =
submitAndWaitForTransaction(
submitAndWaitRequest(party, template.create.commands)
)
.map(response => extractContracts(response.getTransaction).head)
override def create[TCid <: ContractId[T], T <: Template](
actAs: List[Party],
readAs: List[Party],
template: T,
)(implicit companion: ContractCompanion[?, TCid, T]): Future[TCid] =
submitAndWaitForTransaction(
submitAndWaitRequest(actAs, readAs, template.create.commands)
).map(response => extractContracts(response.getTransaction).head)
override def createAndGetTransactionId[TCid <: ContractId[
T
], T <: Template](
party: Party,
template: T,
)(implicit companion: ContractCompanion[?, TCid, T]): Future[(String, TCid)] =
submitAndWaitForTransaction(
submitAndWaitRequest(party, template.create.commands)
)
.map(_.getTransaction)
.map(tx =>
tx.transactionId -> tx.events.collect { case Event(Created(e)) =>
companion.toContractId(new ContractId(e.contractId))
}.head
)
override def exercise[T](
party: Party,
exercise: Update[T],
): Future[TransactionTree] =
submitAndWaitForTransactionTree(
submitAndWaitRequest(party, exercise.commands)
).map(_.getTransaction)
override def exercise[T](
actAs: List[Party],
readAs: List[Party],
exercise: Update[T],
): Future[TransactionTree] =
submitAndWaitForTransactionTree(
submitAndWaitRequest(actAs, readAs, exercise.commands)
).map(_.getTransaction)
override def exerciseForFlatTransaction[T](
party: Party,
exercise: Update[T],
): Future[Transaction] =
submitAndWaitForTransaction(
submitAndWaitRequest(party, exercise.commands)
).map(_.getTransaction)
override def exerciseAndGetContract[TCid <: ContractId[T], T](
party: Party,
exercise: Update[Exercised[TCid]],
)(implicit companion: ContractCompanion[?, TCid, T]): Future[TCid] =
submitAndWaitForTransaction(
submitAndWaitRequest(party, exercise.commands)
)
.map(_.getTransaction)
.map(t => extractContracts(t)(companion))
.map(_.head)
override def exerciseAndGetContractNoDisclose[TCid <: ContractId[?]](
party: Party,
exercise: Update[Exercised[UnitData]],
)(implicit companion: ContractCompanion[?, TCid, ?]): Future[TCid] =
submitAndWaitForTransaction(
submitAndWaitRequest(party, exercise.commands)
)
.map(_.getTransaction)
.map(t => extractContracts(t)(companion))
.map(_.head)
override def exerciseByKey(
party: Party,
template: Identifier,
key: Value,
choice: String,
argument: Value,
): Future[TransactionTree] =
submitAndWaitForTransactionTree(
submitAndWaitRequest(
party,
JList.of(
new ExerciseByKeyCommand(
template,
key,
choice,
argument,
)
),
)
).map(_.getTransaction)
override def submitRequest(
actAs: List[Party],
readAs: List[Party],
commands: JList[Command],
): SubmitRequest =
new SubmitRequest(
Some(
new Commands(
ledgerId = ledgerId,
applicationId = applicationId,
commandId = nextCommandId(),
submissionId = nextSubmissionId(),
actAs = actAs.map(_.getValue),
readAs = readAs.map(_.getValue),
commands = commands.asScala.toSeq.map(c => ApiCommand.fromJavaProto(c.toProtoCommand)),
workflowId = workflowId,
)
)
)
override def submitRequest(party: Party, commands: JList[Command] = JList.of()): SubmitRequest =
new SubmitRequest(
Some(
new Commands(
ledgerId = ledgerId,
applicationId = applicationId,
commandId = nextCommandId(),
submissionId = nextSubmissionId(),
party = party.getValue,
commands = commands.asScala.toSeq.map(c => ApiCommand.fromJavaProto(c.toProtoCommand)),
workflowId = workflowId,
)
)
)
override def submitAndWaitRequest(
actAs: List[Party],
readAs: List[Party],
commands: JList[Command],
): SubmitAndWaitRequest =
new SubmitAndWaitRequest(
Some(
new Commands(
ledgerId = ledgerId,
applicationId = applicationId,
commandId = nextCommandId(),
submissionId = nextSubmissionId(),
actAs = actAs.map(_.getValue),
readAs = readAs.map(_.getValue),
commands = commands.asScala.toSeq.map(c => ApiCommand.fromJavaProto(c.toProtoCommand)),
workflowId = workflowId,
)
)
)
override def submitAndWaitRequest(party: Party, commands: JList[Command]): SubmitAndWaitRequest =
new SubmitAndWaitRequest(
Some(
new Commands(
ledgerId = ledgerId,
applicationId = applicationId,
commandId = nextCommandId(),
submissionId = nextSubmissionId(),
party = party.getValue,
commands = commands.asScala.toSeq.map(c => ApiCommand.fromJavaProto(c.toProtoCommand)),
workflowId = workflowId,
)
)
)
override def submit(request: SubmitRequest): Future[Unit] =
services.commandSubmission.submit(request).map(_ => ())
override def submitAndWait(request: SubmitAndWaitRequest): Future[Unit] =
services.command.submitAndWait(request).map(_ => ())
override def submitAndWaitForTransactionId(
request: SubmitAndWaitRequest
): Future[SubmitAndWaitForTransactionIdResponse] =
services.command.submitAndWaitForTransactionId(request)
override def submitAndWaitForTransaction(
request: SubmitAndWaitRequest
): Future[SubmitAndWaitForTransactionResponse] =
services.command.submitAndWaitForTransaction(request)
override def submitAndWaitForTransactionTree(
request: SubmitAndWaitRequest
): Future[SubmitAndWaitForTransactionTreeResponse] =
services.command
.submitAndWaitForTransactionTree(request)
/** This addresses a narrow case in which we tolerate a
* single occurrence of a specific and transient (and rare) error
* by retrying only a single time.
*/
override def submitRequestAndTolerateGrpcError[T](
errorCodeToTolerateOnce: ErrorCode,
submitAndWaitGeneric: ParticipantTestContext => Future[T],
): Future[T] =
submitAndWaitGeneric(this)
.transform {
case Failure(e: StatusRuntimeException)
if errorCodeToTolerateOnce.category.grpcCode
.map(_.value())
.contains(StatusProto.fromThrowable(e).getCode) =>
Success(Left(e))
case otherTry =>
// Otherwise return a Right with a nested Either that
// let's us create a failed or successful future in the
// default case of the step below.
Success(Right(otherTry.toEither))
}
.flatMap {
case Left(_) => // If we are retrying a single time, back off first for one second.
Delayed.Future.by(1.second)(submitAndWaitGeneric(this))
case Right(firstCallResult) => firstCallResult.fold(Future.failed, Future.successful)
}
override def completionStreamRequest(from: LedgerOffset = referenceOffset)(
parties: Party*
): CompletionStreamRequest =
new CompletionStreamRequest(ledgerId, applicationId, parties.map(_.getValue), Some(from))
override def completionEnd(request: CompletionEndRequest): Future[CompletionEndResponse] =
services.commandCompletion.completionEnd(request)
override def completionStream(
request: CompletionStreamRequest,
streamObserver: StreamObserver[CompletionStreamResponse],
): Unit =
services.commandCompletion.completionStream(request, streamObserver)
override def firstCompletions(request: CompletionStreamRequest): Future[Vector[Completion]] =
new StreamConsumer[CompletionStreamResponse](
services.commandCompletion.completionStream(request, _)
).find(_.completions.nonEmpty)
.map(_.completions.toVector)
override def firstCompletions(parties: Party*): Future[Vector[Completion]] =
firstCompletions(completionStreamRequest()(parties: _*))
override def findCompletionAtOffset(
offset: Ref.HexString,
p: Completion => Boolean,
)(parties: Party*): Future[Option[CompletionResponse]] = {
// We have to request an offset before the reported offset, as offsets are exclusive in the completion service.
val offsetPreviousToReportedOffset = HexOffset
.previous(offset)
.map(offset => LedgerOffset.of(LedgerOffset.Value.Absolute(offset)))
.getOrElse(referenceOffset)
val reportedOffsetCompletionStreamRequest =
completionStreamRequest(offsetPreviousToReportedOffset)(parties: _*)
findCompletion(reportedOffsetCompletionStreamRequest)(p)
}
override def findCompletion(
request: CompletionStreamRequest
)(p: Completion => Boolean): Future[Option[CompletionResponse]] =
new StreamConsumer[CompletionStreamResponse](
services.commandCompletion.completionStream(request, _)
).find(_.completions.exists(p))
.map(response => {
val checkpoint = response.getCheckpoint
response.completions
.find(p)
.map(CompletionResponse(_, checkpoint.getOffset, checkpoint.getRecordTime.asJava))
})
override def findCompletion(parties: Party*)(
p: Completion => Boolean
): Future[Option[CompletionResponse]] =
findCompletion(completionStreamRequest()(parties: _*))(p)
override def checkpoints(n: Int, request: CompletionStreamRequest): Future[Vector[Checkpoint]] =
new StreamConsumer[CompletionStreamResponse](
services.commandCompletion.completionStream(request, _)
).filterTake(_.checkpoint.isDefined)(n)
.map(_.map(_.getCheckpoint))
override def checkpoints(n: Int, from: LedgerOffset)(
parties: Party*
): Future[Vector[Checkpoint]] =
checkpoints(n, completionStreamRequest(from)(parties: _*))
override def firstCheckpoint(request: CompletionStreamRequest): Future[Checkpoint] =
checkpoints(1, request).map(_.head)
override def firstCheckpoint(parties: Party*): Future[Checkpoint] =
firstCheckpoint(completionStreamRequest()(parties: _*))
override def nextCheckpoint(request: CompletionStreamRequest): Future[Checkpoint] =
checkpoints(1, request).map(_.head)
override def nextCheckpoint(from: LedgerOffset, parties: Party*): Future[Checkpoint] =
nextCheckpoint(completionStreamRequest(from)(parties: _*))
override def configuration(overrideLedgerId: Option[String] = None): Future[LedgerConfiguration] =
new StreamConsumer[GetLedgerConfigurationResponse](
services.configuration
.getLedgerConfiguration(
new GetLedgerConfigurationRequest(overrideLedgerId.getOrElse(ledgerId)),
_,
)
).first()
.map(_.fold(sys.error("No ledger configuration available."))(_.getLedgerConfiguration))
override def checkHealth(): Future[HealthCheckResponse] =
services.health.check(HealthCheckRequest())
override def watchHealth(): Future[Seq[HealthCheckResponse]] =
new StreamConsumer[HealthCheckResponse](services.health.watch(HealthCheckRequest(), _))
.within(1.second)
override def getTimeModel(): Future[GetTimeModelResponse] =
services.configManagement.getTimeModel(GetTimeModelRequest())
override def setTimeModel(
mrt: Instant,
generation: Long,
newTimeModel: TimeModel,
): Future[SetTimeModelResponse] =
setTimeModel(setTimeModelRequest(mrt, generation, newTimeModel))
override def setTimeModelRequest(
mrt: Instant,
generation: Long,
newTimeModel: TimeModel,
): SetTimeModelRequest =
SetTimeModelRequest(nextSubmissionId(), Some(mrt.asProtobuf), generation, Some(newTimeModel))
override def setTimeModel(
request: SetTimeModelRequest
): Future[SetTimeModelResponse] =
services.configManagement.setTimeModel(request)
override def prune(
pruneUpTo: LedgerOffset,
attempts: Int = 10,
pruneAllDivulgedContracts: Boolean = false,
): Future[PruneResponse] =
// Distributed ledger participants need to reach global consensus prior to pruning. Hence the "eventually" here:
eventually(assertionName = "Prune", attempts = attempts) {
services.participantPruning
.prune(
PruneRequest(pruneUpTo.getAbsolute, nextSubmissionId(), pruneAllDivulgedContracts)
)
.andThen { case Failure(exception) =>
logger.warn("Failed to prune", exception)(LoggingContext.ForTesting)
}
}
override def pruneCantonSafe(
pruneUpTo: LedgerOffset,
party: Party,
dummyCommand: Party => JList[Command],
pruneAllDivulgedContracts: Boolean = false,
)(implicit ec: ExecutionContext): Future[Unit] =
FutureAssertions.succeedsEventually(
retryDelay = 100.millis,
maxRetryDuration = 10.seconds,
delayMechanism,
"Pruning",
) {
for {
_ <- submitAndWait(submitAndWaitRequest(party, dummyCommand(party)))
_ <- prune(
pruneUpTo = pruneUpTo,
attempts = 1,
pruneAllDivulgedContracts = pruneAllDivulgedContracts,
)
} yield ()
}(ec, LoggingContext.ForTesting)
private[infrastructure] override def preallocateParties(
n: Int,
participants: Iterable[ParticipantTestContext],
): Future[Vector[Party]] =
for {
parties <-
if (partyAllocationConfig.allocateParties) {
allocateParties(n)
} else {
reservePartyNames(n)
}
_ <- waitForParties(participants, parties.toSet)
} yield parties
private def reservePartyNames(n: Int): Future[Vector[Party]] =
Future.successful(Vector.fill(n)(new Party(nextPartyHintId())))
}

View File

@ -1,623 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.participant
import com.daml.error.ErrorCode
import java.time.Instant
import java.util.concurrent.TimeoutException
import com.daml.ledger.api.testtool.infrastructure.Endpoint
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext.IncludeInterfaceView
import com.daml.ledger.api.testtool.infrastructure.time.{DelayMechanism, Durations}
import com.daml.ledger.api.v1.active_contracts_service.GetActiveContractsRequest
import com.daml.ledger.api.v1.admin.config_management_service.{
GetTimeModelResponse,
SetTimeModelRequest,
SetTimeModelResponse,
TimeModel,
}
import com.daml.ledger.api.v1.admin.object_meta.ObjectMeta
import com.daml.ledger.api.v1.admin.package_management_service.{
PackageDetails,
UploadDarFileRequest,
}
import com.daml.ledger.api.v1.admin.participant_pruning_service.PruneResponse
import com.daml.ledger.api.v1.admin.party_management_service.{
AllocatePartyRequest,
AllocatePartyResponse,
GetPartiesRequest,
GetPartiesResponse,
ListKnownPartiesResponse,
PartyDetails,
UpdatePartyDetailsRequest,
UpdatePartyDetailsResponse,
UpdatePartyIdentityProviderRequest,
UpdatePartyIdentityProviderResponse,
}
import com.daml.ledger.api.v1.command_completion_service.{
Checkpoint,
CompletionEndRequest,
CompletionEndResponse,
CompletionStreamRequest,
CompletionStreamResponse,
}
import com.daml.ledger.api.v1.command_service.{
SubmitAndWaitForTransactionIdResponse,
SubmitAndWaitForTransactionResponse,
SubmitAndWaitForTransactionTreeResponse,
SubmitAndWaitRequest,
}
import com.daml.ledger.api.v1.command_submission_service.SubmitRequest
import com.daml.ledger.api.v1.completion.Completion
import com.daml.ledger.api.v1.event.CreatedEvent
import com.daml.ledger.api.v1.ledger_configuration_service.LedgerConfiguration
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.ledger.api.v1.package_service.{GetPackageResponse, PackageStatus}
import com.daml.ledger.api.v1.transaction.{Transaction, TransactionTree}
import com.daml.ledger.api.v1.transaction_filter.{Filters, TransactionFilter}
import com.daml.ledger.api.v1.event_query_service.{
GetEventsByContractIdRequest,
GetEventsByContractIdResponse,
GetEventsByContractKeyRequest,
GetEventsByContractKeyResponse,
}
import com.daml.ledger.api.v1.transaction_service.{
GetTransactionByEventIdRequest,
GetTransactionByIdRequest,
GetTransactionsRequest,
GetTransactionsResponse,
}
import com.daml.ledger.javaapi.data.{Command, Identifier, Party, Template, Value, Unit => UnitData}
import com.daml.ledger.javaapi.data.codegen.{ContractCompanion, ContractId, Exercised, Update}
import com.daml.lf.data.Ref.HexString
import com.daml.timer.Delayed
import com.google.protobuf.ByteString
import io.grpc.health.v1.health.HealthCheckResponse
import io.grpc.stub.StreamObserver
import java.util.{List => JList}
import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future}
class TimeoutParticipantTestContext(timeoutScaleFactor: Double, delegate: ParticipantTestContext)
extends ParticipantTestContext {
private val timeoutDuration = Durations.scaleDuration(15.seconds, timeoutScaleFactor)
override val ledgerId: String = delegate.ledgerId
override val applicationId: String = delegate.applicationId
override val endpointId: String = delegate.endpointId
override private[participant] def services = delegate.services
override private[participant] implicit val ec: ExecutionContext = delegate.ec
override def ledgerEndpoint: Endpoint = delegate.ledgerEndpoint
override def features: Features = delegate.features
override def referenceOffset: LedgerOffset = delegate.referenceOffset
override def nextKeyId: () => String = delegate.nextKeyId
override def nextUserId: () => String = delegate.nextUserId
override def nextPartyId: () => String = delegate.nextUserId
override def nextIdentityProviderId: () => String = delegate.nextIdentityProviderId
override def delayMechanism: DelayMechanism = delegate.delayMechanism
override def currentEnd(): Future[LedgerOffset] =
withTimeout("Get current end", delegate.currentEnd())
override def currentEnd(overrideLedgerId: String): Future[LedgerOffset] = withTimeout(
s"Get current end for ledger id $overrideLedgerId",
delegate.currentEnd(overrideLedgerId),
)
override def offsetBeyondLedgerEnd(): Future[LedgerOffset] = withTimeout(
"Offset beyond ledger end",
delegate.offsetBeyondLedgerEnd(),
)
override def time(): Future[Instant] = withTimeout("Get time", delegate.time())
override def setTime(currentTime: Instant, newTime: Instant): Future[Unit] = withTimeout(
"Set time",
delegate.setTime(currentTime, newTime),
)
override def listKnownPackages(): Future[Seq[PackageDetails]] = withTimeout(
"List known packages",
delegate.listKnownPackages(),
)
override def uploadDarRequest(bytes: ByteString): UploadDarFileRequest =
delegate.uploadDarRequest(bytes)
override def uploadDarFile(request: UploadDarFileRequest): Future[Unit] = withTimeout(
s"Upload dar file ${request.submissionId}",
delegate.uploadDarFile(request),
)
override def participantId(): Future[String] =
withTimeout("Get participant id", delegate.participantId())
override def listPackages(): Future[Seq[String]] =
withTimeout("List packages", delegate.listPackages())
override def getPackage(packageId: String): Future[GetPackageResponse] = withTimeout(
s"Get package $packageId",
delegate.getPackage(packageId),
)
override def getPackageStatus(packageId: String): Future[PackageStatus] = withTimeout(
s"Get package status $packageId",
delegate.getPackageStatus(packageId),
)
override def allocateParty(): Future[Party] =
withTimeout("Allocate party", delegate.allocateParty())
def allocateParty(req: AllocatePartyRequest): Future[AllocatePartyResponse] =
withTimeout("Allocate party", delegate.allocateParty(req))
override def updatePartyDetails(
req: UpdatePartyDetailsRequest
): Future[UpdatePartyDetailsResponse] =
withTimeout("Update party details", delegate.updatePartyDetails(req))
override def updatePartyIdentityProviderId(
request: UpdatePartyIdentityProviderRequest
): Future[UpdatePartyIdentityProviderResponse] =
withTimeout(
"Update party identity provider id",
delegate.updatePartyIdentityProviderId(request),
)
override def allocateParty(
partyIdHint: Option[String] = None,
displayName: Option[String] = None,
localMetadata: Option[ObjectMeta] = None,
identityProviderId: Option[String] = None,
): Future[Party] = withTimeout(
s"Allocate party with hint $partyIdHint and display name $displayName",
delegate.allocateParty(partyIdHint, displayName, localMetadata, identityProviderId),
)
override def getParties(req: GetPartiesRequest): Future[GetPartiesResponse] = withTimeout(
s"Get parties",
delegate.getParties(req),
)
override def allocateParties(n: Int): Future[Vector[Party]] = withTimeout(
s"Allocate $n parties",
delegate.allocateParties(n),
)
override def getParties(parties: Seq[Party]): Future[Seq[PartyDetails]] = withTimeout(
s"Get parties $parties",
delegate.getParties(parties),
)
override def listKnownParties(): Future[Set[Party]] = withTimeout(
"List known parties",
delegate.listKnownParties(),
)
override def listKnownPartiesResp(): Future[ListKnownPartiesResponse] = withTimeout(
"List known parties",
delegate.listKnownPartiesResp(),
)
override def waitForParties(
otherParticipants: Iterable[ParticipantTestContext],
expectedParties: Set[Party],
): Future[Unit] = withTimeout(
s"Wait for parties $expectedParties on participants ${otherParticipants.map(_.ledgerEndpoint)}",
delegate.waitForParties(otherParticipants, expectedParties),
)
override def activeContracts(
request: GetActiveContractsRequest
): Future[(Option[LedgerOffset], Vector[CreatedEvent])] = withTimeout(
s"Active contracts for request $request",
delegate.activeContracts(request),
)
override def activeContractsRequest(
parties: Seq[Party],
templateIds: Seq[Identifier],
interfaceFilters: Seq[(Identifier, IncludeInterfaceView)] = Seq.empty,
activeAtOffset: String = "",
useTemplateIdBasedLegacyFormat: Boolean = true,
): GetActiveContractsRequest =
delegate.activeContractsRequest(
parties,
templateIds,
interfaceFilters,
activeAtOffset,
useTemplateIdBasedLegacyFormat,
)
override def activeContracts(parties: Party*): Future[Vector[CreatedEvent]] =
withTimeout(s"Active contracts for parties $parties", delegate.activeContracts(parties: _*))
override def activeContractsByTemplateId(
templateIds: Seq[Identifier],
parties: Party*
): Future[Vector[CreatedEvent]] = withTimeout(
s"Active contracts by template ids $templateIds for parties $parties",
delegate.activeContractsByTemplateId(templateIds, parties: _*),
)
def transactionFilter(
parties: Seq[Party],
templateIds: Seq[Identifier] = Seq.empty,
interfaceFilters: Seq[(Identifier, IncludeInterfaceView)] = Seq.empty,
useTemplateIdBasedLegacyFormat: Boolean = true,
): TransactionFilter =
delegate.transactionFilter(
parties,
templateIds,
interfaceFilters,
useTemplateIdBasedLegacyFormat,
)
override def filters(
templateIds: Seq[Identifier],
interfaceFilters: Seq[(Identifier, IncludeInterfaceView)],
useTemplateIdBasedLegacyFormat: Boolean = true,
): Filters = delegate.filters(templateIds, interfaceFilters, useTemplateIdBasedLegacyFormat)
override def getTransactionsRequest(
transactionFilter: TransactionFilter,
begin: LedgerOffset,
): GetTransactionsRequest = delegate.getTransactionsRequest(transactionFilter, begin)
override def transactionStream(
request: GetTransactionsRequest,
responseObserver: StreamObserver[GetTransactionsResponse],
): Unit = delegate.transactionStream(request, responseObserver)
override def flatTransactionsByTemplateId(
templateId: Identifier,
parties: Party*
): Future[Vector[Transaction]] = withTimeout(
s"Flat transaction by template id $templateId for parties $parties",
delegate.flatTransactionsByTemplateId(templateId, parties: _*),
)
override def flatTransactions(request: GetTransactionsRequest): Future[Vector[Transaction]] =
withTimeout(s"Flat transactions for request $request", delegate.flatTransactions(request))
override def flatTransactions(parties: Party*): Future[Vector[Transaction]] =
withTimeout(s"Flat transactions for parties $parties", delegate.flatTransactions(parties: _*))
override def flatTransactions(
take: Int,
request: GetTransactionsRequest,
): Future[Vector[Transaction]] = withTimeout(
s"$take flat transactions for request $request",
delegate.flatTransactions(take, request),
)
override def flatTransactions(take: Int, parties: Party*): Future[Vector[Transaction]] =
withTimeout(
s"$take flat transactions for parties $parties",
delegate.flatTransactions(take, parties: _*),
)
override def transactionTreesByTemplateId(
templateId: Identifier,
parties: Party*
): Future[Vector[TransactionTree]] = withTimeout(
s"Transaction trees by template id $templateId for parties $parties",
delegate.transactionTreesByTemplateId(templateId, parties: _*),
)
override def transactionTrees(request: GetTransactionsRequest): Future[Vector[TransactionTree]] =
withTimeout(s"Transaction trees for request $request", delegate.transactionTrees(request))
override def transactionTrees(parties: Party*): Future[Vector[TransactionTree]] =
withTimeout(s"Transaction trees for parties $parties", delegate.transactionTrees(parties: _*))
override def transactionTrees(
take: Int,
request: GetTransactionsRequest,
): Future[Vector[TransactionTree]] = withTimeout(
s"$take transaction trees for request $request",
delegate.transactionTrees(take, request),
)
override def transactionTrees(
take: Int,
parties: Party*
): Future[Vector[TransactionTree]] = withTimeout(
s"$take transaction trees for parties $parties",
delegate.transactionTrees(take, parties: _*),
)
override def getTransactionByIdRequest(
transactionId: String,
parties: Seq[Party],
): GetTransactionByIdRequest =
delegate.getTransactionByIdRequest(transactionId, parties)
override def transactionTreeById(request: GetTransactionByIdRequest): Future[TransactionTree] =
withTimeout(
s"Get transaction tree by id for request $request",
delegate.transactionTreeById(request),
)
override def transactionTreeById(
transactionId: String,
parties: Party*
): Future[TransactionTree] = withTimeout(
s"Get transaction tree by id for transaction id $transactionId and parties $parties",
delegate.transactionTreeById(transactionId, parties: _*),
)
override def flatTransactionById(request: GetTransactionByIdRequest): Future[Transaction] =
withTimeout(
s"Flat transaction by id for request $request",
delegate.flatTransactionById(request),
)
override def flatTransactionById(
transactionId: String,
parties: Party*
): Future[Transaction] = withTimeout(
s"Flat transaction by id for transaction id $transactionId and parties $parties",
delegate.flatTransactionById(transactionId, parties: _*),
)
override def getTransactionByEventIdRequest(
eventId: String,
parties: Seq[Party],
): GetTransactionByEventIdRequest =
delegate.getTransactionByEventIdRequest(eventId, parties)
override def transactionTreeByEventId(
request: GetTransactionByEventIdRequest
): Future[TransactionTree] = withTimeout(
s"Transaction tree by event id for request $request",
delegate.transactionTreeByEventId(request),
)
override def transactionTreeByEventId(
eventId: String,
parties: Party*
): Future[TransactionTree] = withTimeout(
s"Transaction tree by event id for event id $eventId and parties $parties",
delegate.transactionTreeByEventId(eventId, parties: _*),
)
override def flatTransactionByEventId(
request: GetTransactionByEventIdRequest
): Future[Transaction] = withTimeout(
s"Flat transaction by event id for request $request",
delegate.flatTransactionByEventId(request),
)
override def flatTransactionByEventId(
eventId: String,
parties: Party*
): Future[Transaction] = withTimeout(
s"Flat transaction by event id for event id $eventId and parties $parties",
delegate.flatTransactionByEventId(eventId, parties: _*),
)
override def getEventsByContractId(
request: GetEventsByContractIdRequest
): Future[GetEventsByContractIdResponse] = withTimeout(
s"Get events by contract id for request $request",
delegate.getEventsByContractId(request),
)
override def getEventsByContractKey(
request: GetEventsByContractKeyRequest
): Future[GetEventsByContractKeyResponse] = withTimeout(
s"Get events by contract key for request $request",
delegate.getEventsByContractKey(request),
)
override def create[
TCid <: ContractId[T],
T <: Template,
](
party: Party,
template: T,
)(implicit companion: ContractCompanion[?, TCid, T]): Future[TCid] =
withTimeout(s"Create template for party $party", delegate.create(party, template))
override def create[TCid <: ContractId[T], T <: Template](
actAs: List[Party],
readAs: List[Party],
template: T,
)(implicit companion: ContractCompanion[?, TCid, T]): Future[TCid] = withTimeout(
s"Create template for actAs $actAs and readAs $readAs",
delegate.create(actAs, readAs, template),
)
override def createAndGetTransactionId[TCid <: ContractId[
T
], T <: Template](
party: Party,
template: T,
)(implicit companion: ContractCompanion[?, TCid, T]): Future[(String, TCid)] = withTimeout(
s"Create and get transaction id for party $party",
delegate.createAndGetTransactionId(party, template),
)
override def exercise[T](
party: Party,
exercise: Update[T],
): Future[TransactionTree] =
withTimeout(s"Exercise for party $party", delegate.exercise(party, exercise))
override def exercise[T](
actAs: List[Party],
readAs: List[Party],
exercise: Update[T],
): Future[TransactionTree] = withTimeout(
s"Exercise for actAs $actAs and readAs $readAs",
delegate.exercise(actAs, readAs, exercise),
)
override def exerciseForFlatTransaction[T](
party: Party,
exercise: Update[T],
): Future[Transaction] = withTimeout(
s"Exercise for flat transaction for party $party",
delegate.exerciseForFlatTransaction(party, exercise),
)
override def exerciseAndGetContract[TCid <: ContractId[T], T](
party: Party,
exercise: Update[Exercised[TCid]],
)(implicit companion: ContractCompanion[?, TCid, T]): Future[TCid] = withTimeout(
s"Exercise and get contract for party $party",
delegate.exerciseAndGetContract[TCid, T](party, exercise),
)
override def exerciseAndGetContractNoDisclose[TCid <: ContractId[?]](
party: Party,
exercise: Update[Exercised[UnitData]],
)(implicit companion: ContractCompanion[?, TCid, ?]): Future[TCid] = withTimeout(
s"Exercise and get non disclosed contract for party $party",
delegate.exerciseAndGetContractNoDisclose[TCid](party, exercise),
)
override def exerciseByKey(
party: Party,
template: Identifier,
key: Value,
choice: String,
argument: Value,
): Future[TransactionTree] = withTimeout(
s"Exercise by key for party $party, template $template, key $key, choice $choice and argument $argument.",
delegate.exerciseByKey(party, template, key, choice, argument),
)
override def submitRequest(
actAs: List[Party],
readAs: List[Party],
commands: JList[Command],
): SubmitRequest = delegate.submitRequest(actAs, readAs, commands)
override def submitRequest(party: Party, commands: JList[Command] = JList.of()): SubmitRequest =
delegate.submitRequest(party, commands)
override def submitAndWaitRequest(
actAs: List[Party],
readAs: List[Party],
commands: JList[Command],
): SubmitAndWaitRequest = delegate.submitAndWaitRequest(actAs, readAs, commands)
override def submitAndWaitRequest(
party: Party,
commands: JList[Command],
): SubmitAndWaitRequest = delegate.submitAndWaitRequest(party, commands)
override def submit(request: SubmitRequest): Future[Unit] =
withTimeout(s"Submit for request $request", delegate.submit(request))
override def submitAndWait(request: SubmitAndWaitRequest): Future[Unit] = withTimeout(
s"Submit and wait for request $request",
delegate.submitAndWait(request),
)
override def submitAndWaitForTransactionId(
request: SubmitAndWaitRequest
): Future[SubmitAndWaitForTransactionIdResponse] = withTimeout(
s"Submit and wait for transaction id for request $request",
delegate.submitAndWaitForTransactionId(request),
)
override def submitAndWaitForTransaction(
request: SubmitAndWaitRequest
): Future[SubmitAndWaitForTransactionResponse] = withTimeout(
s"Submit and wait for transaction for request $request",
delegate.submitAndWaitForTransaction(request),
)
override def submitAndWaitForTransactionTree(
request: SubmitAndWaitRequest
): Future[SubmitAndWaitForTransactionTreeResponse] = withTimeout(
s"Submit and wait for transaction tree for request $request",
delegate.submitAndWaitForTransactionTree(request),
)
override def submitRequestAndTolerateGrpcError[T](
errorToTolerate: ErrorCode,
submitAndWaitGeneric: ParticipantTestContext => Future[T],
): Future[T] = // timeout enforced by submitAndWaitGeneric
delegate.submitRequestAndTolerateGrpcError(errorToTolerate, submitAndWaitGeneric)
override def completionStreamRequest(from: LedgerOffset)(
parties: Party*
): CompletionStreamRequest = delegate.completionStreamRequest(from)(parties: _*)
override def completionEnd(request: CompletionEndRequest): Future[CompletionEndResponse] =
withTimeout(
s"Completion end for request $request",
delegate.completionEnd(request),
)
override def completionStream(
request: CompletionStreamRequest,
streamObserver: StreamObserver[CompletionStreamResponse],
): Unit = delegate.completionStream(request, streamObserver)
override def firstCompletions(request: CompletionStreamRequest): Future[Vector[Completion]] =
withTimeout(
s"First completions for request $request",
delegate.firstCompletions(request),
)
override def firstCompletions(parties: Party*): Future[Vector[Completion]] =
withTimeout(
s"First completions for parties $parties",
delegate.firstCompletions(parties: _*),
)
override def findCompletionAtOffset(offset: HexString, p: Completion => Boolean)(
parties: Party*
): Future[Option[ParticipantTestContext.CompletionResponse]] = withTimeout(
s"Find completion at offset $offset for parties $parties",
delegate.findCompletionAtOffset(offset, p)(parties: _*),
)
override def findCompletion(request: CompletionStreamRequest)(
p: Completion => Boolean
): Future[Option[ParticipantTestContext.CompletionResponse]] = withTimeout(
s"Find completion for request $request",
delegate.findCompletion(request)(p),
)
override def findCompletion(parties: Party*)(
p: Completion => Boolean
): Future[Option[ParticipantTestContext.CompletionResponse]] =
withTimeout(s"Find completion for parties $parties", delegate.findCompletion(parties: _*)(p))
override def checkpoints(n: Int, request: CompletionStreamRequest): Future[Vector[Checkpoint]] =
withTimeout(s"$n checkpoints for request $request", delegate.checkpoints(n, request))
override def checkpoints(n: Int, from: LedgerOffset)(
parties: Party*
): Future[Vector[Checkpoint]] = withTimeout(
s"$n checkpoints from offset $from for parties $parties",
delegate.checkpoints(n, from)(parties: _*),
)
override def firstCheckpoint(request: CompletionStreamRequest): Future[Checkpoint] = withTimeout(
s"First checkpoint for request $request",
delegate.firstCheckpoint(request),
)
override def firstCheckpoint(parties: Party*): Future[Checkpoint] = withTimeout(
s"First checkpoint for parties $parties",
delegate.firstCheckpoint(parties: _*),
)
override def nextCheckpoint(request: CompletionStreamRequest): Future[Checkpoint] = withTimeout(
s"Next checkpoint for request $request",
delegate.nextCheckpoint(request),
)
override def nextCheckpoint(from: LedgerOffset, parties: Party*): Future[Checkpoint] =
withTimeout(
s"Next checkpoint from offset $from for parties $parties",
delegate.nextCheckpoint(from, parties: _*),
)
override def configuration(overrideLedgerId: Option[String]): Future[LedgerConfiguration] =
withTimeout(
s"Configuration for ledger $overrideLedgerId",
delegate.configuration(overrideLedgerId),
)
override def checkHealth(): Future[HealthCheckResponse] =
withTimeout("Check health", delegate.checkHealth())
override def watchHealth(): Future[Seq[HealthCheckResponse]] =
withTimeout("Watch health", delegate.watchHealth())
override def getTimeModel(): Future[GetTimeModelResponse] =
withTimeout("Get time model", delegate.getTimeModel())
override def setTimeModel(
mrt: Instant,
generation: Long,
newTimeModel: TimeModel,
): Future[SetTimeModelResponse] = withTimeout(
s"Set time model with mrt $mrt, generation $generation and new model $newTimeModel",
delegate.setTimeModel(mrt, generation, newTimeModel),
)
override def setTimeModelRequest(
mrt: Instant,
generation: Long,
newTimeModel: TimeModel,
): SetTimeModelRequest = delegate.setTimeModelRequest(mrt, generation, newTimeModel)
override def setTimeModel(request: SetTimeModelRequest): Future[SetTimeModelResponse] =
withTimeout(
s"Set time model for request $request",
delegate.setTimeModel(request),
)
override private[infrastructure] def preallocateParties(
n: Int,
participants: Iterable[ParticipantTestContext],
) = withTimeout(
s"Preallocate $n parties on participants ${participants.map(_.ledgerEndpoint)}",
delegate.preallocateParties(n, participants),
)
override def prune(
pruneUpTo: LedgerOffset,
attempts: Int,
pruneAllDivulgedContracts: Boolean,
): Future[PruneResponse] = withTimeout(
s"Prune up to $pruneUpTo, with $attempts attempts and divulged contracts [$pruneAllDivulgedContracts]",
delegate.prune(pruneUpTo, attempts, pruneAllDivulgedContracts),
)
override def pruneCantonSafe(
pruneUpTo: LedgerOffset,
party: Party,
dummyCommand: Party => JList[Command],
pruneAllDivulgedContracts: Boolean = false,
)(implicit ec: ExecutionContext): Future[Unit] =
delegate.pruneCantonSafe(pruneUpTo, party, dummyCommand, pruneAllDivulgedContracts)
private def withTimeout[T](hint: String, future: => Future[T]): Future[T] = {
Future.firstCompletedOf(
Seq(
Delayed.Future.by(timeoutDuration)(
Future.failed(
new TimeoutException(s"Operation [$hint] timed out after $timeoutDuration.")
)
),
future,
)
)
}
override def latestPrunedOffsets(): Future[(LedgerOffset, LedgerOffset)] = withTimeout(
"Requesting the latest pruned offsets",
delegate.latestPrunedOffsets(),
)
}

View File

@ -1,190 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.participant
import java.util.concurrent.ConcurrentHashMap
import com.daml.error.definitions.LedgerApiErrors
import com.daml.error.utils.ErrorDetails
import com.daml.ledger.api.testtool.infrastructure.LedgerServices
import com.daml.ledger.api.v1.admin.identity_provider_config_service.{
CreateIdentityProviderConfigRequest,
CreateIdentityProviderConfigResponse,
DeleteIdentityProviderConfigRequest,
DeleteIdentityProviderConfigResponse,
GetIdentityProviderConfigRequest,
GetIdentityProviderConfigResponse,
IdentityProviderConfig,
ListIdentityProviderConfigsRequest,
ListIdentityProviderConfigsResponse,
UpdateIdentityProviderConfigRequest,
UpdateIdentityProviderConfigResponse,
}
import com.daml.ledger.api.v1.admin.user_management_service.UserManagementServiceGrpc.UserManagementService
import com.daml.ledger.api.v1.admin.user_management_service.{
CreateUserRequest,
CreateUserResponse,
DeleteUserRequest,
DeleteUserResponse,
User,
}
import com.google.protobuf.field_mask.FieldMask
import java.util.UUID
import scala.concurrent.{ExecutionContext, Future}
trait UserManagementTestContext {
self: ParticipantTestContext =>
private[participant] def services: LedgerServices
private[participant] implicit val ec: ExecutionContext
/** Users created during execution of the test case on this participant.
*/
private val createdUsersById = new ConcurrentHashMap[String, User]
private val createdIdentityProvidersById = new ConcurrentHashMap[String, IdentityProviderConfig]
def userManagement: UserManagementService =
services.userManagement // TODO (i12059) perhaps remove and create granular accessors
/** Creates a new user.
*
* Additionally keeps track of the created users so that they can be cleaned up automatically when the test case ends.
*/
def createUser(createUserRequest: CreateUserRequest): Future[CreateUserResponse] = {
for {
response <- services.userManagement.createUser(createUserRequest)
user = response.user.get
_ = createdUsersById.put(user.id, user)
} yield response
}
/** Deletes a user.
*
* Additionally keeps track of the created users so that they can be cleaned up automatically when the test case ends.
*/
def deleteUser(request: DeleteUserRequest): Future[DeleteUserResponse] = {
for {
response <- services.userManagement.deleteUser(request)
_ = createdUsersById.remove(request.userId)
} yield response
}
def deleteCreateIdentityProviders(): Future[Unit] = {
import scala.jdk.CollectionConverters._
val deletions = createdIdentityProvidersById
.keys()
.asScala
.map(idpId =>
services.identityProviderConfig
.deleteIdentityProviderConfig(
DeleteIdentityProviderConfigRequest(idpId)
)
.map(_ => ())
.recover {
case e
if ErrorDetails.matches(
e,
LedgerApiErrors.Admin.IdentityProviderConfig.IdentityProviderConfigNotFound,
) =>
()
}
)
Future.sequence(deletions).map(_ => ())
}
/** Intended to be called by the infrastructure code after a test case's execution has ended.
*/
def deleteCreatedUsers(): Future[Unit] = {
import scala.jdk.CollectionConverters._
val deletions = createdUsersById
.keys()
.asScala
.map(userId =>
services.userManagement
.deleteUser(
DeleteUserRequest(userId)
)
.map(_ => ())
.recover {
case e if ErrorDetails.matches(e, LedgerApiErrors.Admin.UserManagement.UserNotFound) =>
()
}
)
Future.sequence(deletions).map(_ => ())
}
def createIdentityProviderConfig(
identityProviderId: String = UUID.randomUUID().toString,
isDeactivated: Boolean = false,
issuer: String = UUID.randomUUID().toString,
jwksUrl: String = "http://daml.com/jwks.json",
): Future[CreateIdentityProviderConfigResponse] =
for {
response <- services.identityProviderConfig.createIdentityProviderConfig(
CreateIdentityProviderConfigRequest(
Some(
IdentityProviderConfig(
identityProviderId = identityProviderId,
isDeactivated = isDeactivated,
issuer = issuer,
jwksUrl = jwksUrl,
)
)
)
)
idp = response.identityProviderConfig.get
_ = createdIdentityProvidersById.put(idp.identityProviderId, idp)
} yield response
def updateIdentityProviderConfig(
identityProviderId: String = UUID.randomUUID().toString,
isDeactivated: Boolean = false,
issuer: String = UUID.randomUUID().toString,
jwksUrl: String = "http://daml.com/jwks.json",
updateMask: Option[FieldMask] = None,
): Future[UpdateIdentityProviderConfigResponse] =
services.identityProviderConfig.updateIdentityProviderConfig(
UpdateIdentityProviderConfigRequest(
Some(
IdentityProviderConfig(
identityProviderId = identityProviderId,
isDeactivated = isDeactivated,
issuer = issuer,
jwksUrl = jwksUrl,
)
),
updateMask,
)
)
def updateIdentityProviderConfig(
request: UpdateIdentityProviderConfigRequest
): Future[UpdateIdentityProviderConfigResponse] =
services.identityProviderConfig.updateIdentityProviderConfig(request)
def createIdentityProviderConfig(
request: CreateIdentityProviderConfigRequest
): Future[CreateIdentityProviderConfigResponse] =
services.identityProviderConfig.createIdentityProviderConfig(request)
def getIdentityProviderConfig(
request: GetIdentityProviderConfigRequest
): Future[GetIdentityProviderConfigResponse] =
services.identityProviderConfig.getIdentityProviderConfig(request)
def deleteIdentityProviderConfig(
request: DeleteIdentityProviderConfigRequest
): Future[DeleteIdentityProviderConfigResponse] =
for {
resp <- services.identityProviderConfig.deleteIdentityProviderConfig(request)
_ = createdIdentityProvidersById.remove(request.identityProviderId)
} yield resp
def listIdentityProviderConfig(): Future[ListIdentityProviderConfigsResponse] =
services.identityProviderConfig.listIdentityProviderConfigs(
ListIdentityProviderConfigsRequest()
)
}

View File

@ -1,29 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.time
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.timer.Delayed
import scala.concurrent.duration.Duration
import scala.concurrent.{ExecutionContext, Future}
trait DelayMechanism {
def delayBy(duration: Duration): Future[Unit]
}
class TimeDelayMechanism()(implicit ec: ExecutionContext) extends DelayMechanism {
override def delayBy(duration: Duration): Future[Unit] = Delayed.by(duration)(())
}
class StaticTimeDelayMechanism(ledger: ParticipantTestContext)(implicit
ec: ExecutionContext
) extends DelayMechanism {
override def delayBy(duration: Duration): Future[Unit] =
ledger
.time()
.flatMap { currentTime =>
ledger.setTime(currentTime, currentTime.plusMillis(duration.toMillis))
}
}

View File

@ -1,22 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.infrastructure.time
import scala.concurrent.duration.{Duration, FiniteDuration}
object Durations {
def scaleDuration(duration: FiniteDuration, timeoutScaleFactor: Double): FiniteDuration =
asFiniteDuration(
duration * timeoutScaleFactor
)
def asFiniteDuration(duration: Duration): FiniteDuration =
duration match {
case duration: FiniteDuration => duration
case _ =>
throw new IllegalArgumentException(s"Duration $duration is not finite.")
}
}

View File

@ -1,35 +0,0 @@
# Copyright (c) 2023 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",
)
load("@os_info//:os_info.bzl", "is_windows")
load("//ledger-test-tool:conformance.bzl", "testtool_lf_versions")
[
da_scala_library(
name = "runner-%s" % lf_version,
srcs = glob([
"src/main/scala/**/*.scala",
]),
tags = ["maven_coordinates=com.daml:ledger-api-tests-runner-%s:__VERSION__" % lf_version],
visibility = ["//:__subpackages__"],
runtime_deps = [
"@maven//:ch_qos_logback_logback_classic",
],
deps = [
"//ledger/ledger-api-common",
"//ledger-test-tool/infrastructure:infrastructure-%s" % lf_version,
"//libs-scala/resources",
"//libs-scala/resources-grpc",
"//libs-scala/resources-pekko",
"@maven//:io_grpc_grpc_api",
"@maven//:io_grpc_grpc_netty",
"@maven//:io_netty_netty_handler",
"@maven//:org_slf4j_slf4j_api",
],
)
for lf_version in testtool_lf_versions
]

View File

@ -1,12 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.runner
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
trait AvailableTests {
def defaultTests: Vector[LedgerTestSuite]
def optionalTests: Vector[LedgerTestSuite]
}

View File

@ -1,57 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.runner
import com.daml.ledger.api.testtool.infrastructure.PartyAllocationConfiguration
import com.daml.ledger.api.testtool.runner
import com.daml.ledger.api.tls.TlsConfiguration
import scala.concurrent.duration.FiniteDuration
final case class Config(
participantsEndpoints: Vector[(String, Int)],
maxConnectionAttempts: Int,
mustFail: Boolean,
verbose: Boolean,
timeoutScaleFactor: Double,
concurrentTestRuns: Int,
extract: Boolean,
tlsConfig: Option[TlsConfiguration],
excluded: Set[String],
included: Set[String],
additional: Set[String],
listTests: Boolean,
listTestSuites: Boolean,
shuffleParticipants: Boolean,
partyAllocation: PartyAllocationConfiguration,
ledgerClockGranularity: FiniteDuration,
uploadDars: Boolean,
) {
def withTlsConfig(modify: TlsConfiguration => TlsConfiguration): Config = {
val base = tlsConfig.getOrElse(TlsConfiguration.Empty)
copy(tlsConfig = Some(modify(base)))
}
}
object Config {
val default: Config = Config(
participantsEndpoints = Vector.empty,
maxConnectionAttempts = 10,
mustFail = false,
verbose = false,
timeoutScaleFactor = Defaults.TimeoutScaleFactor,
concurrentTestRuns = runner.Defaults.ConcurrentRuns,
extract = false,
tlsConfig = None,
excluded = Set.empty,
included = Set.empty,
additional = Set.empty,
listTests = false,
listTestSuites = false,
shuffleParticipants = false,
partyAllocation = PartyAllocationConfiguration.ClosedWorldWaitingForAllParticipants,
ledgerClockGranularity = runner.Defaults.LedgerClockGranularity,
uploadDars = true,
)
}

View File

@ -1,23 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.runner
import com.daml.ledger.api.testtool.infrastructure.{LedgerTestCase, LedgerTestSuite}
final class ConfiguredTests(availableTests: AvailableTests, config: Config) {
val defaultTests: Vector[LedgerTestSuite] = availableTests.defaultTests
val optionalTests: Vector[LedgerTestSuite] = availableTests.optionalTests
val allTests: Vector[LedgerTestSuite] = defaultTests ++ optionalTests
val missingTests: Set[String] = {
val allTestCaseNames = allTests.flatMap(_.tests).map(_.name).toSet
(config.included ++ config.excluded ++ config.additional).filterNot(prefix =>
allTestCaseNames.exists(_.startsWith(prefix))
)
}
val defaultCases: Vector[LedgerTestCase] = defaultTests.flatMap(_.tests)
val optionalCases: Vector[LedgerTestCase] = optionalTests.flatMap(_.tests)
val allCases: Vector[LedgerTestCase] = defaultCases ++ optionalCases
}

View File

@ -1,18 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.runner
import scala.concurrent.duration.{DurationInt, FiniteDuration}
object Defaults {
val LedgerClockGranularity: FiniteDuration = 1.second
val TimeoutScaleFactor: Double = 1.0
// Neither ledgers nor participants scale perfectly with the number of processors.
// We therefore limit the maximum number of concurrent tests, to avoid overwhelming the ledger.
val ConcurrentRuns: Int = sys.runtime.availableProcessors min 4
}

View File

@ -1,222 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.runner
import java.io.File
import java.nio.file.{Files, Paths, StandardCopyOption}
import java.util.concurrent.Executors
import com.daml.ledger.api.testtool.infrastructure._
import com.daml.ledger.api.testtool.runner.TestRunner._
import com.daml.ledger.api.tls.TlsConfiguration
import io.grpc.Channel
import io.grpc.netty.{NegotiationType, NettyChannelBuilder}
import org.slf4j.LoggerFactory
import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
object TestRunner {
private type ResourceOwner[T] = com.daml.resources.AbstractResourceOwner[ExecutionContext, T]
private type Resource[T] = com.daml.resources.Resource[ExecutionContext, T]
private val Resource = new com.daml.resources.ResourceFactories[ExecutionContext]
private val logger = LoggerFactory.getLogger(getClass.getName.stripSuffix("$"))
// The suffix that will be appended to all party and command identifiers to ensure
// they are unique across test runs (but still somewhat stable within a single test run)
// This implementation could fail based on the limitations of System.nanoTime, that you
// can read on here: https://docs.oracle.com/javase/8/docs/api/java/lang/System.html#nanoTime--
// Still, the only way in which this can fail is if two test runs target the same ledger
// with the identifier suffix being computed to the same value, which at the very least
// requires this to happen on what is resolved by the JVM as the very same millisecond.
// This is very unlikely to fail and allows to easily "date" parties on a ledger used
// for testing and compare data related to subsequent runs without any reference
private val identifierSuffix = f"${System.nanoTime}%x"
private val uncaughtExceptionErrorMessage =
"UNEXPECTED UNCAUGHT EXCEPTION ON MAIN THREAD, GATHER THE STACKTRACE AND OPEN A _DETAILED_ TICKET DESCRIBING THE ISSUE HERE: https://github.com/digital-asset/daml/issues/new"
private def exitCode(summaries: Vector[LedgerTestSummary], expectFailure: Boolean): Int =
if (summaries.exists(_.result.isLeft) == expectFailure) 0 else 1
private def printListOfTests[A](tests: Seq[A])(getName: A => String): Unit = {
println("All tests are run by default.")
println()
tests.map(getName).sorted.foreach(println(_))
}
private def printAvailableTestSuites(testSuites: Vector[LedgerTestSuite]): Unit = {
println("Listing test suites. Run with --list-all to see individual tests.")
printListOfTests(testSuites)(_.name)
}
private def printAvailableTests(testSuites: Vector[LedgerTestSuite]): Unit = {
println("Listing all tests. Run with --list to only see test suites.")
printListOfTests(testSuites.flatMap(_.tests))(_.name)
}
private def extractResources(resources: Seq[String]): Unit = {
val pwd = Paths.get(".").toAbsolutePath
println(s"Extracting all Daml resources necessary to run the tests into $pwd.")
for (resource <- resources) {
val is = getClass.getClassLoader.getResourceAsStream(resource)
if (is == null) sys.error(s"Could not find $resource in classpath")
val targetFile = new File(new File(resource).getName)
Files.copy(is, targetFile.toPath, StandardCopyOption.REPLACE_EXISTING)
println(s"Extracted $resource to $targetFile")
}
}
private def matches(prefixes: Iterable[String])(test: LedgerTestCase): Boolean =
prefixes.exists(test.name.startsWith)
}
final class TestRunner(availableTests: AvailableTests, config: Config) {
def runAndExit(): Unit = {
val tests = new ConfiguredTests(availableTests, config)
if (tests.missingTests.nonEmpty) {
println("The following exclusion or inclusion does not match any test:")
tests.missingTests.foreach { testName =>
println(s" - $testName")
}
sys.exit(64)
}
if (config.listTestSuites) {
printAvailableTestSuites(tests.allTests)
sys.exit(0)
}
if (config.listTests) {
printAvailableTests(tests.allTests)
sys.exit(0)
}
if (config.extract) {
extractResources(Dars.resources)
sys.exit(0)
}
if (config.participantsEndpoints.isEmpty) {
println("No participant to test, exiting.")
sys.exit(0)
}
Thread
.currentThread()
.setUncaughtExceptionHandler((_, exception) => {
logger.error(uncaughtExceptionErrorMessage, exception)
sys.exit(1)
})
val includedTests =
if (config.included.isEmpty) tests.defaultCases
else tests.allCases.filter(matches(config.included))
val addedTests = tests.allCases.filter(matches(config.additional))
val (excludedTests, testsToRun) =
(includedTests ++ addedTests).partition(matches(config.excluded))
implicit val resourceManagementExecutionContext: ExecutionContext =
ExecutionContext.fromExecutorService(Executors.newSingleThreadExecutor())
val runner = newLedgerCasesRunner(config, testsToRun)
runner.flatMap(_.runTests(ExecutionContext.global)).onComplete {
case Success(summaries) =>
val excludedTestSummaries =
excludedTests.map { ledgerTestCase =>
LedgerTestSummary(
suite = ledgerTestCase.suite.name,
name = ledgerTestCase.name,
description = ledgerTestCase.description,
result = Right(Result.Excluded("excluded test")),
)
}
new Reporter.ColorizedPrintStreamReporter(
System.out,
config.verbose,
).report(
summaries,
excludedTestSummaries,
Seq(
"identifierSuffix" -> identifierSuffix,
"concurrentTestRuns" -> config.concurrentTestRuns.toString,
"timeoutScaleFactor" -> config.timeoutScaleFactor.toString,
),
)
sys.exit(exitCode(summaries, config.mustFail))
case Failure(exception: Errors.FrameworkException) =>
logger.error(exception.getMessage)
logger.debug(exception.getMessage, exception)
sys.exit(1)
case Failure(exception) =>
logger.error(exception.getMessage, exception)
sys.exit(1)
}
}
private def newLedgerCasesRunner(
config: Config,
cases: Vector[LedgerTestCase],
)(implicit executionContext: ExecutionContext): Future[LedgerTestCasesRunner] =
createLedgerCasesRunner(config, cases, config.concurrentTestRuns)
private def createLedgerCasesRunner(
config: Config,
cases: Vector[LedgerTestCase],
concurrentTestRuns: Int,
)(implicit executionContext: ExecutionContext): Future[LedgerTestCasesRunner] = {
initializeParticipantChannels(
participantEndpoints = config.participantsEndpoints,
tlsConfig = config.tlsConfig,
).asFuture
.map(participantChannels =>
new LedgerTestCasesRunner(
testCases = cases,
participantChannels = participantChannels,
maxConnectionAttempts = config.maxConnectionAttempts,
partyAllocation = config.partyAllocation,
shuffleParticipants = config.shuffleParticipants,
timeoutScaleFactor = config.timeoutScaleFactor,
concurrentTestRuns = concurrentTestRuns,
uploadDars = config.uploadDars,
identifierSuffix = identifierSuffix,
)
)
}
private def initializeParticipantChannel(
host: String,
port: Int,
tlsConfig: Option[TlsConfiguration],
): ResourceOwner[Channel] = {
logger.info(s"Setting up managed channel to participant at $host:$port...")
val channelBuilder = NettyChannelBuilder.forAddress(host, port).usePlaintext()
for (ssl <- tlsConfig; sslContext <- ssl.client()) {
logger.info("Setting up managed channel with transport security.")
channelBuilder
.useTransportSecurity()
.sslContext(sslContext)
.negotiationType(NegotiationType.TLS)
}
channelBuilder.maxInboundMessageSize(10000000)
ResourceOwner.forChannel(channelBuilder, shutdownTimeout = 5.seconds)
}
private def initializeParticipantChannels(
participantEndpoints: Vector[(String, Int)],
tlsConfig: Option[TlsConfiguration],
)(implicit executionContext: ExecutionContext): Resource[Vector[ChannelEndpoint]] =
Resource.sequence(participantEndpoints.map { case (host, port) =>
initializeParticipantChannel(host, port, tlsConfig)
.acquire()
.map(channel => ChannelEndpoint.forRemote(channel = channel, hostname = host, port = port))
})
}

View File

@ -1,141 +0,0 @@
# Copyright (c) 2023 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_suite", "da_scala_test_suite")
load("//daml-lf/language:daml-lf.bzl", "LF_DEV_VERSIONS", "lf_version_configuration")
load(":deps.bzl", "deps")
versions = {
"1.8": {
"srcs": glob([
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8/**/*",
]),
"entrypoints": [
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8.scala",
],
},
"1.14": {
"srcs": glob([
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14/**/*",
]),
"entrypoints": [
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14.scala",
],
},
"1.15": {
"srcs": glob([
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_15/**/*",
]),
"entrypoints": [
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_15.scala",
],
},
"1.dev": {
"srcs": glob([
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_15/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_dev/**/*",
]),
"entrypoints": [
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_15.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_dev.scala",
],
},
"2.1": {
"srcs": glob([
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_15/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_dev/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v2_1/**/*",
]),
"entrypoints": [
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_15.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_dev.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v2_1.scala",
],
},
"2.dev": {
"srcs": glob([
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_15/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_dev/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v2_1/**/*",
"src/main/scala/com/daml/ledger/api/testtool/suites/v2_dev/**/*",
]),
"entrypoints": [
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_8.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_14.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_15.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v1_dev.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v2_1.scala",
"src/main/scala/com/daml/ledger/api/testtool/suites/v2_dev.scala",
],
},
}
[
da_scala_library(
name = "suites-%s" % lf_version,
srcs = config["entrypoints"] + config["srcs"],
scala_deps = [
"@maven//:com_lihaoyi_sourcecode",
],
tags = ["maven_coordinates=com.daml:ledger-api-tests-suites-%s:__VERSION__" % lf_version],
visibility = ["//:__subpackages__"],
deps = deps(lf_version),
)
for (lf_version, config) in versions.items()
]
da_scala_test_suite(
name = "tests",
srcs = glob(["src/test/suite/scala/**/*.scala"]),
scala_deps = [
"@maven//:org_scalacheck_scalacheck",
"@maven//:org_scalactic_scalactic",
"@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 = [
"//daml-lf/data",
"//ledger-test-tool/infrastructure",
"//libs-scala/test-evidence/tag:test-evidence-tag",
"//libs-scala/timer-utils",
"@maven//:org_scalatest_scalatest_compatible",
] + [
":suites-{}".format(version)
for version in LF_DEV_VERSIONS
],
)
[
alias(
name = "suites-%s" % name,
actual = ":suites-%s" % lf_target,
visibility = ["//visibility:public"],
)
for (name, lf_target) in lf_version_configuration.items()
]
alias(
name = "suites",
actual = ":suites-default",
visibility = ["//visibility:public"],
)

View File

@ -1,46 +0,0 @@
# Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
load("//daml-lf/language:daml-lf.bzl", "version_in")
def _has_model_tests(lf_version):
return version_in(
lf_version,
v1_minor_version_range = ("15", "dev"),
v2_minor_version_range = ("0", "dev"),
)
def deps(lf_version):
carbon_tests = [
"//test-common:carbonv1-tests-%s.java-codegen" % lf_version,
"//test-common:carbonv2-tests-%s.java-codegen" % lf_version,
"//test-common:carbonv3-tests-%s.java-codegen" % lf_version,
]
additional_tests = carbon_tests if _has_model_tests(lf_version) else []
return [
"//canton:bindings-java",
"//daml-lf/data",
"//daml-lf/transaction",
"//canton:ledger_api_proto_scala",
"//ledger/error",
"//ledger/ledger-api-errors",
"//ledger/ledger-api-common",
"//ledger-test-tool/infrastructure:infrastructure-%s" % lf_version,
"//libs-scala/ledger-resources",
"//libs-scala/test-evidence/tag:test-evidence-tag",
"//test-common:dar-files-%s-lib" % lf_version,
"//test-common:model-tests-%s.java-codegen" % lf_version,
"//test-common:package_management-tests-%s.java-codegen" % lf_version,
"//test-common:semantic-tests-%s.java-codegen" % lf_version,
"//libs-scala/contextualized-logging",
"//libs-scala/grpc-utils",
"//libs-scala/resources",
"//libs-scala/resources-pekko",
"//libs-scala/resources-grpc",
"//libs-scala/timer-utils",
"@maven//:com_google_api_grpc_proto_google_common_protos",
"@maven//:io_grpc_grpc_api",
"@maven//:io_grpc_grpc_netty",
"@maven//:io_netty_netty_handler",
"@maven//:org_slf4j_slf4j_api",
] + additional_tests

View File

@ -1,19 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.tls.TlsConfiguration
package object v1_14 {
def default(timeoutScaleFactor: Double): Vector[LedgerTestSuite] =
(v1_8.default(timeoutScaleFactor) ++ Vector(
new ExceptionRaceConditionIT,
new ExceptionsIT,
new LimitsIT,
)).sortBy(_.name)
def optional(tlsConfig: Option[TlsConfiguration]): Vector[LedgerTestSuite] =
v1_8.optional(tlsConfig)
}

View File

@ -1,283 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_14
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.testtool.infrastructure.RaceConditionTests._
import com.daml.ledger.api.v1.transaction.{TransactionTree, TreeEvent}
import com.daml.ledger.api.v1.value.RecordField
import com.daml.ledger.javaapi.data.Party
import com.daml.ledger.javaapi.data.codegen.ContractCompanion
import com.daml.ledger.test.java.semantic.exceptionracetests._
import scala.annotation.nowarn
import scala.concurrent.{ExecutionContext, Future}
final class ExceptionRaceConditionIT extends LedgerTestSuite {
import ExceptionRaceConditionIT.ExceptionRaceTests
import ExceptionRaceConditionIT.CompanionImplicits._
raceConditionTest(
"RWRollbackCreateVsNonTransientCreate",
"Cannot create a contract in a rollback and a non-transient contract with the same key",
) { implicit ec => ledger => alice =>
for {
wrapper <- ledger.create(alice, new CreateWrapper(alice))
_ <- executeRepeatedlyWithRandomDelay(
numberOfAttempts = 20,
once = ledger.create(alice, new ContractWithKey(alice)).map(_ => ()),
repeated =
ledger.exercise(alice, wrapper.exerciseCreateWrapper_CreateRollback()).map(_ => ()),
)
transactions <- transactions(ledger, alice)
} yield {
import ExceptionRaceConditionIT.TransactionUtil._
// We deliberately allow situations where no non-transient contract is created and verify the transactions
// order when such contract is actually created.
transactions.find(isCreate(_, ExceptionRaceTests.ContractWithKey.TemplateName)).foreach {
nonTransientCreateTransaction =>
transactions
.filter(isExercise(_, ExceptionRaceTests.CreateWrapper.ChoiceCreateRollback))
.foreach(assertTransactionOrder(_, nonTransientCreateTransaction))
}
}
}
raceConditionTest(
"RWArchiveVsRollbackNonConsumingChoice",
"Cannot exercise a non-consuming choice in a rollback after a contract archival",
) { implicit ec => ledger => alice =>
for {
wrapper: ExerciseWrapper.ContractId <- ledger.create(alice, new ExerciseWrapper(alice))
contract: ContractWithKey.ContractId <- ledger.create(alice, new ContractWithKey(alice))
_ <- executeRepeatedlyWithRandomDelay(
numberOfAttempts = 10,
once = ledger.exercise(alice, contract.exerciseContractWithKey_Archive()),
repeated = ledger.exercise(
alice,
wrapper.exerciseExerciseWrapper_ExerciseNonConsumingRollback(contract),
),
)
transactions <- transactions(ledger, alice)
} yield {
import ExceptionRaceConditionIT.TransactionUtil._
val archivalTransaction = assertSingleton("archivals", transactions.filter(isArchival))
transactions
.filter(isExercise(_, ExceptionRaceTests.ExerciseWrapper.ChoiceNonConsumingRollback))
.foreach(assertTransactionOrder(_, archivalTransaction))
}
}
raceConditionTest(
"RWArchiveVsRollbackConsumingChoice",
"Cannot exercise a consuming choice in a rollback after a contract archival",
) { implicit ec => ledger => alice =>
for {
wrapper: ExerciseWrapper.ContractId <- ledger.create(alice, new ExerciseWrapper(alice))
contract: ContractWithKey.ContractId <- ledger.create(alice, new ContractWithKey(alice))
_ <- executeRepeatedlyWithRandomDelay(
numberOfAttempts = 10,
once = ledger.exercise(alice, contract.exerciseContractWithKey_Archive()),
repeated = ledger.exercise(
alice,
wrapper.exerciseExerciseWrapper_ExerciseConsumingRollback(contract),
),
)
transactions <- transactions(ledger, alice)
} yield {
import ExceptionRaceConditionIT.TransactionUtil._
val archivalTransaction = assertSingleton("archivals", transactions.filter(isArchival))
transactions
.filter(isExercise(_, ExceptionRaceTests.ExerciseWrapper.ChoiceConsumingRollback))
.foreach(assertTransactionOrder(_, archivalTransaction))
}
}
raceConditionTest(
"RWArchiveVsRollbackFetch",
"Cannot fetch in a rollback after a contract archival",
) { implicit ec => ledger => alice =>
for {
contract: ContractWithKey.ContractId <- ledger.create(alice, new ContractWithKey(alice))
fetchConract: FetchWrapper.ContractId <- ledger.create(
alice,
new FetchWrapper(alice, contract),
)
_ <- executeRepeatedlyWithRandomDelay(
numberOfAttempts = 10,
once = ledger.exercise(alice, contract.exerciseContractWithKey_Archive()),
repeated = ledger.exercise(alice, fetchConract.exerciseFetchWrapper_Fetch()),
)
transactions <- transactions(ledger, alice)
} yield {
import ExceptionRaceConditionIT.TransactionUtil._
val archivalTransaction = assertSingleton("archivals", transactions.filter(isArchival))
transactions
.filter(isExercise(_, ExceptionRaceTests.FetchWrapper.ChoiceFetch))
.foreach(assertTransactionOrder(_, archivalTransaction))
}
}
raceConditionTest(
"RWArchiveVsRollbackLookupByKey",
"Cannot successfully lookup by key in a rollback after a contract archival",
) { implicit ec => ledger => alice =>
for {
contract: ContractWithKey.ContractId <- ledger.create(alice, new ContractWithKey(alice))
looker: LookupWrapper.ContractId <- ledger.create(alice, new LookupWrapper(alice))
_ <- executeRepeatedlyWithRandomDelay(
numberOfAttempts = 20,
once = ledger.exercise(alice, contract.exerciseContractWithKey_Archive()),
repeated = ledger.exercise(alice, looker.exerciseLookupWrapper_Lookup()),
)
transactions <- transactions(ledger, alice)
} yield {
import ExceptionRaceConditionIT.TransactionUtil._
val archivalTransaction = assertSingleton("archivals", transactions.filter(isArchival))
transactions
.filter(isRollbackContractLookup(success = true))
.foreach(assertTransactionOrder(_, archivalTransaction))
}
}
raceConditionTest(
"RWArchiveVsRollbackFailedLookupByKey",
"Lookup by key in a rollback cannot fail after a contract creation",
) { implicit ec => ledger => alice =>
for {
looker: LookupWrapper.ContractId <- ledger.create(alice, new LookupWrapper(alice))
_ <- executeRepeatedlyWithRandomDelay(
numberOfAttempts = 5,
once = ledger.create(alice, new ContractWithKey(alice)),
repeated = ledger.exercise(alice, looker.exerciseLookupWrapper_Lookup()),
): @nowarn("cat=lint-infer-any")
transactions <- transactions(ledger, alice)
} yield {
import ExceptionRaceConditionIT.TransactionUtil._
val createNonTransientTransaction = assertSingleton(
"create-non-transient transactions",
transactions.filter(isCreate(_, ExceptionRaceTests.ContractWithKey.TemplateName)),
)
transactions
.filter(isRollbackContractLookup(success = false))
.foreach(assertTransactionOrder(_, createNonTransientTransaction))
}
}
private def raceConditionTest(
shortIdentifier: String,
description: String,
repeated: Int = DefaultRepetitionsNumber,
runConcurrently: Boolean = false,
)(testCase: ExecutionContext => ParticipantTestContext => Party => Future[Unit]): Unit =
test(
shortIdentifier = shortIdentifier,
description = description,
partyAllocation = allocate(SingleParty),
repeated = repeated,
runConcurrently = runConcurrently,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
testCase(ec)(ledger)(party)
})
}
object ExceptionRaceConditionIT {
object TransactionUtil {
private implicit class TransactionTreeTestOps(tx: TransactionTree) {
def hasEventsNumber(expectedNumberOfEvents: Int): Boolean =
tx.eventsById.size == expectedNumberOfEvents
def containsEvent(condition: TreeEvent => Boolean): Boolean =
tx.eventsById.values.toList.exists(condition)
}
private def isCreated(templateName: String)(event: TreeEvent): Boolean =
event.kind.isCreated && event.getCreated.templateId.exists(_.entityName == templateName)
private def isExerciseEvent(choiceName: String)(event: TreeEvent): Boolean =
event.kind.isExercised && event.getExercised.choice == choiceName
def isCreate(tx: TransactionTree, templateName: String): Boolean =
tx.hasEventsNumber(1) &&
tx.containsEvent(isCreated(templateName))
def isExercise(tx: TransactionTree, choiceName: String): Boolean =
tx.hasEventsNumber(1) &&
tx.containsEvent(isExerciseEvent(choiceName))
def isArchival(tx: TransactionTree): Boolean =
tx.hasEventsNumber(1) &&
tx.containsEvent(isExerciseEvent(ExceptionRaceTests.ContractWithKey.ChoiceArchive))
private def isFoundContractField(found: Boolean)(field: RecordField) = {
field.label == "found" && field.value.exists(_.getBool == found)
}
def isRollbackContractLookup(success: Boolean)(tx: TransactionTree): Boolean =
tx.containsEvent { event =>
isCreated(ExceptionRaceTests.LookupResult.TemplateName)(event) &&
event.getCreated.getCreateArguments.fields.exists(isFoundContractField(found = success))
}
}
object ExceptionRaceTests {
object ContractWithKey {
val TemplateName = "ContractWithKey"
val ChoiceArchive = "ContractWithKey_Archive"
}
object FetchWrapper {
val ChoiceFetch = "FetchWrapper_Fetch"
}
object LookupResult {
val TemplateName = "LookupResult"
}
object CreateWrapper {
val ChoiceCreateRollback = "CreateWrapper_CreateRollback"
}
object ExerciseWrapper {
val ChoiceNonConsumingRollback = "ExerciseWrapper_ExerciseNonConsumingRollback"
val ChoiceConsumingRollback = "ExerciseWrapper_ExerciseConsumingRollback"
}
}
private object CompanionImplicits {
implicit val createWrapperCompanion: ContractCompanion.WithoutKey[
CreateWrapper.Contract,
CreateWrapper.ContractId,
CreateWrapper,
] = CreateWrapper.COMPANION
implicit val contractWithKeyCompanion: ContractCompanion.WithKey[
ContractWithKey.Contract,
ContractWithKey.ContractId,
ContractWithKey,
String,
] = ContractWithKey.COMPANION
implicit val lookupWrapperCompanion: ContractCompanion.WithoutKey[
LookupWrapper.Contract,
LookupWrapper.ContractId,
LookupWrapper,
] = LookupWrapper.COMPANION
implicit val fetchWrapperCompanion: ContractCompanion.WithoutKey[
FetchWrapper.Contract,
FetchWrapper.ContractId,
FetchWrapper,
] = FetchWrapper.COMPANION
implicit val exerciseWrapperCompanion: ContractCompanion.WithoutKey[
ExerciseWrapper.Contract,
ExerciseWrapper.ContractId,
ExerciseWrapper,
] = ExerciseWrapper.COMPANION
}
}

View File

@ -1,506 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_14
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.Synchronize.synchronize
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers._
import com.daml.ledger.javaapi.data.codegen.ContractCompanion
import com.daml.ledger.test.java.semantic.da.types
import com.daml.ledger.test.java.semantic.exceptions._
import java.lang
import java.util.regex.Pattern
import scala.jdk.CollectionConverters._
final class ExceptionsIT extends LedgerTestSuite {
import ExceptionsIT.CompanionImplicits._
test(
"ExUncaught",
"Uncaught exception returns INVALID_ARGUMENT",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
failure <- ledger.exercise(party, t.exerciseThrowUncaught()).mustFail("Unhandled exception")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Interpreter.UnhandledException,
Some(Pattern.compile("Unhandled (Daml )?exception")),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"ExCaughtBasic",
"Exceptions can be caught",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
tree <- ledger.exercise(party, t.exerciseThrowCaught())
} yield {
assertLength(s"1 successful exercise", 1, exercisedEvents(tree))
()
}
})
test(
"ExCaughtNested",
"Exceptions can be caught when thrown from a nested try block",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
tree <- ledger.exercise(party, t.exerciseNestedCatch())
} yield {
assertLength(s"1 successful exercise", 1, exercisedEvents(tree))
()
}
})
test(
"ExRollbackActiveFetch",
"Rollback node depends on activeness of contract in a fetch",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
tFetch <- ledger.create(party, new ExceptionTester(party))
_ <- ledger.exercise(party, t.exerciseRollbackFetch(tFetch))
_ <- ledger.exercise(party, tFetch.exerciseArchive())
failure <- ledger
.exercise(party, t.exerciseRollbackFetch(tFetch))
.mustFail("contract is archived")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
Some("Contract could not be found"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"ExRollbackActiveExerciseConsuming",
"Rollback node depends on activeness of contract in a consuming exercise",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
tExercise <- ledger.create(party, new ExceptionTester(party))
_ <- ledger.exercise(party, t.exerciseRollbackConsuming(tExercise))
_ <- ledger.exercise(party, tExercise.exerciseArchive())
failure <- ledger
.exercise(party, t.exerciseRollbackConsuming(tExercise))
.mustFail("contract is archived")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
Some("Contract could not be found"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"ExRollbackActiveExerciseNonConsuming",
"Rollback node depends on activeness of contract in a non-consuming exercise",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
tExercise <- ledger.create(party, new ExceptionTester(party))
_ <- ledger.exercise(party, t.exerciseRollbackNonConsuming(tExercise))
_ <- ledger.exercise(party, tExercise.exerciseArchive())
failure <- ledger
.exercise(party, t.exerciseRollbackNonConsuming(tExercise))
.mustFail("contract is archived")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
Some("Contract could not be found"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"ExRolledbackArchiveConsuming",
"Rolled back archive does not block consuming exercise",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
withKey <- ledger.create(party, new WithSimpleKey(party))
_ <- ledger.exercise(party, t.exerciseRolledbackArchiveConsuming(withKey))
} yield ()
})
test(
"ExRolledbackArchiveNonConsuming",
"Rolled back archive does not block non-consuming exercise",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
withKey <- ledger.create(party, new WithSimpleKey(party))
_ <- ledger.exercise(party, t.exerciseRolledbackArchiveNonConsuming(withKey))
} yield ()
})
test(
"ExRolledbackKeyCreation",
"Rolled back key creation does not block creation of the same key",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
_ <- ledger.exercise(party, t.exerciseRolledbackDuplicateKey())
} yield ()
})
test(
"ExRollbackDuplicateKeyCreated",
"Rollback fails once contract with same key is created",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
_ <- ledger.exercise(party, t.exerciseDuplicateKey())
_ <- ledger.create(party, new WithSimpleKey(party))
failure <- ledger.exercise(party, t.exerciseDuplicateKey()).mustFail("duplicate key")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.DuplicateContractKey,
Some("DuplicateKey"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"ExRollbackDuplicateKeyArchived",
"Rollback succeeds once contract with same key is archived",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
withKey <- ledger.create(party, new WithSimpleKey(party))
failure <- ledger.exercise(party, t.exerciseDuplicateKey()).mustFail("duplicate key")
_ = assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.DuplicateContractKey,
Some("DuplicateKey"),
checkDefiniteAnswerMetadata = true,
)
_ <- ledger.exercise(party, withKey.exerciseArchive())
_ <- ledger.exercise(party, t.exerciseDuplicateKey())
} yield ()
})
test(
"ExRollbackKeyFetchCreated",
"Rollback with key fetch fails once contract is archived",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
withKey <- ledger.create(party, new WithSimpleKey(party))
_ <- ledger.exercise(party, t.exerciseFetchKey())
_ <- ledger.exercise(party, withKey.exerciseArchive())
failure <- ledger.exercise(party, t.exerciseFetchKey()).mustFail("couldn't find key")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some("couldn't find key"),
checkDefiniteAnswerMetadata = true,
)
()
}
})
test(
"ExRollbackKeyFetchArchived",
"Rollback with key fetch succeeds once contract is created",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
failure <- ledger.exercise(party, t.exerciseFetchKey()).mustFail("contract not found")
_ = assertGrpcError(
failure,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some("couldn't find key"),
checkDefiniteAnswerMetadata = true,
)
_ <- ledger.create(party, new WithSimpleKey(party))
_ <- ledger.exercise(party, t.exerciseFetchKey())
} yield ()
})
test(
"ExRollbackHidden",
"Create and exercise in rollback node is not exposed on ledger API",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
tree <- ledger.exercise(party, t.exerciseRollbackCreate())
} yield {
// Create node should not be included
assertLength(s"no creates", 0, createdEvents(tree))
// Only the root exercise should be included not the one in the rollback node.
val exercise = assertSingleton(s"1 exercise", exercisedEvents(tree))
assert(exercise.choice == "RollbackCreate", "Choice name mismatch")
()
}
})
test(
"ExRollbackDivulge",
"Fetch in rollback divulges",
allocate(SingleParty, SingleParty),
)(implicit ec => {
case Participants(Participant(aLedger, aParty), Participant(bLedger, bParty)) =>
for {
divulger <- aLedger.create(aParty, new Divulger(aParty, bParty))
fetcher <- bLedger.create(bParty, new Fetcher(bParty, aParty))
t <- bLedger.create(bParty, new WithSimpleKey(bParty))
_ <- synchronize(aLedger, bLedger)
fetchFailure <- aLedger
.exercise(aParty, fetcher.exerciseFetch(t))
.mustFail("contract could not be found")
_ = assertGrpcError(
fetchFailure,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
Some("Contract could not be found"),
checkDefiniteAnswerMetadata = true,
)
_ <- bLedger.exercise(bParty, divulger.exerciseDivulge(t))
_ <- synchronize(aLedger, bLedger)
_ <- aLedger
.exercise(aParty, fetcher.exerciseFetch(t))
} yield ()
})
test(
"ExRollbackProjectionDivulgence",
"Fetch and fetchbykey in projection divulge",
allocate(SingleParty, SingleParty),
)(implicit ec => {
case Participants(Participant(aLedger, aParty), Participant(bLedger, bParty)) =>
for {
fetcher <- aLedger.create(aParty, new Fetcher(aParty, bParty))
withKey0 <- aLedger.create(aParty, new WithKey(aParty, 0, List.empty.asJava))
withKey1 <- aLedger.create(aParty, new WithKey(aParty, 1, List.empty.asJava))
_ <- synchronize(aLedger, bLedger)
fetchFailure <- bLedger
.exercise(bParty, fetcher.exerciseFetch_(withKey0))
.mustFail("contract could not be found")
_ = assertGrpcError(
fetchFailure,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
Some("Contract could not be found"),
checkDefiniteAnswerMetadata = true,
)
fetchFailure <- bLedger
.exercise(bParty, fetcher.exerciseFetch_(withKey1))
.mustFail("contract could not be found")
_ = assertGrpcError(
fetchFailure,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
Some("Contract could not be found"),
checkDefiniteAnswerMetadata = true,
)
tester <- aLedger.create(aParty, new ExceptionTester(aParty))
_ <- aLedger.exercise(aParty, tester.exerciseProjectionDivulgence(bParty, withKey0))
_ <- synchronize(aLedger, bLedger)
_ <- bLedger
.exercise(bParty, fetcher.exerciseFetch_(withKey0))
_ <- bLedger
.exercise(bParty, fetcher.exerciseFetch_(withKey1))
} yield ()
})
test(
"ExRollbackProjectionNormalization",
"Projection normalization is correctly applied",
allocate(SingleParty, SingleParty, SingleParty),
)(implicit ec => {
// We cannot test projection & normalization directly via the ledger API
// since rollback nodes are erased so this test only ensures
// that the code paths for this are exercised and do not
// throw errors.
case Participants(
Participant(aLedger, aParty),
Participant(bLedger, bParty),
Participant(cLedger, cParty),
) =>
for {
abInformer <- aLedger.create(aParty, new Informer(aParty, List(bParty.getValue).asJava))
acInformer <- aLedger.create(aParty, new Informer(aParty, List(cParty.getValue).asJava))
abcInformer <- aLedger.create(
aParty,
new Informer(aParty, List(bParty, cParty).map(_.getValue).asJava),
)
keyDelegate <- bLedger.create(bParty, new WithKeyDelegate(aParty, bParty))
_ <- synchronize(aLedger, bLedger)
_ <- synchronize(aLedger, cLedger)
tester <- aLedger.create(aParty, new ExceptionTester(aParty))
_ <- aLedger.exercise(
aParty,
tester.exerciseProjectionNormalization(
bParty,
keyDelegate,
abInformer,
acInformer,
abcInformer,
),
)
} yield ()
})
test(
"ExRollbackProjectionNesting",
"Nested rollback nodes are handled properly",
allocate(SingleParty, SingleParty, SingleParty),
)(implicit ec => {
// We cannot test projection & normalization directly via the ledger API
// since rollback nodes are erased so this test only ensures
// that the code paths for this are exercised and do not
// throw errors.
case Participants(
Participant(aLedger, aParty),
Participant(bLedger, bParty),
Participant(cLedger, cParty),
) =>
for {
keyDelegate <- bLedger.create(bParty, new WithKeyDelegate(aParty, bParty))
nestingHelper <- cLedger.create(cParty, new RollbackNestingHelper(aParty, bParty, cParty))
_ <- synchronize(aLedger, bLedger)
_ <- synchronize(aLedger, cLedger)
tester <- aLedger.create(aParty, new ExceptionTester(aParty))
_ <- aLedger.exercise(
aParty,
tester.exerciseProjectionNesting(bParty, keyDelegate, nestingHelper),
)
} yield ()
})
test(
"ExCKRollbackGlobalArchivedLookup",
"Create with key succeeds after archive & rolledback negative lookup",
allocate(SingleParty),
)(implicit ec => {
case Participants(
Participant(ledger, party)
) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
withKey <- ledger.create(party, new WithSimpleKey(party))
_ <- ledger.exercise(party, t.exerciseRollbackGlobalArchivedLookup(withKey))
} yield ()
})
test(
"ExCKRollbackGlobalArchivedCreate",
"Create with key succeeds after archive & rolledback negative lookup",
allocate(SingleParty),
)(implicit ec => {
case Participants(
Participant(ledger, party)
) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
withKey <- ledger.create(party, new WithSimpleKey(party))
_ <- ledger.exercise(party, t.exerciseRollbackGlobalArchivedCreate(withKey))
} yield ()
})
test(
"ExRollbackCreate",
"Archiving a contract created within a rolled-back try-catch block, fails",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new ExceptionTester(party))
failure <- ledger
.exercise(party, t.exerciseRollbackCreateBecomesInactive())
.mustFail("contract is inactive")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
Some("Contract could not be found"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"ExRollbackExerciseCreateLookup",
"Lookup a contract Archiving a contract created within a rolled-back try-catch block, fails",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
helper <- ledger.create(party, new ExceptionTester(party))
withKey <- ledger.create(party, new WithSimpleKey(party))
_ <- ledger.exercise(party, helper.exerciseRollbackExerciseCreateLookup(withKey))
} yield ()
})
}
object ExceptionsIT {
private object CompanionImplicits {
implicit val exceptionTesterCompanion: ContractCompanion.WithoutKey[
ExceptionTester.Contract,
ExceptionTester.ContractId,
ExceptionTester,
] = ExceptionTester.COMPANION
implicit val withSimpleKeyCompanion: ContractCompanion.WithKey[
WithSimpleKey.Contract,
WithSimpleKey.ContractId,
WithSimpleKey,
String,
] = WithSimpleKey.COMPANION
implicit val withKeyCompanion: ContractCompanion.WithKey[
WithKey.Contract,
WithKey.ContractId,
WithKey,
types.Tuple2[String, lang.Long],
] = WithKey.COMPANION
implicit val informerCompanion
: ContractCompanion.WithoutKey[Informer.Contract, Informer.ContractId, Informer] =
Informer.COMPANION
implicit val withKeyDelegateCompanion: ContractCompanion.WithoutKey[
WithKeyDelegate.Contract,
WithKeyDelegate.ContractId,
WithKeyDelegate,
] = WithKeyDelegate.COMPANION
implicit val divulgerCompanion
: ContractCompanion.WithoutKey[Divulger.Contract, Divulger.ContractId, Divulger] =
Divulger.COMPANION
implicit val fetcherCompanion
: ContractCompanion.WithoutKey[Fetcher.Contract, Fetcher.ContractId, Fetcher] =
Fetcher.COMPANION
implicit val rollbackNestingHelperCompanion: ContractCompanion.WithoutKey[
RollbackNestingHelper.Contract,
RollbackNestingHelper.ContractId,
RollbackNestingHelper,
] = RollbackNestingHelper.COMPANION
}
}

View File

@ -1,78 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_14
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.test.java.semantic.limits.{WithList, WithMap}
import scala.jdk.CollectionConverters._
final class LimitsIT extends LedgerTestSuite {
test(
"LLargeMapInContract",
"Create a contract with a field containing large map",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, alice)) =>
val elements = (1 to 10000).map(e => (f"element_$e%08d", alice.getValue)).toMap.asJava
for {
contract: WithMap.ContractId <- ledger.create(alice, new WithMap(alice, elements))(
WithMap.COMPANION
)
_ <- ledger.exercise(alice, contract.exerciseWithMap_Noop())
} yield {
()
}
})
test(
"LLargeMapInChoice",
"Exercise a choice with a large map",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, alice)) =>
val elements = (1 to 10000).map(e => (f"element_$e%08d", alice.getValue)).toMap.asJava
for {
contract: WithMap.ContractId <- ledger.create(
alice,
new WithMap(alice, Map.empty[String, String].asJava),
)(WithMap.COMPANION)
_ <- ledger.exercise(alice, contract.exerciseWithMap_Expand(elements))
} yield {
()
}
})
test(
"LLargeListInContract",
"Create a contract with a field containing large list",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, alice)) =>
val elements = (1 to 10000).map(e => f"element_$e%08d").asJava
for {
contract: WithList.ContractId <- ledger.create(alice, new WithList(alice, elements))(
WithList.COMPANION
)
_ <- ledger.exercise(alice, contract.exerciseWithList_Noop())
} yield {
()
}
})
test(
"LLargeListInChoice",
"Exercise a choice with a large list",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, alice)) =>
val elements = (1 to 10000).map(e => f"element_$e%08d").asJava
for {
contract: WithList.ContractId <- ledger
.create(alice, new WithList(alice, List.empty[String].asJava))(WithList.COMPANION)
_ <- ledger.exercise(alice, contract.exerciseWithList_Expand(elements))
} yield {
()
}
})
}

View File

@ -1,22 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.tls.TlsConfiguration
package object v1_15 {
def default(timeoutScaleFactor: Double): Vector[LedgerTestSuite] =
v1_14.default(timeoutScaleFactor) ++ Vector(
new InterfaceIT,
new InterfaceSubscriptionsIT,
new InterfaceSubscriptionsWithEventBlobsIT,
new TransactionServiceFiltersIT,
new ExplicitDisclosureIT,
new EventQueryServiceIT,
)
def optional(tlsConfig: Option[TlsConfiguration]): Vector[LedgerTestSuite] =
v1_14.optional(tlsConfig)
}

View File

@ -1,333 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_15
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.v1.event_query_service.{
GetEventsByContractIdRequest,
GetEventsByContractKeyRequest,
}
import com.daml.ledger.api.v1.value._
import com.daml.ledger.javaapi.data.Party
import com.daml.ledger.test.java.model.test.{Dummy, _}
import com.daml.lf.value.Value.ContractId
import scalapb.GeneratedMessage
import java.util.{List => JList}
import scala.jdk.CollectionConverters._
import scala.concurrent.Future
class EventQueryServiceIT extends LedgerTestSuite {
import com.daml.ledger.api.testtool.suites.v1_8.CompanionImplicits._
private def toOption(protoString: String): Option[String] = {
if (protoString.nonEmpty) Some(protoString) else None
}
// Note that the Daml template must be inspected to establish the key type and fields
// For the TextKey template the key is: (tkParty, tkKey) : (Party, Text)
// When populating the Record identifiers are not required.
private def makeTextKeyKey(party: Party, keyText: String) = {
Value(
Value.Sum.Record(
Record(fields =
Vector(
RecordField(value = Some(Value(Value.Sum.Party(party)))),
RecordField(value = Some(Value(Value.Sum.Text(keyText)))),
)
)
)
)
}
test(
"TXEventsByContractIdBasic",
"Expose a create event by contract id",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
tx <- ledger.submitAndWaitForTransaction(
ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
)
expected = assertDefined(
tx.transaction.flatMap(_.events.flatMap(_.event.created).headOption),
"Expected a created event",
)
events <- ledger.getEventsByContractId(
GetEventsByContractIdRequest(expected.contractId, Seq(party))
)
} yield {
val actual = assertDefined(events.createEvent, "Expected a created event")
assertEquals("Looked up event should match the transaction event", actual, expected)
}
})
test(
"TXEventsByContractIdConsumed",
"Expose an archive event by contract id",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
dummyCid <- ledger.create(party, new Dummy(party))
tx <- ledger.submitAndWaitForTransaction(
ledger.submitAndWaitRequest(party, dummyCid.exerciseDummyChoice1().commands)
)
expected = assertDefined(
tx.getTransaction.events.flatMap(_.event.archived).headOption,
"Expected an exercised event",
)
events <- ledger.getEventsByContractId(
GetEventsByContractIdRequest(dummyCid.contractId, Seq(party))
)
} yield {
assertDefined(events.createEvent, "Expected a create event")
val actual = assertDefined(events.archiveEvent, "Expected a exercise event")
assertEquals("Looked up event should match the transaction event", actual, expected)
}
})
test(
"TXEventsByContractIdNotExistent",
"No events are returned for a non-existent contract id",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val nonExistentContractId = ContractId.V1.assertFromString("00" * 32 + "0001")
for {
events <- ledger.getEventsByContractId(
GetEventsByContractIdRequest(nonExistentContractId.coid, Seq(party))
)
} yield {
assertIsEmpty(Seq(events.createEvent, events.archiveEvent).flatten[GeneratedMessage])
}
})
test(
"TXEventsByContractIdNotVisible",
"No events are returned for a non-visible contract id",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, party, notTheSubmittingParty)) =>
for {
tx <- ledger.submitAndWaitForTransaction(
ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
)
expected = assertDefined(
tx.transaction.flatMap(_.events.flatMap(_.event.created).headOption),
"Expected a created event",
)
events <- ledger.getEventsByContractId(
GetEventsByContractIdRequest(
expected.contractId,
Seq(notTheSubmittingParty),
)
)
} yield {
assertIsEmpty(Seq(events.createEvent, events.archiveEvent).flatten[GeneratedMessage])
}
})
test(
"TXEventsByContractKeyBasic",
"Expose a visible create event by contract key",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val someKey = "some key"
val key = makeTextKeyKey(party, someKey)
for {
tx <- ledger.submitAndWaitForTransaction(
ledger.submitAndWaitRequest(party, new TextKey(party, someKey, JList.of()).create.commands)
)
expected = assertDefined(
tx.transaction.flatMap(_.events.flatMap(_.event.created).headOption),
"Expected a created event",
)
events <- ledger.getEventsByContractKey(
GetEventsByContractKeyRequest(
contractKey = Some(key),
templateId = Some(TextKey.TEMPLATE_ID.toV1),
requestingParties = Seq(party),
)
)
} yield {
val actual = assertDefined(events.createEvent, "Expected a created event")
assertEquals("Looked up event should match the transaction event", actual, expected)
}
})
test(
"TXArchiveEventByContractKeyBasic",
"Expose a visible archive event by contract key",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val someKey = "some archive key"
val key = makeTextKeyKey(party, someKey)
for {
cId: TextKey.ContractId <- ledger.create(party, new TextKey(party, someKey, JList.of()))
tx <- ledger.submitAndWaitForTransaction(
ledger.submitAndWaitRequest(party, cId.exerciseTextKeyChoice().commands)
)
expected = assertDefined(
tx.transaction.flatMap(_.events.flatMap(_.event.archived).headOption),
"Expected an archived event",
)
events <- ledger.getEventsByContractKey(
GetEventsByContractKeyRequest(
contractKey = Some(key),
templateId = Some(TextKey.TEMPLATE_ID.toV1),
requestingParties = Seq(party),
)
)
} yield {
assertDefined(events.createEvent, "Expected a create event")
val actual = assertDefined(events.archiveEvent, "Expected a archived event")
assertEquals("Looked up event should match the transaction event", actual, expected)
}
})
test(
"TXEventsByContractKeyNoKey",
"No events are returned for a non existent contract key",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val key = makeTextKeyKey(party, "non existent key")
for {
events <- ledger.getEventsByContractKey(
GetEventsByContractKeyRequest(
contractKey = Some(key),
templateId = Some(TextKey.TEMPLATE_ID.toV1),
requestingParties = Seq(party),
)
)
} yield {
assertIsEmpty(Seq(events.createEvent, events.archiveEvent).flatten[GeneratedMessage])
}
})
test(
"TXEventsByContractKeyNotVisible",
"No events are returned for a non visible contract key",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, party, notTheSubmittingParty)) =>
val nonVisibleKey = "non visible key"
val key = makeTextKeyKey(party, nonVisibleKey)
for {
_ <- ledger.submitAndWaitForTransaction(
ledger.submitAndWaitRequest(
party,
new TextKey(party, nonVisibleKey, JList.of()).create.commands,
)
)
events <- ledger.getEventsByContractKey(
GetEventsByContractKeyRequest(
contractKey = Some(key),
templateId = Some(TextKey.TEMPLATE_ID.toV1),
requestingParties = Seq(notTheSubmittingParty),
)
)
} yield {
assertIsEmpty(Seq(events.createEvent, events.archiveEvent).flatten[GeneratedMessage])
}
})
test(
"TXEventsByContractKeyEndExclusive",
"Should return event prior to the end exclusive event",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val exercisedKey = "paging key"
val key = makeTextKeyKey(party, exercisedKey)
def getNextResult(continuationToken: Option[String]): Future[Option[String]] = {
ledger
.getEventsByContractKey(
GetEventsByContractKeyRequest(
contractKey = Some(key),
templateId = Some(TextKey.TEMPLATE_ID.toV1),
requestingParties = Seq(party),
continuationToken = continuationToken.getOrElse(
GetEventsByContractKeyRequest.defaultInstance.continuationToken
),
)
)
.map(r => toOption(r.continuationToken))
}
for {
textKeyCid1: TextKey.ContractId <- ledger.create(
party,
new TextKey(party, exercisedKey, Nil.asJava),
)
_ <- ledger.submitAndWaitForTransaction(
ledger.submitAndWaitRequest(party, textKeyCid1.exerciseTextKeyChoice().commands)
)
textKeyCid2: TextKey.ContractId <- ledger.create(
party,
new TextKey(party, exercisedKey, Nil.asJava),
)
_ <- ledger.submitAndWaitForTransaction(
ledger.submitAndWaitRequest(party, textKeyCid2.exerciseTextKeyChoice().commands)
)
eventId1 <- getNextResult(None)
eventId2 <- getNextResult(Some(assertDefined(eventId1, "Expected eventId2")))
eventId3 <- getNextResult(Some(assertDefined(eventId2, "Expected eventId3")))
} yield {
assertEquals("Expected the final offset to be empty", eventId3, None)
}
})
test(
"TXEventsByContractKeyChained",
"Should not miss events where the choice recreates the key",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val exercisedKey = "paging key"
val key = makeTextKeyKey(party, exercisedKey)
// (contract-id, continuation-token)
def getNextResult(
continuationToken: Option[String]
): Future[(Option[String], Option[String])] = {
ledger
.getEventsByContractKey(
GetEventsByContractKeyRequest(
contractKey = Some(key),
templateId = Some(TextKey.TEMPLATE_ID.toV1),
requestingParties = Seq(party),
continuationToken = continuationToken.getOrElse(
GetEventsByContractKeyRequest.defaultInstance.continuationToken
),
)
)
.map(r => (r.createEvent.map(_.contractId), toOption(r.continuationToken)))
}
for {
expected: TextKey.ContractId <- ledger.create(
party,
new TextKey(party, exercisedKey, Nil.asJava),
)
_ <- ledger.submitAndWaitForTransaction(
ledger.submitAndWaitRequest(
party,
expected.exerciseTextKeyDisclose(JList.of(): JList[String]).commands,
)
)
(cId2, token2) <- getNextResult(None)
(cId1, token1) <- getNextResult(token2)
(cId0, _) <- getNextResult(token1)
} yield {
assertEquals("Expected the first offset to be empty", cId2.isDefined, true)
assertEquals("Expected the final offset to be empty", cId1, Some(expected.contractId))
assertEquals("Expected the final offset to be empty", cId0.isDefined, false)
}
})
}

View File

@ -1,629 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_15
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.Synchronize.synchronize
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers.createdEvents
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.javaapi.data.{CreateCommand, DamlRecord, ExerciseByKeyCommand, Party}
import com.daml.ledger.api.v1.command_service.SubmitAndWaitRequest
import com.daml.ledger.api.v1.commands.DisclosedContract
import com.daml.ledger.api.v1.event.CreatedEvent
import com.daml.ledger.api.v1.transaction_filter.{
Filters,
InclusiveFilters,
TemplateFilter,
TransactionFilter,
}
import com.daml.ledger.api.v1.value.Identifier
import com.daml.ledger.test.java.model.test._
import com.daml.ledger.javaapi
import com.daml.lf.transaction.TransactionCoder
import com.google.protobuf.ByteString
import com.daml.ledger.api.testtool.suites.v1_8.CompanionImplicits._
import java.util.{List => JList}
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Success, Try}
final class ExplicitDisclosureIT extends LedgerTestSuite {
import ExplicitDisclosureIT._
test(
"EDCorrectCreatedEventBlobDisclosure",
"Submission is successful if the correct disclosure as created_event_blob is provided",
allocate(SingleParty, SingleParty),
enabled = _.explicitDisclosure,
)(implicit ec => {
case Participants(
Participant(ownerParticipant, owner),
Participant(delegateParticipant, delegate),
) =>
for {
testContext <- initializeTest(
ownerParticipant = ownerParticipant,
delegateParticipant = delegateParticipant,
owner = owner,
delegate = delegate,
transactionFilter = filterByPartyAndTemplate(owner),
)
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, delegateParticipant)
// Exercise a choice on the Delegation that fetches the Delegated contract
// Fails because the submitter doesn't see the contract being fetched
exerciseFetchError <- testContext
.exerciseFetchDelegated()
.mustFail("the submitter does not see the contract")
// Exercise the same choice, this time using correct explicit disclosure
_ <- testContext.exerciseFetchDelegated(testContext.disclosedContract)
} yield {
assertEquals(!testContext.disclosedContract.createdEventBlob.isEmpty, true)
assertGrpcError(
exerciseFetchError,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
None,
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"EDSuperfluousDisclosure",
"Submission is successful when unnecessary disclosed contract is provided",
allocate(SingleParty, SingleParty),
enabled = _.explicitDisclosure,
)(testCase = implicit ec => {
case Participants(
Participant(ownerParticipant, owner),
Participant(delegateParticipant, delegate),
) =>
for {
testContext <- initializeTest(
ownerParticipant = ownerParticipant,
delegateParticipant = delegateParticipant,
owner = owner,
delegate = delegate,
transactionFilter = filterByPartyAndTemplate(owner),
)
dummyCid: Dummy.ContractId <- ownerParticipant.create(owner, new Dummy(owner))
dummyTxs <- ownerParticipant.flatTransactions(
ownerParticipant.getTransactionsRequest(
filterByPartyAndTemplate(owner, Dummy.TEMPLATE_ID)
)
)
dummyCreate = createdEvents(dummyTxs(0)).head
dummyDisclosedContract = createEventToDisclosedContract(dummyCreate)
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, delegateParticipant)
// Exercise works with provided disclosed contract
_ <- testContext.exerciseFetchDelegated(testContext.disclosedContract)
// Exercise works with the Dummy contract as a superfluous disclosed contract
_ <- testContext.exerciseFetchDelegated(
testContext.disclosedContract,
dummyDisclosedContract,
)
// Archive the Dummy contract
_ <- ownerParticipant.exercise(owner, dummyCid.exerciseArchive())
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, delegateParticipant)
// Exercise works with the archived superfluous disclosed contract
_ <- testContext.exerciseFetchDelegated(
testContext.disclosedContract,
dummyDisclosedContract,
)
} yield ()
})
test(
"EDExerciseByKeyDisclosedContract",
"A disclosed contract can be exercised by key with non-witness readers if authorized",
partyAllocation = allocate(SingleParty, SingleParty),
enabled = _.explicitDisclosure,
) { implicit ec =>
{
case Participants(
Participant(ownerParticipant, owner),
Participant(divulgeeParticipant, divulgee),
) =>
for {
// Create contract with `owner` as only stakeholder
_ <- ownerParticipant.submitAndWait(
ownerParticipant.submitAndWaitRequest(owner, new WithKey(owner).create.commands)
)
txs <- ownerParticipant.flatTransactions(
ownerParticipant.getTransactionsRequest(
filterByPartyAndTemplate(owner, WithKey.TEMPLATE_ID)
)
)
withKeyCreationTx = assertSingleton("Transaction expected non-empty", txs)
withKeyCreate = createdEvents(withKeyCreationTx).head
withKeyDisclosedContract = createEventToDisclosedContract(withKeyCreate)
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, divulgeeParticipant)
exerciseByKeyError <- divulgeeParticipant
.submitAndWait(
exerciseWithKey_byKey_request(divulgeeParticipant, owner, divulgee, None)
)
.mustFail("divulgee does not see the contract")
// Assert that a random party can exercise the contract by key (if authorized)
// when passing the disclosed contract to the submission
_ <- divulgeeParticipant.submitAndWait(
exerciseWithKey_byKey_request(
divulgeeParticipant,
owner,
divulgee,
Some(withKeyDisclosedContract),
)
)
} yield assertGrpcError(
exerciseByKeyError,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
None,
checkDefiniteAnswerMetadata = true,
)
}
}
test(
"EDArchivedDisclosedContracts",
"The ledger rejects archived disclosed contracts",
allocate(SingleParty, SingleParty),
enabled = _.explicitDisclosure,
)(implicit ec => {
case Participants(
Participant(ownerParticipant, owner),
Participant(delegateParticipant, delegate),
) =>
for {
testContext <- initializeTest(
ownerParticipant = ownerParticipant,
delegateParticipant = delegateParticipant,
owner = owner,
delegate = delegate,
transactionFilter = filterByPartyAndTemplate(owner),
)
// Archive the disclosed contract
_ <- ownerParticipant.exercise(owner, testContext.delegatedCid.exerciseArchive())
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, delegateParticipant)
// Exercise the choice using the now inactive disclosed contract
_ <- testContext
.exerciseFetchDelegated(testContext.disclosedContract)
.mustFail("the contract is already archived")
} yield {
// TODO ED: Assert specific error codes once Canton error codes can be accessible from these suites
}
})
test(
"EDDisclosedContractsArchiveRaceTest",
"Only one archival succeeds in a race between a normal exercise and one with disclosed contracts",
allocate(SingleParty, SingleParty),
enabled = _.explicitDisclosure,
repeated = 3,
)(implicit ec => {
case Participants(Participant(ledger1, party1), Participant(ledger2, party2)) =>
val attempts = 10
Future
.traverse((1 to attempts).toList) {
_ =>
for {
contractId: Dummy.ContractId <- ledger1.create(party1, new Dummy(party1))
transactions <- ledger1.flatTransactionsByTemplateId(Dummy.TEMPLATE_ID, party1)
create = createdEvents(transactions(0)).head
disclosedContract = createEventToDisclosedContract(create)
// Submit concurrently two consuming exercise choices (with and without disclosed contract)
party1_exerciseF = ledger1.exercise(party1, contractId.exerciseArchive())
// Ensure participants are synchronized
_ <- synchronize(ledger1, ledger2)
party2_exerciseWithDisclosureF =
ledger2.submitAndWait(
ledger2
.submitAndWaitRequest(party2, contractId.exercisePublicChoice(party2).commands)
.update(_.commands.disclosedContracts := scala.Seq(disclosedContract))
)
// Wait for both commands to finish
party1_exercise_result <- party1_exerciseF.transform(Success(_))
party2_exerciseWithDisclosure <- party2_exerciseWithDisclosureF.transform(Success(_))
} yield {
oneFailedWith(
party1_exercise_result,
party2_exerciseWithDisclosure,
) { _ =>
// TODO ED: Assert specific error codes once Canton error codes can be accessible from these suites
()
}
}
}
.map(_ => ())
})
test(
"EDMalformedDisclosedContractCreatedEventBlob",
"The ledger rejects disclosed contracts with a malformed created event blob",
allocate(SingleParty, SingleParty),
enabled = _.explicitDisclosure,
)(implicit ec => {
case Participants(
Participant(ownerParticipant, owner),
Participant(delegateParticipant, delegate),
) =>
for {
testContext <- initializeTest(
ownerParticipant = ownerParticipant,
delegateParticipant = delegateParticipant,
owner = owner,
delegate = delegate,
transactionFilter = filterByPartyAndTemplate(owner),
)
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, delegateParticipant)
// Exercise a choice using invalid explicit disclosure
failure <- testContext
.exerciseFetchDelegated(
testContext.disclosedContract
.update(_.createdEventBlob.set(ByteString.copyFromUtf8("foo")))
)
.mustFail("using a malformed disclosed contract created event blob")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.InvalidArgument,
Some(
"The submitted command has invalid arguments: Unable to decode disclosed contract event payload: DecodeError"
),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"EDInconsistentDisclosedContract",
"The ledger rejects inconsistent disclosed contract",
allocate(SingleParty, SingleParty),
enabled = _.explicitDisclosure,
)(implicit ec => {
case Participants(
Participant(ownerParticipant, owner),
Participant(delegateParticipant, delegate),
) =>
for {
ownerContext <- initializeTest(
ownerParticipant = ownerParticipant,
delegateParticipant = delegateParticipant,
owner = owner,
delegate = delegate,
transactionFilter = filterByPartyAndTemplate(owner),
)
// Create a new context only for the sake of getting a new disclosed contract
// with the same template id
delegateContext <- initializeTest(
ownerParticipant = delegateParticipant,
delegateParticipant = delegateParticipant,
owner = delegate,
delegate = delegate,
transactionFilter = filterByPartyAndTemplate(delegate),
)
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, delegateParticipant)
otherSalt = TransactionCoder
.decodeFatContractInstance(delegateContext.disclosedContract.createdEventBlob)
.map(_.cantonData)
.getOrElse(fail("contract decode failed"))
tamperedEventBlob = TransactionCoder
.encodeFatContractInstance(
TransactionCoder
.decodeFatContractInstance(ownerContext.disclosedContract.createdEventBlob)
.map(_.setSalt(otherSalt))
.getOrElse(fail("contract decode failed"))
)
.getOrElse(fail("contract encode failed"))
_ <- ownerContext
// Use of inconsistent disclosed contract
// i.e. the delegate cannot fetch the owner's contract with attaching a different disclosed contract
.exerciseFetchDelegated(
ownerContext.disclosedContract.copy(createdEventBlob = tamperedEventBlob)
)
.mustFail("using an inconsistent disclosed contract created event blob")
} yield {
// TODO ED: Assert specific error codes once Canton error codes can be accessible from these suites
// Should be DISCLOSED_CONTRACT_AUTHENTICATION_FAILED
}
})
test(
"EDDuplicates",
"Submission is rejected on duplicate contract ids or key hashes",
allocate(SingleParty, SingleParty),
enabled = _.explicitDisclosure,
)(implicit ec => {
case Participants(
Participant(ownerParticipant, owner),
Participant(delegateParticipant, delegate),
) =>
for {
testContext <- initializeTest(
ownerParticipant = ownerParticipant,
delegateParticipant = delegateParticipant,
owner = owner,
delegate = delegate,
transactionFilter = filterByPartyAndTemplate(owner),
)
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, delegateParticipant)
// Exercise a choice with a disclosed contract
_ <- testContext.exerciseFetchDelegated(testContext.disclosedContract)
// Submission with disclosed contracts with the same contract id should be rejected
errorDuplicateContractId <- testContext
.dummyCreate(testContext.disclosedContract, testContext.disclosedContract)
.mustFail("duplicate contract id")
} yield {
assertGrpcError(
errorDuplicateContractId,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some(
s"Duplicate disclosed contract ID ${testContext.disclosedContract.contractId}"
),
checkDefiniteAnswerMetadata = true,
)
}
})
// TODO ED: Remove this test once feature is deemed stable and not configurable in Canton
test(
"EDFeatureDisabled",
"Submission fails when disclosed contracts provided on feature disabled",
allocate(SingleParty, SingleParty),
enabled = feature => !feature.explicitDisclosure,
)(implicit ec => {
case Participants(
Participant(ownerParticipant, owner),
Participant(delegateParticipant, delegate),
) =>
for {
testContext <- initializeTest(
ownerParticipant = ownerParticipant,
delegateParticipant = delegateParticipant,
owner = owner,
delegate = delegate,
transactionFilter = filterByPartyAndTemplate(owner),
)
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, delegateParticipant)
exerciseFetchError <- testContext
.exerciseFetchDelegated(testContext.disclosedContract)
.mustFail("explicit disclosure feature is disabled")
} yield {
assertGrpcError(
exerciseFetchError,
LedgerApiErrors.RequestValidation.InvalidField,
Some(
"Invalid field disclosed_contracts: feature in development: disclosed_contracts should not be set"
),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"EDRejectOnCreatedEventBlobNotSet",
"Submission is rejected when the disclosed contract created event blob is not set",
allocate(SingleParty, SingleParty),
enabled = _.explicitDisclosure,
)(implicit ec => {
case Participants(
Participant(ownerParticipant, owner),
Participant(delegateParticipant, delegate),
) =>
for {
testContext <- initializeTest(
ownerParticipant = ownerParticipant,
delegateParticipant = delegateParticipant,
owner = owner,
delegate = delegate,
transactionFilter = filterByPartyAndTemplate(owner),
)
// Ensure participants are synchronized
_ <- synchronize(ownerParticipant, delegateParticipant)
failure <- testContext
.exerciseFetchDelegated(
testContext.disclosedContract.copy(
createdEventBlob = ByteString.EMPTY
)
)
.mustFail("Submitter forwarded a contract with unpopulated created_event_blob")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.MissingField,
Some(
"The submitted command is missing a mandatory field: DisclosedContract.createdEventBlob"
),
checkDefiniteAnswerMetadata = true,
)
}
})
private def oneFailedWith(result1: Try[_], result2: Try[_])(
assertError: Throwable => Unit
): Unit =
(result1.isFailure, result2.isFailure) match {
case (true, false) => assertError(result1.failed.get)
case (false, true) => assertError(result2.failed.get)
case (true, true) => fail("Exactly one request should have failed, but both failed")
case (false, false) => fail("Exactly one request should have failed, but both succeeded")
}
}
object ExplicitDisclosureIT {
case class TestContext(
ownerParticipant: ParticipantTestContext,
delegateParticipant: ParticipantTestContext,
owner: Party,
delegate: Party,
contractKey: String,
delegationCid: Delegation.ContractId,
delegatedCid: Delegated.ContractId,
originalCreateEvent: CreatedEvent,
disclosedContract: DisclosedContract,
) {
/** Exercises the FetchDelegated choice as the delegate party, with the given explicit disclosure contracts.
* This choice fetches the Delegation contract which is only visible to the owner.
*/
def exerciseFetchDelegated(disclosedContracts: DisclosedContract*): Future[Unit] = {
val request = delegateParticipant
.submitAndWaitRequest(
delegate,
delegationCid.exerciseFetchDelegated(delegatedCid).commands,
)
.update(_.commands.disclosedContracts := disclosedContracts)
delegateParticipant.submitAndWait(request)
}
def dummyCreate(disclosedContracts: DisclosedContract*): Future[Unit] = {
val request = delegateParticipant
.submitAndWaitRequest(
delegate,
JList.of(
new CreateCommand(
Dummy.TEMPLATE_ID,
new Dummy(delegate.getValue).toValue,
)
),
)
.update(_.commands.disclosedContracts := disclosedContracts)
delegateParticipant.submitAndWait(request)
}
}
private def initializeTest(
ownerParticipant: ParticipantTestContext,
delegateParticipant: ParticipantTestContext,
owner: Party,
delegate: Party,
transactionFilter: TransactionFilter,
)(implicit ec: ExecutionContext): Future[TestContext] = {
val contractKey = ownerParticipant.nextKeyId()
for {
// Create a Delegation contract
// Contract is visible both to owner (as signatory) and delegate (as observer)
delegationCid <- ownerParticipant.create(
owner,
new Delegation(owner.getValue, delegate.getValue),
)
// Create Delegated contract
// This contract is only visible to the owner
delegatedCid <- ownerParticipant.create(owner, new Delegated(owner.getValue, contractKey))
// Get the contract payload from the transaction stream of the owner
delegatedTx <- ownerParticipant.flatTransactions(
ownerParticipant.getTransactionsRequest(transactionFilter)
)
createDelegatedEvent = createdEvents(delegatedTx.head).head
// Copy the actual Delegated contract to a disclosed contract (which can be shared out of band).
disclosedContract = createEventToDisclosedContract(createDelegatedEvent)
} yield TestContext(
ownerParticipant = ownerParticipant,
delegateParticipant = delegateParticipant,
owner = owner,
delegate = delegate,
contractKey = contractKey,
delegationCid = delegationCid,
delegatedCid = delegatedCid,
originalCreateEvent = createDelegatedEvent,
disclosedContract = disclosedContract,
)
}
private def filterByPartyAndTemplate(
owner: Party,
templateId: javaapi.data.Identifier = Delegated.TEMPLATE_ID,
): TransactionFilter = {
val templateIdScalaPB = Identifier.fromJavaProto(templateId.toProto)
new TransactionFilter(
Map(
owner.getValue -> new Filters(
Some(
InclusiveFilters(templateFilters =
Seq(TemplateFilter(Some(templateIdScalaPB), includeCreatedEventBlob = true))
)
)
)
)
)
}
private def createEventToDisclosedContract(ev: CreatedEvent): DisclosedContract =
DisclosedContract(
templateId = ev.templateId,
contractId = ev.contractId,
createdEventBlob = ev.createdEventBlob,
)
private def exerciseWithKey_byKey_request(
ledger: ParticipantTestContext,
owner: Party,
party: Party,
withKeyDisclosedContract: Option[DisclosedContract],
): SubmitAndWaitRequest =
ledger
.submitAndWaitRequest(
party,
JList.of(
new ExerciseByKeyCommand(
WithKey.TEMPLATE_ID,
owner,
"WithKey_NoOp",
new DamlRecord(
new DamlRecord.Field(party)
),
)
),
)
.update(_.commands.disclosedContracts := withKeyDisclosedContract.iterator.toSeq)
}

View File

@ -1,151 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_15
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation.{
Participant,
Participants,
SingleParty,
allocate,
}
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers._
import com.daml.ledger.javaapi.data.{Command, DamlRecord, ExerciseCommand, Identifier}
import com.daml.ledger.javaapi.data.codegen.{ContractCompanion, Update}
import com.daml.ledger.test.java.semantic.interface$._
import com.daml.ledger.test.java.semantic.{interface1, interface2, interface3}
import java.util.{List => JList}
import scala.jdk.CollectionConverters._
class InterfaceIT extends LedgerTestSuite {
implicit val tCompanion: ContractCompanion.WithKey[T.Contract, T.ContractId, T, String] =
T.COMPANION
private[this] val TId = T.TEMPLATE_ID
private[this] val I1Id = interface1.I.TEMPLATE_ID.toV1
private[this] val I2Id = interface2.I.TEMPLATE_ID
private[this] val I3Id = interface3.I.TEMPLATE_ID.toV1
// replace identifier with the wrong identifier for some of these tests
private[this] def useWrongId[X](
update: Update[X],
id: Identifier,
): JList[Command] = {
val command = update.commands.asScala.head
val exe = command.asExerciseCommand.get
val arg = exe.getChoiceArgument.asRecord.get
JList.of(
new ExerciseCommand(
id,
exe.getContractId,
exe.getChoice,
new DamlRecord(arg.getFields),
)
)
}
test(
"ExerciseTemplateSuccess",
"Success but does not set interfaceId in output event",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new T(party))
tree <- ledger.exercise(party, t.exerciseMyArchive())
} yield {
val events = exercisedEvents(tree)
assertLength(s"1 successful exercise", 1, events)
assertEquals(events.head.interfaceId, None)
assertEquals(events.head.getExerciseResult.getText, "Interface.T")
}
})
test(
"ExerciseInterfaceSuccess",
"Success and set interfaceId in output event",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new T(party))
tree <- ledger.exercise(party, t.toInterface(interface1.I.INTERFACE).exerciseMyArchive())
} yield {
val events = exercisedEvents(tree)
assertLength(s"1 successful exercise", 1, events)
assertEquals(events.head.interfaceId, Some(I1Id))
assertEquals(events.head.getExerciseResult.getText, "Interface1.I")
}
})
test(
"ExerciseRetroactiveInterfaceInstanceSuccess",
"Success and set interfaceId in output event",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new T(party))
tree <-
ledger
.exercise(party, (new interface3.I.ContractId(t.contractId)).exerciseMyArchive())
} yield {
val events = exercisedEvents(tree)
assertLength(s"1 successful exercise", 1, events)
assertEquals(events.head.interfaceId, Some(I3Id))
assertEquals(events.head.getExerciseResult.getText, "Interface3.I")
}
})
test(
"ExerciseInterfaceByTemplateFail",
"Cannot exercise an interface choice using templateId",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new T(party))
failure <- ledger
.submitAndWaitForTransactionTree(
ledger.submitAndWaitRequest(
party,
useWrongId(t.toInterface(interface1.I.INTERFACE).exerciseChoiceI1(), TId),
)
)
.mustFail("unknown choice")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some("unknown choice ChoiceI1"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"ExerciseInterfaceByRequiringFail",
"Cannot exercise an interface choice using requiring templateId",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
t <- ledger.create(party, new T(party))
failure <- ledger
.submitAndWaitForTransactionTree(
ledger.submitAndWaitRequest(
party,
useWrongId(t.toInterface(interface1.I.INTERFACE).exerciseChoiceI1(), I2Id),
)
)
.mustFail("unknown choice")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some("unknown choice ChoiceI1"),
checkDefiniteAnswerMetadata = true,
)
}
})
}

View File

@ -1,871 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_15
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation.{
Participant,
Participants,
Parties,
SingleParty,
allocate,
}
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.{Dars, LedgerTestSuite}
import com.daml.ledger.api.testtool.infrastructure.FutureAssertions._
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers._
import com.daml.ledger.api.v1.event.Event.Event
import com.daml.ledger.api.v1.event.{CreatedEvent, InterfaceView}
import com.daml.ledger.api.v1.transaction.Transaction
import com.daml.ledger.api.v1.transaction_filter.TransactionFilter
import com.daml.ledger.api.v1.value.{Identifier, Record}
import com.daml.ledger.test.java.semantic.interfaceviews.{T2, T3, T4, _}
import com.daml.ledger.javaapi
import com.daml.ledger.javaapi.data.codegen.ContractCompanion
import com.daml.ledger.test.java.semantic.da.types
import com.daml.ledger.test.java.{carbonv1, carbonv2, carbonv3}
import com.daml.ledger.test.{Carbonv1TestDar, Carbonv2TestDar, Carbonv3TestDar}
import com.daml.logging.LoggingContext
import java.lang
import java.util.regex.Pattern
import scala.concurrent.duration._
class InterfaceSubscriptionsIT extends InterfaceSubscriptionsITBase("IS", true)
class InterfaceSubscriptionsWithEventBlobsIT extends InterfaceSubscriptionsITBase("ISWP", false)
// Allows using deprecated Protobuf fields for testing
abstract class InterfaceSubscriptionsITBase(prefix: String, useTemplateIdBasedLegacyFormat: Boolean)
extends LedgerTestSuite {
implicit val t1Companion
: ContractCompanion.WithKey[T1.Contract, T1.ContractId, T1, types.Tuple2[String, lang.Long]] =
T1.COMPANION
implicit val t2Companion
: ContractCompanion.WithKey[T2.Contract, T2.ContractId, T2, types.Tuple2[String, lang.Long]] =
T2.COMPANION
implicit val t3Companion: ContractCompanion.WithoutKey[T3.Contract, T3.ContractId, T3] =
T3.COMPANION
implicit val t4Companion: ContractCompanion.WithoutKey[T4.Contract, T4.ContractId, T4] =
T4.COMPANION
implicit val t5Companion: ContractCompanion.WithoutKey[T5.Contract, T5.ContractId, T5] =
T5.COMPANION
implicit val t6Companion: ContractCompanion.WithoutKey[T6.Contract, T6.ContractId, T6] =
T6.COMPANION
test(
s"${prefix}TransactionsBasic",
"Basic functionality for interface subscriptions on transaction streams",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
for {
c1 <- create(party, new T1(party, 1))
c2 <- create(party, new T2(party, 2))
c3 <- create(party, new T3(party, 3))
_ <- create(party, new T4(party, 4))
transactions <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq(T1.TEMPLATE_ID),
Seq((I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
events = transactions.flatMap(createdEvents)
} yield basicAssertions(c1.contractId, c2.contractId, c3.contractId, events)
})
test(
s"${prefix}AcsBasic",
"Basic functionality for interface subscriptions on ACS streams",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
for {
c1 <- create(party, new T1(party, 1))
c2 <- create(party, new T2(party, 2))
c3 <- create(party, new T3(party, 3))
_ <- create(party, new T4(party, 4))
(_, createdEvents) <- activeContracts(
activeContractsRequest(
Seq(party),
Seq(T1.TEMPLATE_ID),
Seq((I.TEMPLATE_ID, true)),
"",
useTemplateIdBasedLegacyFormat,
)
)
} yield basicAssertions(c1.contractId, c2.contractId, c3.contractId, createdEvents)
})
private def basicAssertions(
c1: String,
c2: String,
c3: String,
createdEvents: Vector[CreatedEvent],
): Unit = {
assertLength("3 transactions found", 3, createdEvents)
// T1
val createdEvent1 = createdEvents(0)
assertLength("Create event 1 has a view", 1, createdEvent1.interfaceViews)
assertEquals(
"Create event 1 template ID",
createdEvent1.templateId.get.toString,
T1.TEMPLATE_ID.toV1.toString,
)
assertEquals("Create event 1 contract ID", createdEvent1.contractId, c1)
assertViewEquals(createdEvent1.interfaceViews, I.TEMPLATE_ID.toV1) { value =>
assertLength("View1 has 2 fields", 2, value.fields)
assertEquals("View1.a", value.fields(0).getValue.getInt64, 1)
assertEquals("View1.b", value.fields(1).getValue.getBool, true)
assert(
value.fields.forall(_.label.nonEmpty),
"Expected a view with labels (verbose)",
)
}
assertEquals(
"Create event 1 createArguments must NOT be empty",
createdEvent1.createArguments.isEmpty,
false,
)
assert(
createdEvent1.getCreateArguments.fields.forall(_.label.nonEmpty),
"Expected a contract with labels (verbose)",
)
assertEquals(
"Create event 1 should have a contract key defined",
createdEvent1.contractKey.isDefined,
true,
)
// T2
val createdEvent2 = createdEvents(1)
assertLength("Create event 2 has a view", 1, createdEvent2.interfaceViews)
assertEquals(
"Create event 2 template ID",
createdEvent2.templateId.get.toString,
T2.TEMPLATE_ID.toV1.toString,
)
assertEquals("Create event 2 contract ID", createdEvent2.contractId, c2)
assertViewEquals(createdEvent2.interfaceViews, I.TEMPLATE_ID.toV1) { value =>
assertLength("View2 has 2 fields", 2, value.fields)
assertEquals("View2.a", value.fields(0).getValue.getInt64, 2)
assertEquals("View2.b", value.fields(1).getValue.getBool, false)
}
assertEquals(
"Create event 2 createArguments must be empty",
createdEvent2.createArguments.isEmpty,
true,
)
assertEquals(
"Create event 2 should have a contract key empty, as no match by template_id",
createdEvent2.contractKey.isEmpty,
true,
)
// T3
val createdEvent3 = createdEvents(2)
assertLength("Create event 3 has a view", 1, createdEvent3.interfaceViews)
assertEquals(
"Create event 3 template ID",
createdEvent3.templateId.get.toString,
T3.TEMPLATE_ID.toV1.toString,
)
assertEquals("Create event 3 contract ID", createdEvent3.contractId, c3)
assertViewFailed(createdEvent3.interfaceViews, I.TEMPLATE_ID.toV1)
assertEquals(
"Create event 3 createArguments must be empty",
createdEvent3.createArguments.isEmpty,
true,
)
}
test(
s"${prefix}MultipleWitness",
"Multiple witness",
allocate(Parties(2)),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party1, party2)) =>
import ledger._
for {
c <- create(party1, new T6(party1, party2))
mergedTransactions <- flatTransactions(
getTransactionsRequest(
new TransactionFilter(
Map(
party1.getValue -> filters(
Seq.empty,
Seq((I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
),
party2.getValue -> filters(
Seq.empty,
Seq((I2.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
),
)
)
)
)
party1Transactions <- flatTransactions(
getTransactionsRequest(
new TransactionFilter(
Map(
party1.getValue -> filters(
Seq.empty,
Seq((I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
)
)
} yield {
assertLength("single transaction found", 1, mergedTransactions)
val createdEvent1 = createdEvents(mergedTransactions(0)).head
assertEquals("Create event 1 contract ID", createdEvent1.contractId, c.contractId)
assertViewEquals(createdEvent1.interfaceViews, I.TEMPLATE_ID.toV1) { value =>
assertLength("View1 has 2 fields", 2, value.fields)
assertEquals("View1.a", value.fields(0).getValue.getInt64, 6)
assertEquals("View1.b", value.fields(1).getValue.getBool, true)
}
assertViewEquals(createdEvent1.interfaceViews, I2.TEMPLATE_ID.toV1) { value =>
assertLength("View2 has 1 field", 1, value.fields)
assertEquals("View2.c", value.fields(0).getValue.getInt64, 7)
}
assertLength("single transaction found", 1, party1Transactions)
val createdEvent2 = createdEvents(party1Transactions(0)).head
assertEquals("Create event 1 contract ID", createdEvent2.contractId, c.contractId)
assertLength("single view found", 1, createdEvent2.interfaceViews)
assertViewEquals(createdEvent2.interfaceViews, I.TEMPLATE_ID.toV1) { value =>
assertLength("View1 has 2 fields", 2, value.fields)
assertEquals("View1.a", value.fields(0).getValue.getInt64, 6)
assertEquals("View1.b", value.fields(1).getValue.getBool, true)
}
}
})
test(
s"${prefix}MultipleViews",
"Multiple interface views populated for one event",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
for {
c <- create(party, new T5(party, 31337))
transactions <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq.empty,
Seq((I.TEMPLATE_ID, true), (I2.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
} yield {
assertLength("single transaction found", 1, transactions)
val createdEvent = createdEvents(transactions(0)).head
assertEquals("Create event 1 contract ID", createdEvent.contractId, c.contractId)
assertViewEquals(createdEvent.interfaceViews, I.TEMPLATE_ID.toV1) { value =>
assertLength("View1 has 2 fields", 2, value.fields)
assertEquals("View1.a", value.fields(0).getValue.getInt64, 31337)
assertEquals("View1.b", value.fields(1).getValue.getBool, true)
}
assertViewEquals(createdEvent.interfaceViews, I2.TEMPLATE_ID.toV1) { value =>
assertLength("View2 has 1 field", 1, value.fields)
assertEquals("View2.c", value.fields(0).getValue.getInt64, 1)
}
}
})
test(
s"${prefix}TransactionsIrrelevantTransactions",
"Subscribing on transaction stream by interface with no relevant transactions",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
for {
_ <- create(party, new T4(party, 4))
transactions <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq.empty,
Seq((INoTemplate.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
} yield {
assertLength("0 transactions should be found", 0, transactions)
()
}
})
test(
s"${prefix}TransactionsDuplicateInterfaceFilters",
"Subscribing on transaction stream by interface with duplicate filters and not verbose",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
for {
c1 <- create(party, new T1(party, 1))
c2 <- create(party, new T2(party, 2))
c3 <- create(party, new T3(party, 3))
_ <- create(party, new T4(party, 4))
transactions <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq(T1.TEMPLATE_ID),
Seq((I.TEMPLATE_ID, false), (I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
.update(_.verbose := false)
)
} yield {
val createdEvent1 = createdEvents(transactions(0)).head
assertEquals("Create event 1 contract ID", createdEvent1.contractId, c1.contractId)
val createdEvent2 = createdEvents(transactions(1)).head
assertEquals("Create event 2 contract ID", createdEvent2.contractId, c2.contractId)
// Expect view to be delivered even though there is an ambiguous
// includeInterfaceView flag set to true and false at the same time (true wins)
assertViewEquals(createdEvent2.interfaceViews, I.TEMPLATE_ID.toV1) { value =>
assertLength("View2 has 2 fields", 2, value.fields)
assertEquals("View2.a", value.fields(0).getValue.getInt64, 2)
assertEquals("View2.b", value.fields(1).getValue.getBool, false)
assert(
value.fields.forall(_.label.isEmpty),
s"Expected a view with no labels (verbose = false)",
)
}
val createdEvent3 = createdEvents(transactions(2)).head
assertEquals("Create event 3 contract ID", createdEvent3.contractId, c3.contractId)
}
})
test(
s"${prefix}TransactionsDuplicateTemplateFilters",
"Subscribing on transaction stream by template with duplicate filters",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
for {
c1 <- create(party, new T1(party, 1))
c2 <- create(party, new T2(party, 2))
c3 <- create(party, new T3(party, 3))
_ <- create(party, new T4(party, 4))
transactions <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq(T1.TEMPLATE_ID, T1.TEMPLATE_ID),
Seq((I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
} yield {
val createdEvent1 = createdEvents(transactions(0)).head
assertEquals("Create event 1 contract ID", createdEvent1.contractId, c1.contractId)
assertEquals(
"Create event 1 createArguments must NOT be empty",
createdEvent1.createArguments.isEmpty,
false,
)
val createdEvent2 = createdEvents(transactions(1)).head
assertEquals("Create event 2 contract ID", createdEvent2.contractId, c2.contractId)
// Expect view to be delivered even though there is an ambiguous
// includeInterfaceView flag set to true and false at the same time.
assertViewEquals(createdEvent2.interfaceViews, I.TEMPLATE_ID.toV1) { value =>
assertLength("View2 has 2 fields", 2, value.fields)
assertEquals("View2.a", value.fields(0).getValue.getInt64, 2)
assertEquals("View2.b", value.fields(1).getValue.getBool, false)
}
val createdEvent3 = createdEvents(transactions(2)).head
assertEquals("Create event 3 contract ID", createdEvent3.contractId, c3.contractId)
}
})
test(
s"${prefix}TransactionsNoIncludedView",
"Subscribing on transaction stream by interface or template without included views",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
for {
c1 <- create(party, new T1(party, 1))
_ <- create(party, new T2(party, 2))
_ <- create(party, new T3(party, 3))
_ <- create(party, new T4(party, 4))
transactions <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq(T1.TEMPLATE_ID),
Seq((I.TEMPLATE_ID, false)),
useTemplateIdBasedLegacyFormat,
)
)
)
} yield {
assertLength("3 transactions found", 3, transactions)
val interfaceViewCount: Int =
transactions.flatMap(createdEvents).map(_.interfaceViews.size).sum
assertEquals("No views have been computed and produced", 0, interfaceViewCount)
val createArgumentsCount: Int =
transactions.flatMap(createdEvents).map(_.createArguments.isDefined).count(_ == true)
assertEquals("Only single create arguments must be delivered", 1, createArgumentsCount)
// T1
val createdEvent1 = createdEvents(transactions(0)).head
assertEquals(
"Create event 1 template ID",
createdEvent1.templateId.get.toString,
T1.TEMPLATE_ID.toV1.toString,
)
assertEquals("Create event 1 contract ID", createdEvent1.contractId, c1.contractId)
assertEquals(
"Create event 1 createArguments must NOT be empty",
createdEvent1.createArguments.isEmpty,
false,
)
}
})
test(
s"${prefix}TransactionsEquivalentFilters",
"Subscribing by interface or all implementing templates gives the same result",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
val allImplementations = Seq(T1.TEMPLATE_ID, T2.TEMPLATE_ID, T3.TEMPLATE_ID)
for {
_ <- create(party, new T1(party, 1))
_ <- create(party, new T2(party, 2))
_ <- create(party, new T3(party, 3))
_ <- create(party, new T4(party, 4))
// 1. Subscribe by the interface
transactions1 <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq.empty,
Seq((I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
// 2. Subscribe by all implementing templates
transactions2 <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
allImplementations,
Seq.empty,
useTemplateIdBasedLegacyFormat,
)
)
)
// 3. Subscribe by both the interface and all templates (redundant filters)
transactions3 <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
allImplementations,
Seq((I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
} yield {
assertLength("3 transactions found", 3, transactions1)
assertEquals(
"1 and 2 find the same transactions (but not the same views)",
transactions1.map(_.transactionId),
transactions2.map(_.transactionId),
)
assertEquals(
"2 and 3 find the same contract_arguments (but not the same views)",
transactions2.map(updateTransaction()),
transactions3.map(updateTransaction(emptyView = true)),
)
assertEquals(
"1 and 3 produce the same views (but not the same create arguments)",
// do not check on details since tid is contained and it is expected to be different
transactions1
.map(updateTransaction(emptyDetails = true))
.map(hideTraceIdFromStatusMessages),
transactions3
.map(
updateTransaction(
emptyContractKey = true,
emptyCreateArguments = true,
emptyDetails = true,
)
)
.map(hideTraceIdFromStatusMessages),
)
}
})
test(
s"${prefix}TransactionsUnknownTemplateOrInterface",
"Subscribing on transaction stream by an unknown template fails",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val packageId = I.TEMPLATE_ID.getPackageId
val moduleName = I.TEMPLATE_ID.getModuleName
val unknownTemplate = new javaapi.data.Identifier(packageId, moduleName, "TemplateDoesNotExist")
val unknownInterface =
new javaapi.data.Identifier(packageId, moduleName, "InterfaceDoesNotExist")
import ledger._
for {
_ <- create(party, new T1(party, 1))
failure <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq(unknownTemplate),
Seq.empty,
useTemplateIdBasedLegacyFormat,
)
)
)
.mustFail("subscribing with an unknown template")
failure2 <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq.empty,
Seq((unknownInterface, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
.mustFail("subscribing with an unknown interface")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.RequestValidation.NotFound.TemplateOrInterfaceIdsNotFound,
Some(Pattern.compile("Templates do not exist.*TemplateDoesNotExist]")),
)
assertGrpcErrorRegex(
failure2,
LedgerApiErrors.RequestValidation.NotFound.TemplateOrInterfaceIdsNotFound,
Some(Pattern.compile("Interfaces do not exist.*InterfaceDoesNotExist]")),
)
}
})
test(
s"${prefix}TransactionsMultipleParties",
"Subscribing on transaction stream by multiple parties",
allocate(Parties(2)),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party1, party2)) =>
import ledger._
for {
_ <- create(party1, new T6(party1, party2))
party1Iface1transactionsWithView <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party1),
Seq.empty,
Seq((I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
party1Iface1transactionsWithoutView <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party1),
Seq.empty,
Seq((I.TEMPLATE_ID, false)),
useTemplateIdBasedLegacyFormat,
)
)
)
party2Iface1transactionsWithView <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party2),
Seq.empty,
Seq((I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
party2Iface1transactionsWithoutView <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party2),
Seq.empty,
Seq((I.TEMPLATE_ID, false)),
useTemplateIdBasedLegacyFormat,
)
)
)
} yield {
assertEquals(
party1Iface1transactionsWithView.map(
updateTransaction(emptyView = false, emptyWitness = true)
),
party2Iface1transactionsWithView.map(
updateTransaction(emptyView = false, emptyWitness = true)
),
)
assertEquals(
party1Iface1transactionsWithoutView.map(
updateTransaction(emptyView = false, emptyWitness = true)
),
party2Iface1transactionsWithoutView.map(
updateTransaction(emptyView = false, emptyWitness = true)
),
)
assertEquals(
party1Iface1transactionsWithView.map(
updateTransaction(emptyView = true, emptyWitness = true)
),
party2Iface1transactionsWithoutView.map(
updateTransaction(emptyView = false, emptyWitness = true)
),
)
}
})
test(
s"${prefix}TransactionsSubscribeBeforeTemplateCreated",
"Subscribing on transaction stream by interface before template is created",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
for {
_ <- ledger.uploadDarFile(Dars.read(Carbonv1TestDar.path))
transactionFuture = flatTransactions(
take = 1,
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq.empty,
Seq((carbonv1.carbonv1.I.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
// endless stream here as we would like to keep it open until
// template is uploaded and contract with this template is created
.update(
_.optionalEnd := None
),
)
_ = assertEquals(transactionFuture.isCompleted, false)
_ <- ledger.uploadDarFile(Dars.read(Carbonv2TestDar.path))
_ = assertEquals(transactionFuture.isCompleted, false)
contract <- succeedsEventually(
maxRetryDuration = 10.seconds,
description = "Topology processing around Dar upload can take a bit of time.",
delayMechanism = ledger.delayMechanism,
) {
create(party, new carbonv2.carbonv2.T(party, 21))(carbonv2.carbonv2.T.COMPANION)
}
transactions <- transactionFuture
} yield assertSingleContractWithSimpleView(
transactions = transactions,
contractIdentifier = carbonv2.carbonv2.T.TEMPLATE_ID.toV1,
viewIdentifier = carbonv1.carbonv1.I.TEMPLATE_ID.toV1,
contractId = contract.contractId,
viewValue = 21,
)
})
test(
s"${prefix}TransactionsRetroactiveInterface",
"Subscribe to retroactive interface",
allocate(SingleParty),
enabled = _.templateFilters || useTemplateIdBasedLegacyFormat,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import ledger._
implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
for {
_ <- ledger.uploadDarFile(Dars.read(Carbonv1TestDar.path))
_ <- ledger.uploadDarFile(Dars.read(Carbonv2TestDar.path))
contract <- succeedsEventually(
maxRetryDuration = 10.seconds,
description = "Topology processing around Dar upload can take a bit of time.",
delayMechanism = ledger.delayMechanism,
) {
create(party, new carbonv2.carbonv2.T(party, 77))(carbonv2.carbonv2.T.COMPANION)
}
_ <- ledger.uploadDarFile(Dars.read(Carbonv3TestDar.path))
transactions <- flatTransactions(
getTransactionsRequest(
transactionFilter(
Seq(party),
Seq.empty,
Seq((carbonv3.carbonv3.RetroI.TEMPLATE_ID, true)),
useTemplateIdBasedLegacyFormat,
)
)
)
} yield assertSingleContractWithSimpleView(
transactions = transactions,
contractIdentifier = carbonv2.carbonv2.T.TEMPLATE_ID.toV1,
viewIdentifier = carbonv3.carbonv3.RetroI.TEMPLATE_ID.toV1,
contractId = contract.contractId,
viewValue = 77,
)
})
private def assertSingleContractWithSimpleView(
transactions: Vector[Transaction],
contractIdentifier: Identifier,
viewIdentifier: Identifier,
contractId: String,
viewValue: Long,
): Unit = {
assertLength("transaction should be found", 1, transactions)
val createdEvent = createdEvents(transactions(0)).head
assertLength("Create event has a view", 1, createdEvent.interfaceViews)
assertEquals(
"Create event template ID",
createdEvent.templateId.get.toString,
contractIdentifier.toString,
)
assertEquals("Create event contract ID", createdEvent.contractId, contractId)
assertViewEquals(createdEvent.interfaceViews, viewIdentifier) { value =>
assertLength("View has 1 field", 1, value.fields)
assertEquals("View.value", value.fields(0).getValue.getInt64, viewValue)
}
}
private def updateTransaction(
emptyView: Boolean = false,
emptyWitness: Boolean = false,
emptyCreateArguments: Boolean = false,
emptyContractKey: Boolean = false,
emptyDetails: Boolean = false,
)(tx: com.daml.ledger.api.v1.transaction.Transaction): Transaction = {
tx.copy(
events = tx.events.map { event =>
event.copy(event = event.event match {
case created: Event.Created =>
created.copy(value =
created.value.copy(
witnessParties = if (emptyWitness) Seq.empty else created.value.witnessParties,
interfaceViews =
if (emptyView) Seq.empty
else if (emptyDetails)
created.value.interfaceViews.map(iv =>
iv.copy(viewStatus =
iv.viewStatus.map(status => status.copy(details = Seq.empty))
)
)
else created.value.interfaceViews,
contractKey = if (emptyContractKey) None else created.value.contractKey,
createArguments = if (emptyCreateArguments) None else created.value.createArguments,
)
)
case other => other
})
},
commandId = "",
)
}
private def hideTraceIdFromStatusMessages(
tx: com.daml.ledger.api.v1.transaction.Transaction
): Transaction = {
tx.copy(
events = tx.events.map { event =>
event.copy(event = event.event match {
case created: Event.Created =>
created.copy(value =
created.value.copy(
interfaceViews = created.value.interfaceViews.map(view =>
view.copy(
viewStatus = view.viewStatus.map(status =>
status.copy(message =
status.message.replaceFirst(
"""UNHANDLED_EXCEPTION\(9,.{8}\)""",
"UNHANDLED_EXCEPTION(9,0)",
)
)
)
)
)
)
)
case other => other
})
},
commandId = "",
)
}
private def assertViewFailed(views: Seq[InterfaceView], interfaceId: Identifier): Unit = {
val viewSearch = views.find(_.interfaceId.contains(interfaceId))
val view = assertDefined(viewSearch, "View could not be found")
val actualInterfaceId = assertDefined(view.interfaceId, "Interface ID is not defined")
assertEquals("View has correct interface ID", interfaceId, actualInterfaceId)
val status = assertDefined(view.viewStatus, "Status is not defined")
assertEquals("Status must be invalid argument", status.code, 9)
}
private def assertViewEquals(views: Seq[InterfaceView], interfaceId: Identifier)(
checkValue: Record => Unit
): Unit = {
val viewSearch = views.find(_.interfaceId.contains(interfaceId))
val view = assertDefined(
viewSearch,
s"View could not be found, there are: ${views.map(_.interfaceId).mkString("[", ",", "]")}",
)
val viewCount = views.count(_.interfaceId.contains(interfaceId))
assertEquals(s"Only one view of interfaceId=$interfaceId must be defined", viewCount, 1)
val actualInterfaceId = assertDefined(view.interfaceId, "Interface ID is not defined")
assertEquals("View has correct interface ID", actualInterfaceId, interfaceId)
val status = assertDefined(view.viewStatus, "Status is not defined")
assertEquals("Status must be successful", status.code, 0)
val actualValue = assertDefined(view.viewValue, "Value is not defined")
checkValue(actualValue)
}
}

View File

@ -1,356 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_15
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.v1.active_contracts_service.GetActiveContractsRequest
import com.daml.ledger.api.v1.event.CreatedEvent
import com.daml.ledger.api.v1.transaction_filter.{
Filters,
InclusiveFilters,
InterfaceFilter,
TemplateFilter,
TransactionFilter,
}
import com.daml.ledger.api.v1.value.Identifier
import com.daml.ledger.javaapi.data.Party
import com.daml.ledger.test.java.semantic.interfaceviews._
import scala.concurrent.{ExecutionContext, Future}
// Allows using deprecated Protobuf fields for testing
class TransactionServiceFiltersIT extends LedgerTestSuite {
test(
"TSFInterfaceTemplateIds",
"Combine plain interface filters with template ids",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
testFilterComposition(
ledger,
party,
createTransactionFilter(
party = party,
interfaceFilters = createInterfaceFilter(
includeCreatedEventBlob = false
),
templateIds = createTemplateIdFilter,
),
)
})
test(
"TSFInterfaceTemplatePlainFilters",
"Combine plain interface filters with plain template filters",
allocate(SingleParty),
enabled = _.templateFilters,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
testFilterComposition(
ledger,
party,
createTransactionFilter(
party = party,
interfaceFilters = createInterfaceFilter(
includeCreatedEventBlob = false
),
templateFilters = createTemplateFilter(includeCreatedEventBlob = false),
),
)
})
test(
"TSFInterfaceTemplateFiltersWithEventBlobs",
"Combine plain interface filters with template filters with event blobs",
allocate(SingleParty),
enabled = _.templateFilters,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
testFilterComposition(
ledger,
party,
createTransactionFilter(
party = party,
interfaceFilters = createInterfaceFilter(
includeCreatedEventBlob = false
),
templateFilters = createTemplateFilter(includeCreatedEventBlob = true),
),
)
})
test(
"TSFInterfaceWithEventBlobsTemplateIds",
"Combine interface filters with event blobs with template ids",
allocate(SingleParty),
enabled = _.templateFilters,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
testFilterCompositionFailure(
ledger,
createTransactionFilter(
party = party,
interfaceFilters = createInterfaceFilter(
includeCreatedEventBlob = true
),
templateIds = createTemplateIdFilter,
),
)
})
test(
"TSFInterfaceWithEventBlobsTemplatePlainFilters",
"Combine interface filters with event blobs with plain template filters",
allocate(SingleParty),
enabled = _.templateFilters,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
testFilterComposition(
ledger,
party,
createTransactionFilter(
party = party,
interfaceFilters = createInterfaceFilter(
includeCreatedEventBlob = true
),
templateFilters = createTemplateFilter(includeCreatedEventBlob = false),
),
)
})
test(
"TSFInterfaceWithEventBlobsTemplateFiltersWithEventBlobs",
"Combine interface filters with event blobs with template filters with event blobs",
allocate(SingleParty),
enabled = _.templateFilters,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
testFilterComposition(
ledger,
party,
createTransactionFilter(
party = party,
interfaceFilters = createInterfaceFilter(
includeCreatedEventBlob = true
),
templateFilters = createTemplateFilter(includeCreatedEventBlob = true),
),
)
})
test(
"TSFTemplateIdsWithTemplateFilters",
"Combine template ids with template filters",
allocate(SingleParty),
enabled = _.templateFilters,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
testFilterCompositionFailure(
ledger,
createTransactionFilter(
party = party,
interfaceFilters = createInterfaceFilter(
includeCreatedEventBlob = false
),
templateFilters = createTemplateFilter(includeCreatedEventBlob = true),
templateIds = createTemplateIdFilter,
),
)
})
private def testFilterComposition(
ledger: ParticipantTestContext,
party: Party,
filter: TransactionFilter,
)(implicit ec: ExecutionContext): Future[Unit] = {
import ledger._
for {
c1 <- create(party, new T5(party, 1))(T5.COMPANION)
c2 <- create(party, new T6(party, party))(T6.COMPANION)
c3 <- create(party, new T3(party, 2))(T3.COMPANION)
_ <- create(party, new T4(party, 4))(T4.COMPANION)
txEvents <- flatTransactions(getTransactionsRequest(filter)).map(_.flatMap(createdEvents))
acsEvents <- activeContracts(createActiveContractsRequest(filter)).map(_._2)
} yield {
basicAssertions(
c1.contractId,
c2.contractId,
c3.contractId,
txEvents,
eventBlobFlagFromInterfaces(filter),
eventBlobFlagFromTemplates(filter),
)
basicAssertions(
c1.contractId,
c2.contractId,
c3.contractId,
acsEvents,
eventBlobFlagFromInterfaces(filter),
eventBlobFlagFromTemplates(filter),
)
}
}
private def testFilterCompositionFailure(
ledger: ParticipantTestContext,
filter: TransactionFilter,
)(implicit ec: ExecutionContext): Future[Unit] = {
import ledger._
for {
_ <- flatTransactions(getTransactionsRequest(filter)).mustFail(
"filter composition unsupported for flat transactions"
)
_ <- activeContracts(createActiveContractsRequest(filter)).mustFail(
"filter composition unsupported for acs"
)
} yield ()
}
private def basicAssertions(
c1: String,
c2: String,
c3: String,
createdEvents: Vector[CreatedEvent],
expectEventBlobFromInterfaces: Boolean,
expectEventBlobFromTemplates: Boolean,
): Unit = {
val expectEventBlob = expectEventBlobFromInterfaces || expectEventBlobFromTemplates
assertLength("3 transactions found", 3, createdEvents)
// T5
val createdEvent1 = createdEvents(0)
assertEquals(
"Create event 1 template ID",
createdEvent1.templateId.get,
T5.TEMPLATE_ID.toV1,
)
assertEquals("Create event 1 contract ID", createdEvent1.contractId, c1)
assertLength("Create event 1 has a view", 1, createdEvent1.interfaceViews)
assertEquals(
"Create event 1 createArguments must NOT be empty",
createdEvent1.createArguments.isEmpty,
false,
)
assertEquals(
s"""Create event 1 createdEventBlob must ${if (expectEventBlob) "NOT" else ""} be empty""",
createdEvent1.createdEventBlob.isEmpty,
!expectEventBlob,
)
// T6
val createdEvent2 = createdEvents(1)
assertEquals(
"Create event 2 template ID",
createdEvent2.templateId.get,
T6.TEMPLATE_ID.toV1,
)
assertEquals("Create event 2 contract ID", createdEvent2.contractId, c2)
assertLength("Create event 2 has a view", 1, createdEvent2.interfaceViews)
assertEquals(
"Create event 2 createArguments must be empty",
createdEvent2.createArguments.isEmpty,
true,
)
assertEquals(
s"""Create event 2 createdEventBlob must ${if (expectEventBlobFromInterfaces) "NOT"
else ""} be empty""",
createdEvent2.createdEventBlob.isEmpty,
!expectEventBlobFromInterfaces,
)
// T3
val createdEvent3 = createdEvents(2)
assertEquals(
"Create event 3 template ID",
createdEvent3.templateId.get.toString,
T3.TEMPLATE_ID.toV1.toString,
)
assertEquals("Create event 3 contract ID", createdEvent3.contractId, c3)
assertLength("Create event 3 has no view", 0, createdEvent3.interfaceViews)
assertEquals(
"Create event 3 createArguments must not be empty",
createdEvent3.createArguments.isEmpty,
false,
)
assertEquals(
s"""Create event 3 createdEventBlob must ${if (expectEventBlobFromTemplates) "NOT"
else ""} be empty""",
createdEvent3.createdEventBlob.isEmpty,
!expectEventBlobFromTemplates,
)
}
private def createInterfaceFilter(
includeCreatedEventBlob: Boolean
) = {
Seq(
new InterfaceFilter(
interfaceId = Some(I2.TEMPLATE_ID.toV1),
includeInterfaceView = true,
includeCreatedEventBlob = includeCreatedEventBlob,
)
)
}
private def createTemplateIdFilter: Seq[Identifier] =
Seq(T3.TEMPLATE_ID.toV1, T5.TEMPLATE_ID.toV1)
private def createTemplateFilter(includeCreatedEventBlob: Boolean): Seq[TemplateFilter] =
Seq(
new TemplateFilter(
templateId = Some(T3.TEMPLATE_ID.toV1),
includeCreatedEventBlob = includeCreatedEventBlob,
),
new TemplateFilter(
templateId = Some(T5.TEMPLATE_ID.toV1),
includeCreatedEventBlob = includeCreatedEventBlob,
),
)
private def createTransactionFilter(
party: Party,
interfaceFilters: Seq[InterfaceFilter],
templateIds: Seq[Identifier] = Seq.empty,
templateFilters: Seq[TemplateFilter] = Seq.empty,
): TransactionFilter =
new TransactionFilter(
filtersByParty = Map(
party.getValue -> new Filters(
inclusive = Some(
new InclusiveFilters(
templateIds = templateIds,
templateFilters = templateFilters,
interfaceFilters = interfaceFilters,
)
)
)
)
)
private def createActiveContractsRequest(filter: TransactionFilter) =
new GetActiveContractsRequest(
filter = Some(filter),
verbose = true,
activeAtOffset = "",
)
private def eventBlobFlagFromInterfaces(filter: TransactionFilter): Boolean =
extractFlag(filter, _.includeCreatedEventBlob)
private def eventBlobFlagFromTemplates(filter: TransactionFilter): Boolean =
(for {
byParty <- filter.filtersByParty.headOption.map(_._2)
inclusive <- byParty.inclusive
templateFilter <- inclusive.templateFilters.headOption
} yield templateFilter.includeCreatedEventBlob).getOrElse(false)
private def extractFlag(
filter: TransactionFilter,
extractor: InterfaceFilter => Boolean,
): Boolean =
(for {
byParty <- filter.filtersByParty.headOption.map(_._2)
inclusive <- byParty.inclusive
interfaceFilter <- inclusive.interfaceFilters.headOption
} yield extractor(interfaceFilter)).getOrElse(false)
}

View File

@ -1,67 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.suites.v1_8.objectmeta.{
PartyManagementServiceObjectMetaIT,
UserManagementServiceObjectMetaIT,
}
import com.daml.ledger.api.tls.TlsConfiguration
package object v1_8 {
def default(timeoutScaleFactor: Double): Vector[LedgerTestSuite] =
Vector(
new ActiveContractsServiceIT,
new ClosedWorldIT,
new CommandDeduplicationIT(timeoutScaleFactor),
new CommandDeduplicationParallelIT,
new CommandDeduplicationPeriodValidationIT,
new CommandServiceIT,
new CommandSubmissionCompletionIT,
new CompletionDeduplicationInfoIT(CompletionDeduplicationInfoIT.CommandService),
new CompletionDeduplicationInfoIT(CompletionDeduplicationInfoIT.CommandSubmissionService),
new ConfigManagementServiceIT,
new ContractIdIT,
new ContractKeysIT,
new DeeplyNestedValueIT,
new DivulgenceIT,
new HealthServiceIT,
new IdentityIT,
new LedgerConfigurationServiceIT,
new MultiPartySubmissionIT,
new PackageManagementServiceIT,
new PackageServiceIT,
new ParticipantPruningIT,
new PartyManagementServiceIT,
new PartyManagementServiceObjectMetaIT,
new PartyManagementServiceUpdateRpcIT,
new RaceConditionIT,
new SemanticTests,
new TimeServiceIT,
new TransactionServiceArgumentsIT,
new TransactionServiceAuthorizationIT,
new TransactionServiceCorrectnessIT,
new TransactionServiceExerciseIT,
new TransactionServiceOutputsIT,
new TransactionServiceQueryIT,
new TransactionServiceStakeholdersIT,
new TransactionServiceStreamsIT,
new TransactionServiceValidationIT,
new TransactionServiceVisibilityIT,
new UserManagementServiceIT,
new UserManagementServiceObjectMetaIT,
new UserManagementServiceUpdateRpcIT,
new ValueLimitsIT,
new WitnessesIT,
new WronglyTypedContractIdIT,
new IdentityProviderConfigServiceIT,
)
def optional(tlsConfiguration: Option[TlsConfiguration]): Vector[LedgerTestSuite] =
Vector(
new TLSOnePointThreeIT(tlsConfiguration),
new TLSAtLeastOnePointTwoIT(tlsConfiguration),
)
}

View File

@ -1,848 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.v1.active_contracts_service.GetActiveContractsRequest
import com.daml.ledger.api.v1.event.Event.Event.Created
import com.daml.ledger.api.v1.event.{CreatedEvent, Event}
import com.daml.ledger.api.v1.transaction_filter.{Filters, InclusiveFilters, TransactionFilter}
import com.daml.ledger.api.v1.value.Identifier
import com.daml.ledger.javaapi.data.{Identifier => JavaIdentifier}
import com.daml.ledger.javaapi.data.{Party, Template}
import com.daml.ledger.test.java.model.test.{
Divulgence1,
Divulgence2,
Dummy,
DummyFactory,
DummyWithParam,
TriAgreement,
TriProposal,
WithObservers,
Witnesses => TestWitnesses,
}
import com.daml.ledger.javaapi.data.codegen.ContractId
import scala.collection.immutable.Seq
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.CollectionConverters._
import scala.util.Random
class ActiveContractsServiceIT extends LedgerTestSuite {
import CompanionImplicits._
test(
"ACSinvalidLedgerId",
"The ActiveContractService should fail for requests with an invalid ledger identifier",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, parties @ _*)) =>
val invalidLedgerId = "ACSinvalidLedgerId"
val invalidRequest = ledger
.activeContractsRequest(
parties,
useTemplateIdBasedLegacyFormat = !ledger.features.templateFilters,
)
.update(_.ledgerId := invalidLedgerId)
for {
failure <- ledger.activeContracts(invalidRequest).mustFail("retrieving active contracts")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.LedgerIdMismatch,
Some("not found. Actual Ledger ID"),
)
}
})
test(
"ACSemptyResponse",
"The ActiveContractService should succeed with an empty response if no contracts have been created for a party",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
activeContracts <- ledger.activeContracts(party)
} yield {
assert(
activeContracts.isEmpty,
s"There should be no active contracts, but received $activeContracts",
)
}
})
test(
"ACSallContracts",
"The ActiveContractService should return all active contracts",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
(dummy, dummyWithParam, dummyFactory) <- createDummyContracts(party, ledger)
activeContracts <- ledger.activeContracts(party)
} yield {
assert(
activeContracts.size == 3,
s"Expected 3 contracts, but received ${activeContracts.size}.",
)
assert(
activeContracts.exists(_.contractId == dummy.contractId),
s"Didn't find Dummy contract with contractId $dummy.",
)
assert(
activeContracts.exists(_.contractId == dummyWithParam.contractId),
s"Didn't find DummyWithParam contract with contractId $dummy.",
)
assert(
activeContracts.exists(_.contractId == dummyFactory.contractId),
s"Didn't find DummyFactory contract with contractId $dummy.",
)
val invalidSignatories = activeContracts.filterNot(_.signatories == Seq(party.getValue))
assert(
invalidSignatories.isEmpty,
s"Found contracts with signatories other than $party: $invalidSignatories",
)
val invalidObservers = activeContracts.filterNot(_.observers.isEmpty)
assert(
invalidObservers.isEmpty,
s"Found contracts with non-empty observers: $invalidObservers",
)
}
})
test(
"ACSfilterContracts",
"The ActiveContractService should return contracts filtered by templateId",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
(dummy, _, _) <- createDummyContracts(party, ledger)
activeContracts <- ledger.activeContractsByTemplateId(Seq(Dummy.TEMPLATE_ID), party)
} yield {
assert(
activeContracts.size == 1,
s"Expected 1 contract, but received ${activeContracts.size}.",
)
assert(
activeContracts.head.getTemplateId == Identifier.fromJavaProto(Dummy.TEMPLATE_ID.toProto),
s"Received contract is not of type Dummy, but ${activeContracts.head.templateId}.",
)
assert(
activeContracts.head.contractId == dummy.contractId,
s"Expected contract with contractId $dummy, but received ${activeContracts.head.contractId}.",
)
}
})
test(
"ACSarchivedContracts",
"The ActiveContractService does not return archived contracts",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
(dummy, _, _) <- createDummyContracts(party, ledger)
contractsBeforeExercise <- ledger.activeContracts(party)
_ <- ledger.exercise(party, dummy.exerciseDummyChoice1())
contractsAfterExercise <- ledger.activeContracts(party)
} yield {
// check the contracts BEFORE the exercise
assert(
contractsBeforeExercise.size == 3,
s"Expected 3 contracts, but received ${contractsBeforeExercise.size}.",
)
assert(
contractsBeforeExercise.exists(_.contractId == dummy.contractId),
s"Expected to receive contract with contractId $dummy, but received ${contractsBeforeExercise
.map(_.contractId)
.mkString(", ")} instead.",
)
// check the contracts AFTER the exercise
assert(
contractsAfterExercise.size == 2,
s"Expected 2 contracts, but received ${contractsAfterExercise.size}",
)
assert(
!contractsAfterExercise.exists(_.contractId == dummy.contractId),
s"Expected to not receive contract with contractId $dummy.",
)
}
})
test(
"ACSusableOffset",
"The ActiveContractService should return a usable offset to resume streaming transactions",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
dummy <- ledger.create(party, new Dummy(party))
(Some(offset), onlyDummy) <- ledger.activeContracts(
ledger.activeContractsRequest(
Seq(party),
useTemplateIdBasedLegacyFormat = !ledger.features.templateFilters,
)
)
dummyWithParam <- ledger.create(party, new DummyWithParam(party))
request = ledger.getTransactionsRequest(ledger.transactionFilter(Seq(party)))
fromOffset = request.update(_.begin := offset)
transactions <- ledger.flatTransactions(fromOffset)
} yield {
assert(onlyDummy.size == 1)
assert(
onlyDummy.exists(_.contractId == dummy.contractId),
s"Expected to receive $dummy in active contracts, but didn't receive it.",
)
assert(
transactions.size == 1,
s"Expected to receive only 1 transaction from offset $offset, but received ${transactions.size}.",
)
val transaction = transactions.head
assert(
transaction.events.size == 1,
s"Expected only 1 event in the transaction, but received ${transaction.events.size}.",
)
val createdEvent = transaction.events.collect { case Event(Created(createdEvent)) =>
createdEvent
}
assert(
createdEvent.exists(_.contractId == dummyWithParam.contractId),
s"Expected a CreateEvent for $dummyWithParam, but received $createdEvent.",
)
}
})
test(
"ACSverbosity",
"The ActiveContractService should emit field names only if the verbose flag is set to true",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
_ <- ledger.create(party, new Dummy(party))
verboseRequest = ledger
.activeContractsRequest(
Seq(party),
useTemplateIdBasedLegacyFormat = !ledger.features.templateFilters,
)
.update(_.verbose := true)
nonVerboseRequest = verboseRequest.update(_.verbose := false)
(_, verboseEvents) <- ledger.activeContracts(verboseRequest)
(_, nonVerboseEvents) <- ledger.activeContracts(nonVerboseRequest)
} yield {
val verboseCreateArgs = verboseEvents.map(_.getCreateArguments).flatMap(_.fields)
assert(
verboseEvents.nonEmpty && verboseCreateArgs.forall(_.label.nonEmpty),
s"$party expected a contract with labels, but received $verboseEvents.",
)
val nonVerboseCreateArgs = nonVerboseEvents.map(_.getCreateArguments).flatMap(_.fields)
assert(
nonVerboseEvents.nonEmpty && nonVerboseCreateArgs.forall(_.label.isEmpty),
s"$party expected a contract without labels, but received $nonVerboseEvents.",
)
}
})
test(
"ACSmultiParty",
"The ActiveContractsService should return contracts for the requesting parties",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
for {
_ <- createDummyContracts(alice, ledger)
_ <- createDummyContracts(bob, ledger)
allContractsForAlice <- ledger.activeContracts(alice)
allContractsForBob <- ledger.activeContracts(bob)
allContractsForAliceAndBob <- ledger.activeContracts(alice, bob)
dummyContractsForAlice <- ledger.activeContractsByTemplateId(Seq(Dummy.TEMPLATE_ID), alice)
dummyContractsForAliceAndBob <- ledger.activeContractsByTemplateId(
Seq(Dummy.TEMPLATE_ID),
alice,
bob,
)
} yield {
assert(
allContractsForAlice.size == 3,
s"$alice expected 3 events, but received ${allContractsForAlice.size}.",
)
assertTemplates(Seq(alice), allContractsForAlice, Dummy.TEMPLATE_ID, 1)
assertTemplates(Seq(alice), allContractsForAlice, DummyWithParam.TEMPLATE_ID, 1)
assertTemplates(Seq(alice), allContractsForAlice, DummyFactory.TEMPLATE_ID, 1)
assert(
allContractsForBob.size == 3,
s"$bob expected 3 events, but received ${allContractsForBob.size}.",
)
assertTemplates(Seq(bob), allContractsForBob, Dummy.TEMPLATE_ID, 1)
assertTemplates(Seq(bob), allContractsForBob, DummyWithParam.TEMPLATE_ID, 1)
assertTemplates(Seq(bob), allContractsForBob, DummyFactory.TEMPLATE_ID, 1)
assert(
allContractsForAliceAndBob.size == 6,
s"$alice and $bob expected 6 events, but received ${allContractsForAliceAndBob.size}.",
)
assertTemplates(Seq(alice, bob), allContractsForAliceAndBob, Dummy.TEMPLATE_ID, 2)
assertTemplates(Seq(alice, bob), allContractsForAliceAndBob, DummyWithParam.TEMPLATE_ID, 2)
assertTemplates(Seq(alice, bob), allContractsForAliceAndBob, DummyFactory.TEMPLATE_ID, 2)
assert(
dummyContractsForAlice.size == 1,
s"$alice expected 1 event, but received ${dummyContractsForAlice.size}.",
)
assertTemplates(Seq(alice), dummyContractsForAlice, Dummy.TEMPLATE_ID, 1)
assert(
dummyContractsForAliceAndBob.size == 2,
s"$alice and $bob expected 2 events, but received ${dummyContractsForAliceAndBob.size}.",
)
assertTemplates(Seq(alice, bob), dummyContractsForAliceAndBob, Dummy.TEMPLATE_ID, 2)
}
})
test(
"ACSagreementText",
"The ActiveContractService should properly fill the agreementText field",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
dummyCid <- ledger.create(party, new Dummy(party))
dummyWithParamCid <- ledger.create(party, new DummyWithParam(party))
contracts <- ledger.activeContracts(party)
} yield {
assert(contracts.size == 2, s"$party expected 2 contracts, but received ${contracts.size}.")
val dummyAgreementText = contracts.collect {
case ev if ev.contractId == dummyCid.contractId => ev.agreementText
}
val dummyWithParamAgreementText = contracts.collect {
case ev if ev.contractId == dummyWithParamCid.contractId => ev.agreementText
}
assert(
dummyAgreementText.exists(_.nonEmpty),
s"$party expected a non-empty agreement text, but received $dummyAgreementText.",
)
assert(
dummyWithParamAgreementText.exists(_.nonEmpty),
s"$party expected an empty agreement text, but received $dummyWithParamAgreementText.",
)
}
})
test(
"ACSeventId",
"The ActiveContractService should properly fill the eventId field",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
_ <- ledger.create(party, new Dummy(party))
Vector(dummyEvent) <- ledger.activeContracts(party)
flatTransaction <- ledger.flatTransactionByEventId(dummyEvent.eventId, party)
transactionTree <- ledger.transactionTreeByEventId(dummyEvent.eventId, party)
} yield {
assert(
flatTransaction.transactionId == transactionTree.transactionId,
s"EventId ${dummyEvent.eventId} did not resolve to the same flat transaction (${flatTransaction.transactionId}) and transaction tree (${transactionTree.transactionId}).",
)
}
})
test(
"ACSnoWitnessedContracts",
"The ActiveContractService should not return witnessed contracts",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
for {
witnesses: TestWitnesses.ContractId <- ledger.create(
alice,
new TestWitnesses(alice, bob, bob),
)
_ <- ledger.exercise(bob, witnesses.exerciseWitnessesCreateNewWitnesses())
bobContracts <- ledger.activeContracts(bob)
aliceContracts <- ledger.activeContracts(alice)
} yield {
assert(
bobContracts.size == 2,
s"Expected to receive 2 active contracts for $bob, but received ${bobContracts.size}.",
)
assert(
aliceContracts.size == 1,
s"Expected to receive 1 active contracts for $alice, but received ${aliceContracts.size}.",
)
}
})
test(
"ACSnoDivulgedContracts",
"The ActiveContractService should not return divulged contracts",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
for {
divulgence1 <- ledger.create(alice, new Divulgence1(alice))
divulgence2 <- ledger.create(bob, new Divulgence2(bob, alice))
_ <- ledger.exercise(alice, divulgence2.exerciseDivulgence2Fetch(divulgence1))
bobContracts <- ledger.activeContracts(bob)
aliceContracts <- ledger.activeContracts(alice)
} yield {
assert(
bobContracts.size == 1,
s"Expected to receive 1 active contracts for $bob, but received ${bobContracts.size}.",
)
assert(
aliceContracts.size == 2,
s"Expected to receive 2 active contracts for $alice, but received ${aliceContracts.size}.",
)
}
})
test(
"ACSnoSignatoryObservers",
"The ActiveContractService should not return overlapping signatories and observers",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
for {
_ <- ledger.create(alice, new WithObservers(alice, Seq(alice, bob).map(_.getValue).asJava))
contracts <- ledger.activeContracts(alice)
} yield {
assert(contracts.nonEmpty)
contracts.foreach(ce =>
assert(
ce.observers == Seq(bob.getValue),
s"Expected observers to only contain $bob, but received ${ce.observers}",
)
)
}
})
test(
"ACFilterWitnesses",
"The ActiveContractService should filter witnesses by the transaction filter",
allocate(Parties(3)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie)) =>
for {
_ <- ledger.create(alice, new WithObservers(alice, List(bob, charlie).map(_.getValue).asJava))
bobContracts <- ledger.activeContracts(bob)
aliceBobContracts <- ledger.activeContracts(alice, bob)
bobCharlieContracts <- ledger.activeContracts(bob, charlie)
} yield {
def assertWitnesses(contracts: Vector[CreatedEvent], requesters: Set[Party]): Unit = {
assert(
contracts.size == 1,
s"Expected to receive 1 active contracts for $requesters, but received ${contracts.size}.",
)
assert(
contracts.head.witnessParties.toSet == requesters.map(_.getValue),
s"Expected witness parties to equal to $requesters, but received ${contracts.head.witnessParties}",
)
}
assertWitnesses(bobContracts, Set(bob))
assertWitnesses(aliceBobContracts, Set(alice, bob))
assertWitnesses(bobCharlieContracts, Set(bob, charlie))
}
})
test(
"ACSFilterCombinations",
"Testing ACS filter combinations",
allocate(Parties(3)),
)(implicit ec => { case Participants(Participant(ledger, p1, p2, p3)) =>
// Let us have 3 templates
val templateIds: Vector[Identifier] =
Vector(TriAgreement.TEMPLATE_ID, TriProposal.TEMPLATE_ID, WithObservers.TEMPLATE_ID).map(t =>
Identifier.fromJavaProto(t.toProto)
)
// Let us have 3 parties
val parties: Vector[Party] = Vector(p1, p2, p3)
// Let us have all combinations for the 3 parties
val partyCombinations =
Vector(Set(0), Set(1), Set(2), Set(0, 1), Set(1, 2), Set(0, 2), Set(0, 1, 2))
// Let us populate 3 contracts for each template/partyCombination pair (see createContracts below)
// Then we require the following Filter - Expectations to be upheld
// Key is the index of a test party (see parties)
// Value is
// either empty, meaning a wildcard party filter
// or the Set of indices of a test template (see templateIds)
val * = Set.empty[Int]
type ACSFilter = Map[Int, Set[Int]]
case class FilterCoord(templateId: Int, stakeholders: Set[Int])
def filterCoordsForFilter(filter: ACSFilter): Set[FilterCoord] = {
(for {
(party, templates) <- filter
templateId <- if (templates.isEmpty) templateIds.indices.toSet else templates
allowedPartyCombination <- partyCombinations.filter(_(party))
} yield FilterCoord(templateId, allowedPartyCombination)).toSet
}
val fixtures: Vector[(ACSFilter, Set[FilterCoord])] = Vector(
Map(0 -> *),
Map(0 -> Set(0)),
Map(0 -> Set(1)),
Map(0 -> Set(2)),
Map(0 -> Set(0, 1)),
Map(0 -> Set(0, 2)),
Map(0 -> Set(1, 2)),
Map(0 -> Set(0, 1, 2)),
// multi filter
Map(0 -> *, 1 -> *),
Map(0 -> *, 2 -> *),
Map(0 -> *, 1 -> *, 2 -> *),
Map(0 -> Set(0), 1 -> Set(1)),
Map(0 -> Set(0), 1 -> Set(1), 2 -> Set(2)),
Map(0 -> Set(0, 1), 1 -> Set(0, 2)),
Map(0 -> Set(0, 1), 1 -> Set(0, 2), 2 -> Set(1, 2)),
Map(0 -> *, 1 -> Set(0)),
Map(0 -> *, 1 -> Set(0), 2 -> Set(1, 2)),
).map(filter => filter -> filterCoordsForFilter(filter))
def createContracts: Future[Map[FilterCoord, Set[String]]] = {
def withThreeParties[T <: Template](
f: (String, String, String) => T
)(partySet: Set[Party]): T =
partySet.toList.map(_.getValue) match {
case a :: b :: c :: Nil => f(a, b, c)
case a :: b :: Nil => f(a, b, b)
case a :: Nil => f(a, a, a)
case invalid =>
throw new Exception(s"Invalid partySet, length must be 1 or 2 or 3 but it was $invalid")
}
val templateFactories: Vector[Set[Party] => _ <: Template] =
Vector(
(parties: Set[Party]) => withThreeParties(new TriAgreement(_, _, _))(parties),
(parties: Set[Party]) => withThreeParties(new TriProposal(_, _, _))(parties),
(parties: Set[Party]) =>
new WithObservers(parties.head, parties.toList.map(_.getValue).asJava),
)
def createContractFor(
template: Int,
partyCombination: Int,
): Future[ContractId[_]] = {
val partiesSet = partyCombinations(partyCombination).map(parties)
templateFactories(template)(partiesSet) match {
case agreement: TriAgreement =>
ledger.create(
partiesSet.toList,
partiesSet.toList,
agreement,
)(TriAgreement.COMPANION)
case proposal: TriProposal =>
ledger.create(
partiesSet.toList,
partiesSet.toList,
proposal,
)(TriProposal.COMPANION)
case withObservers: WithObservers =>
ledger.create(
partiesSet.toList,
partiesSet.toList,
withObservers,
)(WithObservers.COMPANION)
case t => throw new RuntimeException(s"the template given has an unexpected type $t")
}
}
val createFs = for {
partyCombinationIndex <- partyCombinations.indices
templateIndex <- templateFactories.indices
_ <- 1 to 3
} yield createContractFor(templateIndex, partyCombinationIndex)
.map((partyCombinationIndex, templateIndex) -> _)
Future
.sequence(createFs)
.map(_.groupBy(_._1).map { case ((partyCombinationIndex, templateIndex), contractId) =>
(
FilterCoord(templateIndex, partyCombinations(partyCombinationIndex)),
contractId.view.map(_._2.contractId).toSet,
)
})
}
val random = new Random(System.nanoTime())
def testForFixtures(
fixtures: Vector[(ACSFilter, Set[FilterCoord])],
allContracts: Map[FilterCoord, Set[String]],
) = {
def activeContractIdsFor(filter: ACSFilter): Future[Vector[String]] =
ledger
.activeContracts(
new GetActiveContractsRequest(
ledgerId = ledger.ledgerId,
filter = Some(
new TransactionFilter(
filter.map {
case (party, templates) if templates.isEmpty =>
(parties(party).getValue, new Filters(None))
case (party, templates) =>
(
parties(party).getValue,
new Filters(
Some(
new InclusiveFilters(random.shuffle(templates.toSeq.map(templateIds)))
)
),
)
}
)
),
verbose = true,
)
)
.map(_._2.map(_.contractId))
def testForFixture(actual: Vector[String], expected: Set[FilterCoord], hint: String): Unit = {
val actualSet = actual.toSet
assert(
expected.forall(allContracts.contains),
s"$hint expected FilterCoord(s) which do not exist(s): ${expected.filterNot(allContracts.contains)}",
)
assert(
actualSet.size == actual.size,
s"$hint ACS returned redundant entries ${actual.groupBy(identity).toList.filter(_._2.size > 1).map(_._1).mkString("\n")}",
)
val errors = allContracts.toList.flatMap {
case (filterCoord, contracts) if expected(filterCoord) && contracts.forall(actualSet) =>
Nil
case (filterCoord, contracts)
if expected(filterCoord) && contracts.forall(x => !actualSet(x)) =>
List(s"$filterCoord is missing from result")
case (filterCoord, _) if expected(filterCoord) =>
List(s"$filterCoord is partially missing from result")
case (filterCoord, contracts) if contracts.forall(actualSet) =>
List(s"$filterCoord is present (too many contracts in result)")
case (filterCoord, contracts) if contracts.exists(actualSet) =>
List(s"$filterCoord is partially present (too many contracts in result)")
case (_, _) => Nil
}
assert(errors == Nil, s"$hint ACS mismatch: ${errors.mkString(", ")}")
val expectedContracts = expected.view.flatMap(allContracts).toSet
// This extra, redundant test is to safeguard the above, more fine grained approach
assert(
expectedContracts == actualSet,
s"$hint ACS mismatch\n Extra contracts: ${actualSet -- expectedContracts}\n Missing contracts: ${expectedContracts -- actualSet}",
)
}
val testFs = fixtures.map { case (filter, expectedResultCoords) =>
activeContractIdsFor(filter).map(
testForFixture(_, expectedResultCoords, s"Filter: $filter")
)
}
Future.sequence(testFs)
}
for {
allContracts <- createContracts
_ <- testForFixtures(fixtures, allContracts)
} yield ()
})
test(
"ActiveAtOffsetInfluencesAcs",
"Allow to specify optional active_at_offset",
enabled = _.acsActiveAtOffsetFeature,
disabledReason = "Requires ACS with active_at_offset",
partyAllocation = allocate(SingleParty),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val transactionFilter =
Some(TransactionFilter(filtersByParty = Map(party.getValue -> Filters())))
for {
c1 <- ledger.create(party, new Dummy(party))
offset1 <- ledger.currentEnd()
c2 <- ledger.create(party, new Dummy(party))
offset2 <- ledger.currentEnd()
// acs at offset1
(acsOffset1, acs1) <- ledger
.activeContractsIds(
GetActiveContractsRequest(
filter = transactionFilter,
activeAtOffset = offset1.getAbsolute,
)
)
_ = assertEquals("acs1", acs1.toSet, Set(c1))
_ = assertEquals("acs1 offset", acsOffset1, Some(offset1))
// acs at offset2
(acsOffset2, acs2) <- ledger
.activeContractsIds(
GetActiveContractsRequest(
filter = transactionFilter,
activeAtOffset = offset2.getAbsolute,
)
)
_ = assertEquals("ACS at the second offset", acs2.toSet, Set(c1, c2))
_ = assertEquals("acs1 offset", acsOffset2, Some(offset2))
// acs at the default offset
(acsOffset3, acs3) <- ledger
.activeContractsIds(
GetActiveContractsRequest(
filter = transactionFilter,
activeAtOffset = "",
)
)
endOffset <- ledger.currentEnd()
_ = assertEquals("ACS at the default offset", acs3.toSet, Set(c1, c2))
_ = assertEquals("acs1 offset", acsOffset3, Some(endOffset))
} yield ()
})
test(
"AcsAtPruningOffsetIsAllowed",
"Allow requesting ACS at the pruning offset",
enabled = _.acsActiveAtOffsetFeature,
disabledReason = "Requires ACS with active_at_offset",
partyAllocation = allocate(SingleParty),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val transactionFilter =
Some(TransactionFilter(filtersByParty = Map(party.getValue -> Filters())))
for {
c1 <- ledger.create(party, new Dummy(party))
anOffset <- ledger.currentEnd()
_ <- ledger.create(party, new Dummy(party))
_ <- ledger.pruneCantonSafe(
pruneUpTo = anOffset,
party = party,
dummyCommand = p => new Dummy(p).create.commands,
)
(acsOffset, acs) <- ledger
.activeContractsIds(
GetActiveContractsRequest(
filter = transactionFilter,
activeAtOffset = anOffset.getAbsolute,
)
)
_ = assertEquals("acs valid_at at pruning offset", acs.toSet, Set(c1))
_ = assertEquals("acs valid_at offset", acsOffset, Some(anOffset))
} yield ()
})
test(
"AcsBeforePruningOffsetIsDisallowed",
"Fail when requesting ACS before the pruning offset",
enabled = _.acsActiveAtOffsetFeature,
disabledReason = "Requires ACS with active_at_offset",
partyAllocation = allocate(SingleParty),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val transactionFilter =
Some(TransactionFilter(filtersByParty = Map(party.getValue -> Filters())))
for {
offset1 <- ledger.currentEnd()
_ <- ledger.create(party, new Dummy(party))
offset2 <- ledger.currentEnd()
_ <- ledger.create(party, new Dummy(party))
_ <- ledger.pruneCantonSafe(
pruneUpTo = offset2,
party = party,
p => new Dummy(p).create.commands,
)
_ <- ledger
.activeContractsIds(
GetActiveContractsRequest(
filter = transactionFilter,
activeAtOffset = offset1.getAbsolute,
)
)
.mustFailWith(
"ACS before the pruning offset",
LedgerApiErrors.RequestValidation.ParticipantPrunedDataAccessed,
)
} yield ()
})
test(
"ActiveAtOffsetInvalidFormat",
"Fail when active_at_offset has invalid format",
enabled = _.acsActiveAtOffsetFeature,
disabledReason = "Requires ACS with active_at_offset",
partyAllocation = allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val transactionFilter =
Some(TransactionFilter(filtersByParty = Map(party.getValue -> Filters())))
for {
_ <- ledger
.activeContracts(
GetActiveContractsRequest(
filter = transactionFilter,
activeAtOffset = s"bad",
)
)
.mustFailWith(
"invalid offset format",
LedgerApiErrors.RequestValidation.NonHexOffset,
)
} yield ()
})
test(
"ActiveAtOffsetAfterLedgerEnd",
"Fail when active_at_offset is after the ledger end offset",
enabled = _.acsActiveAtOffsetFeature,
disabledReason = "Requires ACS with active_at_offset",
partyAllocation = allocate(SingleParty),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val transactionFilter =
Some(TransactionFilter(filtersByParty = Map(party.getValue -> Filters())))
for {
offsetBeyondLedgerEnd <- ledger.offsetBeyondLedgerEnd()
_ <- ledger
.activeContracts(
GetActiveContractsRequest(
filter = transactionFilter,
activeAtOffset = offsetBeyondLedgerEnd.getAbsolute,
)
)
.mustFailWith(
"offset after ledger end",
LedgerApiErrors.RequestValidation.OffsetAfterLedgerEnd,
)
} yield ()
})
private def createDummyContracts(party: Party, ledger: ParticipantTestContext)(implicit
ec: ExecutionContext
): Future[
(
Dummy.ContractId,
DummyWithParam.ContractId,
DummyFactory.ContractId,
)
] = {
for {
dummy <- ledger.create(party, new Dummy(party))
dummyWithParam <- ledger.create(party, new DummyWithParam(party))
dummyFactory <- ledger.create(party, new DummyFactory(party))
} yield (dummy, dummyWithParam, dummyFactory)
}
private def assertTemplates(
party: Seq[Party],
events: Vector[CreatedEvent],
templateId: JavaIdentifier,
count: Int,
): Unit = {
val templateEvents =
events.count(_.getTemplateId == Identifier.fromJavaProto(templateId.toProto))
assert(
templateEvents == count,
s"${party.mkString(" and ")} expected $count $templateId events, but received $templateEvents.",
)
}
}

View File

@ -1,43 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import java.util.regex.Pattern
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.test.java.semantic.semantictests.{Amount, Iou}
import java.math.BigDecimal
class ClosedWorldIT extends LedgerTestSuite {
import CompanionImplicits._
private[this] val onePound = new Amount(BigDecimal.valueOf(1), "GBP")
/*
* All informees in a transaction must be allocated.
*/
test(
"ClosedWorldObserver",
"Cannot execute a transaction that references unallocated observer parties",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(alpha, payer)) =>
for {
failure <- alpha
.create(payer, new Iou(payer, "unallocated", onePound))
.mustFail("referencing an unallocated party")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.WriteServiceRejections.PartyNotKnownOnLedger,
Some(Pattern.compile("Part(y|ies) not known on ledger")),
checkDefiniteAnswerMetadata = true,
)
}
})
}

View File

@ -1,185 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import java.util.UUID
import com.daml.grpc.GrpcException
import com.daml.ledger.api.testtool.infrastructure.Allocation.{
Participant,
Participants,
SingleParty,
allocate,
}
import com.daml.ledger.api.testtool.infrastructure.Assertions.fail
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.ProtobufConverters._
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.v1.command_service.SubmitAndWaitRequest
import com.daml.ledger.api.v1.command_submission_service.SubmitRequest
import com.daml.ledger.api.v1.commands.Commands.DeduplicationPeriod
import com.daml.ledger.javaapi.data.Party
import com.daml.ledger.test.java.model.test.DummyWithAnnotation
import io.grpc.Status
import io.grpc.Status.Code
import scala.concurrent.duration._
import scala.concurrent.{ExecutionContext, Future}
import scala.util.control.NonFatal
import scala.util.{Failure, Random, Success}
class CommandDeduplicationParallelIT extends LedgerTestSuite {
private val deduplicationDuration = 3.seconds
private val numberOfParallelRequests = 10
test(
"DeduplicateParallelSubmissionsUsingCommandSubmissionService",
"Commands submitted at the same, in parallel, using the CommandSubmissionService, should be deduplicated",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
lazy val request = buildSubmitRequest(ledger, party)
runTestWithSubmission[SubmitRequest](
ledger,
party,
() => submitRequestAndGetStatusCode(ledger)(request, party),
)
})
test(
"DeduplicateParallelSubmissionsUsingCommandService",
"Commands submitted at the same, in parallel, using the CommandService, should be deduplicated",
allocate(SingleParty),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = buildSubmitAndWaitRequest(ledger, party)
runTestWithSubmission[SubmitAndWaitRequest](
ledger,
party,
() => submitAndWaitRequestAndGetStatusCode(ledger)(request, party),
)
})
test(
"DeduplicateParallelSubmissionsUsingMixedCommandServiceAndCommandSubmissionService",
"Commands submitted at the same, in parallel, using the CommandService and the CommandSubmissionService, should be deduplicated",
allocate(SingleParty),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val submitAndWaitRequest = buildSubmitAndWaitRequest(ledger, party)
val submitRequest = buildSubmitRequest(ledger, party).update(
_.commands.commandId := submitAndWaitRequest.getCommands.commandId
)
runTestWithSubmission[SubmitAndWaitRequest](
ledger,
party,
() =>
if (Random.nextBoolean())
submitAndWaitRequestAndGetStatusCode(ledger)(submitAndWaitRequest, party)
else submitRequestAndGetStatusCode(ledger)(submitRequest, party),
)
})
private def runTestWithSubmission[T](
ledger: ParticipantTestContext,
party: Party,
submitRequestAndGetStatus: () => Future[Code],
)(implicit
ec: ExecutionContext
) = {
for {
responses <- Future
.traverse(Seq.fill(numberOfParallelRequests)(()))(_ => {
submitRequestAndGetStatus()
})
.map(_.groupBy(identity).view.mapValues(_.size).toMap)
activeContracts <- ledger.activeContracts(party)
} yield {
val expectedDuplicateResponses = numberOfParallelRequests - 1
val okResponses = responses.getOrElse(Code.OK, 0)
val alreadyExistsResponses = responses.getOrElse(Code.ALREADY_EXISTS, 0)
// Canton can return ABORTED for parallel in-flight duplicate submissions
val abortedResponses = responses.getOrElse(Code.ABORTED, 0)
val duplicateResponses =
if (ledger.features.commandDeduplicationFeatures.deduplicationType.isAsyncAndConcurrentSync)
alreadyExistsResponses + abortedResponses
else alreadyExistsResponses
assert(
okResponses == 1 && duplicateResponses == numberOfParallelRequests - 1,
s"Expected $expectedDuplicateResponses duplicate responses and one accepted, got $responses",
)
assert(activeContracts.size == 1)
}
}
private def buildSubmitRequest(
ledger: ParticipantTestContext,
party: Party,
) = ledger
.submitRequest(
party,
new DummyWithAnnotation(party, "Duplicate Using CommandSubmissionService").create.commands,
)
.update(
_.commands.deduplicationPeriod := DeduplicationPeriod.DeduplicationDuration(
deduplicationDuration.asProtobuf
)
)
private def buildSubmitAndWaitRequest(
ledger: ParticipantTestContext,
party: Party,
) = ledger
.submitAndWaitRequest(
party,
new DummyWithAnnotation(party, "Duplicate using CommandService").create.commands,
)
.update(
_.commands.deduplicationDuration := deduplicationDuration.asProtobuf
)
private def submitAndWaitRequestAndGetStatusCode(
ledger: ParticipantTestContext
)(request: SubmitAndWaitRequest, parties: Party*)(implicit ec: ExecutionContext) = {
val submissionId = UUID.randomUUID().toString
val requestWithSubmissionId = request.update(_.commands.submissionId := submissionId)
val submitResult = ledger.submitAndWait(requestWithSubmissionId)
submissionResultToFinalStatusCode(ledger)(submitResult, submissionId, parties: _*)
}
protected def submitRequestAndGetStatusCode(
ledger: ParticipantTestContext
)(request: SubmitRequest, parties: Party*)(implicit ec: ExecutionContext): Future[Code] = {
val submissionId = UUID.randomUUID().toString
val requestWithSubmissionId = request.update(_.commands.submissionId := submissionId)
val submitResult = ledger
.submit(requestWithSubmissionId)
submissionResultToFinalStatusCode(ledger)(submitResult, submissionId, parties: _*)
}
private def submissionResultToFinalStatusCode(
ledger: ParticipantTestContext
)(submitResult: Future[Unit], submissionId: String, parties: Party*)(implicit
ec: ExecutionContext
) = submitResult
.transformWith {
case Failure(exception) =>
exception match {
case GrpcException(status, _) =>
Future.successful(status.getCode)
case NonFatal(otherException) =>
fail(s"Not a GRPC exception $otherException", otherException)
}
case Success(_) =>
ledger
.findCompletion(parties: _*)(completion => {
completion.submissionId == submissionId
})
.map {
case Some(response) =>
Status.fromCodeValue(response.completion.getStatus.code).getCode
case None =>
fail(s"Did not find completion for request with submission id $submissionId")
}
}
}

View File

@ -1,275 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import java.time.Duration
import java.util.regex.Pattern
import com.daml.error.ErrorCode
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.assertions.CommandDeduplicationAssertions.DurationConversion
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.testtool.infrastructure.{FutureAssertions, LedgerTestSuite}
import com.daml.ledger.api.v1.commands.Commands.DeduplicationPeriod
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.ledger.javaapi.data.Party
import com.daml.ledger.test.java.model.test.Dummy
import com.daml.lf.data.Ref
import com.daml.logging.LoggingContext
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
class CommandDeduplicationPeriodValidationIT extends LedgerTestSuite {
import CompanionImplicits._
private implicit val loggingContext: LoggingContext = LoggingContext.ForTesting
test(
"ValidDeduplicationDuration",
"Submission returns OK if deduplication time is within the accepted interval",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
// Submission using the maximum allowed deduplication time
val request = ledger.submitRequest(party, new Dummy(party).create.commands)
for {
config <- ledger.configuration()
maxDedupDuration = config.maxDeduplicationDuration.get
_ <- ledger.submit(request.update(_.commands.deduplicationTime := maxDedupDuration))
} yield {
// No assertions to make, since the command went through as expected
}
})
test(
"NegativeDeduplicationDuration",
"Submission with negative deduplication durations are rejected",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val deduplicationPeriod = DeduplicationPeriod.DeduplicationDuration(
DurationConversion.toProto(Duration.ofSeconds(-1))
)
assertSyncFailedRequest(
ledger,
party,
deduplicationPeriod,
failReason = "Requests with a deduplication period represented by a negative duration",
expectedMessage =
"The submitted command has a field with invalid value: Invalid field deduplication_period: Duration must be positive",
expectedError = LedgerApiErrors.RequestValidation.InvalidField,
)
})
test(
"InvalidOffset",
"Submission with deduplication periods represented by invalid offsets are rejected",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val deduplicationPeriod = DeduplicationPeriod.DeduplicationOffset(
"invalid_offset"
)
assertSyncFailedRequest(
ledger,
party,
deduplicationPeriod,
failReason = "Submitting a command with an invalid offset",
expectedMessage =
"Offset in deduplication_period not specified in hexadecimal: invalid_offset: the deduplication offset has to be a hexadecimal string and not invalid_offset",
expectedError = LedgerApiErrors.RequestValidation.NonHexOffset,
)
})
test(
"DeduplicationDurationExceedsMaxDeduplicationDuration",
"Submission returns expected error codes if deduplication time is too big",
allocate(SingleParty),
enabled = _.commandDeduplicationFeatures.maxDeduplicationDurationEnforced,
disabledReason = "Maximum deduplication duration is not enforced by the ledger",
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitRequest(party, new Dummy(party).create.commands)
val expectedError = LedgerApiErrors.RequestValidation.InvalidDeduplicationPeriodField
for {
config <- ledger.configuration()
maxDedupDuration = config.maxDeduplicationDuration.get
failure <- ledger
.submit(
request.update(
_.commands.deduplicationTime := maxDedupDuration.update(
_.seconds := maxDedupDuration.seconds + 1
)
)
)
.mustFail("submitting a command with a deduplication time that is too big")
_ = assertGrpcErrorRegex(
failure,
expectedError,
optPattern = Some(
Pattern.compile(
"The given deduplication .+ exceeds the maximum deduplication .+"
)
),
)
metadataLongestDuration = extractErrorInfoMetadataValue(failure, "longest_duration")
// we expect that the request is accepted and the metadata value is valid
_ <- ledger.submit(
request.update(
_.commands.deduplicationDuration := DurationConversion.toProto(
Duration.parse(metadataLongestDuration)
)
)
)
} yield {}
})
test(
"OffsetOutsideRange",
"Submission with deduplication periods represented by offsets which are outside the valid range are rejected",
allocate(SingleParty),
enabled =
_.commandDeduplicationFeatures.getDeduplicationPeriodSupport.offsetSupport.isOffsetConvertToDuration,
disabledReason = "Only ledgers that convert offsets to durations fail",
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
end <- ledger.offsetBeyondLedgerEnd()
deduplicationPeriod = DeduplicationPeriod.DeduplicationOffset(
Ref.HexString.assertFromString(end.getAbsolute.toLowerCase)
)
_ <- assertSyncFailedRequest(
ledger,
party,
deduplicationPeriod,
failReason = "Submitting a command with an invalid offset",
expectedMessage =
"The submitted command had an invalid deduplication period: Cannot convert deduplication offset to duration because there is no completion at given offset .*",
expectedError = LedgerApiErrors.RequestValidation.InvalidDeduplicationPeriodField,
)
} yield {}
})
test(
"OffsetPruned",
"Submission with deduplication periods represented by offsets which are pruned are rejected",
allocate(SingleParty),
enabled =
!_.commandDeduplicationFeatures.getDeduplicationPeriodSupport.offsetSupport.isOffsetNotSupported,
disabledReason = "The ledger does not support deduplication periods represented by offsets",
runConcurrently = false, // Pruning is involved
)(implicit ec => { case Participants(Participant(ledger, party)) =>
def submitAndWaitWithDeduplication(
deduplicationPeriod: DeduplicationPeriod.DeduplicationOffset
) = {
ledger
.submitAndWait(
ledger
.submitAndWaitRequest(party, new Dummy(party).create.commands)
.update(
_.commands.deduplicationPeriod := deduplicationPeriod
)
)
}
val isOffsetNativelySupported =
ledger.features.commandDeduplicationFeatures.getDeduplicationPeriodSupport.offsetSupport.isOffsetNativeSupport
for {
start <- ledger.currentEnd()
firstCreate <- ledger.create(party, new Dummy(party))
_ <- ledger.exercise(party, firstCreate.exerciseDummyChoice1())
secondCreate: Dummy.ContractId <- ledger.create(party, new Dummy(party))
_ <- ledger.submitAndWait(
ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
)
end <- ledger.currentEnd()
_ <- ledger.exercise(party, secondCreate.exerciseDummyChoice1())
_ <- FutureAssertions.succeedsEventually(
retryDelay = 10.millis,
maxRetryDuration = 10.seconds,
ledger.delayMechanism,
"Prune offsets",
) {
for {
_ <- ledger.create(party, new Dummy(party))
_ <- ledger.submitAndWait(
ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
)
_ <- ledger.prune(pruneUpTo = end, attempts = 1)
} yield {}
}
failure <- submitAndWaitWithDeduplication(
DeduplicationPeriod.DeduplicationOffset(
start.getAbsolute
)
).mustFail("using an offset which was pruned")
_ = assertGrpcErrorRegex(
failure,
// Canton returns INVALID_DEDUPLICATION_PERIOD with earliest_offset metadata
// KV returns PARTICIPANT_PRUNED_DATA_ACCESSED with earliest_offset metadata
errorCode =
if (isOffsetNativelySupported)
LedgerApiErrors.RequestValidation.InvalidDeduplicationPeriodField
else LedgerApiErrors.RequestValidation.ParticipantPrunedDataAccessed,
None,
)
earliestOffset = extractErrorInfoMetadataValue(
failure,
LedgerApiErrors.EarliestOffsetMetadataKey,
)
_ <-
// Because KV treats deduplication offsets as inclusive, and because the participant pruning offset is inclusive
// we cannot simply use the received offset as a deduplication period, but we have to find the first completion after the given offset
if (isOffsetNativelySupported) {
submitAndWaitWithDeduplication(
DeduplicationPeriod.DeduplicationOffset(earliestOffset)
)
} else {
findFirstOffsetAfterGivenOffset(ledger, earliestOffset)(party).flatMap(offset =>
submitAndWaitWithDeduplication(DeduplicationPeriod.DeduplicationOffset(offset))
)
}
} yield {}
})
private def findFirstOffsetAfterGivenOffset(ledger: ParticipantTestContext, offset: String)(
party: Party
)(implicit ec: ExecutionContext) = {
ledger
.findCompletion(
ledger.completionStreamRequest(
LedgerOffset.of(LedgerOffset.Value.Absolute(offset))
)(party)
)(_ => true)
.map { completionOpt =>
{
val completion = assertDefined(completionOpt, "No completion found")
completion.offset.getAbsolute
}
}
}
private def assertSyncFailedRequest(
ledger: ParticipantTestContext,
party: Party,
deduplicationPeriod: DeduplicationPeriod,
failReason: String,
expectedMessage: String,
expectedError: ErrorCode,
)(implicit ec: ExecutionContext) = {
for {
failure <- ledger
.submit(
ledger
.submitRequest(party, new Dummy(party).create.commands)
.update(
_.commands.deduplicationPeriod := deduplicationPeriod
)
)
.mustFail(failReason)
} yield {
assertGrpcErrorRegex(
failure,
expectedError,
Some(Pattern.compile(expectedMessage)),
)
}
}
}

View File

@ -1,701 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import java.util.regex.Pattern
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.Synchronize.synchronize
import com.daml.ledger.javaapi.data.Command
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers._
import com.daml.ledger.api.v1.command_service.SubmitAndWaitForTransactionResponse
import com.daml.ledger.api.v1.value.{Record, RecordField, Value}
import com.daml.ledger.test.java.model.test._
import CompanionImplicits._
import java.math.BigDecimal
import java.util.{List => JList}
import scala.jdk.CollectionConverters._
final class CommandServiceIT extends LedgerTestSuite {
test(
"CSsubmitAndWaitBasic",
"SubmitAndWait creates a contract of the expected template",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
for {
_ <- ledger.submitAndWait(request)
active <- ledger.activeContracts(party)
} yield {
assert(active.size == 1)
val dummyTemplateId = active.flatMap(_.templateId.toList).head
assert(dummyTemplateId == Dummy.TEMPLATE_ID.toV1)
}
})
test(
"CSsubmitAndWaitForTransactionIdBasic",
"SubmitAndWaitForTransactionId returns a valid transaction identifier",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
for {
transactionIdResponse <- ledger.submitAndWaitForTransactionId(request)
transactionId = transactionIdResponse.transactionId
retrievedTransaction <- ledger.transactionTreeById(transactionId, party)
transactions <- ledger.flatTransactions(party)
} yield {
assert(transactionId.nonEmpty, "The transaction identifier was empty but shouldn't.")
assert(
transactions.size == 1,
s"$party should see only one transaction but sees ${transactions.size}",
)
val events = transactions.head.events
assert(events.size == 1, s"$party should see only one event but sees ${events.size}")
assert(
events.head.event.isCreated,
s"$party should see only one create but sees ${events.head.event}",
)
val created = transactions.head.events.head.getCreated
assert(
retrievedTransaction.transactionId == transactionId,
s"$party should see the transaction for the created contract $transactionId but sees ${retrievedTransaction.transactionId}",
)
assert(
retrievedTransaction.rootEventIds.size == 1,
s"The retrieved transaction should contain a single event but contains ${retrievedTransaction.rootEventIds.size}",
)
val retrievedEvent = retrievedTransaction.eventsById(retrievedTransaction.rootEventIds.head)
assert(
retrievedEvent.kind.isCreated,
s"The only event seen should be a created but instead it's $retrievedEvent",
)
assert(
retrievedEvent.getCreated == created,
s"The retrieved created event does not match the one in the flat transactions: event=$created retrieved=$retrievedEvent",
)
}
})
test(
"CSsubmitAndWaitForTransactionBasic",
"SubmitAndWaitForTransaction returns a transaction",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
for {
transactionResponse <- ledger.submitAndWaitForTransaction(request)
} yield {
assertOnTransactionResponse(transactionResponse)
}
})
test(
"CSsubmitAndWaitForTransactionEmptyLedgerId",
"SubmitAndWaitForTransaction should accept requests with empty ledgerId",
allocate(SingleParty),
enabled = _.optionalLedgerId,
disabledReason = "Optional ledger id must be enabled",
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger
.submitAndWaitRequest(party, new Dummy(party).create.commands)
.update(_.commands.ledgerId := "")
for {
transactionResponse <- ledger
.submitAndWaitForTransaction(request)
} yield {
assertOnTransactionResponse(transactionResponse)
}
})
private def assertOnTransactionResponse(
transactionResponse: SubmitAndWaitForTransactionResponse
): Unit = {
val transaction = transactionResponse.getTransaction
assert(
transaction.transactionId.nonEmpty,
"The transaction identifier was empty but shouldn't.",
)
val event = transaction.events.head
assert(
event.event.isCreated,
s"The returned transaction should contain a created-event, but was ${event.event}",
)
assert(
event.getCreated.getTemplateId == Dummy.TEMPLATE_ID.toV1,
s"The template ID of the created-event should by ${Dummy.TEMPLATE_ID.toV1}, but was ${event.getCreated.getTemplateId}",
)
}
test(
"CSsubmitAndWaitForTransactionTreeBasic",
"SubmitAndWaitForTransactionTree returns a transaction tree",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
for {
transactionTreeResponse <- ledger.submitAndWaitForTransactionTree(request)
} yield {
val transactionTree = transactionTreeResponse.getTransaction
assert(
transactionTree.transactionId.nonEmpty,
"The transaction identifier was empty but shouldn't.",
)
assert(
transactionTree.eventsById.size == 1,
s"The returned transaction tree should contain 1 event, but contained ${transactionTree.eventsById.size}",
)
val event = transactionTree.eventsById.head._2
assert(
event.kind.isCreated,
s"The returned transaction tree should contain a created-event, but was ${event.kind}",
)
assert(
event.getCreated.getTemplateId == Dummy.TEMPLATE_ID.toV1,
s"The template ID of the created-event should by ${Dummy.TEMPLATE_ID.toV1}, but was ${event.getCreated.getTemplateId}",
)
}
})
test(
"CSduplicateSubmitAndWaitBasic",
"SubmitAndWait should fail on duplicate requests",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
for {
_ <- ledger.submitAndWait(request)
failure <- ledger
.submitRequestAndTolerateGrpcError(
LedgerApiErrors.ConsistencyErrors.SubmissionAlreadyInFlight,
_.submitAndWait(request),
)
.mustFail("submitting a duplicate request")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.DuplicateCommand,
None,
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSduplicateSubmitAndWaitForTransactionId",
"SubmitAndWaitForTransactionId should fail on duplicate requests",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
for {
_ <- ledger.submitAndWaitForTransactionId(request)
failure <- ledger
.submitRequestAndTolerateGrpcError(
LedgerApiErrors.ConsistencyErrors.SubmissionAlreadyInFlight,
_.submitAndWaitForTransactionId(request),
)
.mustFail("submitting a duplicate request")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.DuplicateCommand,
None,
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSduplicateSubmitAndWaitForTransactionData",
"SubmitAndWaitForTransaction should fail on duplicate requests",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
for {
_ <- ledger.submitAndWaitForTransaction(request)
failure <- ledger
.submitRequestAndTolerateGrpcError(
LedgerApiErrors.ConsistencyErrors.SubmissionAlreadyInFlight,
_.submitAndWaitForTransaction(request),
)
.mustFail("submitting a duplicate request")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.DuplicateCommand,
None,
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSduplicateSubmitAndWaitForTransactionTree",
"SubmitAndWaitForTransactionTree should fail on duplicate requests",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
for {
_ <- ledger.submitAndWaitForTransactionTree(request)
failure <- ledger
.submitRequestAndTolerateGrpcError(
LedgerApiErrors.ConsistencyErrors.SubmissionAlreadyInFlight,
_.submitAndWaitForTransactionTree(request),
)
.mustFail("submitting a duplicate request")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.ConsistencyErrors.DuplicateCommand,
None,
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSsubmitAndWaitForTransactionIdInvalidLedgerId",
"SubmitAndWaitForTransactionId should fail for invalid ledger IDs",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val invalidLedgerId = "CSsubmitAndWaitForTransactionIdInvalidLedgerId"
val request = ledger
.submitAndWaitRequest(party, new Dummy(party).create.commands)
.update(_.commands.ledgerId := invalidLedgerId)
for {
failure <- ledger
.submitAndWaitForTransactionId(request)
.mustFail("submitting a request with an invalid ledger ID")
} yield assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.LedgerIdMismatch,
Some(s"Ledger ID '$invalidLedgerId' not found."),
checkDefiniteAnswerMetadata = true,
)
})
test(
"CSsubmitAndWaitForTransactionInvalidLedgerId",
"SubmitAndWaitForTransaction should fail for invalid ledger IDs",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val invalidLedgerId = "CSsubmitAndWaitForTransactionInvalidLedgerId"
val request = ledger
.submitAndWaitRequest(party, new Dummy(party).create.commands)
.update(_.commands.ledgerId := invalidLedgerId)
for {
failure <- ledger
.submitAndWaitForTransaction(request)
.mustFail("submitting a request with an invalid ledger ID")
} yield assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.LedgerIdMismatch,
Some(s"Ledger ID '$invalidLedgerId' not found."),
checkDefiniteAnswerMetadata = true,
)
})
test(
"CSsubmitAndWaitForTransactionTreeInvalidLedgerId",
"SubmitAndWaitForTransactionTree should fail for invalid ledger ids",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val invalidLedgerId = "CSsubmitAndWaitForTransactionTreeInvalidLedgerId"
val request = ledger
.submitAndWaitRequest(party, new Dummy(party).create.commands)
.update(_.commands.ledgerId := invalidLedgerId)
for {
failure <- ledger
.submitAndWaitForTransactionTree(request)
.mustFail("submitting a request with an invalid ledger ID")
} yield assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.LedgerIdMismatch,
Some(s"Ledger ID '$invalidLedgerId' not found."),
checkDefiniteAnswerMetadata = true,
)
})
test(
"CSRefuseBadParameter",
"The submission of a creation that contains a bad parameter label should result in an INVALID_ARGUMENT",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val createWithBadArgument =
updateCommands(
new Dummy(party).create.commands,
_.update(_.create.createArguments.fields.foreach(_.label := "INVALID_PARAM")),
)
val badRequest = ledger.submitAndWaitRequest(party, createWithBadArgument)
for {
failure <- ledger
.submitAndWait(badRequest)
.mustFail("submitting a request with a bad parameter label")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some(Pattern.compile(s"Missing record (label|field)")),
checkDefiniteAnswerMetadata = true,
)
}
})
// TODO fix this test: This test is not asserting that an interpretation error is returning a stack trace.
// Furthermore, stack traces are not returned as of 1.18.
// Instead more detailed error messages with the failed transaction are provided.
test(
"CSReturnStackTrace",
"A submission resulting in an interpretation error should return the stack trace",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
dummy: Dummy.ContractId <- ledger.create(party, new Dummy(party))
failure <- ledger
.exercise(party, dummy.exerciseFailingClone())
.mustFail("submitting a request with an interpretation error")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Interpreter.UnhandledException,
Some(
Pattern.compile(
"Interpretation error: Error: (User abort: Assertion failed.?|Unhandled (Daml )?exception: [0-9a-zA-Z\\.:]*@[0-9a-f]*\\{ message = \"Assertion failed\" \\}\\. [Dd]etails(: |=)Last location: \\[[^\\]]*\\], partial transaction: root node)"
)
),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSDiscloseCreateToObservers",
"Disclose create to observers",
allocate(TwoParties, SingleParty),
)(implicit ec => {
case Participants(Participant(alpha, giver, observer1), Participant(beta, observer2)) =>
val template = new WithObservers(giver, List(observer1, observer2).map(_.getValue).asJava)
for {
_ <- alpha.create(giver, template)
_ <- synchronize(alpha, beta)
observer1View <- alpha.transactionTrees(observer1)
observer2View <- beta.transactionTrees(observer2)
} yield {
val observer1Created = assertSingleton(
"The first observer should see exactly one creation",
observer1View.flatMap(createdEvents),
)
val observer2Created = assertSingleton(
"The second observer should see exactly one creation",
observer2View.flatMap(createdEvents),
)
assertEquals(
"The two observers should see the same creation",
observer1Created.getCreateArguments.fields,
observer2Created.getCreateArguments.fields,
)
assertEquals(
"The observers should see the created contract",
observer1Created.getCreateArguments.fields,
Value.fromJavaProto(template.toValue.toProto).getRecord.fields,
)
}
})
test(
"CSDiscloseExerciseToObservers",
"Disclose exercise to observers",
allocate(TwoParties, SingleParty),
)(implicit ec => {
case Participants(Participant(alpha, giver, observer1), Participant(beta, observer2)) =>
val template = new WithObservers(giver, List(observer1, observer2).map(_.getValue).asJava)
for {
withObservers: WithObservers.ContractId <- alpha.create(giver, template)
_ <- alpha.exercise(giver, withObservers.exercisePing())
_ <- synchronize(alpha, beta)
observer1View <- alpha.transactionTrees(observer1)
observer2View <- beta.transactionTrees(observer2)
} yield {
val observer1Exercise = assertSingleton(
"The first observer should see exactly one exercise",
observer1View.flatMap(exercisedEvents),
)
val observer2Exercise = assertSingleton(
"The second observer should see exactly one exercise",
observer2View.flatMap(exercisedEvents),
)
assert(
observer1Exercise.contractId == observer2Exercise.contractId,
"The two observers should see the same exercise",
)
assert(
observer1Exercise.contractId == withObservers.contractId,
"The observers shouls see the exercised contract",
)
}
})
test(
"CSHugeCommandSubmission",
"The server should accept a submission with 15 commands",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val target = 15
val commands = List.fill(target)(new Dummy(party).create.commands.asScala).flatten
val request = ledger.submitAndWaitRequest(party, commands.asJava)
for {
_ <- ledger.submitAndWait(request)
acs <- ledger.activeContracts(party)
} yield {
assert(
acs.size == target,
s"Expected $target contracts to be created, got ${acs.size} instead",
)
}
})
test(
"CSCallablePayout",
"Run CallablePayout and return the right events",
allocate(TwoParties, SingleParty),
)(implicit ec => {
case Participants(Participant(alpha, giver, newReceiver), Participant(beta, receiver)) =>
for {
callablePayout: CallablePayout.ContractId <- alpha.create(
giver,
new CallablePayout(giver, receiver),
)
_ <- synchronize(alpha, beta)
tree <- beta.exercise(receiver, callablePayout.exerciseTransfer(newReceiver))
} yield {
val created = assertSingleton("There should only be one creation", createdEvents(tree))
assertEquals(
"The created event should be the expected one",
created.getCreateArguments.fields,
Value
.fromJavaProto(new CallablePayout(giver, newReceiver).toValue.toProto)
.getRecord
.fields,
)
}
})
test(
"CSReadyForExercise",
"It should be possible to exercise a choice on a created contract",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
factory: DummyFactory.ContractId <- ledger.create(party, new DummyFactory(party))
tree <- ledger.exercise(party, factory.exerciseDummyFactoryCall())
} yield {
val exercise = assertSingleton("There should only be one exercise", exercisedEvents(tree))
assert(exercise.contractId == factory.contractId, "Contract identifier mismatch")
assert(exercise.consuming, "The choice should have been consuming")
val _ = assertLength("Two creations should have occurred", 2, createdEvents(tree))
}
})
test("CSBadNumericValues", "Reject unrepresentable numeric values", allocate(SingleParty))(
implicit ec => { case Participants(Participant(ledger, party)) =>
// Code generation catches bad decimals early so we have to do some work to create (possibly) invalid requests
def rounding(numeric: String): JList[Command] =
updateCommands(
new DecimalRounding(party, BigDecimal.valueOf(0)).create.commands,
_.update(
_.create.createArguments
.fields(1) := RecordField(value = Some(Value(Value.Sum.Numeric(numeric))))
),
)
val wouldLosePrecision = "0.00000000005"
val positiveOutOfBounds = "10000000000000000000000000000.0000000000"
val negativeOutOfBounds = "-10000000000000000000000000000.0000000000"
for {
e1 <- ledger
.submitAndWait(ledger.submitAndWaitRequest(party, rounding(wouldLosePrecision)))
.mustFail("submitting a request which would lose precision")
e2 <- ledger
.submitAndWait(ledger.submitAndWaitRequest(party, rounding(positiveOutOfBounds)))
.mustFail("submitting a request with a positive number out of bounds")
e3 <- ledger
.submitAndWait(ledger.submitAndWaitRequest(party, rounding(negativeOutOfBounds)))
.mustFail("submitting a request with a negative number out of bounds")
} yield {
assertGrpcError(
e1,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some("Cannot represent"),
checkDefiniteAnswerMetadata = true,
)
assertGrpcError(
e2,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some("Out-of-bounds (Numeric 10)"),
checkDefiniteAnswerMetadata = true,
)
assertGrpcError(
e3,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some("Out-of-bounds (Numeric 10)"),
checkDefiniteAnswerMetadata = true,
)
}
}
)
test("CSCreateAndExercise", "Implement create-and-exercise correctly", allocate(SingleParty))(
implicit ec => { case Participants(Participant(ledger, party)) =>
val createAndExercise = new Dummy(party).createAnd.exerciseDummyChoice1().commands
val request = ledger.submitAndWaitRequest(party, createAndExercise)
for {
_ <- ledger.submitAndWait(request)
transactions <- ledger.flatTransactions(party)
trees <- ledger.transactionTrees(party)
} yield {
assert(
transactions.flatMap(_.events).isEmpty,
"A create-and-exercise flat transaction should show no event",
)
assertEquals(
"Unexpected template identifier in create event",
trees.flatMap(createdEvents).map(_.getTemplateId),
Vector(Dummy.TEMPLATE_ID.toV1),
)
val contractId = trees.flatMap(createdEvents).head.contractId
assertEquals(
"Unexpected exercise event triple (choice, contractId, consuming)",
trees.flatMap(exercisedEvents).map(e => (e.choice, e.contractId, e.consuming)),
Vector(("DummyChoice1", contractId, true)),
)
}
}
)
test(
"CSBadCreateAndExercise",
"Fail create-and-exercise on bad create arguments",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val createAndExercise =
updateCommands(
new Dummy(party).createAnd
.exerciseDummyChoice1()
.commands,
_.update(_.createAndExercise.createArguments := Record()),
)
val request = ledger.submitAndWaitRequest(party, createAndExercise)
for {
failure <- ledger
.submitAndWait(request)
.mustFail("submitting a request with bad create arguments")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some("Expecting 1 field for record"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSCreateAndBadExerciseArguments",
"Fail create-and-exercise on bad choice arguments",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val createAndExercise =
updateCommands(
new Dummy(party).createAnd
.exerciseDummyChoice1()
.commands,
_.update(_.createAndExercise.choiceArgument := Value(Value.Sum.Bool(false))),
)
val request = ledger.submitAndWaitRequest(party, createAndExercise)
for {
failure <- ledger
.submitAndWait(request)
.mustFail("submitting a request with bad choice arguments")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some("mismatching type"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSCreateAndBadExerciseChoice",
"Fail create-and-exercise on invalid choice",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val missingChoice = "DoesNotExist"
val createAndExercise =
updateCommands(
new Dummy(party).createAnd
.exerciseDummyChoice1()
.commands,
_.update(_.createAndExercise.choice := missingChoice),
)
val request = ledger.submitAndWaitRequest(party, createAndExercise)
for {
failure <- ledger
.submitAndWait(request)
.mustFail("submitting a request with an invalid choice")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some(
Pattern.compile(
"(unknown|Couldn't find requested) choice " + missingChoice
)
),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSsubmitAndWaitCompletionOffset",
"SubmitAndWait methods return the completion offset in the response",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
def request = ledger.submitAndWaitRequest(party, new Dummy(party).create.commands)
for {
transactionIdResponse <- ledger.submitAndWaitForTransactionId(request)
retrievedTransaction <- ledger.transactionTreeById(transactionIdResponse.transactionId, party)
transactionResponse <- ledger.submitAndWaitForTransaction(request)
transactionTreeResponse <- ledger.submitAndWaitForTransactionTree(request)
} yield {
assert(
transactionIdResponse.completionOffset.nonEmpty &&
transactionIdResponse.completionOffset == retrievedTransaction.offset,
"SubmitAndWaitForTransactionId does not contain the expected completion offset",
)
assert(
transactionResponse.completionOffset.nonEmpty &&
transactionResponse.completionOffset == transactionResponse.getTransaction.offset,
"SubmitAndWaitForTransaction does not contain the expected completion offset",
)
assert(
transactionTreeResponse.completionOffset.nonEmpty
&& transactionTreeResponse.completionOffset
== transactionTreeResponse.getTransaction.offset,
"SubmitAndWaitForTransactionTree does not contain the expected completion offset",
)
}
})
}

View File

@ -1,176 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import java.util.regex.Pattern
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.{LedgerTestSuite, TimeoutException, WithTimeout}
import com.daml.ledger.test.java.model.test.Dummy
import scala.concurrent.duration.DurationInt
final class CommandSubmissionCompletionIT extends LedgerTestSuite {
import CompanionImplicits._
test(
"CSCCompletions",
"Read completions correctly with a correct application identifier and reading party",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitRequest(party, new Dummy(party).create.commands)
for {
_ <- ledger.submit(request)
completions <- ledger.firstCompletions(party)
} yield {
val commandId =
assertSingleton("Expected only one completion", completions.map(_.commandId))
assert(
commandId == request.commands.get.commandId,
"Wrong command identifier on completion",
)
}
})
test(
"CSCNoCompletionsWithoutRightAppId",
"Read no completions without the correct application identifier",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitRequest(party, new Dummy(party).create.commands)
for {
_ <- ledger.submit(request)
invalidRequest = ledger
.completionStreamRequest()(party)
.update(_.applicationId := "invalid-application-id")
failure <- WithTimeout(5.seconds)(ledger.firstCompletions(invalidRequest))
.mustFail("subscribing to completions with an invalid application ID")
} yield {
assert(failure == TimeoutException, "Timeout expected")
}
})
test(
"CSCAfterEnd",
"An OUT_OF_RANGE error should be returned when subscribing to completions past the ledger end",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val request = ledger.submitRequest(party, new Dummy(party).create.commands)
for {
_ <- ledger.submit(request)
futureOffset <- ledger.offsetBeyondLedgerEnd()
invalidRequest = ledger
.completionStreamRequest()(party)
.update(_.offset := futureOffset)
failure <- ledger
.firstCompletions(invalidRequest)
.mustFail("subscribing to completions past the ledger end")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.OffsetAfterLedgerEnd,
Some("is after ledger end"),
)
}
})
test(
"CSCNoCompletionsWithoutRightParty",
"Read no completions without the correct party",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, party, notTheSubmittingParty)) =>
val request = ledger.submitRequest(party, new Dummy(party).create.commands)
for {
_ <- ledger.submit(request)
failure <- WithTimeout(5.seconds)(ledger.firstCompletions(notTheSubmittingParty))
.mustFail("subscribing to completions with the wrong party")
} yield {
assert(failure == TimeoutException, "Timeout expected")
}
})
test(
"CSCRefuseBadChoice",
"The submission of an exercise of a choice that does not exist should yield INVALID_ARGUMENT",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val badChoice = "THIS_IS_NOT_A_VALID_CHOICE"
for {
dummy <- ledger.create(party, new Dummy(party))
exercise = dummy.exerciseDummyChoice1().commands
wrongExercise = updateCommands(exercise, _.update(_.exercise.choice := badChoice))
wrongRequest = ledger.submitRequest(party, wrongExercise)
failure <- ledger.submit(wrongRequest).mustFail("submitting an invalid choice")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
Some(
Pattern.compile(
"(unknown|Couldn't find requested) choice " + badChoice
)
),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSCSubmitWithInvalidLedgerId",
"Submit should fail for an invalid ledger identifier",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val invalidLedgerId = "CSsubmitAndWaitInvalidLedgerId"
val request = ledger
.submitRequest(party, new Dummy(party).create.commands)
.update(_.commands.ledgerId := invalidLedgerId)
for {
failure <- ledger.submit(request).mustFail("submitting with an invalid ledger ID")
} yield assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.LedgerIdMismatch,
Some(s"Ledger ID '$invalidLedgerId' not found."),
checkDefiniteAnswerMetadata = true,
)
})
test(
"CSCDisallowEmptyTransactionsSubmission",
"The submission of an empty command should be rejected with INVALID_ARGUMENT",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val emptyRequest = ledger.submitRequest(party)
for {
failure <- ledger.submit(emptyRequest).mustFail("submitting an empty command")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.MissingField,
Some("commands"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CSCHandleMultiPartySubscriptions",
"Listening for completions should support multi-party subscriptions",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
val aliceRequest = ledger.submitRequest(alice, new Dummy(alice).create.commands)
val bobRequest = ledger.submitRequest(bob, new Dummy(bob).create.commands)
val aliceCommandId = aliceRequest.getCommands.commandId
val bobCommandId = bobRequest.getCommands.commandId
for {
_ <- ledger.submit(aliceRequest)
_ <- ledger.submit(bobRequest)
_ <- WithTimeout(5.seconds)(ledger.findCompletion(alice, bob)(_.commandId == aliceCommandId))
_ <- WithTimeout(5.seconds)(ledger.findCompletion(alice, bob)(_.commandId == bobCommandId))
} yield {
// Nothing to do, if the two completions are found the test is passed
}
})
}

View File

@ -1,122 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.ledger.javaapi.data.codegen.ContractCompanion
import com.daml.ledger.test.java.model.da.types
import com.daml.ledger.test.java.model.iou.Iou
import com.daml.ledger.test.java.model.test
import com.daml.ledger.test.java.model.test.{
Agreement,
AgreementFactory,
CallablePayout,
Delegated,
Delegation,
Divulgence1,
Divulgence2,
Dummy,
DummyFactory,
DummyWithParam,
LocalKeyVisibilityOperations,
MaintainerNotSignatory,
ShowDelegated,
TextKey,
TextKeyOperations,
WithObservers,
Witnesses => TestWitnesses,
}
import com.daml.ledger.test.java.semantic.semantictests
object CompanionImplicits {
implicit val dummyCompanion
: ContractCompanion.WithoutKey[Dummy.Contract, Dummy.ContractId, Dummy] = Dummy.COMPANION
implicit val dummyWithParamCompanion: ContractCompanion.WithoutKey[
DummyWithParam.Contract,
DummyWithParam.ContractId,
DummyWithParam,
] = DummyWithParam.COMPANION
implicit val dummyFactoryCompanion
: ContractCompanion.WithoutKey[DummyFactory.Contract, DummyFactory.ContractId, DummyFactory] =
DummyFactory.COMPANION
implicit val withObserversCompanion: ContractCompanion.WithoutKey[
WithObservers.Contract,
WithObservers.ContractId,
WithObservers,
] = WithObservers.COMPANION
implicit val textKeyCompanion: ContractCompanion.WithKey[
TextKey.Contract,
TextKey.ContractId,
TextKey,
types.Tuple2[String, String],
] = TextKey.COMPANION
implicit val textKeyOperationsCompanion: ContractCompanion.WithoutKey[
TextKeyOperations.Contract,
TextKeyOperations.ContractId,
TextKeyOperations,
] = TextKeyOperations.COMPANION
implicit val callablePayoutCompanion: ContractCompanion.WithoutKey[
CallablePayout.Contract,
CallablePayout.ContractId,
CallablePayout,
] = CallablePayout.COMPANION
implicit val delegatedCompanion: ContractCompanion.WithKey[
Delegated.Contract,
Delegated.ContractId,
Delegated,
types.Tuple2[String, String],
] = Delegated.COMPANION
implicit val delegationCompanion
: ContractCompanion.WithoutKey[Delegation.Contract, Delegation.ContractId, Delegation] =
Delegation.COMPANION
implicit val showDelegatedCompanion: ContractCompanion.WithoutKey[
ShowDelegated.Contract,
ShowDelegated.ContractId,
ShowDelegated,
] = ShowDelegated.COMPANION
implicit val maintainerNotSignatoryCompanion: ContractCompanion.WithKey[
MaintainerNotSignatory.Contract,
MaintainerNotSignatory.ContractId,
MaintainerNotSignatory,
String,
] = MaintainerNotSignatory.COMPANION
implicit val testWitnessesCompanion: ContractCompanion.WithoutKey[
TestWitnesses.Contract,
TestWitnesses.ContractId,
TestWitnesses,
] = TestWitnesses.COMPANION
implicit val divulgence1Companion
: ContractCompanion.WithoutKey[Divulgence1.Contract, Divulgence1.ContractId, Divulgence1] =
Divulgence1.COMPANION
implicit val divulgence2Companion
: ContractCompanion.WithoutKey[Divulgence2.Contract, Divulgence2.ContractId, Divulgence2] =
Divulgence2.COMPANION
implicit val localKeyVisibilityOperationsCompanion: ContractCompanion.WithoutKey[
LocalKeyVisibilityOperations.Contract,
LocalKeyVisibilityOperations.ContractId,
LocalKeyVisibilityOperations,
] = LocalKeyVisibilityOperations.COMPANION
implicit val withKeyCompanion: ContractCompanion.WithKey[
test.WithKey.Contract,
test.WithKey.ContractId,
test.WithKey,
String,
] = test.WithKey.COMPANION
implicit val semanticTestsIouCompanion: ContractCompanion.WithoutKey[
semantictests.Iou.Contract,
semantictests.Iou.ContractId,
semantictests.Iou,
] = semantictests.Iou.COMPANION
implicit val iouCompanion: ContractCompanion.WithoutKey[Iou.Contract, Iou.ContractId, Iou] =
Iou.COMPANION
implicit val agreementFactoryCompanion: ContractCompanion.WithoutKey[
AgreementFactory.Contract,
AgreementFactory.ContractId,
AgreementFactory,
] = AgreementFactory.COMPANION
implicit val agreementCompanion
: ContractCompanion.WithoutKey[Agreement.Contract, Agreement.ContractId, Agreement] =
Agreement.COMPANION
}

View File

@ -1,195 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.ledger.api.SubmissionIdGenerator
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions.assertDefined
import com.daml.ledger.api.testtool.infrastructure.{LedgerTestSuite, WithTimeout}
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.testtool.suites.v1_8.CompletionDeduplicationInfoIT._
import com.daml.ledger.api.v1.command_service.SubmitAndWaitRequest
import com.daml.ledger.api.v1.command_submission_service.SubmitRequest
import com.daml.ledger.api.v1.completion.Completion
import com.daml.ledger.api.v1.ledger_offset.LedgerOffset
import com.daml.ledger.javaapi.data.{Command, Party}
import com.daml.ledger.test.java.model.test.Dummy
import com.daml.lf.data.Ref
import com.daml.lf.data.Ref.SubmissionId
import io.grpc.Status
import java.util.{List => JList}
import scala.concurrent.duration.DurationInt
import scala.concurrent.{ExecutionContext, Future}
final class CompletionDeduplicationInfoIT[ServiceRequest](
service: Service[ServiceRequest]
) extends LedgerTestSuite {
private val serviceName: String = service.productPrefix
override def name = super.name + serviceName
test(
shortIdentifier = s"CCDIIncludeDedupInfo$serviceName",
description = s"Deduplication information is preserved in completions ($serviceName)",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
val requestWithoutSubmissionId = service.buildRequest(ledger, party)
val requestWithSubmissionId = service.buildRequest(ledger, party, Some(RandomSubmissionId))
for {
optNoDeduplicationSubmittedCompletion <- service.submitRequest(
ledger,
party,
requestWithoutSubmissionId,
)
optSubmissionIdSubmittedCompletion <- service
.submitRequest(ledger, party, requestWithSubmissionId)
} yield {
assertApplicationIdIsPreserved(ledger.applicationId, optNoDeduplicationSubmittedCompletion)
service.assertCompletion(optNoDeduplicationSubmittedCompletion)
assertDeduplicationPeriodIsReported(optNoDeduplicationSubmittedCompletion)
assertSubmissionIdIsPreserved(optSubmissionIdSubmittedCompletion, RandomSubmissionId)
}
})
}
private[testtool] object CompletionDeduplicationInfoIT {
private[testtool] sealed trait Service[ProtoRequestType] extends Serializable with Product {
def buildRequest(
ledger: ParticipantTestContext,
party: Party,
optSubmissionId: Option[Ref.SubmissionId] = None,
): ProtoRequestType
def submitRequest(
ledger: ParticipantTestContext,
party: Party,
request: ProtoRequestType,
)(implicit ec: ExecutionContext): Future[Option[Completion]]
def assertCompletion(optCompletion: Option[Completion]): Unit
}
case object CommandService extends Service[SubmitAndWaitRequest] {
override def buildRequest(
ledger: ParticipantTestContext,
party: Party,
optSubmissionId: Option[SubmissionId],
): SubmitAndWaitRequest = {
val request = ledger.submitAndWaitRequest(party, simpleCreate(party))
optSubmissionId
.map { submissionId =>
request.update(_.commands.submissionId := submissionId)
}
.getOrElse(request)
}
override def submitRequest(
ledger: ParticipantTestContext,
party: Party,
request: SubmitAndWaitRequest,
)(implicit ec: ExecutionContext): Future[Option[Completion]] =
for {
offset <- ledger.currentEnd()
_ <- ledger.submitAndWait(request)
completion <- singleCompletionAfterOffset(ledger, party, offset)
} yield completion
override def assertCompletion(optCompletion: Option[Completion]): Unit = {
val completion = assertDefined(optCompletion, "No completion has been produced")
assert(completion.status.forall(_.code == Status.Code.OK.value()))
assert(
Ref.SubmissionId.fromString(completion.submissionId).isRight,
"Missing or invalid submission ID in completion",
)
}
}
case object CommandSubmissionService extends Service[SubmitRequest] {
override def buildRequest(
ledger: ParticipantTestContext,
party: Party,
optSubmissionId: Option[SubmissionId],
): SubmitRequest = {
val request = ledger.submitRequest(party, simpleCreate(party))
optSubmissionId
.map { submissionId =>
request.update(_.commands.submissionId := submissionId)
}
.getOrElse(request)
}
override def submitRequest(
ledger: ParticipantTestContext,
party: Party,
request: SubmitRequest,
)(implicit ec: ExecutionContext): Future[Option[Completion]] =
for {
offset <- ledger.currentEnd()
_ <- ledger.submit(request)
completion <- singleCompletionAfterOffset(ledger, party, offset)
} yield completion
override def assertCompletion(optCompletion: Option[Completion]): Unit = {
val completion = assertDefined(optCompletion, "No completion has been produced")
assert(completion.status.forall(_.code == Status.Code.OK.value()))
}
}
private def singleCompletionAfterOffset(
ledger: ParticipantTestContext,
party: Party,
offset: LedgerOffset,
)(implicit ec: ExecutionContext): Future[Option[Completion]] =
WithTimeout(5.seconds)(
ledger
.findCompletion(ledger.completionStreamRequest(offset)(party))(_ => true)
.map(_.map(_.completion))
)
private def assertSubmissionIdIsPreserved(
optCompletion: Option[Completion],
requestedSubmissionId: Ref.SubmissionId,
): Unit = {
val submissionIdCompletion = assertDefined(optCompletion, "No completion has been produced")
val actualSubmissionId = submissionIdCompletion.submissionId
assert(submissionIdCompletion.status.forall(_.code == Status.Code.OK.value()))
assert(
actualSubmissionId == requestedSubmissionId,
"Wrong submission ID in completion, " +
s"expected: $requestedSubmissionId, actual: $actualSubmissionId",
)
}
private def assertDeduplicationPeriodIsReported(
optCompletion: Option[Completion]
): Unit = {
val completion = assertDefined(optCompletion, "No completion has been produced")
assert(completion.status.forall(_.code == Status.Code.OK.value()))
assert(completion.deduplicationPeriod.isDefined, "The deduplication period was not reported")
}
private def assertApplicationIdIsPreserved(
requestedApplicationId: String,
optCompletion: Option[Completion],
): Unit = {
val expectedApplicationId = requestedApplicationId
assertDefined(optCompletion, "No completion has been produced")
val applicationIdCompletion = optCompletion.get
assert(applicationIdCompletion.status.forall(_.code == Status.Code.OK.value()))
val actualApplicationId = applicationIdCompletion.applicationId
assert(
Ref.ApplicationId.fromString(actualApplicationId).contains(expectedApplicationId),
"Wrong application ID in completion, " +
s"expected: $expectedApplicationId, actual: $actualApplicationId",
)
}
private def simpleCreate(party: Party): JList[Command] = new Dummy(party.getValue).create.commands
private val RandomSubmissionId =
Ref.SubmissionId.assertFromString(SubmissionIdGenerator.Random.generate())
}

View File

@ -1,216 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.Synchronize.synchronize
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.api.v1.admin.config_management_service.{SetTimeModelRequest, TimeModel}
import com.google.protobuf.duration.Duration
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success}
final class ConfigManagementServiceIT extends LedgerTestSuite {
test(
"CMSetAndGetTimeModel",
"It should be able to get, set and restore the time model",
allocate(NoParties),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(ledger)) =>
val newTimeModel = TimeModel(
avgTransactionLatency = Some(Duration(0, 1)),
minSkew = Some(Duration(60, 0)),
maxSkew = Some(Duration(120, 0)),
)
for {
// Get the current time model
response1 <- ledger.getTimeModel()
oldTimeModel = {
assert(response1.timeModel.isDefined, "Expected time model to be defined")
response1.timeModel.get
}
// Set a new temporary time model
t1 <- ledger.time()
_ <- ledger.setTimeModel(
mrt = t1.plusSeconds(30),
generation = response1.configurationGeneration,
newTimeModel = newTimeModel,
)
// Retrieve the new model
response2 <- ledger.getTimeModel()
// Restore the original time model
t2 <- ledger.time()
_ <- ledger.setTimeModel(
mrt = t2.plusSeconds(30),
generation = response2.configurationGeneration,
newTimeModel = oldTimeModel,
)
// Verify that we've restored the original time model
response3 <- ledger.getTimeModel()
// Above operation finished on a timeout, but may still succeed asynchronously.
// Stabilize the ledger state before leaving the test case.
_ <- stabilize(ledger)
} yield {
assert(
response1.configurationGeneration < response2.configurationGeneration,
"Expected configuration generation to have increased after setting time model",
)
assert(
response2.configurationGeneration < response3.configurationGeneration,
"Expected configuration generation to have increased after setting time model the second time",
)
assert(response2.timeModel.contains(newTimeModel), "Setting the new time model failed")
assert(
response3.timeModel.equals(response1.timeModel),
"Restoring the original time model succeeded",
)
}
})
// TODO(JM): Test that sets the time model and verifies that a transaction with invalid
// ttl/mrt won't be accepted. Can only implement once ApiSubmissionService properly
// uses currently set configuration.
test(
"CMSetConflicting",
"Conflicting generation should be rejected",
allocate(NoParties),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(ledger)) =>
for {
// Get the current time model
response1 <- ledger.getTimeModel()
oldTimeModel = {
assert(response1.timeModel.isDefined, "Expected time model to be defined")
response1.timeModel.get
}
// Set a new time model with the next generation
t1 <- ledger.time()
_ <- ledger.setTimeModel(
mrt = t1.plusSeconds(30),
generation = response1.configurationGeneration,
newTimeModel = oldTimeModel,
)
// Set a new time model with the same generation
t2 <- ledger.time()
failure <- ledger
.setTimeModel(
mrt = t2.plusSeconds(30),
generation = response1.configurationGeneration,
newTimeModel = oldTimeModel,
)
.mustFail("setting Time Model with an outdated generation")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.InvalidArgument,
exceptionMessageSubstring = None,
)
}
})
test(
"CMConcurrentSetConflicting",
"Two concurrent conflicting generation should be rejected/accepted",
allocate(NoParties),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(ledger)) =>
for {
// Get the current time model
preUpdateTimeModelResponse <- ledger.getTimeModel()
oldTimeModel = {
assert(preUpdateTimeModelResponse.timeModel.isDefined, "Expected time model to be defined")
preUpdateTimeModelResponse.timeModel.get
}
// Set a new time model with the next generation in parallel
t1 <- ledger.time()
f1 = ledger.setTimeModel(
mrt = t1.plusSeconds(30),
generation = preUpdateTimeModelResponse.configurationGeneration,
newTimeModel = oldTimeModel,
)
f2 = ledger.setTimeModel(
mrt = t1.plusSeconds(30),
generation = preUpdateTimeModelResponse.configurationGeneration,
newTimeModel = oldTimeModel,
)
failure <- f1.transformWith {
case Failure(ex) =>
f2.map(_ => ex)
case Success(_) =>
f2.mustFail("setting Time Model with an outdated generation")
}
// Check if generation got updated (meaning, one of the above succeeded)
postUpdateTimeModelResponse <- ledger.getTimeModel()
} yield {
assert(
preUpdateTimeModelResponse.configurationGeneration + 1 == postUpdateTimeModelResponse.configurationGeneration,
s"New configuration's generation (${postUpdateTimeModelResponse.configurationGeneration} should be original configurations's generation (${preUpdateTimeModelResponse.configurationGeneration} + 1) )",
)
assertGrpcErrorOneOf(
failure,
LedgerApiErrors.Admin.ConfigurationEntryRejected,
LedgerApiErrors.RequestValidation.InvalidArgument,
)
}
})
test(
"CMDuplicateSubmissionId",
"Duplicate submission ids are accepted when config changed twice",
allocate(NoParties, NoParties),
runConcurrently = false,
)(implicit ec => { case Participants(Participant(alpha), Participant(beta)) =>
// Multiple config changes should never cause duplicates. Participant adds extra entropy to the
// submission id to ensure client does not inadvertently cause problems by poor selection
// of submission ids.
for {
(req1, req2) <- generateRequest(alpha).map(r =>
r -> r
.update(_.configurationGeneration := r.configurationGeneration + 1)
)
_ <- alpha.setTimeModel(req1)
_ <- synchronize(alpha, beta)
_ <- beta.setTimeModel(req2)
} yield ()
})
def generateRequest(
participant: ParticipantTestContext
)(implicit ec: ExecutionContext): Future[SetTimeModelRequest] =
for {
response <- participant.getTimeModel()
oldTimeModel = response.timeModel.getOrElse(
throw new AssertionError("Expected time model to be defined")
)
t1 <- participant.time()
req = participant.setTimeModelRequest(
mrt = t1.plusSeconds(30),
generation = response.configurationGeneration,
newTimeModel = oldTimeModel,
)
} yield req
// Stabilize the ledger by writing a new element and observing it in the indexDb.
// The allocateParty method fits the bill.
private def stabilize(ledger: ParticipantTestContext)(implicit
executionContext: ExecutionContext
): Future[Unit] =
ledger.allocateParty().map(_ => ())
}

View File

@ -1,226 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.error.ErrorCode
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions.{
assertErrorCode,
assertGrpcError,
fail,
}
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.participant.{Features, ParticipantTestContext}
import com.daml.ledger.api.testtool.suites.v1_8.ContractIdIT._
import com.daml.ledger.javaapi.data.{ContractId, DamlRecord, Party}
import com.daml.ledger.javaapi.data.codegen.ContractCompanion
import com.daml.ledger.test.java.semantic.contractidtests._
import io.grpc.StatusRuntimeException
import scala.concurrent.{ExecutionContext, Future}
import scala.util.{Failure, Success, Try}
// See `daml-lf/spec/contract-id.rst` for more information on contract ID formats.
// Check the Ledger API accepts or rejects non-suffixed contract ID.
// - Central committer ledger implementations (sandboxes, KV...) may accept non-suffixed CID
// - Distributed ledger implementations (e.g. Canton) must reject non-suffixed CID
final class ContractIdIT extends LedgerTestSuite {
implicit val contractCompanion
: ContractCompanion.WithoutKey[Contract.Contract$, Contract.ContractId, Contract] =
Contract.COMPANION
implicit val contractRefCompanion: ContractCompanion.WithKey[
ContractRef.Contract,
ContractRef.ContractId,
ContractRef,
String,
] = ContractRef.COMPANION
List(
// Support for v0 contract ids existed only in sandbox-classic in
// SDK 1.18 and older and has been dropped completely.
TestConfiguration(
description = "v0",
example = v0Cid,
accepted = false,
),
TestConfiguration(
description = "non-suffixed v1",
example = nonSuffixedV1Cid,
accepted = true,
isSupported = features => features.contractIds.v1.isNonSuffixed,
disabledReason = "non-suffixed V1 contract IDs are not supported",
failsInPreprocessing = true,
),
TestConfiguration(
description = "non-suffixed v1",
example = nonSuffixedV1Cid,
accepted = false,
isSupported = features => !features.contractIds.v1.isNonSuffixed,
disabledReason = "non-suffixed V1 contract IDs are supported",
failsInPreprocessing = true,
),
TestConfiguration(
description = "suffixed v1",
example = suffixedV1Cid,
accepted = true,
),
).foreach {
case TestConfiguration(
cidDescription,
example,
accepted,
isSupported,
disabledReason,
failsInPreprocessing,
) =>
val result = if (accepted) "Accept" else "Reject"
def test(
description: String,
parseErrorCode: ErrorCode = LedgerApiErrors.RequestValidation.InvalidArgument,
)(
update: ExecutionContext => (
ParticipantTestContext,
Party,
) => Future[Try[_]]
): Unit = {
super.test(
shortIdentifier = result + camelCase(cidDescription) + "Cid" + camelCase(description),
description = result + "s " + cidDescription + " Contract Id in " + description,
partyAllocation = allocate(SingleParty),
enabled = isSupported,
disabledReason = disabledReason,
)(implicit ec => { case Participants(Participant(alpha, party)) =>
update(ec)(alpha, party).map {
case Success(_) if accepted => ()
case Failure(err: Throwable) if !accepted =>
val (prefix, errorCode) =
if (failsInPreprocessing)
(
"Illegal Contract ID",
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
)
else
("cannot parse ContractId", parseErrorCode)
assertGrpcError(
err,
errorCode,
Some(s"""$prefix "$example""""),
checkDefiniteAnswerMetadata = true,
)
()
case otherwise =>
fail("Unexpected " + otherwise.fold(err => s"failure: $err", _ => "success"))
}
})
}
test("create payload") { implicit ec => (alpha, party) =>
alpha
.create(party, new ContractRef(party, new Contract.ContractId(example)))
.transformWith(Future.successful)
}
test("exercise target", parseErrorCode = LedgerApiErrors.RequestValidation.InvalidField) {
implicit ec => (alpha, party) =>
for {
contractCid <- alpha.create(party, new Contract(party))
result <-
alpha
.exercise(
party,
new ContractRef.ContractId(example).exerciseChange(contractCid),
)
.transformWith(Future.successful)
} yield result match {
case Failure(exception: StatusRuntimeException)
if Try(
assertErrorCode(
statusRuntimeException = exception,
expectedErrorCode = LedgerApiErrors.ConsistencyErrors.ContractNotFound,
)
).isSuccess =>
Success(())
case Success(_) => Failure(new UnknownError("Unexpected Success"))
case otherwise => otherwise.map(_ => ())
}
}
test("choice argument") { implicit ec => (alpha, party) =>
for {
contractCid <- alpha.create(party, new Contract(party))
contractRefCid <- alpha.create(party, new ContractRef(party, contractCid))
result <- alpha
.exercise(party, contractRefCid.exerciseChange(new Contract.ContractId(example)))
.transformWith(Future.successful)
} yield result
}
test("create-and-exercise payload") { implicit ec => (alpha, party) =>
for {
contractCid <- alpha.create(party, new Contract(party))
result <- alpha
.exercise(
party,
new ContractRef(party, new Contract.ContractId(example)).createAnd
.exerciseChange(contractCid),
)
.transformWith(Future.successful)
} yield result
}
test("create-and-exercise choice argument") { implicit ec => (alpha, party) =>
for {
contractCid <- alpha.create(party, new Contract(party))
result <- alpha
.exercise(
party,
new ContractRef(party, contractCid).createAnd
.exerciseChange(new Contract.ContractId(example)),
)
.transformWith(Future.successful)
} yield result
}
test("exercise by key") { implicit ec => (alpha, party) =>
for {
contractCid <- alpha.create(party, new Contract(party))
_ <- alpha.create(party, new ContractRef(party, contractCid))
result <- alpha
.exerciseByKey(
party,
ContractRef.TEMPLATE_ID,
party,
"Change",
new DamlRecord(
new DamlRecord.Field(new ContractId(example))
),
)
.transformWith(Future.successful)
} yield result
}
}
}
object ContractIdIT {
private val v0Cid = "#V0 Contract ID"
private val nonSuffixedV1Cid = (0 to 32).map("%02x".format(_)).mkString
private val suffixedV1Cid = (0 to 48).map("%02x".format(_)).mkString
private def camelCase(s: String): String =
s.split("[ -]").iterator.map(_.capitalize).mkString("")
final private case class TestConfiguration(
description: String,
example: String,
accepted: Boolean,
isSupported: Features => Boolean = _ => true,
disabledReason: String = "",
// Invalid v1 cids (e.g. no suffix when one is required) fail during command preprocessing
// while invalid v0 cids fail earlier.
failsInPreprocessing: Boolean = false,
)
}

View File

@ -1,569 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import java.util.regex.Pattern
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.Eventually.eventually
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.Synchronize.synchronize
import com.daml.ledger.api.testtool.infrastructure.TransactionHelpers._
import com.daml.ledger.api.v1.value.{RecordField, Value}
import com.daml.ledger.javaapi.data.{DamlRecord, Party, Text}
import com.daml.ledger.test.java.model.da.types.Tuple2
import com.daml.ledger.test.java.model
import com.daml.ledger.test.java.model.test.{CallablePayout, WithKeyDivulgenceHelper}
import scala.jdk.CollectionConverters._
import scala.jdk.OptionConverters._
final class ContractKeysIT extends LedgerTestSuite {
import CompanionImplicits._
test(
"CKNoContractKey",
"There should be no contract key if the template does not specify one",
allocate(SingleParty, SingleParty),
)(implicit ec => {
case Participants(Participant(alpha @ _, receiver), Participant(beta, giver)) =>
for {
_ <- beta.create(giver, new CallablePayout(giver, receiver))
transactions <- beta.flatTransactions(giver, receiver)
} yield {
val contract = assertSingleton("NoContractKey", transactions.flatMap(createdEvents))
assert(
contract.getContractKey.sum.isEmpty,
s"The key is not empty: ${contract.getContractKey}",
)
}
})
test(
"CKFetchOrLookup",
"Divulged contracts cannot be fetched or looked up by key by non-stakeholders",
allocate(SingleParty, SingleParty),
)(implicit ec => { case Participants(Participant(alpha, owner), Participant(beta, delegate)) =>
import model.test.{Delegated, Delegation, ShowDelegated}
val key = alpha.nextKeyId()
for {
// create contracts to work with
delegated <- alpha.create(owner, new Delegated(owner, key))
delegation <- alpha.create(owner, new Delegation(owner, delegate))
showDelegated <- alpha.create(owner, new ShowDelegated(owner, delegate))
// divulge the contract
_ <- alpha.exercise(owner, showDelegated.exerciseShowIt(delegated))
// fetch delegated
_ <- eventually("exerciseFetchDelegated") {
beta.exercise(delegate, delegation.exerciseFetchDelegated(delegated))
}
// fetch by key should fail during interpretation
// Reason: Only stakeholders see the result of fetchByKey, beta is neither stakeholder nor divulgee
fetchFailure <- beta
.exercise(delegate, delegation.exerciseFetchByKeyDelegated(owner, key))
.mustFail("fetching by key with a party that cannot see the contract")
// lookup by key delegation should fail during validation
// Reason: During command interpretation, the lookup did not find anything due to privacy rules,
// but validation determined that this result is wrong as the contract is there.
lookupByKeyFailure <- beta
.exercise(delegate, delegation.exerciseLookupByKeyDelegated(owner, key))
.mustFail("looking up by key with a party that cannot see the contract")
} yield {
assertGrpcError(
fetchFailure,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some("couldn't find key"),
)
assertGrpcErrorRegex(
lookupByKeyFailure,
LedgerApiErrors.ConsistencyErrors.InconsistentContractKey,
Some(Pattern.compile("Inconsistent|Contract key lookup with different results")),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CKNoFetchUndisclosed",
"Contract Keys should reject fetching an undisclosed contract",
allocate(SingleParty, SingleParty),
)(implicit ec => { case Participants(Participant(alpha, owner), Participant(beta, delegate)) =>
import model.test.{Delegated, Delegation}
val key = alpha.nextKeyId()
for {
// create contracts to work with
delegated <- alpha.create(owner, new Delegated(owner, key))
delegation <- alpha.create(owner, new Delegation(owner, delegate))
_ <- synchronize(alpha, beta)
// fetch should fail
// Reason: contract not divulged to beta
fetchFailure <- beta
.exercise(delegate, delegation.exerciseFetchDelegated(delegated))
.mustFail("fetching a contract with a party that cannot see it")
// fetch by key should fail
// Reason: Only stakeholders see the result of fetchByKey, beta is only a divulgee
fetchByKeyFailure <- beta
.exercise(delegate, delegation.exerciseFetchByKeyDelegated(owner, key))
.mustFail("fetching a contract by key with a party that cannot see it")
// lookup by key should fail
// Reason: During command interpretation, the lookup did not find anything due to privacy rules,
// but validation determined that this result is wrong as the contract is there.
lookupByKeyFailure <- beta
.exercise(delegate, delegation.exerciseLookupByKeyDelegated(owner, key))
.mustFail("looking up a contract by key with a party that cannot see it")
} yield {
assertGrpcError(
fetchFailure,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
Some("Contract could not be found"),
checkDefiniteAnswerMetadata = true,
)
assertGrpcError(
fetchByKeyFailure,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some("couldn't find key"),
)
assertGrpcErrorRegex(
lookupByKeyFailure,
LedgerApiErrors.ConsistencyErrors.InconsistentContractKey,
Some(Pattern.compile("Inconsistent|Contract key lookup with different results")),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CKMaintainerScoped",
"Contract keys should be scoped by maintainer",
allocate(SingleParty, SingleParty),
)(implicit ec => { case Participants(Participant(alpha, alice), Participant(beta, bob)) =>
import model.test.{MaintainerNotSignatory, TextKey, TextKeyOperations}
val key1 = alpha.nextKeyId()
val key2 = alpha.nextKeyId()
val unknownKey = alpha.nextKeyId()
for {
// create contracts to work with
tk1 <- alpha.create(alice, new TextKey(alice, key1, List(bob.getValue).asJava))
tk2 <- alpha.create(alice, new TextKey(alice, key2, List(bob.getValue).asJava))
aliceTKO <- alpha.create(alice, new TextKeyOperations(alice))
bobTKO <- beta.create(bob, new TextKeyOperations(bob))
_ <- synchronize(alpha, beta)
// creating a contract with a duplicate key should fail
duplicateKeyFailure <- alpha
.create(alice, new TextKey(alice, key1, List(bob.getValue).asJava))
.mustFail("creating a contract with a duplicate key")
// trying to lookup an unauthorized key should fail
bobLooksUpTextKeyFailure <- beta
.exercise(bob, bobTKO.exerciseTKOLookup(new Tuple2(alice, key1), Some(tk1).toJava))
.mustFail("looking up a contract with an unauthorized key")
// trying to lookup an unauthorized non-existing key should fail
bobLooksUpBogusTextKeyFailure <- beta
.exercise(bob, bobTKO.exerciseTKOLookup(new Tuple2(alice, unknownKey), None.toJava))
.mustFail("looking up a contract with an unauthorized, non-existing key")
// successful, authorized lookup
_ <- alpha.exercise(
alice,
aliceTKO.exerciseTKOLookup(new Tuple2(alice, key1), Some(tk1).toJava),
)
// successful fetch
_ <- alpha.exercise(alice, aliceTKO.exerciseTKOFetch(new Tuple2(alice, key1), tk1))
// successful, authorized lookup of non-existing key
_ <- alpha.exercise(
alice,
aliceTKO.exerciseTKOLookup(new Tuple2(alice, unknownKey), None.toJava),
)
// failing fetch
aliceFailedFetch <- alpha
.exercise(alice, aliceTKO.exerciseTKOFetch(new Tuple2(alice, unknownKey), tk1))
.mustFail("fetching a contract by an unknown key")
// now we exercise the contract, thus archiving it, and then verify
// that we cannot look it up anymore
_ <- alpha.exercise(alice, tk1.exerciseTextKeyChoice())
_ <- alpha.exercise(alice, aliceTKO.exerciseTKOLookup(new Tuple2(alice, key1), None.toJava))
// lookup the key, consume it, then verify we cannot look it up anymore
_ <- alpha.exercise(
alice,
aliceTKO.exerciseTKOConsumeAndLookup(tk2, new Tuple2(alice, key2)),
)
// failing create when a maintainer is not a signatory
maintainerNotSignatoryFailed <- alpha
.create(alice, new MaintainerNotSignatory(alice, bob))
.mustFail("creating a contract where a maintainer is not a signatory")
} yield {
assertGrpcErrorRegex(
duplicateKeyFailure,
LedgerApiErrors.ConsistencyErrors.DuplicateContractKey,
Some(Pattern.compile("Inconsistent|contract key is not unique")),
checkDefiniteAnswerMetadata = true,
)
assertGrpcError(
bobLooksUpTextKeyFailure,
LedgerApiErrors.CommandExecution.Interpreter.AuthorizationError,
Some("requires authorizers"),
checkDefiniteAnswerMetadata = true,
)
assertGrpcError(
bobLooksUpBogusTextKeyFailure,
LedgerApiErrors.CommandExecution.Interpreter.AuthorizationError,
Some("requires authorizers"),
checkDefiniteAnswerMetadata = true,
)
assertGrpcError(
aliceFailedFetch,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some("couldn't find key"),
checkDefiniteAnswerMetadata = true,
)
assertGrpcError(
maintainerNotSignatoryFailed,
LedgerApiErrors.CommandExecution.Interpreter.AuthorizationError,
Some("are not a subset of the signatories"),
checkDefiniteAnswerMetadata = true,
)
}
})
test("CKRecreate", "Contract keys can be recreated in single transaction", allocate(SingleParty))(
implicit ec => { case Participants(Participant(ledger, owner)) =>
import model.test.Delegated
val key = ledger.nextKeyId()
for {
delegated1TxTree <- ledger
.submitAndWaitForTransactionTree(
ledger.submitAndWaitRequest(owner, new Delegated(owner, key).create.commands)
)
.map(_.getTransaction)
delegated1Id = new Delegated.ContractId(
delegated1TxTree.eventsById.head._2.getCreated.contractId
)
delegated2TxTree <- ledger.exercise(owner, delegated1Id.exerciseRecreate())
} yield {
assert(delegated2TxTree.eventsById.size == 2)
val event = delegated2TxTree.eventsById.filter(_._2.kind.isCreated).head._2
assert(
delegated1Id.contractId != event.getCreated.contractId,
"New contract was not created",
)
assert(
event.getCreated.contractKey == delegated1TxTree.eventsById.head._2.getCreated.contractKey,
"Contract keys did not match",
)
}
}
)
test(
"CKTransients",
"Contract keys created by transient contracts are properly archived",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, owner)) =>
import model.test.{Delegated, Delegation}
val key = ledger.nextKeyId()
val key2 = ledger.nextKeyId()
for {
delegation <- ledger.create(owner, new Delegation(owner, owner))
delegated <- ledger.create(owner, new Delegated(owner, key))
failedFetch <- ledger
.exercise(owner, delegation.exerciseFetchByKeyDelegated(owner, key2))
.mustFail("fetching a contract with an unknown key")
// Create a transient contract with a key that is created and archived in same transaction.
_ <- ledger.exercise(owner, delegated.exerciseCreateAnotherAndArchive(key2))
// Try it again, expecting it to succeed.
_ <- ledger.exercise(owner, delegated.exerciseCreateAnotherAndArchive(key2))
} yield {
assertGrpcError(
failedFetch,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some("couldn't find key"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CKExposedByTemplate",
"The contract key should be exposed if the template specifies one",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import model.test.TextKey
val expectedKey = ledger.nextKeyId()
for {
_ <- ledger.create(party, new TextKey(party, expectedKey, List.empty.asJava))
transactions <- ledger.flatTransactions(party)
} yield {
val contract = assertSingleton("CKExposedByTemplate", transactions.flatMap(createdEvents))
assertEquals(
"CKExposedByTemplate",
contract.getContractKey.getRecord.fields,
Seq(
RecordField("_1", Some(Value(Value.Sum.Party(party.getValue)))),
RecordField("_2", Some(Value(Value.Sum.Text(expectedKey)))),
),
)
}
})
test(
"CKExerciseByKey",
"Exercising by key should be possible only when the corresponding contract is available",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
import model.test.TextKey
val keyString = ledger.nextKeyId()
val expectedKey = new DamlRecord(
new DamlRecord.Field("_1", new Party(party.getValue)),
new DamlRecord.Field("_2", new Text(keyString)),
)
for {
failureBeforeCreation <- ledger
.exerciseByKey(
party,
TextKey.TEMPLATE_ID,
expectedKey,
"TextKeyChoice",
new DamlRecord(),
)
.mustFail("exercising before creation")
_ <- ledger.create(party, new TextKey(party, keyString, List.empty.asJava))
_ <- ledger.exerciseByKey(
party,
TextKey.TEMPLATE_ID,
expectedKey,
"TextKeyChoice",
new DamlRecord(),
)
failureAfterConsuming <- ledger
.exerciseByKey(
party,
TextKey.TEMPLATE_ID,
expectedKey,
"TextKeyChoice",
new DamlRecord(),
)
.mustFail("exercising after consuming")
} yield {
assertGrpcError(
failureBeforeCreation,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some("dependency error: couldn't find key"),
checkDefiniteAnswerMetadata = true,
)
assertGrpcError(
failureAfterConsuming,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some("dependency error: couldn't find key"),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"CKLocalLookupByKeyVisibility",
"Visibility should not be checked for lookup-by-key of contracts created in the current transaction",
allocate(SingleParty, SingleParty),
)(implicit ec => {
case Participants(Participant(ledger1, party1), Participant(ledger2, party2)) =>
import model.test.LocalKeyVisibilityOperations
for {
ops: LocalKeyVisibilityOperations.ContractId <- ledger1.create(
party1,
new LocalKeyVisibilityOperations(party1, party2),
)
_ <- synchronize(ledger1, ledger2)
_ <- ledger2.exercise(party2, ops.exerciseLocalLookup())
} yield ()
})
test(
"CKLocalFetchByKeyVisibility",
"Visibility should not be checked for fetch-by-key of contracts created in the current transaction",
allocate(SingleParty, SingleParty),
)(implicit ec => {
case Participants(Participant(ledger1, party1), Participant(ledger2, party2)) =>
import model.test.LocalKeyVisibilityOperations
for {
ops: LocalKeyVisibilityOperations.ContractId <- ledger1.create(
party1,
new LocalKeyVisibilityOperations(party1, party2),
)
_ <- synchronize(ledger1, ledger2)
_ <- ledger2.exercise(party2, ops.exerciseLocalFetch())
} yield ()
})
test(
"CKDisclosedContractKeyReusabilityBasic",
"Subsequent disclosed contracts can use the same contract key",
allocate(SingleParty, SingleParty),
)(implicit ec => {
case Participants(Participant(ledger1, party1), Participant(ledger2, party2)) =>
import model.test.{WithKey, WithKeyCreator, WithKeyFetcher}
for {
// Create a helper contract and exercise a choice creating and disclosing a WithKey contract
creator1 <- ledger1.create(party1, new WithKeyCreator(party1, party2))(
WithKeyCreator.COMPANION
)
withKey1 <- ledger1
.exerciseAndGetContract[WithKey.ContractId, WithKey](
party1,
creator1.exerciseWithKeyCreator_DiscloseCreate(party1),
)
// Verify that the withKey1 contract is usable by the party2
fetcher <- ledger1.create(party1, new WithKeyFetcher(party1, party2))(
WithKeyFetcher.COMPANION
)
_ <- synchronize(ledger1, ledger2)
_ <- ledger2.exercise(party2, fetcher.exerciseWithKeyFetcher_Fetch(withKey1))
// Archive the disclosed contract
_ <- ledger1.exercise(party1, withKey1.exerciseArchive())
_ <- synchronize(ledger1, ledger2)
// Verify that fetching the contract is no longer possible after it was archived
_ <- ledger2
.exercise(party2, fetcher.exerciseWithKeyFetcher_Fetch(withKey1))
.mustFail("fetching an archived contract")
// Repeat the same steps for the second time
creator2 <- ledger1.create(party1, new WithKeyCreator(party1, party2))(
WithKeyCreator.COMPANION
)
_ <- ledger1.exerciseAndGetContract[model.test.WithKey.ContractId, model.test.WithKey](
party1,
creator2.exerciseWithKeyCreator_DiscloseCreate(party1),
)
// Synchronize to verify that the second participant is working
_ <- synchronize(ledger1, ledger2)
} yield ()
})
test(
"CKDisclosedContractKeyReusabilityAsSubmitter",
"Subsequent disclosed contracts can use the same contract key (disclosure because of submitting)",
allocate(SingleParty, SingleParty),
)(implicit ec => {
case Participants(Participant(ledger1, party1), Participant(ledger2, party2)) =>
import model.test.{WithKey, WithKeyCreatorAlternative}
for {
// Create a helper contract and exercise a choice creating and disclosing a WithKey contract
creator1: WithKeyCreatorAlternative.ContractId <- ledger1.create(
party1,
new WithKeyCreatorAlternative(party1, party2),
)(WithKeyCreatorAlternative.COMPANION)
_ <- synchronize(ledger1, ledger2)
_ <- ledger2.exercise(
party2,
creator1.exerciseWithKeyCreatorAlternative_DiscloseCreate(),
)
_ <- synchronize(ledger1, ledger2)
Seq(withKey1Event) <- ledger1.activeContractsByTemplateId(List(WithKey.TEMPLATE_ID), party1)
withKey1 = new WithKey.ContractId(withKey1Event.contractId)
// Archive the disclosed contract
_ <- ledger1.exercise(party1, withKey1.exerciseArchive())
_ <- synchronize(ledger1, ledger2)
// Repeat the same steps for the second time
_ <- ledger2.exercise(
party2,
creator1.exerciseWithKeyCreatorAlternative_DiscloseCreate(),
)
_ <- synchronize(ledger1, ledger2)
} yield ()
})
test(
"CKGlocalKeyVisibility",
"Contract keys should be visible",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
for {
// create contracts to work with
cid <- ledger.create(alice, new model.test.WithKey(alice))
// double check its key can be found if visible
_ <- ledger.submit(
ledger.submitRequest(
alice,
model.test.WithKey.byKey(alice).exerciseWithKey_NoOp(alice).commands,
)
)
// divulge the contract
helper <- ledger.create(bob, new model.test.WithKeyDivulgenceHelper(bob, alice))(
WithKeyDivulgenceHelper.COMPANION
)
_ <- ledger.exercise(alice, helper.exerciseWithKeyDivulgenceHelper_Fetch(cid))
// double check it is properly divulged
_ <- ledger.exercise(bob, cid.exerciseWithKey_NoOp(bob))
request = ledger.submitRequest(
bob,
// exercise by key the contract
model.test.WithKey.byKey(alice).exerciseWithKey_NoOp(bob).commands,
)
failure1 <- ledger.submit(request).mustFail("exercise of a non visible key")
request = ledger.submitRequest(
bob,
// bring the contract in the engine cache
(cid.exerciseWithKey_NoOp(bob).commands.asScala ++
// exercise by key the contract
model.test.WithKey.byKey(alice).exerciseWithKey_NoOp(bob).commands.asScala).asJava,
)
failure2 <- ledger.submit(request).mustFail("exercise of a non visible key")
} yield {
List(failure1, failure2).foreach { failure =>
assertGrpcError(
failure,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some("couldn't find key"),
checkDefiniteAnswerMetadata = true,
)
}
}
})
}

View File

@ -1,220 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.error.ErrorCode
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.javaapi
import com.daml.ledger.javaapi.data.Party
import com.daml.ledger.javaapi.data.codegen.{ContractCompanion, Update}
import com.daml.ledger.test.java.semantic.deeplynestedvalue._
import scala.annotation.tailrec
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Success
final class DeeplyNestedValueIT extends LedgerTestSuite {
implicit val handlerCompanion
: ContractCompanion.WithoutKey[Handler.Contract, Handler.ContractId, Handler] =
Handler.COMPANION
@tailrec
private[this] def toNat(i: Long, acc: Nat = new nat.Z(javaapi.data.Unit.getInstance)): Nat =
if (i == 0) acc else toNat(i - 1, new nat.S(acc))
private[this] def waitForTransactionId(
alpha: ParticipantTestContext,
party: Party,
command: Update[_],
)(implicit
ec: ExecutionContext
): Future[Either[Throwable, String]] =
alpha
.submitAndWaitForTransactionId(
alpha.submitAndWaitRequest(party, command.commands)
)
.transform(x => Success(x.map(_.transactionId).toEither))
private[this] def camlCase(s: String) =
s.split(" ").iterator.map(_.capitalize).mkString("")
List[Long](46, 100, 101, 110, 200).foreach { nesting =>
val accepted = nesting <= 100
val result = if (accepted) "Accept" else "Reject"
// Once converted to Nat, `n` will have a nesting `nesting`.
// Note that Nat.Z(()) has nesting 1.
val n = nesting - 1
// Choice argument are always wrapped in a record
val nChoiceArgument = n - 1
// The nesting of the payload of a `Contract` is one more than the nat it contains
val nContract = n - 1
// The nesting of the key of a `ContractWithKey` is one more than the nat it contains
val nKey = n - 1
def test[T](description: String, errorCodeIfExpected: ErrorCode)(
update: ExecutionContext => (
ParticipantTestContext,
Party,
) => Future[Either[Throwable, T]]
): Unit =
super.test(
result + camlCase(description) + nesting.toString,
s"${result.toLowerCase}s $description with a nesting of $nesting",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(alpha, party)) =>
update(ec)(alpha, party).map {
case Right(_) if accepted => ()
case Left(err: Throwable) if !accepted =>
assertGrpcError(
err,
errorCodeIfExpected,
None,
checkDefiniteAnswerMetadata = true,
)
case otherwise =>
fail("Unexpected " + otherwise.fold(err => s"failure: $err", _ => "success"))
}
})
test(
"create command",
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
) { implicit ec => (alpha, party) =>
waitForTransactionId(alpha, party, new Contract(party, nContract, toNat(nContract)).create)
}
test(
"exercise command",
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
) { implicit ec => (alpha, party) =>
for {
handler: Handler.ContractId <- alpha.create(party, new Handler(party))
result <- waitForTransactionId(
alpha,
party,
handler.exerciseDestruct(toNat(nChoiceArgument)),
)
} yield result
}
test(
"create argument in CreateAndExercise command",
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
) { implicit ec => (alpha, party) =>
waitForTransactionId(
alpha,
party,
new Contract(party, nContract, toNat(nContract)).createAnd
.exerciseArchive(),
)
}
test(
"choice argument in CreateAndExercise command",
LedgerApiErrors.CommandExecution.Preprocessing.PreprocessingFailed,
) { implicit ec => (alpha, party) =>
waitForTransactionId(
alpha,
party,
new Handler(party).createAnd.exerciseDestruct(toNat(nChoiceArgument)),
)
}
test(
"exercise argument",
LedgerApiErrors.CommandExecution.Interpreter.ValueNesting,
) { implicit ec => (alpha, party) =>
for {
handler: Handler.ContractId <- alpha.create(party, new Handler(party))
result <-
waitForTransactionId(
alpha,
party,
handler.exerciseConstructThenDestruct(nChoiceArgument),
)
} yield result
}
test(
"exercise output",
LedgerApiErrors.CommandExecution.Interpreter.ValueNesting,
) { implicit ec => (alpha, party) =>
for {
handler: Handler.ContractId <- alpha.create(party, new Handler(party))
result <-
waitForTransactionId(alpha, party, handler.exerciseConstruct(n))
} yield result
}
test(
"create argument",
LedgerApiErrors.CommandExecution.Interpreter.ValueNesting,
) { implicit ec => (alpha, party) =>
for {
handler: Handler.ContractId <- alpha.create(party, new Handler(party))
result <- waitForTransactionId(alpha, party, handler.exerciseCreate(nContract))
} yield result
}
test(
"contract key",
LedgerApiErrors.CommandExecution.Interpreter.ValueNesting,
) { implicit ec => (alpha, party) =>
for {
handler: Handler.ContractId <- alpha.create(party, new Handler(party))
result <- waitForTransactionId(alpha, party, handler.exerciseCreateKey(nKey))
} yield result
}
if (accepted) {
// Because we cannot create contracts with nesting > 100,
// it does not make sense to test fetch of those kinds of contracts.
test(
"fetch by key",
LedgerApiErrors.CommandExecution.Interpreter.ValueNesting,
) { implicit ec => (alpha, party) =>
for {
handler: Handler.ContractId <- alpha.create(party, new Handler(party))
_ <- alpha.exercise(party, handler.exerciseCreateKey(nKey))
result <- waitForTransactionId(alpha, party, handler.exerciseFetchByKey(nKey))
} yield result
}
}
test(
"failing lookup by key",
LedgerApiErrors.CommandExecution.Interpreter.ValueNesting,
) { implicit ec => (alpha, party) =>
for {
handler: Handler.ContractId <- alpha.create(party, new Handler(party))
result <- waitForTransactionId(alpha, party, handler.exerciseLookupByKey(nKey))
} yield result
}
if (accepted) {
// Because we cannot create contracts with key nesting > 100,
// it does not make sens to test successful lookup for those keys.
test(
"successful lookup by key",
LedgerApiErrors.CommandExecution.Interpreter.ValueNesting,
) { implicit ec => (alpha, party) =>
for {
handler: Handler.ContractId <- alpha.create(party, new Handler(party))
_ <- alpha.exercise(party, handler.exerciseCreateKey(nKey))
result <-
waitForTransactionId(alpha, party, handler.exerciseLookupByKey(nKey))
} yield result
}
}
}
}

View File

@ -1,270 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.Synchronize.{synchronize, waitForContract}
import com.daml.ledger.test.java.model.test.{
Asset,
Divulgence1,
Divulgence2,
Proposal,
WithKey,
WithKeyDivulgenceHelper,
}
final class DivulgenceIT extends LedgerTestSuite {
import CompanionImplicits._
test(
"DivulgenceTx",
"Divulged contracts should not be exposed by the transaction service",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
for {
divulgence1 <- ledger.create(alice, new Divulgence1(alice))
divulgence2 <- ledger.create(bob, new Divulgence2(bob, alice))
_ <- ledger.exercise(alice, divulgence2.exerciseDivulgence2Archive(divulgence1))
bobTransactions <- ledger.flatTransactions(bob)
bobTrees <- ledger.transactionTrees(bob)
transactionsForBoth <- ledger.flatTransactions(alice, bob)
} yield {
// Inspecting the flat transaction stream as seen by Bob
// We expect only one transaction containing only one create event for Divulgence2.
// We expect to _not_ see the create or archive for Divulgence1, even if Divulgence1 was divulged
// to Bob, and even if the exercise is visible to Bob in the transaction trees.
assert(
bobTransactions.size == 1,
s"${bob.getValue} should see exactly one transaction but sees ${bobTransactions.size} instead",
)
val events = bobTransactions.head.events
assert(
events.size == 1,
s"The transaction should contain exactly one event but contains ${events.size} instead",
)
val event = events.head.event
assert(
event.isCreated,
s"The only event in the transaction was expected to be a created event",
)
val contractId = event.created.get.contractId
assert(
contractId == divulgence2.contractId,
s"The only visible event should be the creation of the second contract (expected $divulgence2, got $contractId instead)",
)
// Inspecting the transaction trees as seen by Bob
// then what we expect for Bob's tree transactions. note that here we witness the exercise that
// caused the archive of div1Cid, even if we did _not_ see the archive event in the flat transaction
// stream above
// We expect to see two transactions: one for the second create and one for the exercise.
assert(
bobTrees.size == 2,
s"$bob should see exactly two transaction trees but sees ${bobTrees.size} instead",
)
val createDivulgence2Transaction = bobTrees(0)
assert(
createDivulgence2Transaction.rootEventIds.size == 1,
s"The transaction that creates Divulgence2 should contain exactly one root event, but it contains ${createDivulgence2Transaction.rootEventIds.size} instead",
)
val createDivulgence2 =
createDivulgence2Transaction.eventsById(createDivulgence2Transaction.rootEventIds.head)
assert(
createDivulgence2.kind.isCreated,
s"Event expected to be a create",
)
val createDivulgence2ContractId = createDivulgence2.getCreated.contractId
assert(
createDivulgence2ContractId == divulgence2.contractId,
s"The event where Divulgence2 is created should have the same contract identifier as the created contract (expected $divulgence2, got $createDivulgence2ContractId instead)",
)
val exerciseOnDivulgence2Transaction = bobTrees(1)
assert(
exerciseOnDivulgence2Transaction.rootEventIds.size == 1,
s"The transaction where a choice is exercised on Divulgence2 should contain exactly one root event contains ${exerciseOnDivulgence2Transaction.rootEventIds.size} instead",
)
val exerciseOnDivulgence2 = exerciseOnDivulgence2Transaction.eventsById(
exerciseOnDivulgence2Transaction.rootEventIds.head
)
assert(
exerciseOnDivulgence2.kind.isExercised,
s"Expected event to be an exercise",
)
assert(exerciseOnDivulgence2.getExercised.contractId == divulgence2.contractId)
assert(exerciseOnDivulgence2.getExercised.childEventIds.size == 1)
val exerciseOnDivulgence1 =
exerciseOnDivulgence2Transaction.eventsById(
exerciseOnDivulgence2.getExercised.childEventIds.head
)
assert(exerciseOnDivulgence1.kind.isExercised)
assert(exerciseOnDivulgence1.getExercised.contractId == divulgence1.contractId)
assert(exerciseOnDivulgence1.getExercised.childEventIds.isEmpty)
// Alice should see:
// - create Divulgence1
// - create Divulgence2
// - archive Divulgence1
// Note that we do _not_ see the exercise of Divulgence2 because it is nonconsuming.
assert(
transactionsForBoth.size == 3,
s"Filtering for both $alice and $bob should result in three transactions seen but ${transactionsForBoth.size} are seen instead",
)
val firstTransactionForBoth = transactionsForBoth.head
assert(
firstTransactionForBoth.events.size == 1,
s"The first transaction seen by filtering for both $alice and $bob should contain exactly one event but it contains ${firstTransactionForBoth.events.size} events instead",
)
val firstEventForBoth = transactionsForBoth.head.events.head.event
assert(
firstEventForBoth.isCreated,
s"The first event seen by filtering for both $alice and $bob was expected to be a creation",
)
val firstCreationForBoth = firstEventForBoth.created.get
assert(
firstCreationForBoth.contractId == divulgence1.contractId,
s"The creation seen by filtering for both $alice and $bob was expected to be $divulgence1 but is ${firstCreationForBoth.contractId} instead",
)
assert(
firstCreationForBoth.witnessParties == Seq(alice.getValue),
s"The creation seen by filtering for both $alice and $bob was expected to be witnessed by $alice but is instead ${firstCreationForBoth.witnessParties}",
)
}
})
test(
"DivulgenceAcs",
"Divulged contracts should not be exposed by the active contract service",
allocate(TwoParties),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
for {
divulgence1 <- ledger.create(alice, new Divulgence1(alice))
divulgence2 <- ledger.create(bob, new Divulgence2(bob, alice))
_ <- ledger.exercise(alice, divulgence2.exerciseDivulgence2Fetch(divulgence1))
activeForBobOnly <- ledger.activeContracts(bob)
activeForBoth <- ledger.activeContracts(alice, bob)
} yield {
// Bob only sees Divulgence2
assert(
activeForBobOnly.size == 1,
s"$bob should see only one active contract but sees ${activeForBobOnly.size} instead",
)
assert(
activeForBobOnly.head.contractId == divulgence2.contractId,
s"$bob should see $divulgence2 but sees ${activeForBobOnly.head.contractId} instead",
)
// Since we're filtering for Bob only Bob will be the only reported witness even if Alice sees the contract
assert(
activeForBobOnly.head.witnessParties == Seq(bob.getValue),
s"The witness parties as seen by $bob should only include him but it is instead ${activeForBobOnly.head.witnessParties}",
)
// Alice sees both
assert(
activeForBoth.size == 2,
s"The active contracts as seen by $alice and $bob should be two but are ${activeForBoth.size} instead",
)
val divulgence1ContractId = divulgence1.contractId
val divulgence2ContractId = divulgence2.contractId
val activeForBothContractIds = activeForBoth.map(_.contractId).sorted
val expectedContractIds = Seq(divulgence1ContractId, divulgence2ContractId).sorted
assert(
activeForBothContractIds == expectedContractIds,
s"$divulgence1 and $divulgence2 are expected to be seen when filtering for $alice and $bob but instead the following contract identifiers are seen: $activeForBothContractIds",
)
val divulgence1Witnesses =
activeForBoth.find(_.contractId == divulgence1ContractId).get.witnessParties.sorted
val divulgence2Witnesses =
activeForBoth.find(_.contractId == divulgence2ContractId).get.witnessParties.sorted
assert(
divulgence1Witnesses == Seq(alice.getValue),
s"The witness parties of the first contract should only include $alice but it is instead $divulgence1Witnesses ($bob)",
)
assert(
divulgence2Witnesses == Seq(alice, bob).map(_.getValue).sorted,
s"The witness parties of the second contract should include $alice and $bob but it is instead $divulgence2Witnesses",
)
}
})
test(
"DivulgenceKeys",
"Divulgence should behave as expected in a workflow involving keys",
allocate(SingleParty, SingleParty),
)(implicit ec => { case Participants(Participant(alpha, proposer), Participant(beta, owner)) =>
for {
offer <- alpha.create(proposer, new Proposal(proposer, owner))(Proposal.COMPANION)
asset <- beta.create(owner, new Asset(owner, owner))(Asset.COMPANION)
_ <- waitForContract(beta, owner, offer)
_ <- beta.exercise(owner, offer.exerciseProposalAccept(asset))
} yield {
// nothing to test, if the workflow ends successfully the test is considered successful
}
})
test(
"DivulgenceDivulgeAfterArchival",
"Divulging, archiving and divulging again a contract with key should be possible",
allocate(SingleParty, SingleParty),
)(implicit ec => { case Participants(Participant(alpha, partyA), Participant(beta, partyB)) =>
for {
// Create a helper contract where partyB is a signatory
helper <- beta.create(
partyB,
new WithKeyDivulgenceHelper(partyB, partyA),
)(WithKeyDivulgenceHelper.COMPANION)
_ <- synchronize(alpha, beta)
// Create a WithKey contract owned by the partyA
withKey1 <- alpha.create(partyA, new WithKey(partyA))
// Divulge the withKey1 contract
_ <- alpha.exercise(partyA, helper.exerciseWithKeyDivulgenceHelper_Fetch(withKey1))
_ <- synchronize(alpha, beta)
// Archive the withKey1 contract
_ <- alpha.exercise(partyA, withKey1.exerciseArchive())
_ <- synchronize(alpha, beta)
// Create another WithKey contract with the same key as previously
withKey2 <- alpha.create(partyA, new WithKey(partyA))
// Divulge the withKey2 contract
_ <- alpha.exercise(partyA, helper.exerciseWithKeyDivulgenceHelper_Fetch(withKey2))
// Synchronize to make sure that both participant are functional
_ <- synchronize(alpha, beta)
} yield ()
})
}

View File

@ -1,32 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import io.grpc.health.v1.health.HealthCheckResponse
class HealthServiceIT extends LedgerTestSuite {
test("HScheck", "The Health.Check endpoint reports everything is well", allocate(NoParties))(
implicit ec => { case Participants(Participant(ledger)) =>
for {
health <- ledger.checkHealth()
} yield {
assertEquals("HSisServing", health.status, HealthCheckResponse.ServingStatus.SERVING)
}
}
)
test("HSwatch", "The Health.Watch endpoint reports everything is well", allocate(NoParties))(
implicit ec => { case Participants(Participant(ledger)) =>
for {
healthSeq <- ledger.watchHealth()
} yield {
val health = assertSingleton("HScontinuesToServe", healthSeq)
assert(health.status == HealthCheckResponse.ServingStatus.SERVING)
}
}
)
}

View File

@ -1,23 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import scala.concurrent.Future
final class IdentityIT extends LedgerTestSuite {
test(
"IdNotEmpty",
"A ledger should return a non-empty string as its identity",
allocate(NoParties),
) { implicit ec =>
{ case Participants(Participant(ledger)) =>
Future {
assert(ledger.ledgerId.nonEmpty, "The returned ledger identifier was empty")
}
}
}
}

View File

@ -1,423 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.error.ErrorCode
import com.daml.error.definitions.LedgerApiErrors
import java.util.UUID
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.v1.admin.identity_provider_config_service.{
CreateIdentityProviderConfigRequest,
DeleteIdentityProviderConfigRequest,
DeleteIdentityProviderConfigResponse,
GetIdentityProviderConfigRequest,
IdentityProviderConfig,
UpdateIdentityProviderConfigRequest,
}
import com.google.protobuf.field_mask.FieldMask
import scala.concurrent.Future
class IdentityProviderConfigServiceIT extends UserManagementServiceITBase {
test(
"CreateConfigInvalidArguments",
"Test argument validation for IdentityProviderConfigService#CreateIdentityProviderConfig",
allocate(NoParties),
enabled = _.userManagement.supported,
disabledReason = "requires user management feature",
)(implicit ec => { case Participants(Participant(ledger)) =>
def createAndCheck(
problem: String,
expectedErrorCode: ErrorCode,
identityProviderId: String = UUID.randomUUID().toString,
isDeactivated: Boolean = false,
issuer: String = UUID.randomUUID().toString,
jwksUrl: String = "http://daml.com/jwks.json",
): Future[Unit] = ledger
.createIdentityProviderConfig(
identityProviderId,
isDeactivated,
issuer,
jwksUrl,
)
.mustFailWith(context = problem, expectedErrorCode)
for {
_ <- createAndCheck(
"empty identity_provider_id",
LedgerApiErrors.RequestValidation.MissingField,
identityProviderId = "",
)
_ <- createAndCheck(
"invalid identity_provider_id",
LedgerApiErrors.RequestValidation.InvalidField,
identityProviderId = "!@",
)
_ <- createAndCheck(
"empty issuer",
LedgerApiErrors.RequestValidation.MissingField,
issuer = "",
)
_ <- createAndCheck(
"empty jwks_url",
LedgerApiErrors.RequestValidation.MissingField,
jwksUrl = "",
)
_ <- createAndCheck(
"non valid jwks_url",
LedgerApiErrors.RequestValidation.InvalidField,
jwksUrl = "url.com",
)
_ <- ledger
.createIdentityProviderConfig(CreateIdentityProviderConfigRequest(None))
.mustFailWith(
context = "empty identity_provider_config",
LedgerApiErrors.RequestValidation.MissingField,
)
} yield ()
})
test(
"GetConfigInvalidArguments",
"Test argument validation for IdentityProviderConfigService#GetIdentityProviderConfig",
allocate(NoParties),
enabled = _.userManagement.supported,
disabledReason = "requires user management feature",
)(implicit ec => { case Participants(Participant(ledger)) =>
def createAndCheck(
problem: String,
expectedErrorCode: ErrorCode,
identityProviderId: String,
): Future[Unit] = ledger
.getIdentityProviderConfig(
GetIdentityProviderConfigRequest(identityProviderId)
)
.mustFailWith(context = problem, expectedErrorCode)
for {
_ <- createAndCheck(
"empty identity_provider_id",
LedgerApiErrors.RequestValidation.MissingField,
identityProviderId = "",
)
_ <- createAndCheck(
"invalid identity_provider_id",
LedgerApiErrors.RequestValidation.InvalidField,
identityProviderId = "!@",
)
} yield ()
})
test(
"UpdateConfigInvalidArguments",
"Test argument validation for IdentityProviderConfigService#UpdateIdentityProviderConfig",
allocate(NoParties),
enabled = _.userManagement.supported,
disabledReason = "requires user management feature",
)(implicit ec => { case Participants(Participant(ledger)) =>
def createAndCheck(
problem: String,
expectedErrorCode: ErrorCode,
identityProviderId: String = UUID.randomUUID().toString,
isDeactivated: Boolean = false,
issuer: String = UUID.randomUUID().toString,
jwksUrl: String = "http://daml.com/jwks.json",
updateMask: Option[FieldMask] = Some(FieldMask(Seq("is_deactivated"))),
): Future[Unit] = ledger
.updateIdentityProviderConfig(
identityProviderId,
isDeactivated,
issuer,
jwksUrl,
updateMask,
)
.mustFailWith(context = problem, expectedErrorCode)
for {
_ <- createAndCheck(
"empty identity_provider_id",
LedgerApiErrors.RequestValidation.MissingField,
identityProviderId = "",
)
_ <- createAndCheck(
"invalid identity_provider_id",
LedgerApiErrors.RequestValidation.InvalidField,
identityProviderId = "!@",
)
_ <- createAndCheck(
"non valid url",
LedgerApiErrors.RequestValidation.InvalidField,
jwksUrl = "url.com",
)
_ <- createAndCheck(
"empty update_mask",
LedgerApiErrors.RequestValidation.MissingField,
updateMask = None,
)
_ <- ledger
.updateIdentityProviderConfig(UpdateIdentityProviderConfigRequest(None))
.mustFailWith(
context = "empty identity_provider_config",
LedgerApiErrors.RequestValidation.MissingField,
)
createdIdp <- ledger.createIdentityProviderConfig()
_ <- ledger
.updateIdentityProviderConfig(
UpdateIdentityProviderConfigRequest(
Some(createdIdp.identityProviderConfig.get),
Some(FieldMask(Seq.empty)),
)
)
.mustFailWith(
context = "empty update_mask",
LedgerApiErrors.Admin.IdentityProviderConfig.InvalidUpdateIdentityProviderConfigRequest,
)
} yield ()
})
test(
"DeleteConfigInvalidArguments",
"Test argument validation for IdentityProviderConfigService#DeleteIdentityProviderConfig",
allocate(NoParties),
enabled = _.userManagement.supported,
disabledReason = "requires user management feature",
)(implicit ec => { case Participants(Participant(ledger)) =>
def createAndCheck(
problem: String,
expectedErrorCode: ErrorCode,
identityProviderId: String,
): Future[Unit] = ledger
.deleteIdentityProviderConfig(
DeleteIdentityProviderConfigRequest(identityProviderId)
)
.mustFailWith(context = problem, expectedErrorCode)
for {
_ <- createAndCheck(
"empty identity_provider_id",
LedgerApiErrors.RequestValidation.MissingField,
identityProviderId = "",
)
_ <- createAndCheck(
"invalid identity_provider_id",
LedgerApiErrors.RequestValidation.InvalidField,
identityProviderId = "!@",
)
} yield ()
})
test(
"CreateConfig",
"Exercise CreateIdentityProviderConfig",
allocate(NoParties),
enabled = _.userManagement.supported,
disabledReason = "requires user management feature",
)(implicit ec => { case Participants(Participant(ledger)) =>
val identityProviderId = UUID.randomUUID().toString
val isDeactivated = false
val issuer = UUID.randomUUID().toString
val jwksUrl = "http://daml.com/jwks.json"
val config = IdentityProviderConfig(
identityProviderId,
isDeactivated,
issuer,
jwksUrl,
)
for {
response1 <- ledger.createIdentityProviderConfig(
CreateIdentityProviderConfigRequest(Some(config))
)
response2 <- ledger.createIdentityProviderConfig(
isDeactivated = true
)
_ <- ledger
.createIdentityProviderConfig(
identityProviderId,
isDeactivated,
UUID.randomUUID().toString,
jwksUrl,
)
.mustFailWith(
"Creating duplicate IDP with the same ID",
LedgerApiErrors.Admin.IdentityProviderConfig.IdentityProviderConfigAlreadyExists,
)
_ <- ledger
.createIdentityProviderConfig(
issuer = issuer
)
.mustFailWith(
"Creating duplicate IDP with the same issuer",
LedgerApiErrors.Admin.IdentityProviderConfig.IdentityProviderConfigIssuerAlreadyExists,
)
} yield {
assertEquals(response1.identityProviderConfig, Some(config))
assertIdentityProviderConfig(response2.identityProviderConfig) { config =>
assertEquals(config.isDeactivated, true)
}
}
})
test(
"UpdateConfig",
"Exercise UpdateIdentityProviderConfig",
allocate(NoParties),
enabled = _.userManagement.supported,
disabledReason = "requires user management feature",
)(implicit ec => { case Participants(Participant(ledger)) =>
for {
response <- ledger.createIdentityProviderConfig()
response2 <- ledger.createIdentityProviderConfig()
response3 <- ledger.createIdentityProviderConfig()
isDeactivatedUpdate <- ledger
.updateIdentityProviderConfig(
UpdateIdentityProviderConfigRequest(
identityProviderConfig =
response.identityProviderConfig.map(_.copy(isDeactivated = true)),
updateMask = Some(FieldMask(Seq("is_deactivated"))),
)
)
jwksUrlUpdate <- ledger
.updateIdentityProviderConfig(
UpdateIdentityProviderConfigRequest(
identityProviderConfig =
response.identityProviderConfig.map(_.copy(jwksUrl = "http://daml.com/jwks2.json")),
updateMask = Some(FieldMask(Seq("jwks_url"))),
)
)
newIssuer = UUID.randomUUID().toString
issuerUpdate <- ledger
.updateIdentityProviderConfig(
UpdateIdentityProviderConfigRequest(
identityProviderConfig =
response.identityProviderConfig.map(_.copy(issuer = newIssuer)),
updateMask = Some(FieldMask(Seq("issuer"))),
)
)
duplicateIssuer = response2.identityProviderConfig.get.issuer
_ <- ledger
.updateIdentityProviderConfig(
UpdateIdentityProviderConfigRequest(
identityProviderConfig =
response3.identityProviderConfig.map(_.copy(issuer = duplicateIssuer)),
updateMask = Some(FieldMask(Seq("issuer"))),
)
)
.mustFailWith(
"Updating to the issuer which already exists",
LedgerApiErrors.Admin.IdentityProviderConfig.IdentityProviderConfigIssuerAlreadyExists,
)
} yield {
assertIdentityProviderConfig(isDeactivatedUpdate.identityProviderConfig) { config =>
assertEquals(config.isDeactivated, true)
}
assertIdentityProviderConfig(jwksUrlUpdate.identityProviderConfig) { config =>
assertEquals(config.jwksUrl, "http://daml.com/jwks2.json")
}
assertIdentityProviderConfig(issuerUpdate.identityProviderConfig) { config =>
assertEquals(config.issuer, newIssuer)
}
}
})
test(
"GetConfig",
"Exercise GetIdentityProviderConfig",
allocate(NoParties),
enabled = _.userManagement.supported,
disabledReason = "requires user management feature",
)(implicit ec => { case Participants(Participant(ledger)) =>
val identityProviderId = UUID.randomUUID().toString
val isDeactivated = false
val issuer = UUID.randomUUID().toString
val jwksUrl = "http://daml.com/jwks.json"
val config = IdentityProviderConfig(
identityProviderId,
isDeactivated,
issuer,
jwksUrl,
)
for {
response1 <- ledger.createIdentityProviderConfig(
CreateIdentityProviderConfigRequest(Some(config))
)
response2 <- ledger.getIdentityProviderConfig(
GetIdentityProviderConfigRequest(identityProviderId)
)
_ <- ledger
.getIdentityProviderConfig(
GetIdentityProviderConfigRequest(
UUID.randomUUID().toString
)
)
.mustFailWith(
"non existing idp",
LedgerApiErrors.Admin.IdentityProviderConfig.IdentityProviderConfigNotFound,
)
} yield {
assertEquals(response1.identityProviderConfig, Some(config))
assertEquals(response2.identityProviderConfig, Some(config))
}
})
test(
"ListConfig",
"Exercise ListIdentityProviderConfig",
allocate(NoParties),
enabled = _.userManagement.supported,
disabledReason = "requires user management feature",
)(implicit ec => { case Participants(Participant(ledger)) =>
val id1 = UUID.randomUUID().toString
val id2 = UUID.randomUUID().toString
for {
_ <- ledger.createIdentityProviderConfig(identityProviderId = id1)
_ <- ledger.createIdentityProviderConfig(identityProviderId = id2)
listResponse <- ledger.listIdentityProviderConfig()
} yield {
val ids = listResponse.identityProviderConfigs.map(_.identityProviderId)
assertEquals(ids.contains(id1), true)
assertEquals(ids.contains(id2), true)
}
})
test(
"DeleteConfig",
"Exercise DeleteIdentityProviderConfig",
allocate(NoParties),
enabled = _.userManagement.supported,
disabledReason = "requires user management feature",
)(implicit ec => { case Participants(Participant(ledger)) =>
val id = UUID.randomUUID().toString
for {
_ <- ledger.createIdentityProviderConfig(identityProviderId = id)
deleted <- ledger.deleteIdentityProviderConfig(DeleteIdentityProviderConfigRequest(id))
_ <- ledger
.deleteIdentityProviderConfig(DeleteIdentityProviderConfigRequest(id))
.mustFailWith(
"config does not exist anymore",
LedgerApiErrors.Admin.IdentityProviderConfig.IdentityProviderConfigNotFound,
)
} yield {
assertEquals(deleted, DeleteIdentityProviderConfigResponse())
}
})
private def assertIdentityProviderConfig(config: Option[IdentityProviderConfig])(
f: IdentityProviderConfig => Unit
): Unit = {
config match {
case Some(value) => f(value)
case None => fail("identity_provider_config expected")
}
}
}

View File

@ -1,42 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
class LedgerConfigurationServiceIT extends LedgerTestSuite {
test("ConfigSucceeds", "Return a valid configuration for a valid request", allocate(NoParties))(
implicit ec => { case Participants(Participant(ledger)) =>
for {
config <- ledger.configuration()
} yield {
assert(
config.maxDeduplicationDuration.isDefined,
"The maxDeduplicationDuration field of the configuration is empty",
)
}
}
)
test("ConfigLedgerId", "Return NOT_FOUND to invalid ledger identifier", allocate(NoParties))(
implicit ec => { case Participants(Participant(ledger)) =>
val invalidLedgerId = "THIS_IS_AN_INVALID_LEDGER_ID"
for {
failure <- ledger
.configuration(overrideLedgerId = Some(invalidLedgerId))
.mustFail("retrieving ledger configuration with an invalid ledger ID")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.LedgerIdMismatch,
Some(s"Ledger ID '$invalidLedgerId' not found."),
)
}
}
)
}

View File

@ -1,432 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import java.util.UUID
import java.util.regex.Pattern
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.api.testtool.infrastructure.participant.ParticipantTestContext
import com.daml.ledger.javaapi.data.Party
import com.daml.ledger.javaapi.data.codegen.ContractCompanion
import com.daml.ledger.test.java.model.test._
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.CollectionConverters._
import scala.jdk.OptionConverters._
final class MultiPartySubmissionIT extends LedgerTestSuite {
implicit val multiPartyContractCompanion: ContractCompanion.WithKey[
MultiPartyContract.Contract,
MultiPartyContract.ContractId,
MultiPartyContract,
MultiPartyContract,
] = MultiPartyContract.COMPANION
test(
"MPSSubmit",
"Submit creates a multi-party contract",
allocate(Parties(2)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
// Create a contract for (Alice, Bob)
val request = ledger.submitRequest(
actAs = List(alice, bob),
readAs = List.empty,
commands = new MultiPartyContract(List(alice, bob).map(_.getValue).asJava, "").create.commands,
)
for {
_ <- ledger.submit(request)
completions <- ledger.firstCompletions(bob)
} yield {
assert(completions.length == 1)
assert(completions.head.commandId == request.commands.get.commandId)
}
})
test(
"MPSCreateSuccess",
"Create succeeds with sufficient authorization",
allocate(Parties(2)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob)) =>
for {
// Create a contract for (Alice, Bob)
_ <- ledger.create(
actAs = List(alice, bob),
readAs = List.empty,
template = new MultiPartyContract(List(alice, bob).map(_.getValue).asJava, ""),
)
} yield ()
})
test(
"MPSCreateInsufficientAuthorization",
"Create fails with insufficient authorization",
allocate(Parties(3)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie)) =>
for {
// Create a contract for (Alice, Bob, Charlie), but only submit as (Alice, Bob).
// Should fail because required authorizer Charlie is missing from submitters.
failure <- ledger
.create(
actAs = List(alice, bob),
readAs = List.empty,
template = new MultiPartyContract(List(alice, bob, charlie).map(_.getValue).asJava, ""),
)
.mustFail("submitting a contract with a missing authorizers")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Interpreter.AuthorizationError,
None,
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"MPSAddSignatoriesSuccess",
"Exercise AddSignatories succeeds with sufficient authorization",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create a contract for (Alice, Bob)
(contract, _) <- createMultiPartyContract(ledger, List(alice, bob))
// Exercise a choice to add (Charlie, David)
// Requires authorization from all four parties
_ <- ledger.exercise(
actAs = List(alice, bob, charlie, david),
readAs = List.empty,
exercise =
contract.exerciseMPAddSignatories(List(alice, bob, charlie, david).map(_.getValue).asJava),
)
} yield ()
})
test(
"MPSAddSignatoriesInsufficientAuthorization",
"Exercise AddSignatories fails with insufficient authorization",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create a contract for (Alice, Bob)
(contract, _) <- createMultiPartyContract(ledger, List(alice, bob))
// Exercise a choice to add (Charlie, David) to the list of signatories
// Should fail as it's missing authorization from one of the original signatories (Alice)
failure <- ledger
.exercise(
actAs = List(bob, charlie, david),
readAs = List.empty,
exercise = contract.exerciseMPAddSignatories(
List(alice, bob, charlie, david).map(_.getValue).asJava
),
)
.mustFail("exercising a choice with a missing authorizers")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Interpreter.AuthorizationError,
None,
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"MPSFetchOtherSuccess",
"Exercise FetchOther succeeds with sufficient authorization and read delegation",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create contract A for (Alice, Bob)
(contractA, _) <- createMultiPartyContract(ledger, List(alice, bob))
// Create contract B for (Alice, Bob, Charlie, David)
(contractB, _) <- createMultiPartyContract(ledger, List(alice, bob, charlie, david))
// Fetch contract A through contract B as (Charlie, David)
_ <- ledger.exercise(
actAs = List(charlie, david),
readAs = List(alice),
exercise =
contractB.exerciseMPFetchOther(contractA, List(charlie, david).map(_.getValue).asJava),
)
} yield ()
})
test(
"MPSFetchOtherInsufficientAuthorization",
"Exercise FetchOther fails with insufficient authorization",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create contract A for (Alice, Bob)
(contractA, _) <- createMultiPartyContract(ledger, List(alice, bob))
// Create contract B for (Charlie, David)
(contractB, _) <- createMultiPartyContract(ledger, List(charlie, david))
// Fetch contract A through contract B as (Charlie, David)
// Should fail with an authorization error
failure <- ledger
.exercise(
actAs = List(charlie, david),
readAs = List(bob, alice),
exercise =
contractB.exerciseMPFetchOther(contractA, List(charlie, david).map(_.getValue).asJava),
)
.mustFail("exercising a choice without authorization to fetch another contract")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Interpreter.AuthorizationError,
Some(Pattern.compile("of the fetched contract to be an authorizer, but authorizers were")),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"MPSFetchOtherInvisible",
"Exercise FetchOther fails because the contract isn't visible",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create contract A for (Alice, Bob)
(contractA, _) <- createMultiPartyContract(ledger, List(alice, bob))
// Create contract B for (Alice, Bob, Charlie, David)
(contractB, _) <- createMultiPartyContract(ledger, List(alice, bob, charlie, david))
// Fetch contract A through contract B as (Charlie, David)
// Should fail with an interpretation error because the fetched contract isn't visible to any submitter
failure <- ledger
.exercise(
actAs = List(charlie, david),
readAs = List.empty,
exercise =
contractB.exerciseMPFetchOther(contractA, List(charlie, david).map(_.getValue).asJava),
)
.mustFail("exercising a choice without authorization to fetch another contract")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.ConsistencyErrors.ContractNotFound,
Some(Pattern.compile("Contract could not be found")),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"MPSFetchOtherByKeyOtherSuccess",
"Exercise FetchOtherByKey succeeds with sufficient authorization and read delegation",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create contract A for (Alice, Bob)
(_, keyA) <- createMultiPartyContract(ledger, List(alice, bob))
// Create contract B for (Alice, Bob, Charlie, David)
(contractB, _) <- createMultiPartyContract(ledger, List(alice, bob, charlie, david))
// Fetch contract A through contract B as (Charlie, David)
_ <- ledger.exercise(
actAs = List(charlie, david),
readAs = List(alice),
exercise =
contractB.exerciseMPFetchOtherByKey(keyA, List(charlie, david).map(_.getValue).asJava),
)
} yield ()
})
test(
"MPSFetchOtherByKeyInsufficientAuthorization",
"Exercise FetchOtherByKey fails with insufficient authorization",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create contract A for (Alice, Bob)
(_, keyA) <- createMultiPartyContract(ledger, List(alice, bob))
// Create contract B for (Charlie, David)
(contractB, _) <- createMultiPartyContract(ledger, List(charlie, david))
// Fetch contract A through contract B as (Charlie, David)
// Should fail with an authorization error
failure <- ledger
.exercise(
actAs = List(charlie, david),
readAs = List(bob, alice),
exercise =
contractB.exerciseMPFetchOtherByKey(keyA, List(charlie, david).map(_.getValue).asJava),
)
.mustFail("exercising a choice without authorization to fetch another contract by key")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Interpreter.AuthorizationError,
Some(Pattern.compile("of the fetched contract to be an authorizer, but authorizers were")),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"MPSFetchOtherByKeyInvisible",
"Exercise FetchOtherByKey fails because the contract isn't visible",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create contract A for (Alice, Bob)
(_, keyA) <- createMultiPartyContract(ledger, List(alice, bob))
// Create contract B for (Alice, Bob, Charlie, David)
(contractB, _) <- createMultiPartyContract(ledger, List(alice, bob, charlie, david))
// Fetch contract A through contract B as (Charlie, David)
// Should fail with an interpretation error because the fetched contract isn't visible to any submitter
failure <- ledger
.exercise(
actAs = List(charlie, david),
readAs = List.empty,
exercise =
contractB.exerciseMPFetchOtherByKey(keyA, List(charlie, david).map(_.getValue).asJava),
)
.mustFail("exercising a choice without authorization to fetch another contract by key")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Interpreter.LookupErrors.ContractKeyNotFound,
Some(Pattern.compile("dependency error: couldn't find key")),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"MPSLookupOtherByKeyOtherSuccess",
"Exercise LookupOtherByKey succeeds with sufficient authorization and read delegation",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create contract A for (Alice, Bob)
(contractA, keyA) <- createMultiPartyContract(ledger, List(alice, bob))
// Create contract B for (Alice, Bob, Charlie, David)
(contractB, _) <- createMultiPartyContract(ledger, List(alice, bob, charlie, david))
// Fetch contract A through contract B as (Charlie, David)
_ <- ledger.exercise(
actAs = List(charlie, david),
readAs = List(alice),
exercise = contractB
.exerciseMPLookupOtherByKey(
keyA,
List(charlie, david).map(_.getValue).asJava,
Some(contractA).toJava,
),
)
} yield ()
})
test(
"MPSLookupOtherByKeyInsufficientAuthorization",
"Exercise LookupOtherByKey fails with insufficient authorization",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create contract A for (Alice, Bob)
(contractA, keyA) <- createMultiPartyContract(ledger, List(alice, bob))
// Create contract B for (Charlie, David)
(contractB, _) <- createMultiPartyContract(ledger, List(charlie, david))
// Fetch contract A through contract B as (Charlie, David)
// Should fail with an authorization error
failure <- ledger
.exercise(
actAs = List(charlie, david),
readAs = List(bob, alice),
exercise = contractB
.exerciseMPLookupOtherByKey(
keyA,
List(charlie, david).map(_.getValue).asJava,
Some(contractA).toJava,
),
)
.mustFail("exercising a choice without authorization to look up another contract by key")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Interpreter.AuthorizationError,
Some(Pattern.compile("requires authorizers (.*) for lookup by key, but it only has")),
checkDefiniteAnswerMetadata = true,
)
}
})
test(
"MPSLookupOtherByKeyInvisible",
"Exercise LookupOtherByKey fails because the contract isn't visible",
allocate(Parties(4)),
)(implicit ec => { case Participants(Participant(ledger, alice, bob, charlie, david)) =>
for {
// Create contract A for (Alice, Bob)
(contractA, keyA) <- createMultiPartyContract(ledger, List(alice, bob))
// Create contract B for (Alice, Bob, Charlie, David)
(contractB, _) <- createMultiPartyContract(ledger, List(alice, bob, charlie, david))
// Fetch contract A through contract B as (Charlie, David)
// Should fail with an interpretation error because the fetched contract isn't visible to any submitter
failure <- ledger
.exercise(
actAs = List(charlie, david),
readAs = List.empty,
exercise = contractB
.exerciseMPLookupOtherByKey(
keyA,
List(charlie, david).map(_.getValue).asJava,
Some(contractA).toJava,
),
)
.mustFail("exercising a choice without authorization to look up another contract by key")
} yield {
assertGrpcErrorRegex(
failure,
LedgerApiErrors.CommandExecution.Interpreter.UnhandledException,
Some(
Pattern.compile(
"Interpretation error: Error: (" +
"User abort: Assertion failed." +
"|User abort: LookupOtherByKey value matches" +
"|Unhandled (Daml )?exception: [0-9a-zA-Z\\.:]*@[0-9a-f]*\\{ message = \"LookupOtherByKey value matches\" \\}\\." +
")( [Dd]etails(: |=)Last location: \\[[^\\]]*\\], partial transaction: root node)?"
)
),
checkDefiniteAnswerMetadata = true,
)
}
})
private[this] def createMultiPartyContract(
ledger: ParticipantTestContext,
submitters: List[Party],
value: String = UUID.randomUUID().toString,
)(implicit
ec: ExecutionContext
): Future[(MultiPartyContract.ContractId, MultiPartyContract)] =
ledger
.create(
actAs = submitters,
readAs = List.empty,
template = new MultiPartyContract(submitters.map(_.getValue).asJava, value),
)
.map(cid => cid -> new MultiPartyContract(submitters.map(_.getValue).asJava, value))
}

View File

@ -1,98 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import java.util.regex.Pattern
import com.daml.error.definitions.PackageServiceError
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
import com.daml.ledger.test.PackageManagementTestDar
import com.daml.ledger.test.java.package_management.packagemanagementtest.PackageManagementTestTemplate
import com.google.protobuf.ByteString
import scala.concurrent.{ExecutionContext, Future}
final class PackageManagementServiceIT extends LedgerTestSuite {
private[this] val testPackageResourcePath = PackageManagementTestDar.path
private def loadTestPackage()(implicit ec: ExecutionContext): Future[ByteString] = {
val testPackage = Future {
val in = getClass.getClassLoader.getResourceAsStream(testPackageResourcePath)
assert(in != null, s"Unable to load test package resource at '$testPackageResourcePath'")
in
}
val bytes = testPackage.map(ByteString.readFrom)
bytes.onComplete(_ => testPackage.map(_.close()))
bytes
}
test(
"PMEmptyUpload",
"An attempt at uploading an empty payload should fail",
allocate(NoParties),
)(implicit ec => { case Participants(Participant(ledger)) =>
for {
failure <- ledger.uploadDarFile(ByteString.EMPTY).mustFail("uploading an empty package")
} yield {
assertGrpcErrorRegex(
failure,
PackageServiceError.Reading.InvalidDar,
Some(Pattern.compile("Invalid DAR: package-upload|Dar file is corrupt")),
)
}
})
test(
"PMDuplicateSubmissionId",
"Duplicate submission ids are accepted when package uploaded twice",
allocate(NoParties, NoParties),
)(implicit ec => { case Participants(Participant(alpha), Participant(beta)) =>
// Multiple package updates should always succeed. Participant adds extra entropy to the
// submission id to ensure client does not inadvertently cause problems by poor selection
// of submission ids.
for {
testPackage <- loadTestPackage()
request = alpha.uploadDarRequest(testPackage)
_ <- alpha.uploadDarFile(request)
_ <- beta.uploadDarFile(request)
} yield ()
})
test(
"PMLoad",
"Concurrent uploads of the same package should be idempotent and result in the package being available for use",
allocate(SingleParty),
)(implicit ec => { case Participants(Participant(ledger, party)) =>
for {
testPackage <- loadTestPackage()
_ <- Future.sequence(Vector.fill(8)(ledger.uploadDarFile(testPackage)))
knownPackages <- ledger.listKnownPackages()
contract <- ledger.create(party, new PackageManagementTestTemplate(party))(
PackageManagementTestTemplate.COMPANION
)
acsBefore <- ledger.activeContracts(party)
_ <- ledger.exercise(party, contract.exerciseTestChoice())
acsAfter <- ledger.activeContracts(party)
} yield {
val duplicatePackageIds =
knownPackages.groupBy(_.packageId).view.mapValues(_.size).filter(_._2 > 1).toMap
assert(
duplicatePackageIds.isEmpty,
s"There are duplicate package identifiers: ${duplicatePackageIds
.map { case (name, count) => s"$name ($count)" }
.mkString(", ")}",
)
assert(
acsBefore.size == 1,
"After the contract has been created there should be one active contract but there's none",
)
assert(
acsAfter.isEmpty,
s"There should be no active package after the contract has been consumed: ${acsAfter.map(_.contractId).mkString(", ")}",
)
}
})
}

View File

@ -1,89 +0,0 @@
// Copyright (c) 2023 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
// SPDX-License-Identifier: Apache-2.0
package com.daml.ledger.api.testtool.suites.v1_8
import com.daml.error.definitions.LedgerApiErrors
import com.daml.ledger.api.testtool.infrastructure.Allocation._
import com.daml.ledger.api.testtool.infrastructure.Assertions._
import com.daml.ledger.api.testtool.infrastructure.LedgerTestSuite
final class PackageServiceIT extends LedgerTestSuite {
/** A package ID that is guaranteed to not be uploaded */
private[this] val unknownPackageId = " "
test("PackagesList", "Listing packages should return a result", allocate(NoParties))(
implicit ec => { case Participants(Participant(ledger)) =>
for {
knownPackages <- ledger.listPackages()
} yield assert(
knownPackages.size >= 3,
s"List of packages was expected to contain at least 3 packages, got ${knownPackages.size} instead.",
)
}
)
test(
"PackagesGetKnown",
"Getting package content should return a valid result",
allocate(NoParties),
)(implicit ec => { case Participants(Participant(ledger)) =>
for {
somePackageId <- ledger
.listPackages()
.map(_.headOption.getOrElse(fail("No package found")))
somePackage <- ledger.getPackage(somePackageId)
} yield {
assert(somePackage.hash.length > 0, s"Package $somePackageId has an empty hash.")
assert(
somePackage.hash == somePackageId,
s"Package $somePackageId has hash ${somePackage.hash}, expected hash to be equal to the package ID.",
)
assert(somePackage.archivePayload.size() >= 0, s"Package $somePackageId has zero size.")
}
})
test(
"PackagesGetUnknown",
"Getting package content for an unknown package should fail",
allocate(NoParties),
)(implicit ec => { case Participants(Participant(ledger)) =>
for {
failure <- ledger
.getPackage(unknownPackageId)
.mustFail("getting the contents of an unknown package")
} yield {
assertGrpcError(
failure,
LedgerApiErrors.RequestValidation.NotFound.Package,
None,
)
}
})
test(
"PackagesStatusKnown",
"Getting package status should return a valid result",
allocate(NoParties),
)(implicit ec => { case Participants(Participant(ledger)) =>
for {
somePackageId <- ledger.listPackages().map(_.headOption.getOrElse(fail("No package found")))
status <- ledger.getPackageStatus(somePackageId)
} yield {
assert(status.isRegistered, s"Package $somePackageId is not registered.")
}
})
test(
"PackagesStatusUnknown",
"Getting package status for an unknown package should fail",
allocate(NoParties),
)(implicit ec => { case Participants(Participant(ledger)) =>
for {
status <- ledger.getPackageStatus(unknownPackageId)
} yield {
assert(status.isUnknown, s"Package $unknownPackageId is not unknown.")
}
})
}

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