From 8dfedb4f9114a40b25ab3e476637d073bd9e6d18 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Thu, 19 Aug 2021 12:06:52 +0200 Subject: [PATCH 01/42] Augment completion.proto with deduplication-related info CHANGELOG_BEGIN CHANGELOG_END --- .../com/daml/ledger/api/v1/completion.proto | 38 +++++++++++++++++++ .../store/CompletionFromTransaction.scala | 16 +++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto index 65fe3a101ca1..baa160e9ca76 100644 --- a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto +++ b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto @@ -5,6 +5,8 @@ syntax = "proto3"; package com.daml.ledger.api.v1; +import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; import "google/rpc/status.proto"; option java_outer_classname = "CompletionOuterClass"; @@ -29,4 +31,40 @@ message Completion { // Must be a valid LedgerString (as described in ``value.proto``). // Optional string transaction_id = 3; + + // The application ID that was used for the submission, as described in ``commands.proto``. + // Must be a valid LedgerString (as described in ``value.proto``). + // Optional for historic completions where this data is not available. + string application_id = 4; + + // The set of parties on whose behalf the commands were executed. + // Contains the union of ``party`` and ``act_as`` from ``commands.proto``. + // The order of the parties need not be the same as in the submission. + // Each element must be a valid PartyIdString (as described in ``value.proto``). + // Optional for historic completions where this data is not available. + repeated string act_as = 5; + + // The submission ID this completion refers to, as described in ``commands.proto``. + // Must be a valid LedgerString (as described in ``value.proto``). + // Optional for historic completions where this data is not available. + string submission_id = 6; + + // The actual deduplication window used for the submission, which is derived from + // ``Commands.deduplication_period``. The ledger may convert the deduplication period into other + // descriptions and extend the period in implementation-specified ways. + // + // Used to audit the deduplication guarantee described in ``commands.proto``. + // + // Optional; the deduplication guarantee applies even if the completion omits this field. + oneof deduplication_period { + // Specifies the length of the deduplication period. + // It is interpreted relative to the local clock at some point during the submission's processing. + // + // Must be non-negative. + google.protobuf.Duration deduplication_time = 11; + + // Specifies a point in time in the past after which this submission should be de-duplicated w.r.t. previous submissions of the same commands. + // It is interpreted relative to the local clock at some point during the submission's processing. + google.protobuf.Timestamp deduplication_start = 12; + } } diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index e51780b71009..3f6b5488d784 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -29,7 +29,13 @@ private[platform] object CompletionFromTransaction { ): CompletionStreamResponse = CompletionStreamResponse.of( checkpoint = Some(toApiCheckpoint(recordTime, offset)), - completions = Seq(Completion.of(commandId, Some(OkStatus), transactionId)), + completions = Seq( + Completion().update( + _.commandId := commandId, + _.status := OkStatus, + _.transactionId := transactionId, + ) + ), ) def rejectedCompletion( @@ -40,7 +46,13 @@ private[platform] object CompletionFromTransaction { ): CompletionStreamResponse = CompletionStreamResponse.of( checkpoint = Some(toApiCheckpoint(recordTime, offset)), - completions = Seq(Completion.of(commandId, Some(status), RejectionTransactionId)), + completions = Seq( + Completion().update( + _.commandId := commandId, + _.status := status, + _.transactionId := RejectionTransactionId, + ) + ), ) private def toApiCheckpoint(recordTime: Instant, offset: Offset): Checkpoint = From e43f2fc36a87f96949c2539de30595c8535b1333 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 23 Aug 2021 15:33:03 +0200 Subject: [PATCH 02/42] Explicitly specify fields not yet filled in when building Completion --- .../store/CompletionFromTransaction.scala | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index 3f6b5488d784..04d340adf866 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -30,10 +30,14 @@ private[platform] object CompletionFromTransaction { CompletionStreamResponse.of( checkpoint = Some(toApiCheckpoint(recordTime, offset)), completions = Seq( - Completion().update( - _.commandId := commandId, - _.status := OkStatus, - _.transactionId := transactionId, + Completion.of( + commandId = commandId, + status = Some(OkStatus), + transactionId = transactionId, + applicationId = null, + actAs = null, + submissionId = null, + deduplicationPeriod = null, ) ), ) @@ -47,10 +51,14 @@ private[platform] object CompletionFromTransaction { CompletionStreamResponse.of( checkpoint = Some(toApiCheckpoint(recordTime, offset)), completions = Seq( - Completion().update( - _.commandId := commandId, - _.status := status, - _.transactionId := RejectionTransactionId, + Completion.of( + commandId = commandId, + status = Some(status), + transactionId = RejectionTransactionId, + applicationId = null, + actAs = null, + submissionId = null, + deduplicationPeriod = null, ) ), ) From d566b97286063e9b3c166d8a1e1829fc5118976d Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 23 Aug 2021 15:36:31 +0200 Subject: [PATCH 03/42] Time-based deduplication periods are measured in record time of completions --- .../com/daml/ledger/api/v1/completion.proto | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto index baa160e9ca76..7f49f1d05ca1 100644 --- a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto +++ b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto @@ -58,13 +58,15 @@ message Completion { // Optional; the deduplication guarantee applies even if the completion omits this field. oneof deduplication_period { // Specifies the length of the deduplication period. - // It is interpreted relative to the local clock at some point during the submission's processing. + // It is measured in record time of completions. // // Must be non-negative. google.protobuf.Duration deduplication_time = 11; - // Specifies a point in time in the past after which this submission should be de-duplicated w.r.t. previous submissions of the same commands. - // It is interpreted relative to the local clock at some point during the submission's processing. + // Specifies a point in time in the past after which this submission should be de-duplicated + // w.r.t. previous submissions of the same commands. + // + // It is measured in record time of completions. google.protobuf.Timestamp deduplication_start = 12; } } From 89336ab5ee7491a0f0d45e7ea2243d21af9239eb Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 23 Aug 2021 15:38:49 +0200 Subject: [PATCH 04/42] Add deduplication_offset as a deduplication_period option --- .../grpc-definitions/com/daml/ledger/api/v1/completion.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto index 7f49f1d05ca1..6aa023b2fc5b 100644 --- a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto +++ b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto @@ -57,6 +57,11 @@ message Completion { // // Optional; the deduplication guarantee applies even if the completion omits this field. oneof deduplication_period { + // Specifies the start of the deduplication period by a completion stream offset. + // + // Must be a valid LedgerString (as described in ``value.proto``). + string deduplication_offset = 10; + // Specifies the length of the deduplication period. // It is measured in record time of completions. // From eb14dd99ffc7f1a3a549b439fdd341dba37aa836 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 23 Aug 2021 15:39:32 +0200 Subject: [PATCH 05/42] Don't skip proto field numbers --- .../com/daml/ledger/api/v1/completion.proto | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto index 6aa023b2fc5b..8b62f47e8540 100644 --- a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto +++ b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto @@ -60,18 +60,18 @@ message Completion { // Specifies the start of the deduplication period by a completion stream offset. // // Must be a valid LedgerString (as described in ``value.proto``). - string deduplication_offset = 10; + string deduplication_offset = 7; // Specifies the length of the deduplication period. // It is measured in record time of completions. // // Must be non-negative. - google.protobuf.Duration deduplication_time = 11; + google.protobuf.Duration deduplication_time = 8; // Specifies a point in time in the past after which this submission should be de-duplicated // w.r.t. previous submissions of the same commands. // // It is measured in record time of completions. - google.protobuf.Timestamp deduplication_start = 12; + google.protobuf.Timestamp deduplication_start = 9; } } From 1ba2045843810a8d28bfc1664e2c6854130cce3e Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 23 Aug 2021 16:31:57 +0200 Subject: [PATCH 06/42] CompletionFromTransaction: use default Completion constructor --- .../platform/store/CompletionFromTransaction.scala | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index 04d340adf866..7c6cfbef105c 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -30,14 +30,10 @@ private[platform] object CompletionFromTransaction { CompletionStreamResponse.of( checkpoint = Some(toApiCheckpoint(recordTime, offset)), completions = Seq( - Completion.of( + Completion( commandId = commandId, status = Some(OkStatus), transactionId = transactionId, - applicationId = null, - actAs = null, - submissionId = null, - deduplicationPeriod = null, ) ), ) @@ -51,14 +47,10 @@ private[platform] object CompletionFromTransaction { CompletionStreamResponse.of( checkpoint = Some(toApiCheckpoint(recordTime, offset)), completions = Seq( - Completion.of( + Completion( commandId = commandId, status = Some(status), transactionId = RejectionTransactionId, - applicationId = null, - actAs = null, - submissionId = null, - deduplicationPeriod = null, ) ), ) From efa01ff39c2cb0df98e46bed0a2535412beabfb6 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 23 Aug 2021 17:23:09 +0200 Subject: [PATCH 07/42] submission_rank: reserve proto field for future use --- .../com/daml/ledger/api/v1/completion.proto | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto index 8b62f47e8540..6c8d2e9f8d86 100644 --- a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto +++ b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto @@ -49,6 +49,9 @@ message Completion { // Optional for historic completions where this data is not available. string submission_id = 6; + reserved "submission_rank"; + reserved 7; + // The actual deduplication window used for the submission, which is derived from // ``Commands.deduplication_period``. The ledger may convert the deduplication period into other // descriptions and extend the period in implementation-specified ways. @@ -60,18 +63,18 @@ message Completion { // Specifies the start of the deduplication period by a completion stream offset. // // Must be a valid LedgerString (as described in ``value.proto``). - string deduplication_offset = 7; + string deduplication_offset = 8; // Specifies the length of the deduplication period. // It is measured in record time of completions. // // Must be non-negative. - google.protobuf.Duration deduplication_time = 8; + google.protobuf.Duration deduplication_time = 9; // Specifies a point in time in the past after which this submission should be de-duplicated // w.r.t. previous submissions of the same commands. // // It is measured in record time of completions. - google.protobuf.Timestamp deduplication_start = 9; + google.protobuf.Timestamp deduplication_start = 10; } } From 73fd7a22659d84f226ca4c3caa05b5d920628101 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 23 Aug 2021 17:26:10 +0200 Subject: [PATCH 08/42] Add comment about reserved proto field --- .../grpc-definitions/com/daml/ledger/api/v1/completion.proto | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto index 6c8d2e9f8d86..73aa5f5e9b7b 100644 --- a/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto +++ b/ledger-api/grpc-definitions/com/daml/ledger/api/v1/completion.proto @@ -49,8 +49,8 @@ message Completion { // Optional for historic completions where this data is not available. string submission_id = 6; - reserved "submission_rank"; - reserved 7; + reserved "submission_rank"; // For future use. + reserved 7; // For future use. // The actual deduplication window used for the submission, which is derived from // ``Commands.deduplication_period``. The ledger may convert the deduplication period into other From 8e54bb52b8f7f1f97b38799a45293fdea1d30306 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Tue, 24 Aug 2021 11:35:49 +0200 Subject: [PATCH 09/42] Add command deduplication columns to completions in append-only schemas CHANGELOG_BEGIN CHANGELOG_END --- .../V1__Append_only_schema.sha256 | 2 +- .../V1__Append_only_schema.sql | 13 +++++++++++++ .../V1__Append_only_schema.sha256 | 2 +- .../V1__Append_only_schema.sql | 13 +++++++++++++ ...d_deduplication_info_to_completions.sha256 | 1 + ..._add_deduplication_info_to_completions.sql | 19 +++++++++++++++++++ 6 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 create mode 100644 ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 index b064847afbf9..cc7271155288 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -aae0b43e4735b3ffbdb782a61bad75f741c58a282327f3d3e18b0e41da5c69f6 +c9d6d032d1371e29bb84bf9e6dff92cb0dc162594c89c73a72786d2c41ec15cc diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql index e30d65e7b813..9e6b58e7f0c6 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql @@ -122,6 +122,19 @@ CREATE TABLE participant_command_completions ( command_id VARCHAR NOT NULL, -- The transaction ID is `NULL` for rejected transactions. transaction_id VARCHAR, + -- The application ID has to be provided by the application. + application_id VARCHAR NOT NULL, + -- The submission ID will be provided by the participant or driver if the application didn't provide one. + submission_id VARCHAR NOT NULL, + -- The three alternatives below are mutually exclusive, i.e. the deduplication + -- interval could have specified by the application as one of: + -- 1. an initial offset + -- 2. an initial timestamp + -- 3. a duration (split into two columns, seconds and nanos, mapping protobuf's 1:1) + deduplication_offset VARCHAR, + deduplication_start TIMESTAMP, + deduplication_time_seconds BIGINT, + deduplication_time_nanos INT, -- The three columns below are `NULL` if the completion is for an accepted transaction. -- The `rejection_status_details` column contains a Protocol-Buffers-serialized message of type -- `daml.platform.index.StatusDetails`, containing the code, message, and further details diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 index a11b49d629a2..751e44f690c9 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -c9148396eec01471c1135ff384d0b83442442ada1d6ca12d731f8e84b6f4869f +882dbf2fb1f48e5d240d21a5ba6bab7d60d6cc9306cbc0466ee9db4e0cf72458 diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql index c4a1f43c8882..4d3fd6c45c13 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql @@ -128,6 +128,19 @@ CREATE TABLE participant_command_completions record_time TIMESTAMP NOT NULL, application_id NVARCHAR2(1000) NOT NULL, + -- The submission ID will be provided by the participant or driver if the application didn't provide one. + submission_id NVARCHAR2(1000) NOT NULL, + + -- The three alternatives below are mutually exclusive, i.e. the deduplication + -- interval could have specified by the application as one of: + -- 1. an initial offset + -- 2. an initial timestamp + -- 3. a duration (split into two columns, seconds and nanos, mapping protobuf's 1:1) + deduplication_offset VARCHAR2(4000), + deduplication_start TIMESTAMP, + deduplication_time_seconds NUMBER, + deduplication_time_nanos NUMBER, + submitters CLOB NOT NULL CONSTRAINT ensure_json_submitters CHECK (submitters IS JSON), command_id NVARCHAR2(1000) NOT NULL, diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 new file mode 100644 index 000000000000..3c898e2d3173 --- /dev/null +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 @@ -0,0 +1 @@ +6701a42bcb0229b873317f62e424297116bb79fcfbb3cb465076b81108dc6aac diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql new file mode 100644 index 000000000000..ee7440761f81 --- /dev/null +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql @@ -0,0 +1,19 @@ +-- Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. +-- SPDX-License-Identifier: Apache-2.0 + +-- Add columns to store command deduplication information in completions. + +ALTER TABLE participant_command_completions + -- The application ID has to be provided by the application. + ADD COLUMN application_id text NOT NULL, + -- The submission ID will be provided by the participant or driver if the application didn't provide one. + ADD COLUMN submission_id text NOT NULL, + -- The three alternatives below are mutually exclusive, i.e. the deduplication + -- interval could have specified by the application as one of: + -- 1. an initial offset + -- 2. an initial timestamp + -- 3. a duration (split into two columns, seconds and nanos, mapping protobuf's 1:1) + ADD COLUMN deduplication_offset text, + ADD COLUMN deduplication_start timestamp, + ADD COLUMN deduplication_time_seconds bigint, + ADD COLUMN deduplication_time_nanos integer; From 80796733b4364a9df72f78c1a6b0e62268f57873 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Tue, 24 Aug 2021 13:32:26 +0200 Subject: [PATCH 10/42] Remove duplicated `application_id` field from Postgres and H2 schemas --- .../migration/h2database-appendonly/V1__Append_only_schema.sql | 2 -- .../V108_add_deduplication_info_to_completions.sql | 2 -- 2 files changed, 4 deletions(-) diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql index 9e6b58e7f0c6..39f023210835 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql @@ -122,8 +122,6 @@ CREATE TABLE participant_command_completions ( command_id VARCHAR NOT NULL, -- The transaction ID is `NULL` for rejected transactions. transaction_id VARCHAR, - -- The application ID has to be provided by the application. - application_id VARCHAR NOT NULL, -- The submission ID will be provided by the participant or driver if the application didn't provide one. submission_id VARCHAR NOT NULL, -- The three alternatives below are mutually exclusive, i.e. the deduplication diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql index ee7440761f81..9322a65409d3 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql @@ -4,8 +4,6 @@ -- Add columns to store command deduplication information in completions. ALTER TABLE participant_command_completions - -- The application ID has to be provided by the application. - ADD COLUMN application_id text NOT NULL, -- The submission ID will be provided by the participant or driver if the application didn't provide one. ADD COLUMN submission_id text NOT NULL, -- The three alternatives below are mutually exclusive, i.e. the deduplication From 3e5fa10afc7f72d5fc174ad3ed00805f57ddfbc2 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Tue, 24 Aug 2021 13:33:22 +0200 Subject: [PATCH 11/42] Make `application_id` and `submission_id` nullable --- .../h2database-appendonly/V1__Append_only_schema.sql | 7 +++++-- .../migration/oracle-appendonly/V1__Append_only_schema.sql | 7 +++++-- .../V108_add_deduplication_info_to_completions.sql | 3 ++- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql index 39f023210835..f131a48c85ce 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql @@ -117,13 +117,16 @@ CREATE TABLE participant_command_submissions ( CREATE TABLE participant_command_completions ( completion_offset VARCHAR NOT NULL, record_time TIMESTAMP NOT NULL, - application_id VARCHAR NOT NULL, + -- The application ID has to be provided by the application. + -- Nullable for alignment with the PostgreSQL schema. + application_id VARCHAR, submitters ARRAY NOT NULL, command_id VARCHAR NOT NULL, -- The transaction ID is `NULL` for rejected transactions. transaction_id VARCHAR, -- The submission ID will be provided by the participant or driver if the application didn't provide one. - submission_id VARCHAR NOT NULL, + -- Nullable to support historical data. + submission_id VARCHAR, -- The three alternatives below are mutually exclusive, i.e. the deduplication -- interval could have specified by the application as one of: -- 1. an initial offset diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql index 4d3fd6c45c13..098930a1e5ce 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql @@ -127,9 +127,12 @@ CREATE TABLE participant_command_completions completion_offset VARCHAR2(4000) NOT NULL, record_time TIMESTAMP NOT NULL, - application_id NVARCHAR2(1000) NOT NULL, + -- The application ID has to be provided by the application. + -- Nullable for alignment with the PostgreSQL schema. + application_id NVARCHAR2(1000), -- The submission ID will be provided by the participant or driver if the application didn't provide one. - submission_id NVARCHAR2(1000) NOT NULL, + -- Nullable to support historical data. + submission_id NVARCHAR2(1000), -- The three alternatives below are mutually exclusive, i.e. the deduplication -- interval could have specified by the application as one of: diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql index 9322a65409d3..e0103ea932fe 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql @@ -5,7 +5,8 @@ ALTER TABLE participant_command_completions -- The submission ID will be provided by the participant or driver if the application didn't provide one. - ADD COLUMN submission_id text NOT NULL, + -- Nullable to support historical data. + ADD COLUMN submission_id text, -- The three alternatives below are mutually exclusive, i.e. the deduplication -- interval could have specified by the application as one of: -- 1. an initial offset From f68a130202c87bab60225b0c95a25c09d10b8123 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Tue, 24 Aug 2021 13:37:45 +0200 Subject: [PATCH 12/42] Re-generate migrations' sha256 --- .../h2database-appendonly/V1__Append_only_schema.sha256 | 2 +- .../migration/oracle-appendonly/V1__Append_only_schema.sha256 | 2 +- .../V108_add_deduplication_info_to_completions.sha256 | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 index cc7271155288..2949a4e32dfc 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -c9d6d032d1371e29bb84bf9e6dff92cb0dc162594c89c73a72786d2c41ec15cc +c899a89c17c95ddc56047d42e19b7e91a78b9b6a8bd6bc88a5416e27ecb245c9 diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 index 751e44f690c9..b60b88846930 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -882dbf2fb1f48e5d240d21a5ba6bab7d60d6cc9306cbc0466ee9db4e0cf72458 +f42af1e06edafe491ceafeb5ef5ca49afa6797b64649202fdce3af5ae1ebab97 diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 index 3c898e2d3173..f80d3147986b 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 @@ -1 +1 @@ -6701a42bcb0229b873317f62e424297116bb79fcfbb3cb465076b81108dc6aac +e1af16d4a97863537c94f256090ccd316b66fc2c18e1c5b6b6f771463230c22f From 0cb77e401d467676b606d073d642245f9dd875db Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Wed, 25 Aug 2021 13:52:04 +0200 Subject: [PATCH 13/42] Tidy-up --- .../h2database-appendonly/V1__Append_only_schema.sha256 | 2 +- .../h2database-appendonly/V1__Append_only_schema.sql | 6 +++--- .../oracle-appendonly/V1__Append_only_schema.sha256 | 2 +- .../oracle-appendonly/V1__Append_only_schema.sql | 6 +++--- .../V108__add_deduplication_info_to_completions.sha256 | 1 + ...ql => V108__add_deduplication_info_to_completions.sql} | 8 +++++--- .../V108_add_deduplication_info_to_completions.sha256 | 1 - 7 files changed, 14 insertions(+), 12 deletions(-) create mode 100644 ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sha256 rename ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/{V108_add_deduplication_info_to_completions.sql => V108__add_deduplication_info_to_completions.sql} (67%) delete mode 100644 ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 index 2949a4e32dfc..8f5cab833cb4 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -c899a89c17c95ddc56047d42e19b7e91a78b9b6a8bd6bc88a5416e27ecb245c9 +f79b1767dee45bc230f0461ec922d36a74aec978ecc17b36c927437d677876d4 diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql index f131a48c85ce..4784d8add635 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql @@ -130,12 +130,12 @@ CREATE TABLE participant_command_completions ( -- The three alternatives below are mutually exclusive, i.e. the deduplication -- interval could have specified by the application as one of: -- 1. an initial offset - -- 2. an initial timestamp - -- 3. a duration (split into two columns, seconds and nanos, mapping protobuf's 1:1) + -- 2. a duration (split into two columns, seconds and nanos, mapping protobuf's 1:1) + -- 3. an initial timestamp deduplication_offset VARCHAR, - deduplication_start TIMESTAMP, deduplication_time_seconds BIGINT, deduplication_time_nanos INT, + deduplication_start TIMESTAMP, -- The three columns below are `NULL` if the completion is for an accepted transaction. -- The `rejection_status_details` column contains a Protocol-Buffers-serialized message of type -- `daml.platform.index.StatusDetails`, containing the code, message, and further details diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 index b60b88846930..bac3aa31c3c6 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -f42af1e06edafe491ceafeb5ef5ca49afa6797b64649202fdce3af5ae1ebab97 +9d54f9f76ba5187a1a05ad6cdd891af6ab761066f98ad3299f477b2b9cb5728d diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql index 098930a1e5ce..b142f2b5a7fe 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql @@ -137,12 +137,12 @@ CREATE TABLE participant_command_completions -- The three alternatives below are mutually exclusive, i.e. the deduplication -- interval could have specified by the application as one of: -- 1. an initial offset - -- 2. an initial timestamp - -- 3. a duration (split into two columns, seconds and nanos, mapping protobuf's 1:1) + -- 2. a duration (split into two columns, seconds and nanos, mapping protobuf's 1:1) + -- 3. an initial timestamp deduplication_offset VARCHAR2(4000), - deduplication_start TIMESTAMP, deduplication_time_seconds NUMBER, deduplication_time_nanos NUMBER, + deduplication_start TIMESTAMP, submitters CLOB NOT NULL CONSTRAINT ensure_json_submitters CHECK (submitters IS JSON), command_id NVARCHAR2(1000) NOT NULL, diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sha256 new file mode 100644 index 000000000000..7de754663a2f --- /dev/null +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sha256 @@ -0,0 +1 @@ +cd03a505429cf2cacc742eb110fe881b7d6f48d3c94ae85a87ccd115c897717b diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sql similarity index 67% rename from ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql rename to ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sql index e0103ea932fe..6b1e71d987af 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sql @@ -1,7 +1,9 @@ -- Copyright (c) 2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. -- SPDX-License-Identifier: Apache-2.0 --- Add columns to store command deduplication information in completions. +--------------------------------------------------------------------------------------------------- +-- V108: Add columns to store command deduplication information in completions +--------------------------------------------------------------------------------------------------- ALTER TABLE participant_command_completions -- The submission ID will be provided by the participant or driver if the application didn't provide one. @@ -13,6 +15,6 @@ ALTER TABLE participant_command_completions -- 2. an initial timestamp -- 3. a duration (split into two columns, seconds and nanos, mapping protobuf's 1:1) ADD COLUMN deduplication_offset text, - ADD COLUMN deduplication_start timestamp, ADD COLUMN deduplication_time_seconds bigint, - ADD COLUMN deduplication_time_nanos integer; + ADD COLUMN deduplication_time_nanos integer, + ADD COLUMN deduplication_start timestamp; diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 deleted file mode 100644 index f80d3147986b..000000000000 --- a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108_add_deduplication_info_to_completions.sha256 +++ /dev/null @@ -1 +0,0 @@ -e1af16d4a97863537c94f256090ccd316b66fc2c18e1c5b6b6f771463230c22f From 1cab32b7be17490b8f7483ab3e3e65637392b694 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Wed, 25 Aug 2021 14:00:45 +0200 Subject: [PATCH 14/42] Add RW logic for new command dedup columns in completions --- .../scala/platform/store/backend/DbDto.scala | 5 + .../store/backend/UpdateToDbDto.scala | 41 ++- .../CompletionStorageBackendTemplate.scala | 113 ++++++-- .../backend/StorageBackendTestValues.scala | 18 ++ .../store/backend/UpdateToDbDtoSpec.scala | 258 ++++++++++++------ 5 files changed, 321 insertions(+), 114 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/DbDto.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/DbDto.scala index ac25caa19101..baddfebee5a7 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/DbDto.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/DbDto.scala @@ -136,6 +136,11 @@ object DbDto { rejection_status_code: Option[Int], rejection_status_message: Option[String], rejection_status_details: Option[Array[Byte]], + submission_id: Option[String], + deduplication_offset: Option[String], + deduplication_time_seconds: Option[Long], + deduplication_time_nanos: Option[Int], + deduplication_start: Option[Instant], ) extends DbDto final case class CommandDeduplication(deduplication_key: String) extends DbDto diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala index 01c35fdb2e3a..8ec381452742 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala @@ -5,7 +5,8 @@ package com.daml.platform.store.backend import java.util.UUID -import com.daml.ledger.api.domain +import com.daml.ledger.api.DeduplicationPeriod.{DeduplicationDuration, DeduplicationOffset} +import com.daml.ledger.api.{domain, DeduplicationPeriod} import com.daml.ledger.configuration.Configuration import com.daml.ledger.offset.Offset import com.daml.ledger.participant.state.{v2 => state} @@ -27,6 +28,8 @@ object UpdateToDbDto { import state.Update._ { case u: CommandRejected => + val (deduplicationTimeSeconds, deduplicationTimeNanos) = + maybeDeduplicationTime(u.completionInfo.optDeduplicationPeriod) Iterator( DbDto.CommandCompletion( completion_offset = offset.toHexString, @@ -39,6 +42,12 @@ object UpdateToDbDto { rejection_status_message = Some(u.reasonTemplate.message), rejection_status_details = Some(StatusDetails.of(u.reasonTemplate.status.details).toByteArray), + submission_id = Some(u.completionInfo.submissionId), + deduplication_offset = + maybeDeduplicationOffset(u.completionInfo.optDeduplicationPeriod).map(_.toHexString), + deduplication_time_seconds = deduplicationTimeSeconds, + deduplication_time_nanos = deduplicationTimeNanos, + deduplication_start = None, ), DbDto.CommandDeduplication( DeduplicationKeyMaker.make( @@ -265,6 +274,8 @@ object UpdateToDbDto { } val completions = u.optCompletionInfo.iterator.map { completionInfo => + val (deduplication_time_seconds, deduplication_time_nanos) = + maybeDeduplicationTime(completionInfo.optDeduplicationPeriod) DbDto.CommandCompletion( completion_offset = offset.toHexString, record_time = u.recordTime.toInstant, @@ -275,6 +286,12 @@ object UpdateToDbDto { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = Some(completionInfo.submissionId), + deduplication_offset = + maybeDeduplicationOffset(completionInfo.optDeduplicationPeriod).map(_.toHexString), + deduplication_start = None, + deduplication_time_seconds = deduplication_time_seconds, + deduplication_time_nanos = deduplication_time_nanos, ) } @@ -282,4 +299,26 @@ object UpdateToDbDto { } } + private def maybeDeduplicationOffset( + maybeDeduplicationPeriod: Option[DeduplicationPeriod] + ): Option[Offset] = + maybeDeduplicationPeriod.flatMap { + case DeduplicationOffset(offset) => Some(offset) + case _ => None + } + + private def maybeDeduplicationTime( + maybeDeduplicationPeriod: Option[DeduplicationPeriod] + ): (Option[Long], Option[Int]) = + maybeDeduplicationPeriod + .flatMap { + case DeduplicationDuration(duration) => + Some((duration.getSeconds, duration.getNano)) + case _ => None + } + .fold[(Option[Long], Option[Int])] { + (None, None) + } { case (seconds, nanos) => + (Some(seconds), Some(nanos)) + } } diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala index 5a8ed5718064..63d15aa2b725 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala @@ -3,11 +3,12 @@ package com.daml.platform.store.backend.common +import java.io.InputStream import java.sql.Connection import java.time.Instant -import anorm.SqlParser.{binaryStream, int, str} -import anorm.{RowParser, ~} +import anorm.SqlParser.{binaryStream, int, long, str} +import anorm.{~, RowParser} import com.daml.ledger.api.v1.command_completion_service.CompletionStreamResponse import com.daml.ledger.offset.Offset import com.daml.lf.data.Ref @@ -16,38 +17,13 @@ import com.daml.platform.index.index.StatusDetails import com.daml.platform.store.CompletionFromTransaction import com.daml.platform.store.Conversions.{instant, offset} import com.daml.platform.store.backend.CompletionStorageBackend +import com.google.protobuf.any import com.google.rpc.status.{Status => StatusProto} trait CompletionStorageBackendTemplate extends CompletionStorageBackend { def queryStrategy: QueryStrategy - private val sharedColumns: RowParser[Offset ~ Instant ~ String] = - offset("completion_offset") ~ instant("record_time") ~ str("command_id") - - private val acceptedCommandParser: RowParser[CompletionStreamResponse] = - sharedColumns ~ str("transaction_id") map { - case offset ~ recordTime ~ commandId ~ transactionId => - CompletionFromTransaction.acceptedCompletion(recordTime, offset, commandId, transactionId) - } - - private val rejectedCommandParser: RowParser[CompletionStreamResponse] = - sharedColumns ~ - int("rejection_status_code") ~ - str("rejection_status_message") ~ - binaryStream("rejection_status_details").? map { - case offset ~ recordTime ~ commandId ~ - rejectionStatusCode ~ rejectionStatusMessage ~ rejectionStatusDetails => - val details = rejectionStatusDetails - .map(stream => StatusDetails.parseFrom(stream).details) - .getOrElse(Seq.empty) - val status = StatusProto.of(rejectionStatusCode, rejectionStatusMessage, details) - CompletionFromTransaction.rejectedCompletion(recordTime, offset, commandId, status) - } - - private val completionParser: RowParser[CompletionStreamResponse] = - acceptedCommandParser | rejectedCommandParser - override def commandCompletions( startExclusive: Offset, endInclusive: Offset, @@ -65,7 +41,13 @@ trait CompletionStorageBackendTemplate extends CompletionStorageBackend { transaction_id, rejection_status_code, rejection_status_message, - rejection_status_details + rejection_status_details, + application_id, + submission_id, + deduplication_offset, + deduplication_time_seconds, + deduplication_time_nanos, + deduplication_start FROM participant_command_completions WHERE @@ -76,4 +58,77 @@ trait CompletionStorageBackendTemplate extends CompletionStorageBackend { ORDER BY completion_offset ASC""" .as(completionParser.*)(connection) } + + private val sharedColumns: RowParser[Offset ~ Instant ~ String ~ String ~ Option[String]] = + offset("completion_offset") ~ + instant("record_time") ~ + str("command_id") ~ + str("application_id") ~ + str("submission_id").? + + private val acceptedCommandSharedColumns + : RowParser[Offset ~ Instant ~ String ~ String ~ Option[String] ~ String] = + sharedColumns ~ str("transaction_id") + + private val deduplicationOffsetColumn: RowParser[Option[String]] = str("deduplication_offset").? + private val deduplicationTimeSecondsColumn: RowParser[Option[Long]] = long( + "deduplication_time_seconds" + ).? + private val deduplicationTimeNanosColumn: RowParser[Option[Int]] = int( + "deduplication_time_nanos" + ).? + private val deduplicationStartColumn: RowParser[Instant] = instant("deduplication_start") + + private val acceptedCommandParser: RowParser[CompletionStreamResponse] = + acceptedCommandSharedColumns ~ + deduplicationOffsetColumn ~ + deduplicationTimeSecondsColumn ~ deduplicationTimeNanosColumn ~ + deduplicationStartColumn map { + case offset ~ recordTime ~ commandId ~ _ ~ _ ~ transactionId ~ + _ ~ _ ~ _ ~ _ => + CompletionFromTransaction.acceptedCompletion(recordTime, offset, commandId, transactionId) + } + + private val rejectionStatusCodeColumn: RowParser[Int] = int("rejection_status_code") + private val rejectionStatusMessageColumn: RowParser[String] = str("rejection_status_message") + private val rejectionStatusDetailsColumn: RowParser[Option[InputStream]] = + binaryStream("rejection_status_details").? + + private val rejectedCommandParser: RowParser[CompletionStreamResponse] = + sharedColumns ~ + deduplicationOffsetColumn ~ + deduplicationTimeSecondsColumn ~ deduplicationTimeNanosColumn ~ + deduplicationStartColumn ~ + rejectionStatusCodeColumn ~ + rejectionStatusMessageColumn ~ + rejectionStatusDetailsColumn map { + case offset ~ recordTime ~ commandId ~ _ ~ _ ~ + _ ~ _ ~ _ ~ _ ~ + rejectionStatusCode ~ rejectionStatusMessage ~ rejectionStatusDetails => + val status = + buildStatusProto(rejectionStatusCode, rejectionStatusMessage, rejectionStatusDetails) + CompletionFromTransaction.rejectedCompletion(recordTime, offset, commandId, status) + } + + private val completionParser: RowParser[CompletionStreamResponse] = + acceptedCommandParser | rejectedCommandParser + + private def buildStatusProto( + rejectionStatusCode: Int, + rejectionStatusMessage: String, + rejectionStatusDetails: Option[InputStream], + ): StatusProto = + StatusProto.of( + rejectionStatusCode, + rejectionStatusMessage, + parseRejectionStatusDetails(rejectionStatusDetails), + ) + + private def parseRejectionStatusDetails( + rejectionStatusDetails: Option[InputStream] + ): Seq[any.Any] = + rejectionStatusDetails + .map(stream => StatusDetails.parseFrom(stream).details) + .getOrElse(Seq.empty) + } diff --git a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala index 6d6446766704..0a3062fc7428 100644 --- a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala +++ b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala @@ -15,6 +15,7 @@ import com.daml.lf.ledger.EventId import com.daml.lf.transaction.NodeId import com.daml.platform.store.appendonlydao.JdbcLedgerDao import com.google.protobuf.ByteString +import scalaz.{Either3, Left3, Middle3} /** Except where specified, values should be treated as opaque */ @@ -221,8 +222,11 @@ private[backend] object StorageBackendTestValues { submitter: String = "signatory", commandId: String = UUID.randomUUID().toString, applicationId: String = someApplicationId, + submissionId: Option[String] = Some(UUID.randomUUID().toString), + deduplication: Option[Either3[String, (Long, Int), Instant]] = None, ): DbDto.CommandCompletion = { val transactionId = transactionIdFromOffset(offset) + DbDto.CommandCompletion( completion_offset = offset.toHexString, record_time = someTime, @@ -233,6 +237,20 @@ private[backend] object StorageBackendTestValues { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = submissionId, + deduplication_offset = deduplication.flatMap { + case Left3(offset) => Some(offset) + case _ => None + }, + deduplication_time_seconds = deduplication.flatMap { + case Middle3((seconds, _)) => Some(seconds) + case _ => None + }, + deduplication_time_nanos = deduplication.flatMap { + case Middle3((_, nanos)) => Some(nanos) + case _ => None + }, + deduplication_start = None, ) } diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala index 5a69df559e3e..540595571baf 100644 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala +++ b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala @@ -6,6 +6,7 @@ package com.daml.platform.store.backend import java.time.Duration import com.daml.daml_lf_dev.DamlLf +import com.daml.ledger.api.DeduplicationPeriod.{DeduplicationDuration, DeduplicationOffset} import com.daml.ledger.api.domain import com.daml.ledger.api.v1.event.{CreatedEvent, ExercisedEvent} import com.daml.ledger.configuration.{Configuration, LedgerTimeModel} @@ -36,6 +37,7 @@ import com.google.rpc.status.{Status => StatusProto} import io.grpc.Status import org.scalactic.TripleEquals._ import org.scalatest.matchers.should.Matchers +import org.scalatest.prop.TableDrivenPropertyChecks._ import org.scalatest.wordspec.AnyWordSpec import scala.concurrent.{ExecutionContext, Future} @@ -251,47 +253,81 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { ) } + val deduplicationPeriods = Table( + ( + "Deduplication period", + "Expected deduplication offset", + "Expected deduplication time seconds", + "Expected deduplication time nanos", + ), + (None, None, None, None), + ( + Some(DeduplicationOffset(Offset.beforeBegin)), + Some(Offset.beforeBegin.toHexString), + None, + None, + ), + ( + Some(DeduplicationDuration(Duration.ofDays(1L))), + None, + Some(Duration.ofDays(1L).toMinutes * 60L), + Some(0), + ), + ) + "handle CommandRejected" in { - val completionInfo = state.CompletionInfo( - actAs = List(someParty), - applicationId = someApplicationId, - commandId = someCommandId, - optDeduplicationPeriod = None, - submissionId = someSubmissionId, - ) val status = StatusProto.of(Status.Code.ABORTED.value(), "test reason", Seq.empty) - val update = state.Update.CommandRejected( - someRecordTime, - completionInfo, - new state.Update.CommandRejected.FinalReason(status), - ) - val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( - someOffset - )(update).toList + forAll(deduplicationPeriods) { + case ( + deduplicationPeriod, + expectedDeduplicationOffset, + expectedDeduplicationTimeSeconds, + expectedDeduplicationTimeNanos, + ) => + val completionInfo = state.CompletionInfo( + actAs = List(someParty), + applicationId = someApplicationId, + commandId = someCommandId, + optDeduplicationPeriod = deduplicationPeriod, + submissionId = someSubmissionId, + ) + val update = state.Update.CommandRejected( + someRecordTime, + completionInfo, + state.Update.CommandRejected.FinalReason(status), + ) + val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( + someOffset + )(update).toList - dtos should contain theSameElementsInOrderAs List( - DbDto.CommandCompletion( - completion_offset = someOffset.toHexString, - record_time = someRecordTime.toInstant, - application_id = someApplicationId, - submitters = Set(someParty), - command_id = someCommandId, - transaction_id = None, - rejection_status_code = Some(status.code), - rejection_status_message = Some(status.message), - rejection_status_details = Some(StatusDetails.of(status.details).toByteArray), - ), - DbDto.CommandDeduplication( - DeduplicationKeyMaker.make( - domain.CommandId(completionInfo.commandId), - completionInfo.actAs, + dtos should contain theSameElementsInOrderAs List( + DbDto.CommandCompletion( + completion_offset = someOffset.toHexString, + record_time = someRecordTime.toInstant, + application_id = someApplicationId, + submitters = Set(someParty), + command_id = someCommandId, + transaction_id = None, + rejection_status_code = Some(status.code), + rejection_status_message = Some(status.message), + rejection_status_details = Some(StatusDetails.of(status.details).toByteArray), + submission_id = Some(someSubmissionId), + deduplication_offset = expectedDeduplicationOffset, + deduplication_time_seconds = expectedDeduplicationTimeSeconds, + deduplication_time_nanos = expectedDeduplicationTimeNanos, + deduplication_start = None, + ), + DbDto.CommandDeduplication( + DeduplicationKeyMaker.make( + domain.CommandId(completionInfo.commandId), + completionInfo.actAs, + ) + ), ) - ), - ) + } } "handle TransactionAccepted (single create node)" in { - val completionInfo = someCompletionInfo val transactionMeta = someTransactionMeta val builder = new TransactionBuilder() val createNode = builder.create( @@ -304,56 +340,70 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { ) val createNodeId = builder.add(createNode) val transaction = builder.buildCommitted() - val update = state.Update.TransactionAccepted( - optCompletionInfo = Some(completionInfo), - transactionMeta = transactionMeta, - transaction = transaction, - transactionId = Ref.TransactionId.assertFromString("TransactionId"), - recordTime = someRecordTime, - divulgedContracts = List.empty, - blindingInfo = None, - ) - val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( - someOffset - )(update).toList + forAll(deduplicationPeriods) { + case ( + deduplicationPeriod, + expectedDeduplicationOffset, + expectedDeduplicationTimeSeconds, + expectedDeduplicationTimeNanos, + ) => + val completionInfo = someCompletionInfo.copy(optDeduplicationPeriod = deduplicationPeriod) + val update = state.Update.TransactionAccepted( + optCompletionInfo = Some(completionInfo), + transactionMeta = transactionMeta, + transaction = transaction, + transactionId = Ref.TransactionId.assertFromString("TransactionId"), + recordTime = someRecordTime, + divulgedContracts = List.empty, + blindingInfo = None, + ) + val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( + someOffset + )(update).toList - dtos should contain theSameElementsInOrderAs List( - DbDto.EventCreate( - event_offset = Some(someOffset.toHexString), - transaction_id = Some(update.transactionId), - ledger_effective_time = Some(transactionMeta.ledgerEffectiveTime.toInstant), - command_id = Some(completionInfo.commandId), - workflow_id = transactionMeta.workflowId, - application_id = Some(completionInfo.applicationId), - submitters = Some(completionInfo.actAs.toSet), - node_index = Some(createNodeId.index), - event_id = Some(EventId(update.transactionId, createNodeId).toLedgerString), - contract_id = createNode.coid.coid, - template_id = Some(createNode.coinst.template.toString), - flat_event_witnesses = Set("signatory", "observer"), // stakeholders - tree_event_witnesses = Set("signatory", "observer"), // informees - create_argument = Some(emptyArray), - create_signatories = Some(Set("signatory")), - create_observers = Some(Set("observer")), - create_agreement_text = None, - create_key_value = None, - create_key_hash = None, - create_argument_compression = compressionAlgorithmId, - create_key_value_compression = None, - event_sequential_id = 0, - ), - DbDto.CommandCompletion( - completion_offset = someOffset.toHexString, - record_time = update.recordTime.toInstant, - application_id = completionInfo.applicationId, - submitters = completionInfo.actAs.toSet, - command_id = completionInfo.commandId, - transaction_id = Some(update.transactionId), - rejection_status_code = None, - rejection_status_message = None, - rejection_status_details = None, - ), - ) + dtos should contain theSameElementsInOrderAs List( + DbDto.EventCreate( + event_offset = Some(someOffset.toHexString), + transaction_id = Some(update.transactionId), + ledger_effective_time = Some(transactionMeta.ledgerEffectiveTime.toInstant), + command_id = Some(completionInfo.commandId), + workflow_id = transactionMeta.workflowId, + application_id = Some(completionInfo.applicationId), + submitters = Some(completionInfo.actAs.toSet), + node_index = Some(createNodeId.index), + event_id = Some(EventId(update.transactionId, createNodeId).toLedgerString), + contract_id = createNode.coid.coid, + template_id = Some(createNode.coinst.template.toString), + flat_event_witnesses = Set("signatory", "observer"), // stakeholders + tree_event_witnesses = Set("signatory", "observer"), // informees + create_argument = Some(emptyArray), + create_signatories = Some(Set("signatory")), + create_observers = Some(Set("observer")), + create_agreement_text = None, + create_key_value = None, + create_key_hash = None, + create_argument_compression = compressionAlgorithmId, + create_key_value_compression = None, + event_sequential_id = 0, + ), + DbDto.CommandCompletion( + completion_offset = someOffset.toHexString, + record_time = update.recordTime.toInstant, + application_id = completionInfo.applicationId, + submitters = completionInfo.actAs.toSet, + command_id = completionInfo.commandId, + transaction_id = Some(update.transactionId), + rejection_status_code = None, + rejection_status_message = None, + rejection_status_details = None, + submission_id = Some(someSubmissionId), + deduplication_offset = expectedDeduplicationOffset, + deduplication_time_seconds = expectedDeduplicationTimeSeconds, + deduplication_time_nanos = expectedDeduplicationTimeNanos, + deduplication_start = None, + ), + ) + } } "handle TransactionAccepted (single create node with agreement text)" in { @@ -420,6 +470,11 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = None, + deduplication_offset = None, + deduplication_time_nanos = None, + deduplication_time_seconds = None, + deduplication_start = None, ), ) } @@ -500,6 +555,11 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = None, + deduplication_offset = None, + deduplication_time_nanos = None, + deduplication_time_seconds = None, + deduplication_start = None, ), ) } @@ -580,6 +640,11 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = None, + deduplication_offset = None, + deduplication_time_nanos = None, + deduplication_time_seconds = None, + deduplication_start = None, ), ) } @@ -743,6 +808,11 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = None, + deduplication_offset = None, + deduplication_time_nanos = None, + deduplication_time_seconds = None, + deduplication_start = None, ), ) } @@ -840,6 +910,11 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = None, + deduplication_offset = None, + deduplication_time_nanos = None, + deduplication_time_seconds = None, + deduplication_start = None, ), ) } @@ -961,6 +1036,11 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = None, + deduplication_offset = None, + deduplication_time_nanos = None, + deduplication_time_seconds = None, + deduplication_start = None, ), ) } @@ -1059,6 +1139,11 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = None, + deduplication_offset = None, + deduplication_time_nanos = None, + deduplication_time_seconds = None, + deduplication_start = None, ), ) } @@ -1134,6 +1219,11 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, + submission_id = None, + deduplication_offset = None, + deduplication_time_nanos = None, + deduplication_time_seconds = None, + deduplication_start = None, ), ) } @@ -1284,7 +1374,7 @@ object UpdateToDbDtoSpec { // DbDto case classes contain serialized values in Arrays (sometimes wrapped in Options), // because this representation can efficiently be passed to Jdbc. // Using Arrays means DbDto instances are not comparable, so we have to define a custom equality operator. - private implicit val DbDtoEq: org.scalactic.Equality[DbDto] = { + implicit private val DbDtoEq: org.scalactic.Equality[DbDto] = { case (a: DbDto, b: DbDto) => (a.productPrefix === b.productPrefix) && (a.productArity == b.productArity) && From 6db6a867b6df32fd2f915fde2e87eb310370c0c8 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Wed, 25 Aug 2021 15:55:41 +0200 Subject: [PATCH 15/42] Declare `deduplication_start` as a nullable column --- .../CompletionStorageBackendTemplate.scala | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala index 63d15aa2b725..2d35e19505ef 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala @@ -70,14 +70,14 @@ trait CompletionStorageBackendTemplate extends CompletionStorageBackend { : RowParser[Offset ~ Instant ~ String ~ String ~ Option[String] ~ String] = sharedColumns ~ str("transaction_id") - private val deduplicationOffsetColumn: RowParser[Option[String]] = str("deduplication_offset").? - private val deduplicationTimeSecondsColumn: RowParser[Option[Long]] = long( - "deduplication_time_seconds" - ).? - private val deduplicationTimeNanosColumn: RowParser[Option[Int]] = int( - "deduplication_time_nanos" - ).? - private val deduplicationStartColumn: RowParser[Instant] = instant("deduplication_start") + private val deduplicationOffsetColumn: RowParser[Option[String]] = + str("deduplication_offset").? + private val deduplicationTimeSecondsColumn: RowParser[Option[Long]] = + long("deduplication_time_seconds").? + private val deduplicationTimeNanosColumn: RowParser[Option[Int]] = + int("deduplication_time_nanos").? + private val deduplicationStartColumn: RowParser[Option[Instant]] = + instant("deduplication_start").? private val acceptedCommandParser: RowParser[CompletionStreamResponse] = acceptedCommandSharedColumns ~ From 484db4a97ab4785acab9531c9718958cee3eb71f Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Wed, 25 Aug 2021 16:25:28 +0200 Subject: [PATCH 16/42] Fix UpdateToDbDtoSpec --- .../suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala index ae09e4791e70..3eafe9d64798 100644 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala +++ b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala @@ -1205,7 +1205,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, - submission_id = None, + submission_id = Some(completionInfo.submissionId), deduplication_offset = None, deduplication_time_nanos = None, deduplication_time_seconds = None, From 8dbe592fbfd039ee8dd2b8e302f90eba7ed83b26 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Wed, 25 Aug 2021 16:31:37 +0200 Subject: [PATCH 17/42] Fix merge from main --- .../V108__add_deduplication_info_to_completions.sha256 | 1 - .../V109__add_deduplication_info_to_completions.sha256 | 1 + ...ions.sql => V109__add_deduplication_info_to_completions.sql} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sha256 create mode 100644 ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sha256 rename ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/{V108__add_deduplication_info_to_completions.sql => V109__add_deduplication_info_to_completions.sql} (94%) diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sha256 deleted file mode 100644 index 7de754663a2f..000000000000 --- a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sha256 +++ /dev/null @@ -1 +0,0 @@ -cd03a505429cf2cacc742eb110fe881b7d6f48d3c94ae85a87ccd115c897717b diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sha256 new file mode 100644 index 000000000000..6ccb6d8362ef --- /dev/null +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sha256 @@ -0,0 +1 @@ +02f23873151b46a624266ef55fadb7b303cf3fa817350669de6e52100966427e diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sql b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sql similarity index 94% rename from ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sql rename to ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sql index 6b1e71d987af..f6df8b224154 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V108__add_deduplication_info_to_completions.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sql @@ -2,7 +2,7 @@ -- SPDX-License-Identifier: Apache-2.0 --------------------------------------------------------------------------------------------------- --- V108: Add columns to store command deduplication information in completions +-- V109: Add columns to store command deduplication information in completions --------------------------------------------------------------------------------------------------- ALTER TABLE participant_command_completions From 19856a2faa9e8fc00b1c280b9a4a02d64130d147 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Wed, 25 Aug 2021 17:53:38 +0200 Subject: [PATCH 18/42] Replace Either3 with an ADT in StorageBackendTestValues --- .../backend/StorageBackendTestValues.scala | 60 ++++++++++++------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala index 0c8f1fff998c..f3f23a1b6dd8 100644 --- a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala +++ b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala @@ -15,7 +15,6 @@ import com.daml.lf.ledger.EventId import com.daml.lf.transaction.NodeId import com.daml.platform.store.appendonlydao.JdbcLedgerDao import com.google.protobuf.ByteString -import scalaz.{Either3, Left3, Middle3} /** Except where specified, values should be treated as opaque */ @@ -69,7 +68,7 @@ private[backend] object StorageBackendTestValues { def dtoPartyEntry( offset: Offset, - party: String = someParty.toString, + party: String = someParty, ): DbDto.PartyEntry = DbDto.PartyEntry( ledger_offset = offset.toHexString, recorded_at = someTime, @@ -206,42 +205,61 @@ private[backend] object StorageBackendTestValues { ) } + sealed trait Deduplication extends Product with Serializable { + import Deduplication.{Offset => DedupOffset, _} + + def startOffset: Option[String] = this match { + case DedupOffset(offset) => Some(offset) + case _ => None + } + def duration: Option[SecondsAndNanos] = this match { + case Span(span) => Some(span) + case _ => None + } + def startInstant: Option[Instant] = this match { + case Start(instant) => Some(instant) + case _ => None + } + } + object Deduplication { + final case class SecondsAndNanos(seconds: Long, nanos: Int) { + def allComponentsAreNonNegative: Boolean = seconds >= 0 && nanos >= 0 + } + + final case class Offset(offset: String) extends Deduplication + final case class Span(span: SecondsAndNanos) extends Deduplication { + require( + span.allComponentsAreNonNegative, + s"All the deduplication window components must not be negative: $span", + ) + } + final case class Start(instant: Instant) extends Deduplication + } + def dtoCompletion( offset: Offset, submitter: String = "signatory", commandId: String = UUID.randomUUID().toString, applicationId: String = someApplicationId, submissionId: Option[String] = Some(UUID.randomUUID().toString), - deduplication: Option[Either3[String, (Long, Int), Instant]] = None, - ): DbDto.CommandCompletion = { - val transactionId = transactionIdFromOffset(offset) - + deduplication: Option[Deduplication] = None, + ): DbDto.CommandCompletion = DbDto.CommandCompletion( completion_offset = offset.toHexString, record_time = someTime, application_id = applicationId, submitters = Set(submitter), command_id = commandId, - transaction_id = Some(transactionId), + transaction_id = Some(transactionIdFromOffset(offset)), rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, submission_id = submissionId, - deduplication_offset = deduplication.flatMap { - case Left3(offset) => Some(offset) - case _ => None - }, - deduplication_time_seconds = deduplication.flatMap { - case Middle3((seconds, _)) => Some(seconds) - case _ => None - }, - deduplication_time_nanos = deduplication.flatMap { - case Middle3((_, nanos)) => Some(nanos) - case _ => None - }, - deduplication_start = None, + deduplication_offset = deduplication.flatMap(_.startOffset), + deduplication_time_seconds = deduplication.flatMap(_.duration.map(_.seconds)), + deduplication_time_nanos = deduplication.flatMap(_.duration.map(_.nanos)), + deduplication_start = deduplication.flatMap(_.startInstant), ) - } def dtoTransactionId(dto: DbDto): Ref.TransactionId = { dto match { From 2ba045dbbf27ff534ac55cb1245c8123826a8f34 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Wed, 25 Aug 2021 18:03:21 +0200 Subject: [PATCH 19/42] Test with non-zero nanos --- .../scala/platform/store/backend/UpdateToDbDtoSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala index 3eafe9d64798..028da8c9ad85 100644 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala +++ b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala @@ -254,10 +254,10 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { None, ), ( - Some(DeduplicationDuration(Duration.ofDays(1L))), + Some(DeduplicationDuration(Duration.ofDays(1L).plusNanos(100))), None, Some(Duration.ofDays(1L).toMinutes * 60L), - Some(0), + Some(100), ), ) From 477e219d33394f732c8f7b0f9255caa40cf84cc7 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Wed, 25 Aug 2021 18:55:27 +0200 Subject: [PATCH 20/42] Fix UpdateToDbDtoSpec --- .../platform/store/backend/UpdateToDbDtoSpec.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala index 028da8c9ad85..0d395e78e6c3 100644 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala +++ b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala @@ -456,7 +456,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, - submission_id = None, + submission_id = Some(completionInfo.submissionId), deduplication_offset = None, deduplication_time_nanos = None, deduplication_time_seconds = None, @@ -541,7 +541,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, - submission_id = None, + submission_id = Some(completionInfo.submissionId), deduplication_offset = None, deduplication_time_nanos = None, deduplication_time_seconds = None, @@ -626,7 +626,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, - submission_id = None, + submission_id = Some(completionInfo.submissionId), deduplication_offset = None, deduplication_time_nanos = None, deduplication_time_seconds = None, @@ -794,7 +794,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, - submission_id = None, + submission_id = Some(completionInfo.submissionId), deduplication_offset = None, deduplication_time_nanos = None, deduplication_time_seconds = None, @@ -896,7 +896,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, - submission_id = None, + submission_id = Some(completionInfo.submissionId), deduplication_offset = None, deduplication_time_nanos = None, deduplication_time_seconds = None, @@ -1022,7 +1022,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, - submission_id = None, + submission_id = Some(completionInfo.submissionId), deduplication_offset = None, deduplication_time_nanos = None, deduplication_time_seconds = None, @@ -1125,7 +1125,7 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, - submission_id = None, + submission_id = Some(completionInfo.submissionId), deduplication_offset = None, deduplication_time_nanos = None, deduplication_time_seconds = None, From 143230889f8a6931b55009ee1702d586718b0b4a Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Thu, 26 Aug 2021 08:28:03 +0200 Subject: [PATCH 21/42] Also change Schema.scala --- .../main/scala/platform/store/backend/common/Field.scala | 5 +++++ .../main/scala/platform/store/backend/common/Schema.scala | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Field.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Field.scala index f042f7a1ef29..eedd3baa29a8 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Field.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Field.scala @@ -73,6 +73,11 @@ private[backend] case class IntOptional[FROM](extract: FROM => Option[Int]) private[backend] case class Bigint[FROM](extract: FROM => Long) extends TrivialField[FROM, Long] +private[backend] case class BigintOptional[FROM](extract: FROM => Option[Long]) + extends Field[FROM, Option[Long], java.lang.Long] { + override def convert: Option[Long] => java.lang.Long = _.map(Long.box).orNull +} + private[backend] case class SmallintOptional[FROM](extract: FROM => Option[Int]) extends Field[FROM, Option[Int], java.lang.Integer] { override def convert: Option[Int] => Integer = _.map(Int.box).orNull diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Schema.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Schema.scala index f467713bcdf6..bd4ae4085064 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Schema.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Schema.scala @@ -47,6 +47,9 @@ private[backend] object AppendOnlySchema { def bigint[FROM, _](extractor: FROM => Long): Field[FROM, Long, _] = Bigint(extractor) + def bigintOptional[FROM, _](extractor: FROM => Option[Long]): Field[FROM, Option[Long], _] = + BigintOptional(extractor) + def smallintOptional[FROM, _](extractor: FROM => Option[Int]): Field[FROM, Option[Int], _] = SmallintOptional(extractor) @@ -219,6 +222,11 @@ private[backend] object AppendOnlySchema { "rejection_status_code" -> fieldStrategy.intOptional(_.rejection_status_code), "rejection_status_message" -> fieldStrategy.stringOptional(_.rejection_status_message), "rejection_status_details" -> fieldStrategy.byteaOptional(_.rejection_status_details), + "submission_id" -> fieldStrategy.stringOptional(_.submission_id), + "deduplication_offset" -> fieldStrategy.stringOptional(_.submission_id), + "deduplication_time_seconds" -> fieldStrategy.bigintOptional(_.deduplication_time_seconds), + "deduplication_time_nanos" -> fieldStrategy.intOptional(_.deduplication_time_nanos), + "deduplication_start" -> fieldStrategy.timestampOptional(_.deduplication_start), ) val commandSubmissionDeletes: Table[DbDto.CommandDeduplication] = From 0e27dbe1c98a40fbccf7a312e6a9e2dedd08282b Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Thu, 26 Aug 2021 14:27:10 +0200 Subject: [PATCH 22/42] Simplify DbDto.CommandCompletion construction --- .../store/backend/UpdateToDbDto.scala | 104 ++++++++---------- 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala index d468dcfc77e2..0852a99bccec 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala @@ -6,11 +6,12 @@ package com.daml.platform.store.backend import java.util.UUID import com.daml.ledger.api.DeduplicationPeriod.{DeduplicationDuration, DeduplicationOffset} -import com.daml.ledger.api.{domain, DeduplicationPeriod} +import com.daml.ledger.api.domain import com.daml.ledger.configuration.Configuration import com.daml.ledger.offset.Offset +import com.daml.ledger.participant.state.v2.CompletionInfo import com.daml.ledger.participant.state.{v2 => state} -import com.daml.lf.data.Ref +import com.daml.lf.data.{Ref, Time} import com.daml.lf.engine.Blinding import com.daml.lf.ledger.EventId import com.daml.platform.index.index.StatusDetails @@ -28,26 +29,12 @@ object UpdateToDbDto { import state.Update._ { case u: CommandRejected => - val (deduplicationTimeSeconds, deduplicationTimeNanos) = - maybeDeduplicationTime(u.completionInfo.optDeduplicationPeriod) Iterator( - DbDto.CommandCompletion( - completion_offset = offset.toHexString, - record_time = u.recordTime.toInstant, - application_id = u.completionInfo.applicationId, - submitters = u.completionInfo.actAs.toSet, - command_id = u.completionInfo.commandId, - transaction_id = None, + commandCompletion(offset, u.recordTime, u.completionInfo).copy( rejection_status_code = Some(u.reasonTemplate.code), rejection_status_message = Some(u.reasonTemplate.message), rejection_status_details = Some(StatusDetails.of(u.reasonTemplate.status.details).toByteArray), - submission_id = Some(u.completionInfo.submissionId), - deduplication_offset = - maybeDeduplicationOffset(u.completionInfo.optDeduplicationPeriod).map(_.toHexString), - deduplication_time_seconds = deduplicationTimeSeconds, - deduplication_time_nanos = deduplicationTimeNanos, - deduplication_start = None, ), DbDto.CommandDeduplication( DeduplicationKeyMaker.make( @@ -266,52 +253,51 @@ object UpdateToDbDto { ) } - val completions = u.optCompletionInfo.iterator.map { completionInfo => - val (deduplication_time_seconds, deduplication_time_nanos) = - maybeDeduplicationTime(completionInfo.optDeduplicationPeriod) - DbDto.CommandCompletion( - completion_offset = offset.toHexString, - record_time = u.recordTime.toInstant, - application_id = completionInfo.applicationId, - submitters = completionInfo.actAs.toSet, - command_id = completionInfo.commandId, - transaction_id = Some(u.transactionId), - rejection_status_code = None, - rejection_status_message = None, - rejection_status_details = None, - submission_id = Some(completionInfo.submissionId), - deduplication_offset = - maybeDeduplicationOffset(completionInfo.optDeduplicationPeriod).map(_.toHexString), - deduplication_start = None, - deduplication_time_seconds = deduplication_time_seconds, - deduplication_time_nanos = deduplication_time_nanos, - ) - } + val completions = + u.optCompletionInfo.iterator.map(commandCompletion(offset, u.recordTime, _)) events ++ divulgences ++ completions } } - private def maybeDeduplicationOffset( - maybeDeduplicationPeriod: Option[DeduplicationPeriod] - ): Option[Offset] = - maybeDeduplicationPeriod.flatMap { - case DeduplicationOffset(offset) => Some(offset) - case _ => None - } + private def commandCompletion( + offset: Offset, + recordTime: Time.Timestamp, + completionInfo: CompletionInfo, + ): DbDto.CommandCompletion = { + val (deduplicationTimeSeconds, deduplicationTimeNanos) = + completionInfo.optDeduplicationPeriod + .flatMap { + case DeduplicationDuration(duration) => + Some((duration.getSeconds, duration.getNano)) + case _ => None + } + .fold[(Option[Long], Option[Int])] { + (None, None) + } { case (seconds, nanos) => + (Some(seconds), Some(nanos)) + } - private def maybeDeduplicationTime( - maybeDeduplicationPeriod: Option[DeduplicationPeriod] - ): (Option[Long], Option[Int]) = - maybeDeduplicationPeriod - .flatMap { - case DeduplicationDuration(duration) => - Some((duration.getSeconds, duration.getNano)) - case _ => None - } - .fold[(Option[Long], Option[Int])] { - (None, None) - } { case (seconds, nanos) => - (Some(seconds), Some(nanos)) - } + DbDto.CommandCompletion( + completion_offset = offset.toHexString, + record_time = recordTime.toInstant, + application_id = completionInfo.applicationId, + submitters = completionInfo.actAs.toSet, + command_id = completionInfo.commandId, + transaction_id = None, + rejection_status_code = None, + rejection_status_message = None, + rejection_status_details = None, + submission_id = Some(completionInfo.submissionId), + deduplication_offset = completionInfo.optDeduplicationPeriod + .flatMap { + case DeduplicationOffset(offset) => Some(offset) + case _ => None + } + .map(_.toHexString), + deduplication_time_seconds = deduplicationTimeSeconds, + deduplication_time_nanos = deduplicationTimeNanos, + deduplication_start = None, + ) + } } From 39ded6d566107975dd809327ae1520cb2072a041 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Thu, 26 Aug 2021 15:52:46 +0200 Subject: [PATCH 23/42] Fix merge from `main` (migration SHAs and filename) --- .../h2database-appendonly/V1__Append_only_schema.sha256 | 2 +- .../migration/oracle-appendonly/V1__Append_only_schema.sha256 | 2 +- .../V109__add_deduplication_info_to_completions.sha256 | 1 - .../V110__add_deduplication_info_to_completions.sha256 | 1 + ...ions.sql => V110__add_deduplication_info_to_completions.sql} | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) delete mode 100644 ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sha256 create mode 100644 ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V110__add_deduplication_info_to_completions.sha256 rename ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/{V109__add_deduplication_info_to_completions.sql => V110__add_deduplication_info_to_completions.sql} (94%) diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 index 15050c98dd5a..0d89bb7d0137 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -056573ad2da9442106340628b62e07a37b1cb4c890e9a763117537a07678c472 +5655bad712744cc5da9caa9c269c80fec368a96d78ab91ec82e8c531e02a3ea9 diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 index c2a51a2f22a8..e8d623700f29 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -a93ce669480ae2f52b1e4f44166bf768853764a317ea3820e24a7fa264ee2e99 +1016c9541ccf547c77b6cc1f9c9ac6bb4669c66330107d09ac4e549de7a25766 diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sha256 deleted file mode 100644 index 6ccb6d8362ef..000000000000 --- a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sha256 +++ /dev/null @@ -1 +0,0 @@ -02f23873151b46a624266ef55fadb7b303cf3fa817350669de6e52100966427e diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V110__add_deduplication_info_to_completions.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V110__add_deduplication_info_to_completions.sha256 new file mode 100644 index 000000000000..474cddebecd5 --- /dev/null +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V110__add_deduplication_info_to_completions.sha256 @@ -0,0 +1 @@ +281330bf9e361ecf12a2160c93ba3ce006a5a40564b6e5cf987b3ead71009ddb diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sql b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V110__add_deduplication_info_to_completions.sql similarity index 94% rename from ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sql rename to ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V110__add_deduplication_info_to_completions.sql index f6df8b224154..f52d5b0e3dee 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V109__add_deduplication_info_to_completions.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/postgres-appendonly/V110__add_deduplication_info_to_completions.sql @@ -2,7 +2,7 @@ -- SPDX-License-Identifier: Apache-2.0 --------------------------------------------------------------------------------------------------- --- V109: Add columns to store command deduplication information in completions +-- V110: Add columns to store command deduplication information in completions --------------------------------------------------------------------------------------------------- ALTER TABLE participant_command_completions From 15223dec35b4a544b5160eb339b45be6d1f27c6e Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 09:22:43 +0200 Subject: [PATCH 24/42] Fix UpdateToDbDto: restore transactionId --- .../scala/platform/store/backend/UpdateToDbDto.scala | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala index 0852a99bccec..99d391045ae5 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala @@ -30,7 +30,7 @@ object UpdateToDbDto { { case u: CommandRejected => Iterator( - commandCompletion(offset, u.recordTime, u.completionInfo).copy( + commandCompletion(offset, u.recordTime, transactionId = None, u.completionInfo).copy( rejection_status_code = Some(u.reasonTemplate.code), rejection_status_message = Some(u.reasonTemplate.message), rejection_status_details = @@ -254,7 +254,9 @@ object UpdateToDbDto { } val completions = - u.optCompletionInfo.iterator.map(commandCompletion(offset, u.recordTime, _)) + u.optCompletionInfo.iterator.map( + commandCompletion(offset, u.recordTime, Some(u.transactionId), _) + ) events ++ divulgences ++ completions } @@ -263,6 +265,7 @@ object UpdateToDbDto { private def commandCompletion( offset: Offset, recordTime: Time.Timestamp, + transactionId: Option[Ref.TransactionId], completionInfo: CompletionInfo, ): DbDto.CommandCompletion = { val (deduplicationTimeSeconds, deduplicationTimeNanos) = @@ -284,7 +287,7 @@ object UpdateToDbDto { application_id = completionInfo.applicationId, submitters = completionInfo.actAs.toSet, command_id = completionInfo.commandId, - transaction_id = None, + transaction_id = transactionId, rejection_status_code = None, rejection_status_message = None, rejection_status_details = None, From 24d1d6ee69795df5de7a748f5517a194313879d1 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 13:59:15 +0200 Subject: [PATCH 25/42] Fix completions' application_id: it must be non-nullable in all schemas, as in PSQL --- .../h2database-appendonly/V1__Append_only_schema.sha256 | 2 +- .../h2database-appendonly/V1__Append_only_schema.sql | 4 +--- .../migration/oracle-appendonly/V1__Append_only_schema.sha256 | 2 +- .../db/migration/oracle-appendonly/V1__Append_only_schema.sql | 4 +--- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 index 0d89bb7d0137..c2da8298b130 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -5655bad712744cc5da9caa9c269c80fec368a96d78ab91ec82e8c531e02a3ea9 +a2d8ca60fa98745d92e698608532442fdfcd8d55417d810cdbeaea6c064fecdb diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql index 89bfc232c61c..904211ad0532 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/h2database-appendonly/V1__Append_only_schema.sql @@ -106,9 +106,7 @@ CREATE TABLE participant_command_submissions ( CREATE TABLE participant_command_completions ( completion_offset VARCHAR NOT NULL, record_time TIMESTAMP NOT NULL, - -- The application ID has to be provided by the application. - -- Nullable for alignment with the PostgreSQL schema. - application_id VARCHAR, + application_id VARCHAR NOT NULL, submitters ARRAY NOT NULL, command_id VARCHAR NOT NULL, -- The transaction ID is `NULL` for rejected transactions. diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 index e8d623700f29..9f7dc90141a1 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 +++ b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sha256 @@ -1 +1 @@ -1016c9541ccf547c77b6cc1f9c9ac6bb4669c66330107d09ac4e549de7a25766 +e2e3db85fd3e660ac30e09e1be46594a37f7a99d615b757d5f3a0606a629410a diff --git a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql index f0237ae91e48..bbb017b3682c 100644 --- a/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql +++ b/ledger/participant-integration-api/src/main/resources/db/migration/oracle-appendonly/V1__Append_only_schema.sql @@ -112,10 +112,8 @@ CREATE TABLE participant_command_completions ( completion_offset VARCHAR2(4000) NOT NULL, record_time TIMESTAMP NOT NULL, + application_id NVARCHAR2(1000) NOT NULL, - -- The application ID has to be provided by the application. - -- Nullable for alignment with the PostgreSQL schema. - application_id NVARCHAR2(1000), -- The submission ID will be provided by the participant or driver if the application didn't provide one. -- Nullable to support historical data. submission_id NVARCHAR2(1000), From eb44d3c209da5d1df85de0b8fc7d72f82013be48 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 14:00:23 +0200 Subject: [PATCH 26/42] Actually use newly read columns --- .../store/CompletionFromTransaction.scala | 97 +++++++++++++++++-- .../CompletionStorageBackendTemplate.scala | 32 ++++-- .../store/dao/CommandCompletionsTable.scala | 22 +++-- .../ledger/inmemory/InMemoryLedger.scala | 2 + 4 files changed, 131 insertions(+), 22 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index 7c6cfbef105c..f2b1d9f32d66 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -11,6 +11,7 @@ import com.daml.ledger.api.v1.completion.Completion import com.daml.ledger.api.v1.ledger_offset.LedgerOffset import com.daml.ledger.offset.Offset import com.daml.platform.ApiOffset.ApiOffsetConverter +import com.google.protobuf.duration.Duration import com.google.rpc.status.{Status => StatusProto} import io.grpc.Status @@ -26,14 +27,24 @@ private[platform] object CompletionFromTransaction { offset: Offset, commandId: String, transactionId: String, + applicationId: String, + maybeSubmissionId: Option[String] = None, + maybeDeduplicationOffset: Option[String] = None, + maybeDeduplicationTimeSeconds: Option[Long] = None, + maybeDeduplicationTimeNanos: Option[Int] = None, ): CompletionStreamResponse = CompletionStreamResponse.of( checkpoint = Some(toApiCheckpoint(recordTime, offset)), completions = Seq( - Completion( - commandId = commandId, - status = Some(OkStatus), - transactionId = transactionId, + toApiCompletion( + commandId, + transactionId, + applicationId, + Some(OkStatus), + maybeSubmissionId, + maybeDeduplicationOffset, + maybeDeduplicationTimeSeconds, + maybeDeduplicationTimeNanos, ) ), ) @@ -43,14 +54,24 @@ private[platform] object CompletionFromTransaction { offset: Offset, commandId: String, status: StatusProto, + applicationId: String, + maybeSubmissionId: Option[String] = None, + maybeDeduplicationOffset: Option[String] = None, + maybeDeduplicationTimeSeconds: Option[Long] = None, + maybeDeduplicationTimeNanos: Option[Int] = None, ): CompletionStreamResponse = CompletionStreamResponse.of( checkpoint = Some(toApiCheckpoint(recordTime, offset)), completions = Seq( - Completion( - commandId = commandId, - status = Some(status), - transactionId = RejectionTransactionId, + toApiCompletion( + commandId, + RejectionTransactionId, + applicationId, + Some(status), + maybeSubmissionId, + maybeDeduplicationOffset, + maybeDeduplicationTimeSeconds, + maybeDeduplicationTimeNanos, ) ), ) @@ -60,4 +81,64 @@ private[platform] object CompletionFromTransaction { recordTime = Some(fromInstant(recordTime)), offset = Some(LedgerOffset.of(LedgerOffset.Value.Absolute(offset.toApiString))), ) + + private def toApiCompletion( + commandId: String, + transactionId: String, + applicationId: String, + maybeStatus: Option[StatusProto], + maybeSubmissionId: Option[String], + maybeDeduplicationOffset: Option[String], + maybeDeduplicationTimeSeconds: Option[Long], + maybeDeduplicationTimeNanos: Option[Int], + ): Completion = { + val deduplicationPeriod = toApiDeduplicationPeriod( + maybeDeduplicationOffset, + maybeDeduplicationTimeNanos, + maybeDeduplicationTimeSeconds, + ) + maybeSubmissionId match { + case Some(submissionId) => + Completion( + commandId = commandId, + status = maybeStatus, + transactionId = transactionId, + applicationId = applicationId, + submissionId = submissionId, + deduplicationPeriod = deduplicationPeriod, + ) + case _ => + Completion( + commandId = commandId, + status = Some(OkStatus), + transactionId = transactionId, + applicationId = applicationId, + deduplicationPeriod = deduplicationPeriod, + ) + } + } + + private def toApiDeduplicationPeriod( + maybeDeduplicationOffset: Option[String], + maybeDeduplicationTimeNanos: Option[Int], + maybeDeduplicationTimeSeconds: Option[Long], + ): Completion.DeduplicationPeriod = + // The only invariant tha should hold, considering legacy data, is that either + // the deduplication time seconds and nanos are both populated, or neither is. + (maybeDeduplicationOffset, (maybeDeduplicationTimeSeconds, maybeDeduplicationTimeNanos)) match { + case (Some(offset), _) => + Completion.DeduplicationPeriod.DeduplicationOffset(offset) + case (_, (Some(deduplicationTimeSeconds), Some(deduplicationTimeNanos))) => + Completion.DeduplicationPeriod.DeduplicationTime( + new Duration( + seconds = deduplicationTimeSeconds, + nanos = deduplicationTimeNanos, + ) + ) + case _ => + throw new IllegalArgumentException( + "One of deduplication time's seconds and nanos has been provided " + + "but they must be either both provided or both absent" + ) + } } diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala index 2d35e19505ef..6597b15c6feb 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala @@ -84,9 +84,19 @@ trait CompletionStorageBackendTemplate extends CompletionStorageBackend { deduplicationOffsetColumn ~ deduplicationTimeSecondsColumn ~ deduplicationTimeNanosColumn ~ deduplicationStartColumn map { - case offset ~ recordTime ~ commandId ~ _ ~ _ ~ transactionId ~ - _ ~ _ ~ _ ~ _ => - CompletionFromTransaction.acceptedCompletion(recordTime, offset, commandId, transactionId) + case offset ~ recordTime ~ commandId ~ applicationId ~ submissionId ~ transactionId ~ + deduplicationOffset ~ deduplicationTimeSeconds ~ deduplicationTimeNanos ~ _ => + CompletionFromTransaction.acceptedCompletion( + recordTime, + offset, + commandId, + transactionId, + applicationId, + submissionId, + deduplicationOffset, + deduplicationTimeSeconds, + deduplicationTimeNanos, + ) } private val rejectionStatusCodeColumn: RowParser[Int] = int("rejection_status_code") @@ -102,12 +112,22 @@ trait CompletionStorageBackendTemplate extends CompletionStorageBackend { rejectionStatusCodeColumn ~ rejectionStatusMessageColumn ~ rejectionStatusDetailsColumn map { - case offset ~ recordTime ~ commandId ~ _ ~ _ ~ - _ ~ _ ~ _ ~ _ ~ + case offset ~ recordTime ~ commandId ~ applicationId ~ submissionId ~ + deduplicationOffset ~ deduplicationTimeSeconds ~ deduplicationTimeNanos ~ _ ~ rejectionStatusCode ~ rejectionStatusMessage ~ rejectionStatusDetails => val status = buildStatusProto(rejectionStatusCode, rejectionStatusMessage, rejectionStatusDetails) - CompletionFromTransaction.rejectedCompletion(recordTime, offset, commandId, status) + CompletionFromTransaction.rejectedCompletion( + recordTime, + offset, + commandId, + status, + applicationId, + submissionId, + deduplicationOffset, + deduplicationTimeSeconds, + deduplicationTimeNanos, + ) } private val completionParser: RowParser[CompletionStreamResponse] = diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/dao/CommandCompletionsTable.scala b/ledger/participant-integration-api/src/main/scala/platform/store/dao/CommandCompletionsTable.scala index ae1dca0a71d4..55d9c3541eca 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/dao/CommandCompletionsTable.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/dao/CommandCompletionsTable.scala @@ -5,7 +5,7 @@ package com.daml.platform.store.dao import java.time.Instant -import anorm.{Row, RowParser, SimpleSql, SqlParser, SqlStringInterpolation, ~} +import anorm.{~, Row, RowParser, SimpleSql, SqlParser, SqlStringInterpolation} import com.daml.ledger.api.v1.command_completion_service.CompletionStreamResponse import com.daml.ledger.offset.Offset import com.daml.lf.data.Ref @@ -18,20 +18,26 @@ private[platform] object CommandCompletionsTable { import SqlParser.{int, str} - private val sharedColumns: RowParser[Offset ~ Instant ~ String] = - offset("completion_offset") ~ instant("record_time") ~ str("command_id") + private val sharedColumns: RowParser[Offset ~ Instant ~ String ~ String] = + offset("completion_offset") ~ instant("record_time") ~ str("command_id") ~ str("application_id") private val acceptedCommandParser: RowParser[CompletionStreamResponse] = sharedColumns ~ str("transaction_id") map { - case offset ~ recordTime ~ commandId ~ transactionId => - CompletionFromTransaction.acceptedCompletion(recordTime, offset, commandId, transactionId) + case offset ~ recordTime ~ commandId ~ applicationId ~ transactionId => + CompletionFromTransaction.acceptedCompletion( + recordTime, + offset, + commandId, + transactionId, + applicationId, + ) } private val rejectedCommandParser: RowParser[CompletionStreamResponse] = sharedColumns ~ int("status_code") ~ str("status_message") map { - case offset ~ recordTime ~ commandId ~ statusCode ~ statusMessage => + case offset ~ recordTime ~ commandId ~ applicationId ~ statusCode ~ statusMessage => val status = StatusProto.of(statusCode, statusMessage, Seq.empty) - CompletionFromTransaction.rejectedCompletion(recordTime, offset, commandId, status) + CompletionFromTransaction.rejectedCompletion(recordTime, offset, commandId, status, applicationId) } val parser: RowParser[CompletionStreamResponse] = acceptedCommandParser | rejectedCommandParser @@ -45,7 +51,7 @@ private[platform] object CommandCompletionsTable { ): SimpleSql[Row] = { val submittersInPartiesClause = sqlFunctions.arrayIntersectionWhereClause("submitters", parties) - SQL"select completion_offset, record_time, command_id, transaction_id, status_code, status_message from participant_command_completions where ($startExclusive is null or completion_offset > $startExclusive) and completion_offset <= $endInclusive and application_id = $applicationId and #$submittersInPartiesClause order by completion_offset asc" + SQL"select completion_offset, record_time, command_id, transaction_id, status_code, status_message, application_id from participant_command_completions where ($startExclusive is null or completion_offset > $startExclusive) and completion_offset <= $endInclusive and application_id = $applicationId and #$submittersInPartiesClause order by completion_offset asc" } def prepareCompletionsDelete(endInclusive: Offset): SimpleSql[Row] = diff --git a/ledger/sandbox-classic/src/main/scala/platform/sandbox/stores/ledger/inmemory/InMemoryLedger.scala b/ledger/sandbox-classic/src/main/scala/platform/sandbox/stores/ledger/inmemory/InMemoryLedger.scala index b4ad9c1aaba9..c4e287a54e03 100644 --- a/ledger/sandbox-classic/src/main/scala/platform/sandbox/stores/ledger/inmemory/InMemoryLedger.scala +++ b/ledger/sandbox-classic/src/main/scala/platform/sandbox/stores/ledger/inmemory/InMemoryLedger.scala @@ -194,6 +194,7 @@ private[sandbox] final class InMemoryLedger( offset, commandId, transactionId, + appId, ) case ( @@ -208,6 +209,7 @@ private[sandbox] final class InMemoryLedger( offset, commandId, status, + appId ) } } From 5643b95f2c15ba95bde2a0e5c4edbe21b5fbc4aa Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 14:54:03 +0200 Subject: [PATCH 27/42] Add DB round-trip tests for new fields in StorageBackendTestValues --- .../backend/StorageBackendTestValues.scala | 45 +---- .../StorageBackendTestsCompletions.scala | 181 ++++++++++++++++++ 2 files changed, 190 insertions(+), 36 deletions(-) diff --git a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala index f3f23a1b6dd8..6fd529aca67f 100644 --- a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala +++ b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala @@ -41,6 +41,7 @@ private[backend] object StorageBackendTestValues { ParameterStorageBackend.IdentityParams(someLedgerId, someParticipantId) val someParty: Ref.Party = Ref.Party.assertFromString("party") val someApplicationId: Ref.ApplicationId = Ref.ApplicationId.assertFromString("application_id") + val someSubmissionId: String = "submission_id" val someArchive: DamlLf.Archive = DamlLf.Archive.newBuilder .setHash("00001") @@ -205,44 +206,16 @@ private[backend] object StorageBackendTestValues { ) } - sealed trait Deduplication extends Product with Serializable { - import Deduplication.{Offset => DedupOffset, _} - - def startOffset: Option[String] = this match { - case DedupOffset(offset) => Some(offset) - case _ => None - } - def duration: Option[SecondsAndNanos] = this match { - case Span(span) => Some(span) - case _ => None - } - def startInstant: Option[Instant] = this match { - case Start(instant) => Some(instant) - case _ => None - } - } - object Deduplication { - final case class SecondsAndNanos(seconds: Long, nanos: Int) { - def allComponentsAreNonNegative: Boolean = seconds >= 0 && nanos >= 0 - } - - final case class Offset(offset: String) extends Deduplication - final case class Span(span: SecondsAndNanos) extends Deduplication { - require( - span.allComponentsAreNonNegative, - s"All the deduplication window components must not be negative: $span", - ) - } - final case class Start(instant: Instant) extends Deduplication - } - def dtoCompletion( offset: Offset, submitter: String = "signatory", commandId: String = UUID.randomUUID().toString, applicationId: String = someApplicationId, submissionId: Option[String] = Some(UUID.randomUUID().toString), - deduplication: Option[Deduplication] = None, + deduplicationOffset: Option[String] = None, + deduplicationTimeSeconds: Option[Long] = None, + deduplicationTimeNanos: Option[Int] = None, + deduplicationStart: Option[Instant] = None, ): DbDto.CommandCompletion = DbDto.CommandCompletion( completion_offset = offset.toHexString, @@ -255,10 +228,10 @@ private[backend] object StorageBackendTestValues { rejection_status_message = None, rejection_status_details = None, submission_id = submissionId, - deduplication_offset = deduplication.flatMap(_.startOffset), - deduplication_time_seconds = deduplication.flatMap(_.duration.map(_.seconds)), - deduplication_time_nanos = deduplication.flatMap(_.duration.map(_.nanos)), - deduplication_start = deduplication.flatMap(_.startInstant), + deduplication_offset = deduplicationOffset, + deduplication_time_seconds = deduplicationTimeSeconds, + deduplication_time_nanos = deduplicationTimeNanos, + deduplication_start = deduplicationStart, ) def dtoTransactionId(dto: DbDto): Ref.TransactionId = { diff --git a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestsCompletions.scala b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestsCompletions.scala index 6705580a48c2..240b1092e9b1 100644 --- a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestsCompletions.scala +++ b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestsCompletions.scala @@ -3,6 +3,8 @@ package com.daml.platform.store.backend +import java.time.Duration + import com.daml.ledger.offset.Offset import org.scalatest.Inside import org.scalatest.flatspec.AsyncFlatSpec @@ -53,4 +55,183 @@ private[backend] trait StorageBackendTestsCompletions } } + it should "correctly persist and retrieve application IDs" in { + val party = someParty + val applicationId = someApplicationId + + val dtos = Vector( + dtoConfiguration(offset(1)), + dtoCompletion(offset(2), submitter = party), + ) + + for { + _ <- executeSql(backend.initializeParameters(someIdentityParams)) + _ <- executeSql(ingest(dtos, _)) + _ <- executeSql(backend.updateLedgerEnd(ParameterStorageBackend.LedgerEnd(offset(2), 1L))) + completions <- executeSql( + backend.commandCompletions(offset(1), offset(2), applicationId, Set(party)) + ) + } yield { + completions should have length 1 + completions.head.completions should have length 1 + completions.head.completions.head.applicationId should be(applicationId) + } + } + + it should "correctly persist and retrieve submission IDs" in { + val party = someParty + val submissionId = Some(someSubmissionId) + + val dtos = Vector( + dtoConfiguration(offset(1)), + dtoCompletion(offset(2), submitter = party, submissionId = submissionId), + dtoCompletion(offset(3), submitter = party, submissionId = None), + ) + + for { + _ <- executeSql(backend.initializeParameters(someIdentityParams)) + _ <- executeSql(ingest(dtos, _)) + _ <- executeSql(backend.updateLedgerEnd(ParameterStorageBackend.LedgerEnd(offset(3), 2L))) + completions <- executeSql( + backend.commandCompletions(offset(1), offset(3), someApplicationId, Set(party)) + ) + } yield { + completions should have length 2 + val List(completionWithSubmissionId, completionWithoutSubmissionId) = completions + completionWithSubmissionId.completions should have length 1 + completionWithSubmissionId.completions.head.submissionId should be(submissionId) + completionWithoutSubmissionId.completions should have length 1 + completionWithoutSubmissionId.completions.head.submissionId should be("") + } + } + + it should "correctly persist and retrieve command deduplication offsets" in { + val party = someParty + val anOffset = "someOffset" + + val dtos = Vector( + dtoConfiguration(offset(1)), + dtoCompletion( + offset(2), + submitter = party, + deduplicationOffset = Some(anOffset), + ), + dtoCompletion(offset(3), submitter = party, deduplicationOffset = None), + ) + + for { + _ <- executeSql(backend.initializeParameters(someIdentityParams)) + _ <- executeSql(ingest(dtos, _)) + _ <- executeSql(backend.updateLedgerEnd(ParameterStorageBackend.LedgerEnd(offset(3), 2L))) + completions <- executeSql( + backend.commandCompletions(offset(1), offset(3), someApplicationId, Set(party)) + ) + } yield { + completions should have length 2 + val List(completionWithDeduplicationOffset, completionWithoutDeduplicationOffset) = + completions + completionWithDeduplicationOffset.completions should have length 1 + completionWithDeduplicationOffset.completions.head.deduplicationPeriod.deduplicationOffset should be( + Some(anOffset) + ) + completionWithoutDeduplicationOffset.completions should have length 1 + completionWithoutDeduplicationOffset.completions.head.deduplicationPeriod.deduplicationOffset should not be defined + } + } + + it should "correctly persist and retrieve command deduplication times" in { + val party = someParty + val seconds = 100L + val nanos = 10 + val expectedDuration = Duration.ofSeconds(seconds).plusNanos(nanos.toLong) + + val dtos = Vector( + dtoConfiguration(offset(1)), + dtoCompletion( + offset(2), + submitter = party, + deduplicationTimeSeconds = Some(seconds), + deduplicationTimeNanos = Some(nanos), + ), + dtoCompletion( + offset(3), + submitter = party, + deduplicationTimeSeconds = None, + deduplicationTimeNanos = None, + ), + ) + + for { + _ <- executeSql(backend.initializeParameters(someIdentityParams)) + _ <- executeSql(ingest(dtos, _)) + _ <- executeSql(backend.updateLedgerEnd(ParameterStorageBackend.LedgerEnd(offset(3), 2L))) + completions <- executeSql( + backend.commandCompletions(offset(1), offset(3), someApplicationId, Set(party)) + ) + } yield { + completions should have length 2 + val List(completionWithDeduplicationOffset, completionWithoutDeduplicationOffset) = + completions + completionWithDeduplicationOffset.completions should have length 1 + completionWithDeduplicationOffset.completions.head.deduplicationPeriod.deduplicationOffset should be( + expectedDuration + ) + completionWithoutDeduplicationOffset.completions should have length 1 + completionWithoutDeduplicationOffset.completions.head.deduplicationPeriod.deduplicationTime should not be defined + } + } + + it should "fail on broken command deduplication times in DB" in { + val party = someParty + val seconds = 100L + val nanos = 10 + + val dtos1 = Vector( + dtoConfiguration(offset(1)), + dtoCompletion( + offset(2), + submitter = party, + deduplicationTimeSeconds = Some(seconds), + deduplicationTimeNanos = None, + ), + ) + + for { + _ <- executeSql(backend.initializeParameters(someIdentityParams)) + _ <- executeSql(ingest(dtos1, _)) + _ <- executeSql(backend.updateLedgerEnd(ParameterStorageBackend.LedgerEnd(offset(2), 1L))) + result <- executeSql( + backend.commandCompletions(offset(1), offset(2), someApplicationId, Set(party)) + ).failed + } yield { + result.getCause shouldBe an[IllegalArgumentException] + result.getCause.getMessage should be( + "One of deduplication time seconds and nanos has been provided " + + "but they must be either both provided or both absent" + ) + } + + val dtos2 = Vector( + dtoCompletion( + offset(3), + submitter = party, + deduplicationTimeSeconds = None, + deduplicationTimeNanos = Some(nanos), + ) + ) + + for { + _ <- executeSql(ingest(dtos2, _)) + _ <- executeSql(backend.updateLedgerEnd(ParameterStorageBackend.LedgerEnd(offset(3), 2L))) + result <- executeSql( + backend.commandCompletions(offset(2), offset(3), someApplicationId, Set(party)) + ).failed + } yield { + result.getCause shouldBe an[IllegalArgumentException] + result.getCause.getMessage should be( + "One of deduplication time seconds and nanos has been provided " + + "but they must be either both provided or both absent" + ) + } + } } From 51ab2547e895a0cec3f33518ab43594a818396e4 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 14:59:47 +0200 Subject: [PATCH 28/42] Format --- .../platform/store/dao/CommandCompletionsTable.scala | 8 +++++++- .../sandbox/stores/ledger/inmemory/InMemoryLedger.scala | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/dao/CommandCompletionsTable.scala b/ledger/participant-integration-api/src/main/scala/platform/store/dao/CommandCompletionsTable.scala index 55d9c3541eca..1b67fb4c0ba3 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/dao/CommandCompletionsTable.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/dao/CommandCompletionsTable.scala @@ -37,7 +37,13 @@ private[platform] object CommandCompletionsTable { sharedColumns ~ int("status_code") ~ str("status_message") map { case offset ~ recordTime ~ commandId ~ applicationId ~ statusCode ~ statusMessage => val status = StatusProto.of(statusCode, statusMessage, Seq.empty) - CompletionFromTransaction.rejectedCompletion(recordTime, offset, commandId, status, applicationId) + CompletionFromTransaction.rejectedCompletion( + recordTime, + offset, + commandId, + status, + applicationId, + ) } val parser: RowParser[CompletionStreamResponse] = acceptedCommandParser | rejectedCommandParser diff --git a/ledger/sandbox-classic/src/main/scala/platform/sandbox/stores/ledger/inmemory/InMemoryLedger.scala b/ledger/sandbox-classic/src/main/scala/platform/sandbox/stores/ledger/inmemory/InMemoryLedger.scala index c4e287a54e03..5693f9332079 100644 --- a/ledger/sandbox-classic/src/main/scala/platform/sandbox/stores/ledger/inmemory/InMemoryLedger.scala +++ b/ledger/sandbox-classic/src/main/scala/platform/sandbox/stores/ledger/inmemory/InMemoryLedger.scala @@ -209,7 +209,7 @@ private[sandbox] final class InMemoryLedger( offset, commandId, status, - appId + appId, ) } } From 87da1dd0a9469fbe8920a4e01c3e6ed8ef29c740 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 15:36:36 +0200 Subject: [PATCH 29/42] StorageBackendTestValues: property type someSubmissionId --- .../scala/platform/store/backend/StorageBackendTestValues.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala index 6fd529aca67f..cf2a10fa9bb9 100644 --- a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala +++ b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestValues.scala @@ -41,7 +41,7 @@ private[backend] object StorageBackendTestValues { ParameterStorageBackend.IdentityParams(someLedgerId, someParticipantId) val someParty: Ref.Party = Ref.Party.assertFromString("party") val someApplicationId: Ref.ApplicationId = Ref.ApplicationId.assertFromString("application_id") - val someSubmissionId: String = "submission_id" + val someSubmissionId: Ref.SubmissionId = Ref.SubmissionId.assertFromString("submission_id") val someArchive: DamlLf.Archive = DamlLf.Archive.newBuilder .setHash("00001") From 482891ead9e4523cbd868aa08213010222c0c96f Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 15:37:23 +0200 Subject: [PATCH 30/42] UpdateToDbDto: remove duplicated logic --- .../store/backend/UpdateToDbDto.scala | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala index 99d391045ae5..a6d5266afc32 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala @@ -268,17 +268,21 @@ object UpdateToDbDto { transactionId: Option[Ref.TransactionId], completionInfo: CompletionInfo, ): DbDto.CommandCompletion = { - val (deduplicationTimeSeconds, deduplicationTimeNanos) = + val (deduplicationOffset, deduplicationTimeSeconds, deduplicationTimeNanos) = completionInfo.optDeduplicationPeriod .flatMap { + case DeduplicationOffset(offset) => + Some(Left(offset)) case DeduplicationDuration(duration) => - Some((duration.getSeconds, duration.getNano)) - case _ => None + Some(Right((duration.getSeconds, duration.getNano))) } - .fold[(Option[Long], Option[Int])] { - (None, None) - } { case (seconds, nanos) => - (Some(seconds), Some(nanos)) + .fold[(Option[String], Option[Long], Option[Int])] { + (None, None, None) + } { + case Left(offset) => + (Some(offset.toHexString), None, None) + case Right((seconds, nanos)) => + (None, Some(seconds), Some(nanos)) } DbDto.CommandCompletion( @@ -292,12 +296,7 @@ object UpdateToDbDto { rejection_status_message = None, rejection_status_details = None, submission_id = Some(completionInfo.submissionId), - deduplication_offset = completionInfo.optDeduplicationPeriod - .flatMap { - case DeduplicationOffset(offset) => Some(offset) - case _ => None - } - .map(_.toHexString), + deduplication_offset = deduplicationOffset, deduplication_time_seconds = deduplicationTimeSeconds, deduplication_time_nanos = deduplicationTimeNanos, deduplication_start = None, From 804c99e9dac488a03319c99991814cf86fd5a2ea Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 15:47:28 +0200 Subject: [PATCH 31/42] Simplify UpdateToDbDto.commandCompletion --- .../store/backend/UpdateToDbDto.scala | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala index a6d5266afc32..598bf96bba30 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala @@ -270,20 +270,15 @@ object UpdateToDbDto { ): DbDto.CommandCompletion = { val (deduplicationOffset, deduplicationTimeSeconds, deduplicationTimeNanos) = completionInfo.optDeduplicationPeriod - .flatMap { + .map { case DeduplicationOffset(offset) => - Some(Left(offset)) - case DeduplicationDuration(duration) => - Some(Right((duration.getSeconds, duration.getNano))) - } - .fold[(Option[String], Option[Long], Option[Int])] { - (None, None, None) - } { - case Left(offset) => (Some(offset.toHexString), None, None) - case Right((seconds, nanos)) => - (None, Some(seconds), Some(nanos)) - } + case DeduplicationDuration(duration) => + (None, Some(duration.getSeconds), Some(duration.getNano)) + } match { + case Some(value) => value + case _ => (None, None, None) + } DbDto.CommandCompletion( completion_offset = offset.toHexString, From f32346e0ceb574ac7a315f4c25880028ec41f9cc Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 15:56:02 +0200 Subject: [PATCH 32/42] Fix comment in CompletionFromTransaction.toApiDeduplicationPeriod --- .../main/scala/platform/store/CompletionFromTransaction.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index f2b1d9f32d66..e0b7c63da4ee 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -123,7 +123,7 @@ private[platform] object CompletionFromTransaction { maybeDeduplicationTimeNanos: Option[Int], maybeDeduplicationTimeSeconds: Option[Long], ): Completion.DeduplicationPeriod = - // The only invariant tha should hold, considering legacy data, is that either + // The only invariant that should hold, considering legacy data, is that either // the deduplication time seconds and nanos are both populated, or neither is. (maybeDeduplicationOffset, (maybeDeduplicationTimeSeconds, maybeDeduplicationTimeNanos)) match { case (Some(offset), _) => From 4ebd1e091d48d8e1cfbf147f2275b63c12d50f19 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 16:29:40 +0200 Subject: [PATCH 33/42] Fix CompletionFromTransaction.toApiCompletion --- .../store/CompletionFromTransaction.scala | 38 ++++++++++++++----- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index e0b7c63da4ee..29a3bffaa613 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -92,13 +92,13 @@ private[platform] object CompletionFromTransaction { maybeDeduplicationTimeSeconds: Option[Long], maybeDeduplicationTimeNanos: Option[Int], ): Completion = { - val deduplicationPeriod = toApiDeduplicationPeriod( + val maybeDeduplicationPeriod = toApiDeduplicationPeriod( maybeDeduplicationOffset, maybeDeduplicationTimeNanos, maybeDeduplicationTimeSeconds, ) - maybeSubmissionId match { - case Some(submissionId) => + (maybeSubmissionId, maybeDeduplicationPeriod) match { + case (Some(submissionId), Some(deduplicationPeriod)) => Completion( commandId = commandId, status = maybeStatus, @@ -107,13 +107,28 @@ private[platform] object CompletionFromTransaction { submissionId = submissionId, deduplicationPeriod = deduplicationPeriod, ) + case (Some(submissionId), _) => + Completion( + commandId = commandId, + status = maybeStatus, + transactionId = transactionId, + applicationId = applicationId, + submissionId = submissionId, + ) + case (None, Some(deduplicationPeriod)) => + Completion( + commandId = commandId, + status = maybeStatus, + transactionId = transactionId, + applicationId = applicationId, + deduplicationPeriod = deduplicationPeriod, + ) case _ => Completion( commandId = commandId, status = Some(OkStatus), transactionId = transactionId, applicationId = applicationId, - deduplicationPeriod = deduplicationPeriod, ) } } @@ -122,17 +137,20 @@ private[platform] object CompletionFromTransaction { maybeDeduplicationOffset: Option[String], maybeDeduplicationTimeNanos: Option[Int], maybeDeduplicationTimeSeconds: Option[Long], - ): Completion.DeduplicationPeriod = + ): Option[Completion.DeduplicationPeriod] = // The only invariant that should hold, considering legacy data, is that either // the deduplication time seconds and nanos are both populated, or neither is. (maybeDeduplicationOffset, (maybeDeduplicationTimeSeconds, maybeDeduplicationTimeNanos)) match { + case (None, (None, None)) => None case (Some(offset), _) => - Completion.DeduplicationPeriod.DeduplicationOffset(offset) + Some(Completion.DeduplicationPeriod.DeduplicationOffset(offset)) case (_, (Some(deduplicationTimeSeconds), Some(deduplicationTimeNanos))) => - Completion.DeduplicationPeriod.DeduplicationTime( - new Duration( - seconds = deduplicationTimeSeconds, - nanos = deduplicationTimeNanos, + Some( + Completion.DeduplicationPeriod.DeduplicationTime( + new Duration( + seconds = deduplicationTimeSeconds, + nanos = deduplicationTimeNanos, + ) ) ) case _ => From b1bc3d437090bfa6f35b5528cdb5c5ae3799e1c2 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 19:15:19 +0200 Subject: [PATCH 34/42] Fix deduplication_offset in Schema --- .../src/main/scala/platform/store/backend/common/Schema.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Schema.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Schema.scala index bd4ae4085064..d32ca263ac56 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Schema.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/Schema.scala @@ -223,7 +223,7 @@ private[backend] object AppendOnlySchema { "rejection_status_message" -> fieldStrategy.stringOptional(_.rejection_status_message), "rejection_status_details" -> fieldStrategy.byteaOptional(_.rejection_status_details), "submission_id" -> fieldStrategy.stringOptional(_.submission_id), - "deduplication_offset" -> fieldStrategy.stringOptional(_.submission_id), + "deduplication_offset" -> fieldStrategy.stringOptional(_.deduplication_offset), "deduplication_time_seconds" -> fieldStrategy.bigintOptional(_.deduplication_time_seconds), "deduplication_time_nanos" -> fieldStrategy.intOptional(_.deduplication_time_nanos), "deduplication_start" -> fieldStrategy.timestampOptional(_.deduplication_start), From 85ed113f8cd52dabc587e9490c668eca25dde2df Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 19:16:32 +0200 Subject: [PATCH 35/42] Make parameters explicit in CompletionFromTransaction and CompletionStorageBackendTemplate calls --- .../store/CompletionFromTransaction.scala | 40 +++++++++---------- .../CompletionStorageBackendTemplate.scala | 37 +++++++++-------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index 29a3bffaa613..7fff37c59d3f 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -37,14 +37,14 @@ private[platform] object CompletionFromTransaction { checkpoint = Some(toApiCheckpoint(recordTime, offset)), completions = Seq( toApiCompletion( - commandId, - transactionId, - applicationId, - Some(OkStatus), - maybeSubmissionId, - maybeDeduplicationOffset, - maybeDeduplicationTimeSeconds, - maybeDeduplicationTimeNanos, + commandId = commandId, + transactionId = transactionId, + applicationId = applicationId, + maybeStatus = Some(OkStatus), + maybeSubmissionId = maybeSubmissionId, + maybeDeduplicationOffset = maybeDeduplicationOffset, + maybeDeduplicationTimeSeconds = maybeDeduplicationTimeSeconds, + maybeDeduplicationTimeNanos = maybeDeduplicationTimeNanos, ) ), ) @@ -64,14 +64,14 @@ private[platform] object CompletionFromTransaction { checkpoint = Some(toApiCheckpoint(recordTime, offset)), completions = Seq( toApiCompletion( - commandId, - RejectionTransactionId, - applicationId, - Some(status), - maybeSubmissionId, - maybeDeduplicationOffset, - maybeDeduplicationTimeSeconds, - maybeDeduplicationTimeNanos, + commandId = commandId, + transactionId = RejectionTransactionId, + applicationId = applicationId, + maybeStatus = Some(status), + maybeSubmissionId = maybeSubmissionId, + maybeDeduplicationOffset = maybeDeduplicationOffset, + maybeDeduplicationTimeSeconds = maybeDeduplicationTimeSeconds, + maybeDeduplicationTimeNanos = maybeDeduplicationTimeNanos, ) ), ) @@ -93,9 +93,9 @@ private[platform] object CompletionFromTransaction { maybeDeduplicationTimeNanos: Option[Int], ): Completion = { val maybeDeduplicationPeriod = toApiDeduplicationPeriod( - maybeDeduplicationOffset, - maybeDeduplicationTimeNanos, - maybeDeduplicationTimeSeconds, + maybeDeduplicationOffset = maybeDeduplicationOffset, + maybeDeduplicationTimeSeconds = maybeDeduplicationTimeSeconds, + maybeDeduplicationTimeNanos = maybeDeduplicationTimeNanos, ) (maybeSubmissionId, maybeDeduplicationPeriod) match { case (Some(submissionId), Some(deduplicationPeriod)) => @@ -135,8 +135,8 @@ private[platform] object CompletionFromTransaction { private def toApiDeduplicationPeriod( maybeDeduplicationOffset: Option[String], - maybeDeduplicationTimeNanos: Option[Int], maybeDeduplicationTimeSeconds: Option[Long], + maybeDeduplicationTimeNanos: Option[Int], ): Option[Completion.DeduplicationPeriod] = // The only invariant that should hold, considering legacy data, is that either // the deduplication time seconds and nanos are both populated, or neither is. diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala index 6597b15c6feb..12952bd28725 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/common/CompletionStorageBackendTemplate.scala @@ -87,15 +87,15 @@ trait CompletionStorageBackendTemplate extends CompletionStorageBackend { case offset ~ recordTime ~ commandId ~ applicationId ~ submissionId ~ transactionId ~ deduplicationOffset ~ deduplicationTimeSeconds ~ deduplicationTimeNanos ~ _ => CompletionFromTransaction.acceptedCompletion( - recordTime, - offset, - commandId, - transactionId, - applicationId, - submissionId, - deduplicationOffset, - deduplicationTimeSeconds, - deduplicationTimeNanos, + recordTime = recordTime, + offset = offset, + commandId = commandId, + transactionId = transactionId, + applicationId = applicationId, + maybeSubmissionId = submissionId, + maybeDeduplicationOffset = deduplicationOffset, + maybeDeduplicationTimeSeconds = deduplicationTimeSeconds, + maybeDeduplicationTimeNanos = deduplicationTimeNanos, ) } @@ -118,15 +118,15 @@ trait CompletionStorageBackendTemplate extends CompletionStorageBackend { val status = buildStatusProto(rejectionStatusCode, rejectionStatusMessage, rejectionStatusDetails) CompletionFromTransaction.rejectedCompletion( - recordTime, - offset, - commandId, - status, - applicationId, - submissionId, - deduplicationOffset, - deduplicationTimeSeconds, - deduplicationTimeNanos, + recordTime = recordTime, + offset = offset, + commandId = commandId, + status = status, + applicationId = applicationId, + maybeSubmissionId = submissionId, + maybeDeduplicationOffset = deduplicationOffset, + maybeDeduplicationTimeSeconds = deduplicationTimeSeconds, + maybeDeduplicationTimeNanos = deduplicationTimeNanos, ) } @@ -150,5 +150,4 @@ trait CompletionStorageBackendTemplate extends CompletionStorageBackend { rejectionStatusDetails .map(stream => StatusDetails.parseFrom(stream).details) .getOrElse(Seq.empty) - } From 8da5af0f524989d1b718e849cfc81b7ad7b2daa8 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 19:18:50 +0200 Subject: [PATCH 36/42] Shorten error message in CompletionFromTransaction --- .../main/scala/platform/store/CompletionFromTransaction.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index 7fff37c59d3f..b5e29b0f89c8 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -155,7 +155,7 @@ private[platform] object CompletionFromTransaction { ) case _ => throw new IllegalArgumentException( - "One of deduplication time's seconds and nanos has been provided " + + "One of deduplication time seconds and nanos has been provided " + "but they must be either both provided or both absent" ) } From 166cd83f97bd05beba462679c8ae5016f2ddda2e Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 19:21:11 +0200 Subject: [PATCH 37/42] Fix StorageBackendTestsCompletions --- .../StorageBackendTestsCompletions.scala | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestsCompletions.scala b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestsCompletions.scala index 240b1092e9b1..d92b025f3a6b 100644 --- a/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestsCompletions.scala +++ b/ledger/participant-integration-api/src/test/lib/scala/platform/store/backend/StorageBackendTestsCompletions.scala @@ -3,9 +3,8 @@ package com.daml.platform.store.backend -import java.time.Duration - import com.daml.ledger.offset.Offset +import com.google.protobuf.duration.Duration import org.scalatest.Inside import org.scalatest.flatspec.AsyncFlatSpec import org.scalatest.matchers.should.Matchers @@ -99,7 +98,7 @@ private[backend] trait StorageBackendTestsCompletions completions should have length 2 val List(completionWithSubmissionId, completionWithoutSubmissionId) = completions completionWithSubmissionId.completions should have length 1 - completionWithSubmissionId.completions.head.submissionId should be(submissionId) + completionWithSubmissionId.completions.head.submissionId should be(someSubmissionId) completionWithoutSubmissionId.completions should have length 1 completionWithoutSubmissionId.completions.head.submissionId should be("") } @@ -107,14 +106,14 @@ private[backend] trait StorageBackendTestsCompletions it should "correctly persist and retrieve command deduplication offsets" in { val party = someParty - val anOffset = "someOffset" + val anOffsetHex = offset(0).toHexString val dtos = Vector( dtoConfiguration(offset(1)), dtoCompletion( offset(2), submitter = party, - deduplicationOffset = Some(anOffset), + deduplicationOffset = Some(anOffsetHex), ), dtoCompletion(offset(3), submitter = party, deduplicationOffset = None), ) @@ -132,7 +131,7 @@ private[backend] trait StorageBackendTestsCompletions completions completionWithDeduplicationOffset.completions should have length 1 completionWithDeduplicationOffset.completions.head.deduplicationPeriod.deduplicationOffset should be( - Some(anOffset) + Some(anOffsetHex) ) completionWithoutDeduplicationOffset.completions should have length 1 completionWithoutDeduplicationOffset.completions.head.deduplicationPeriod.deduplicationOffset should not be defined @@ -143,7 +142,7 @@ private[backend] trait StorageBackendTestsCompletions val party = someParty val seconds = 100L val nanos = 10 - val expectedDuration = Duration.ofSeconds(seconds).plusNanos(nanos.toLong) + val expectedDuration = Duration.of(seconds, nanos) val dtos = Vector( dtoConfiguration(offset(1)), @@ -173,8 +172,8 @@ private[backend] trait StorageBackendTestsCompletions val List(completionWithDeduplicationOffset, completionWithoutDeduplicationOffset) = completions completionWithDeduplicationOffset.completions should have length 1 - completionWithDeduplicationOffset.completions.head.deduplicationPeriod.deduplicationOffset should be( - expectedDuration + completionWithDeduplicationOffset.completions.head.deduplicationPeriod.deduplicationTime should be( + Some(expectedDuration) ) completionWithoutDeduplicationOffset.completions should have length 1 completionWithoutDeduplicationOffset.completions.head.deduplicationPeriod.deduplicationTime should not be defined @@ -186,6 +185,10 @@ private[backend] trait StorageBackendTestsCompletions val seconds = 100L val nanos = 10 + val expectedErrorMessage = + "One of deduplication time seconds and nanos has been provided " + + "but they must be either both provided or both absent" + val dtos1 = Vector( dtoConfiguration(offset(1)), dtoCompletion( @@ -204,11 +207,8 @@ private[backend] trait StorageBackendTestsCompletions backend.commandCompletions(offset(1), offset(2), someApplicationId, Set(party)) ).failed } yield { - result.getCause shouldBe an[IllegalArgumentException] - result.getCause.getMessage should be( - "One of deduplication time seconds and nanos has been provided " + - "but they must be either both provided or both absent" - ) + result shouldBe an[IllegalArgumentException] + result.getMessage should be(expectedErrorMessage) } val dtos2 = Vector( @@ -227,11 +227,8 @@ private[backend] trait StorageBackendTestsCompletions backend.commandCompletions(offset(2), offset(3), someApplicationId, Set(party)) ).failed } yield { - result.getCause shouldBe an[IllegalArgumentException] - result.getCause.getMessage should be( - "One of deduplication time seconds and nanos has been provided " + - "but they must be either both provided or both absent" - ) + result shouldBe an[IllegalArgumentException] + result.getMessage should be(expectedErrorMessage) } } } From 3b7a9e5a335eda99a31510aeaf9b776d0a97e486 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Mon, 30 Aug 2021 19:28:30 +0200 Subject: [PATCH 38/42] Fix CompletionFromTransaction --- .../main/scala/platform/store/CompletionFromTransaction.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index b5e29b0f89c8..3e750e035532 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -126,7 +126,7 @@ private[platform] object CompletionFromTransaction { case _ => Completion( commandId = commandId, - status = Some(OkStatus), + status = maybeStatus, transactionId = transactionId, applicationId = applicationId, ) From bde3d1ed9e0807e40435b5bf2c261f91b7c92cad Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Tue, 31 Aug 2021 09:48:06 +0200 Subject: [PATCH 39/42] Separate command dedup-related test cases in UpdateToDbDtoSpec --- .../store/backend/UpdateToDbDtoSpec.scala | 367 +++++++++++------- 1 file changed, 236 insertions(+), 131 deletions(-) diff --git a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala index 0d395e78e6c3..db2389f435d2 100644 --- a/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala +++ b/ledger/participant-integration-api/src/test/suite/scala/platform/store/backend/UpdateToDbDtoSpec.scala @@ -239,78 +239,42 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { ) } - val deduplicationPeriods = Table( - ( - "Deduplication period", - "Expected deduplication offset", - "Expected deduplication time seconds", - "Expected deduplication time nanos", - ), - (None, None, None, None), - ( - Some(DeduplicationOffset(Offset.beforeBegin)), - Some(Offset.beforeBegin.toHexString), - None, - None, - ), - ( - Some(DeduplicationDuration(Duration.ofDays(1L).plusNanos(100))), - None, - Some(Duration.ofDays(1L).toMinutes * 60L), - Some(100), - ), - ) - "handle CommandRejected" in { val status = StatusProto.of(Status.Code.ABORTED.value(), "test reason", Seq.empty) - forAll(deduplicationPeriods) { - case ( - deduplicationPeriod, - expectedDeduplicationOffset, - expectedDeduplicationTimeSeconds, - expectedDeduplicationTimeNanos, - ) => - val completionInfo = state.CompletionInfo( - actAs = List(someParty), - applicationId = someApplicationId, - commandId = someCommandId, - optDeduplicationPeriod = deduplicationPeriod, - submissionId = someSubmissionId, - ) - val update = state.Update.CommandRejected( - someRecordTime, - completionInfo, - state.Update.CommandRejected.FinalReason(status), - ) - val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( - someOffset - )(update).toList + val completionInfo = someCompletionInfo + val update = state.Update.CommandRejected( + someRecordTime, + completionInfo, + state.Update.CommandRejected.FinalReason(status), + ) + val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( + someOffset + )(update).toList - dtos should contain theSameElementsInOrderAs List( - DbDto.CommandCompletion( - completion_offset = someOffset.toHexString, - record_time = someRecordTime.toInstant, - application_id = someApplicationId, - submitters = Set(someParty), - command_id = someCommandId, - transaction_id = None, - rejection_status_code = Some(status.code), - rejection_status_message = Some(status.message), - rejection_status_details = Some(StatusDetails.of(status.details).toByteArray), - submission_id = Some(someSubmissionId), - deduplication_offset = expectedDeduplicationOffset, - deduplication_time_seconds = expectedDeduplicationTimeSeconds, - deduplication_time_nanos = expectedDeduplicationTimeNanos, - deduplication_start = None, - ), - DbDto.CommandDeduplication( - DeduplicationKeyMaker.make( - domain.CommandId(completionInfo.commandId), - completionInfo.actAs, - ) - ), + dtos should contain theSameElementsInOrderAs List( + DbDto.CommandCompletion( + completion_offset = someOffset.toHexString, + record_time = someRecordTime.toInstant, + application_id = someApplicationId, + submitters = Set(someParty), + command_id = someCommandId, + transaction_id = None, + rejection_status_code = Some(status.code), + rejection_status_message = Some(status.message), + rejection_status_details = Some(StatusDetails.of(status.details).toByteArray), + submission_id = Some(someSubmissionId), + deduplication_offset = None, + deduplication_time_seconds = None, + deduplication_time_nanos = None, + deduplication_start = None, + ), + DbDto.CommandDeduplication( + DeduplicationKeyMaker.make( + domain.CommandId(completionInfo.commandId), + completionInfo.actAs, ) - } + ), + ) } "handle TransactionAccepted (single create node)" in { @@ -326,70 +290,62 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { ) val createNodeId = builder.add(createNode) val transaction = builder.buildCommitted() - forAll(deduplicationPeriods) { - case ( - deduplicationPeriod, - expectedDeduplicationOffset, - expectedDeduplicationTimeSeconds, - expectedDeduplicationTimeNanos, - ) => - val completionInfo = someCompletionInfo.copy(optDeduplicationPeriod = deduplicationPeriod) - val update = state.Update.TransactionAccepted( - optCompletionInfo = Some(completionInfo), - transactionMeta = transactionMeta, - transaction = transaction, - transactionId = Ref.TransactionId.assertFromString("TransactionId"), - recordTime = someRecordTime, - divulgedContracts = List.empty, - blindingInfo = None, - ) - val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( - someOffset - )(update).toList + val completionInfo = someCompletionInfo + val update = state.Update.TransactionAccepted( + optCompletionInfo = Some(completionInfo), + transactionMeta = transactionMeta, + transaction = transaction, + transactionId = Ref.TransactionId.assertFromString("TransactionId"), + recordTime = someRecordTime, + divulgedContracts = List.empty, + blindingInfo = None, + ) + val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( + someOffset + )(update).toList - dtos should contain theSameElementsInOrderAs List( - DbDto.EventCreate( - event_offset = Some(someOffset.toHexString), - transaction_id = Some(update.transactionId), - ledger_effective_time = Some(transactionMeta.ledgerEffectiveTime.toInstant), - command_id = Some(completionInfo.commandId), - workflow_id = transactionMeta.workflowId, - application_id = Some(completionInfo.applicationId), - submitters = Some(completionInfo.actAs.toSet), - node_index = Some(createNodeId.index), - event_id = Some(EventId(update.transactionId, createNodeId).toLedgerString), - contract_id = createNode.coid.coid, - template_id = Some(createNode.coinst.template.toString), - flat_event_witnesses = Set("signatory", "observer"), // stakeholders - tree_event_witnesses = Set("signatory", "observer"), // informees - create_argument = Some(emptyArray), - create_signatories = Some(Set("signatory")), - create_observers = Some(Set("observer")), - create_agreement_text = None, - create_key_value = None, - create_key_hash = None, - create_argument_compression = compressionAlgorithmId, - create_key_value_compression = None, - event_sequential_id = 0, - ), - DbDto.CommandCompletion( - completion_offset = someOffset.toHexString, - record_time = update.recordTime.toInstant, - application_id = completionInfo.applicationId, - submitters = completionInfo.actAs.toSet, - command_id = completionInfo.commandId, - transaction_id = Some(update.transactionId), - rejection_status_code = None, - rejection_status_message = None, - rejection_status_details = None, - submission_id = Some(someSubmissionId), - deduplication_offset = expectedDeduplicationOffset, - deduplication_time_seconds = expectedDeduplicationTimeSeconds, - deduplication_time_nanos = expectedDeduplicationTimeNanos, - deduplication_start = None, - ), - ) - } + dtos should contain theSameElementsInOrderAs List( + DbDto.EventCreate( + event_offset = Some(someOffset.toHexString), + transaction_id = Some(update.transactionId), + ledger_effective_time = Some(transactionMeta.ledgerEffectiveTime.toInstant), + command_id = Some(completionInfo.commandId), + workflow_id = transactionMeta.workflowId, + application_id = Some(completionInfo.applicationId), + submitters = Some(completionInfo.actAs.toSet), + node_index = Some(createNodeId.index), + event_id = Some(EventId(update.transactionId, createNodeId).toLedgerString), + contract_id = createNode.coid.coid, + template_id = Some(createNode.coinst.template.toString), + flat_event_witnesses = Set("signatory", "observer"), // stakeholders + tree_event_witnesses = Set("signatory", "observer"), // informees + create_argument = Some(emptyArray), + create_signatories = Some(Set("signatory")), + create_observers = Some(Set("observer")), + create_agreement_text = None, + create_key_value = None, + create_key_hash = None, + create_argument_compression = compressionAlgorithmId, + create_key_value_compression = None, + event_sequential_id = 0, + ), + DbDto.CommandCompletion( + completion_offset = someOffset.toHexString, + record_time = update.recordTime.toInstant, + application_id = completionInfo.applicationId, + submitters = completionInfo.actAs.toSet, + command_id = completionInfo.commandId, + transaction_id = Some(update.transactionId), + rejection_status_code = None, + rejection_status_message = None, + rejection_status_details = None, + submission_id = Some(someSubmissionId), + deduplication_offset = None, + deduplication_time_seconds = None, + deduplication_time_nanos = None, + deduplication_start = None, + ), + ) } "handle TransactionAccepted (single create node with agreement text)" in { @@ -1270,6 +1226,155 @@ class UpdateToDbDtoSpec extends AnyWordSpec with Matchers { ) } + val deduplicationPeriods = Table( + ( + "Deduplication period", + "Expected deduplication offset", + "Expected deduplication time seconds", + "Expected deduplication time nanos", + ), + (None, None, None, None), + ( + Some(DeduplicationOffset(Offset.beforeBegin)), + Some(Offset.beforeBegin.toHexString), + None, + None, + ), + ( + Some(DeduplicationDuration(Duration.ofDays(1L).plusNanos(100))), + None, + Some(Duration.ofDays(1L).toMinutes * 60L), + Some(100), + ), + ) + + "handle CommandRejected (all deduplication data)" in { + val status = StatusProto.of(Status.Code.ABORTED.value(), "test reason", Seq.empty) + forAll(deduplicationPeriods) { + case ( + deduplicationPeriod, + expectedDeduplicationOffset, + expectedDeduplicationTimeSeconds, + expectedDeduplicationTimeNanos, + ) => { + val completionInfo = someCompletionInfo.copy(optDeduplicationPeriod = deduplicationPeriod) + val update = state.Update.CommandRejected( + someRecordTime, + completionInfo, + state.Update.CommandRejected.FinalReason(status), + ) + val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( + someOffset + )(update).toList + + dtos should contain theSameElementsInOrderAs List( + DbDto.CommandCompletion( + completion_offset = someOffset.toHexString, + record_time = someRecordTime.toInstant, + application_id = someApplicationId, + submitters = Set(someParty), + command_id = someCommandId, + transaction_id = None, + rejection_status_code = Some(status.code), + rejection_status_message = Some(status.message), + rejection_status_details = Some(StatusDetails.of(status.details).toByteArray), + submission_id = Some(someSubmissionId), + deduplication_offset = expectedDeduplicationOffset, + deduplication_time_seconds = expectedDeduplicationTimeSeconds, + deduplication_time_nanos = expectedDeduplicationTimeNanos, + deduplication_start = None, + ), + DbDto.CommandDeduplication( + DeduplicationKeyMaker.make( + domain.CommandId(completionInfo.commandId), + completionInfo.actAs, + ) + ), + ) + } + } + } + + "handle TransactionAccepted (all deduplication data)" in { + val transactionMeta = someTransactionMeta + val builder = new TransactionBuilder() + val createNode = builder.create( + id = builder.newCid, + template = "pkgid:M:T", + argument = Value.ValueUnit, + signatories = List("signatory"), + observers = List("observer"), + key = None, + ) + val createNodeId = builder.add(createNode) + val transaction = builder.buildCommitted() + + forAll(deduplicationPeriods) { + case ( + deduplicationPeriod, + expectedDeduplicationOffset, + expectedDeduplicationTimeSeconds, + expectedDeduplicationTimeNanos, + ) => { + val completionInfo = someCompletionInfo.copy(optDeduplicationPeriod = deduplicationPeriod) + val update = state.Update.TransactionAccepted( + optCompletionInfo = Some(completionInfo), + transactionMeta = transactionMeta, + transaction = transaction, + transactionId = Ref.TransactionId.assertFromString("TransactionId"), + recordTime = someRecordTime, + divulgedContracts = List.empty, + blindingInfo = None, + ) + val dtos = UpdateToDbDto(someParticipantId, valueSerialization, compressionStrategy)( + someOffset + )(update).toList + + dtos should contain theSameElementsInOrderAs List( + DbDto.EventCreate( + event_offset = Some(someOffset.toHexString), + transaction_id = Some(update.transactionId), + ledger_effective_time = Some(transactionMeta.ledgerEffectiveTime.toInstant), + command_id = Some(completionInfo.commandId), + workflow_id = transactionMeta.workflowId, + application_id = Some(completionInfo.applicationId), + submitters = Some(completionInfo.actAs.toSet), + node_index = Some(createNodeId.index), + event_id = Some(EventId(update.transactionId, createNodeId).toLedgerString), + contract_id = createNode.coid.coid, + template_id = Some(createNode.coinst.template.toString), + flat_event_witnesses = Set("signatory", "observer"), // stakeholders + tree_event_witnesses = Set("signatory", "observer"), // informees + create_argument = Some(emptyArray), + create_signatories = Some(Set("signatory")), + create_observers = Some(Set("observer")), + create_agreement_text = None, + create_key_value = None, + create_key_hash = None, + create_argument_compression = compressionAlgorithmId, + create_key_value_compression = None, + event_sequential_id = 0, + ), + DbDto.CommandCompletion( + completion_offset = someOffset.toHexString, + record_time = update.recordTime.toInstant, + application_id = completionInfo.applicationId, + submitters = completionInfo.actAs.toSet, + command_id = completionInfo.commandId, + transaction_id = Some(update.transactionId), + rejection_status_code = None, + rejection_status_message = None, + rejection_status_details = None, + submission_id = Some(someSubmissionId), + deduplication_offset = expectedDeduplicationOffset, + deduplication_time_seconds = expectedDeduplicationTimeSeconds, + deduplication_time_nanos = expectedDeduplicationTimeNanos, + deduplication_start = None, + ), + ) + } + } + } } } From 4a94562fc8d44858c5db53ad2ccc52df66ca0646 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Tue, 31 Aug 2021 13:16:16 +0200 Subject: [PATCH 40/42] Simplify further UpdateToDbDto.commandCompletion --- .../main/scala/platform/store/backend/UpdateToDbDto.scala | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala index 598bf96bba30..449c507c627f 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala @@ -275,10 +275,7 @@ object UpdateToDbDto { (Some(offset.toHexString), None, None) case DeduplicationDuration(duration) => (None, Some(duration.getSeconds), Some(duration.getNano)) - } match { - case Some(value) => value - case _ => (None, None, None) - } + }.getOrElse((None, None, None)) DbDto.CommandCompletion( completion_offset = offset.toHexString, From 2749c75958658b3b4a184770ee004839ee73f2b6 Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Tue, 31 Aug 2021 13:28:34 +0200 Subject: [PATCH 41/42] Simplify CompletionFromTransaction.toApiCompletion --- .../store/CompletionFromTransaction.scala | 39 +++++++------------ 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala index 3e750e035532..291061998a3e 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/CompletionFromTransaction.scala @@ -82,7 +82,7 @@ private[platform] object CompletionFromTransaction { offset = Some(LedgerOffset.of(LedgerOffset.Value.Absolute(offset.toApiString))), ) - private def toApiCompletion( + private[store] def toApiCompletion( commandId: String, transactionId: String, applicationId: String, @@ -92,6 +92,12 @@ private[platform] object CompletionFromTransaction { maybeDeduplicationTimeSeconds: Option[Long], maybeDeduplicationTimeNanos: Option[Int], ): Completion = { + val completionWithMandatoryFields = Completion( + commandId = commandId, + status = maybeStatus, + transactionId = transactionId, + applicationId = applicationId, + ) val maybeDeduplicationPeriod = toApiDeduplicationPeriod( maybeDeduplicationOffset = maybeDeduplicationOffset, maybeDeduplicationTimeSeconds = maybeDeduplicationTimeSeconds, @@ -99,37 +105,20 @@ private[platform] object CompletionFromTransaction { ) (maybeSubmissionId, maybeDeduplicationPeriod) match { case (Some(submissionId), Some(deduplicationPeriod)) => - Completion( - commandId = commandId, - status = maybeStatus, - transactionId = transactionId, - applicationId = applicationId, + completionWithMandatoryFields.copy( submissionId = submissionId, deduplicationPeriod = deduplicationPeriod, ) - case (Some(submissionId), _) => - Completion( - commandId = commandId, - status = maybeStatus, - transactionId = transactionId, - applicationId = applicationId, - submissionId = submissionId, + case (Some(submissionId), None) => + completionWithMandatoryFields.copy( + submissionId = submissionId ) case (None, Some(deduplicationPeriod)) => - Completion( - commandId = commandId, - status = maybeStatus, - transactionId = transactionId, - applicationId = applicationId, - deduplicationPeriod = deduplicationPeriod, + completionWithMandatoryFields.copy( + deduplicationPeriod = deduplicationPeriod ) case _ => - Completion( - commandId = commandId, - status = maybeStatus, - transactionId = transactionId, - applicationId = applicationId, - ) + completionWithMandatoryFields } } From db74dd0077e09e3b39bd58276e1fa735edc9778e Mon Sep 17 00:00:00 2001 From: Fabio Tudone Date: Tue, 31 Aug 2021 13:46:14 +0200 Subject: [PATCH 42/42] Format --- .../src/main/scala/platform/store/backend/UpdateToDbDto.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala index 449c507c627f..71696945c3b8 100644 --- a/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala +++ b/ledger/participant-integration-api/src/main/scala/platform/store/backend/UpdateToDbDto.scala @@ -275,7 +275,8 @@ object UpdateToDbDto { (Some(offset.toHexString), None, None) case DeduplicationDuration(duration) => (None, Some(duration.getSeconds), Some(duration.getNano)) - }.getOrElse((None, None, None)) + } + .getOrElse((None, None, None)) DbDto.CommandCompletion( completion_offset = offset.toHexString,