@distrohelena/canton-typescript-sdk 0.1.45 → 0.1.47
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/types/canton-logger.js +2 -0
- package/dist/cjs/index.js +10 -6
- package/dist/cjs/query/canton-manager.js +4 -1
- package/dist/cjs/query/errors/contract-cache-required-error.js +21 -0
- package/dist/cjs/query/errors/history-walk-required-error.js +20 -0
- package/dist/cjs/query/grpc/grpc-contract-cache.js +293 -9
- package/dist/cjs/query/grpc/grpc-query-client.js +113 -130
- package/dist/cjs/query/grpc/grpc-query-snapshot-reader.js +5 -3
- package/dist/cjs/query/pqs/pqs-query-client.js +3 -0
- package/dist/core/types/canton-logger.d.ts +4 -0
- package/dist/core/types/canton-logger.js +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/query/canton-manager-options.d.ts +26 -0
- package/dist/query/canton-manager.js +4 -1
- package/dist/query/errors/contract-cache-required-error.d.ts +11 -0
- package/dist/query/errors/contract-cache-required-error.js +17 -0
- package/dist/query/errors/history-walk-required-error.d.ts +10 -0
- package/dist/query/errors/history-walk-required-error.js +16 -0
- package/dist/query/grpc/grpc-contract-cache.d.ts +40 -3
- package/dist/query/grpc/grpc-contract-cache.js +293 -9
- package/dist/query/grpc/grpc-query-client.d.ts +11 -1
- package/dist/query/grpc/grpc-query-client.js +113 -130
- package/dist/query/grpc/grpc-query-snapshot-reader.d.ts +4 -0
- package/dist/query/grpc/grpc-query-snapshot-reader.js +5 -3
- package/dist/query/grpc/grpc-relation-mapper.d.ts +3 -1
- package/dist/query/pqs/pqs-query-client.d.ts +2 -1
- package/dist/query/pqs/pqs-query-client.js +3 -0
- package/dist/query/query-client.d.ts +20 -0
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.
|
|
5
|
-
exports.
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
8
|
-
exports.EndpointNotConfiguredError = exports.ObjectDisposedError = exports.NotSupportedError = exports.ConflictError = exports.TimeoutError = exports.SigningError = exports.GrpcTransportError = exports.TransportError = exports.AuthorizationError = exports.AuthenticationError = exports.ActiveContractsTraversalError = exports.ValidationError = exports.CantonError = exports.SynchronizerTime = exports.ParticipantNodeStatus = exports.PartyDetails = exports.UploadDarFileResponse = exports.GrantUserRightsResponse = exports.SubmitCommandResponse = exports.TopologyListPartiesResponse = exports.TopologyListVettedPackagesResponse = exports.ListVettedPackagesResponse = exports.ListUsersResponse = exports.ListUserRightsResponse = exports.ListSynchronizerTrustCertificateResponse = exports.ListSynchronizerParametersStateResponse = exports.ListSequencingParametersStateResponse = exports.ListSequencerSynchronizerStateResponse = exports.ListPartyToParticipantResponse = exports.ListPartyToKeyMappingResponse = exports.ListPartyHostingLimitsResponse = exports.ListParticipantSynchronizerPermissionResponse = exports.ListKeyOwnersResponse = exports.ListKnownPartiesResponse = exports.ListKnownPackagesResponse = exports.ListIdentityProviderConfigsResponse = exports.ListDarsResponse = exports.ListPackagesResponse = exports.ListOwnerToKeyMappingResponse = exports.ListNamespaceDelegationResponse = exports.ListMediatorSynchronizerStateResponse = exports.ListLsuSequencerConnectionSuccessorResponse = exports.ListLsuAnnouncementResponse = exports.ListDecentralizedNamespaceDefinitionResponse = void 0;
|
|
3
|
+
exports.ExternalPartyOnboardingTransaction = exports.ExternalPartyCryptoKeyFormat = exports.TransportKind = exports.TransportCapability = exports.SignCommandResult = exports.SignCommandRequest = exports.BearerTokenAuthProvider = exports.SynchronizerConnectivityServiceClient = exports.VersionServiceClient = exports.UserManagementServiceClient = exports.UpdateServiceClient = exports.TrafficControlServiceClient = exports.TopologyManagerWriteServiceClient = exports.TopologyManagerReadServiceClient = exports.TopologyAggregationServiceClient = exports.StateServiceClient = exports.ResourceManagementServiceClient = exports.PruningServiceClient = exports.PartyManagementServiceClient = exports.ParticipantStatusServiceClient = exports.ParticipantRepairServiceClient = exports.ParticipantPackageServiceClient = exports.ParticipantPartyManagementServiceClient = exports.ParticipantInspectionServiceClient = exports.PackageServiceClient = exports.PackageManagementServiceClient = exports.IdentityProviderConfigServiceClient = exports.IdentityInitializationServiceClient = exports.HealthServiceClient = exports.EventQueryServiceClient = exports.ContractServiceClient = exports.CommandSubmissionServiceClient = exports.CommandServiceClient = exports.CommandInspectionServiceClient = exports.CommandCompletionServiceClient = exports.ExternalPartyActivationResponse = exports.ExternalPartyActivationRequest = exports.ExternalPartyActivationClient = exports.CantonClientOptions = exports.CantonHashingClient = exports.PqsSchemaProfileError = exports.PqsQueryError = exports.QuerySnapshotIncompleteError = exports.QueryCapabilityError = exports.HistoryWalkRequiredError = exports.ContractCacheRequiredError = exports.MemoryQueryCache = exports.QuerySource = exports.CantonManager = exports.CantonClient = void 0;
|
|
4
|
+
exports.ActiveContractsTraversalOptions = exports.OperationDeadline = exports.RequestOptions = exports.ReceivedAcsCommitmentState = exports.ReceivedAcsCommitmentPerSynchronizer = exports.ReceivedAcsCommitment = exports.PruningSchedule = exports.ParticipantPruningSchedule = exports.PendingOperationMetadata = exports.PackageStatus = exports.CantonHashPurpose = exports.HashFunction = exports.PackageFormat = exports.OffsetCheckpoint = exports.ObjectMeta = exports.LedgerUser = exports.KnownPackageDetails = exports.IdentityProviderConfig = exports.UserRightKind = exports.EventStreamKind = exports.DamlParty = exports.DamlNumeric = exports.ContractCreated = exports.ContractArchived = exports.CounterParticipantInfo = exports.CommitmentTimeRange = exports.CommitmentInterval = exports.Completion = exports.CommandUpdateSummary = exports.CommandTiming = exports.CommandStatus = exports.CommandState = exports.CommandRequestStatistics = exports.CommandInspectionContract = exports.ConnectedSynchronizerStatus = exports.ConnectedSynchronizer = exports.ConnectedSynchronizerHealth = exports.AdminTopologyQueueStatus = exports.AdminNotInitializedStatus = exports.AdminNotInitializedExternalInputKind = exports.AdminNodeStatus = exports.AdminComponentStatus = exports.AdminComponentHealthKind = exports.SubmissionMode = exports.GrpcChannelSecurity = exports.ExternalPartySigningPublicKey = exports.ExternalPartySigningKeySpec = exports.ExternalPartySigningAlgorithmSpec = exports.ExternalPartySignatureFormat = exports.ExternalPartySignature = void 0;
|
|
5
|
+
exports.TopologyPartyParticipantSynchronizerPermission = exports.TopologyPartyParticipant = exports.TopologyMappingResult = exports.TopologyMappingOperation = exports.TopologyMappingCode = exports.TopologyKeyOwnerResult = exports.MultiTopologyTransactionSignature = exports.GeneratedTopologyTransaction = exports.TopologyEncryptionPublicKey = exports.ExternalTopologySignature = exports.TopologyDuration = exports.TopologyBaseResult = exports.TopologyBaseQuery = exports.SynchronizerTrustCertificate = exports.SequencerSynchronizerState = exports.PartyToParticipantParticipant = exports.PartyToParticipantOnboarding = exports.PartyToParticipant = exports.PartyToKeyMapping = exports.PartyHostingLimits = exports.ParticipantSynchronizerPermission = exports.ParticipantSynchronizerLimits = exports.ParticipantPermission = exports.OwnerToKeyMapping = exports.OnboardingRestriction = exports.NamespaceDelegationRestrictionKind = exports.NamespaceDelegationRestriction = exports.NamespaceDelegation = exports.MediatorSynchronizerState = exports.LsuSequencerConnectionSuccessor = exports.LsuSequencerConnection = exports.LsuAnnouncement = exports.DynamicSynchronizerParameters = exports.DynamicSequencingParameters = exports.DecentralizedNamespaceDefinition = exports.AcsCommitmentsCatchUpConfig = exports.WaitCommitmentsSetup = exports.TopologyStateFilter = exports.SynchronizerTimeRange = exports.SlowCounterParticipantSynchronizerConfig = exports.SentAcsCommitmentState = exports.SentAcsCommitmentPerSynchronizer = exports.SentAcsCommitment = exports.SdkCommand = exports.SafeToPruneCommitmentState = exports.ParticipantPackageDescription = exports.ParticipantModuleDescription = exports.ParticipantDarDescription = exports.PackageReference = exports.PackageMetadataFilter = void 0;
|
|
6
|
+
exports.GetCommandStatusRequest = exports.GetActiveContractsRequest = exports.GetActiveContractsPageRequest = exports.GetDarRequest = exports.GetDarContentsRequest = exports.AssembleSignedTopologyTransactionsRequest = exports.SubmitCommandTransactionResponse = exports.DamlRecord = exports.DamlEnum = exports.DamlVariant = exports.DamlGenMap = exports.DamlTextMap = exports.DamlTimestamp = exports.DamlDate = exports.DamlUnit = exports.DamlContractId = exports.PreparedCommandSubmission = exports.DisclosedContract = exports.AllocatePartyRequest = exports.CreateExternalPartyRequest = exports.PreparedDecentralizedParty = exports.FinalizeDecentralizedPartyRequest = exports.CreateDecentralizedPartyRequest = exports.ExerciseCommand = exports.ExerciseByKeyCommand = exports.CreateCommand = exports.CreateAndExerciseCommand = exports.SignedTopologyTransaction = exports.PreparedTopologyTransaction = exports.VettedPackages = exports.VettedPackage = exports.TrafficControlParameters = exports.TopologyVettedPackages = exports.TopologyVettedPackage = exports.TopologyTransactions = exports.TopologyTransactionItem = exports.TopologyTransactionSignature = exports.TopologyTimeRange = exports.TopologyStoreTemporary = exports.TopologyStoreSynchronizer = exports.TopologyStoreKind = exports.TopologyStoreId = exports.TopologyStoreAuthorized = exports.TopologySigningPublicKey = exports.TopologySigningKeysWithThreshold = exports.TopologySignatureFormat = exports.TopologySignatureDelegation = exports.TopologyForceFlag = exports.TopologyPublicKey = exports.TopologyPartyResult = void 0;
|
|
7
|
+
exports.GetPartiesResponse = exports.GetPackageStatusResponse = exports.GetPackageResponse = exports.GetLedgerEndResponse = exports.GetLatestPrunedOffsetsResponse = exports.GetIdentityProviderConfigResponse = exports.GetCommandStatusResponse = exports.GetConnectedSynchronizersResponse = exports.GetActiveContractsPageResponse = exports.GetDarResponse = exports.GetDarContentsResponse = exports.AllocatePartyResponse = exports.UploadDarFileRequest = exports.GrantUserRightsRequest = exports.WaitForPartyHostingRequest = exports.TopologyListPartiesRequest = exports.SubmitCommandsRequest = exports.ListUsersRequest = exports.ListUserRightsRequest = exports.ListKeyOwnersRequest = exports.ListIdentityProviderConfigsRequest = exports.ListDarsRequest = exports.ListKnownPackagesRequest = exports.ListKnownPartiesRequest = exports.TopologyListVettedPackagesRequest = exports.ListSynchronizerTrustCertificateRequest = exports.ListSynchronizerParametersStateRequest = exports.ListSequencingParametersStateRequest = exports.ListSequencerSynchronizerStateRequest = exports.ListPartyToParticipantRequest = exports.ListPartyToKeyMappingRequest = exports.ListVettedPackagesRequest = exports.ListPartyHostingLimitsRequest = exports.ListParticipantSynchronizerPermissionRequest = exports.ListOwnerToKeyMappingRequest = exports.ListNamespaceDelegationRequest = exports.ListMediatorSynchronizerStateRequest = exports.ListLsuSequencerConnectionSuccessorRequest = exports.ListLsuAnnouncementRequest = exports.GetUserRequest = exports.GetPartiesRequest = exports.GetLedgerEndRequest = exports.GetLatestPrunedOffsetsRequest = exports.GetConnectedSynchronizersRequest = exports.GetPackageStatusRequest = exports.GetPackageRequest = exports.GetIdentityProviderConfigRequest = exports.ListDecentralizedNamespaceDefinitionRequest = exports.ListAllRequest = exports.ListPackagesRequest = void 0;
|
|
8
|
+
exports.EndpointNotConfiguredError = exports.ObjectDisposedError = exports.NotSupportedError = exports.ConflictError = exports.TimeoutError = exports.SigningError = exports.GrpcTransportError = exports.TransportError = exports.AuthorizationError = exports.AuthenticationError = exports.ActiveContractsTraversalError = exports.ValidationError = exports.CantonError = exports.SynchronizerTime = exports.ParticipantNodeStatus = exports.PartyDetails = exports.UploadDarFileResponse = exports.GrantUserRightsResponse = exports.SubmitCommandResponse = exports.TopologyListPartiesResponse = exports.TopologyListVettedPackagesResponse = exports.ListVettedPackagesResponse = exports.ListUsersResponse = exports.ListUserRightsResponse = exports.ListSynchronizerTrustCertificateResponse = exports.ListSynchronizerParametersStateResponse = exports.ListSequencingParametersStateResponse = exports.ListSequencerSynchronizerStateResponse = exports.ListPartyToParticipantResponse = exports.ListPartyToKeyMappingResponse = exports.ListPartyHostingLimitsResponse = exports.ListParticipantSynchronizerPermissionResponse = exports.ListKeyOwnersResponse = exports.ListKnownPartiesResponse = exports.ListKnownPackagesResponse = exports.ListIdentityProviderConfigsResponse = exports.ListDarsResponse = exports.ListPackagesResponse = exports.ListOwnerToKeyMappingResponse = exports.ListNamespaceDelegationResponse = exports.ListMediatorSynchronizerStateResponse = exports.ListLsuSequencerConnectionSuccessorResponse = exports.ListLsuAnnouncementResponse = exports.ListDecentralizedNamespaceDefinitionResponse = exports.ListAllResponse = exports.GetUserResponse = void 0;
|
|
9
9
|
var canton_client_js_1 = require("./client/canton-client.js");
|
|
10
10
|
Object.defineProperty(exports, "CantonClient", { enumerable: true, get: function () { return canton_client_js_1.CantonClient; } });
|
|
11
11
|
var canton_manager_js_1 = require("./query/canton-manager.js");
|
|
@@ -14,6 +14,10 @@ var query_source_js_1 = require("./query/query-source.js");
|
|
|
14
14
|
Object.defineProperty(exports, "QuerySource", { enumerable: true, get: function () { return query_source_js_1.QuerySource; } });
|
|
15
15
|
var memory_query_cache_js_1 = require("./query/cache/memory-query-cache.js");
|
|
16
16
|
Object.defineProperty(exports, "MemoryQueryCache", { enumerable: true, get: function () { return memory_query_cache_js_1.MemoryQueryCache; } });
|
|
17
|
+
var contract_cache_required_error_js_1 = require("./query/errors/contract-cache-required-error.js");
|
|
18
|
+
Object.defineProperty(exports, "ContractCacheRequiredError", { enumerable: true, get: function () { return contract_cache_required_error_js_1.ContractCacheRequiredError; } });
|
|
19
|
+
var history_walk_required_error_js_1 = require("./query/errors/history-walk-required-error.js");
|
|
20
|
+
Object.defineProperty(exports, "HistoryWalkRequiredError", { enumerable: true, get: function () { return history_walk_required_error_js_1.HistoryWalkRequiredError; } });
|
|
17
21
|
var query_capability_error_js_1 = require("./query/errors/query-capability-error.js");
|
|
18
22
|
Object.defineProperty(exports, "QueryCapabilityError", { enumerable: true, get: function () { return query_capability_error_js_1.QueryCapabilityError; } });
|
|
19
23
|
var query_snapshot_incomplete_error_js_1 = require("./query/errors/query-snapshot-incomplete-error.js");
|
|
@@ -37,9 +37,12 @@ class CantonManager {
|
|
|
37
37
|
updateService: this.grpc.updateService,
|
|
38
38
|
packageService: this.grpc.packageService,
|
|
39
39
|
endpointScope: options.grpc.ledgerEndpoint ?? "ledger",
|
|
40
|
+
incrementalHistory: options.incrementalHistory,
|
|
41
|
+
walkHistory: options.walkHistory,
|
|
42
|
+
logger: options.logger,
|
|
40
43
|
contractCache: options.cache === undefined
|
|
41
44
|
? undefined
|
|
42
|
-
: new grpc_contract_cache_js_1.GrpcContractCache(this.grpc.stateService, options.cache.store, options.cache.ttlMs, options.grpc.ledgerEndpoint ?? "ledger"),
|
|
45
|
+
: new grpc_contract_cache_js_1.GrpcContractCache(this.grpc.stateService, options.cache.store, options.cache.ttlMs, options.grpc.ledgerEndpoint ?? "ledger", undefined, options.cache.maxPageSize, this.grpc.updateService, { enabled: options.cache.betaDeltaRefresh, maxOffsetGap: options.cache.maxDeltaOffsetGap, maxUpdates: options.cache.maxDeltaUpdates }),
|
|
43
46
|
});
|
|
44
47
|
}
|
|
45
48
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ContractCacheRequiredError = void 0;
|
|
4
|
+
const canton_error_js_1 = require("../../core/errors/canton-error.js");
|
|
5
|
+
/**
|
|
6
|
+
* gRPC typed queries never download the ACS implicitly: reading active contracts requires an explicitly
|
|
7
|
+
* warmed contract cache, so the (potentially very large) ACS transfer only ever happens when the caller
|
|
8
|
+
* asks for it via cacheContracts(). Thrown when an ACS-backed query runs without a warm entry for its
|
|
9
|
+
* party scope.
|
|
10
|
+
*/
|
|
11
|
+
class ContractCacheRequiredError extends canton_error_js_1.CantonError {
|
|
12
|
+
parties;
|
|
13
|
+
constructor(parties) {
|
|
14
|
+
super("gRPC active-contract queries require a warmed contract cache. Configure the cache "
|
|
15
|
+
+ "({ store, ttlMs }) and call cacheContracts("
|
|
16
|
+
+ (parties === undefined ? "" : JSON.stringify({ parties }))
|
|
17
|
+
+ ") before querying; the prewarm's party scope must match the query's.");
|
|
18
|
+
this.parties = parties;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.ContractCacheRequiredError = ContractCacheRequiredError;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HistoryWalkRequiredError = void 0;
|
|
4
|
+
const canton_error_js_1 = require("../../core/errors/canton-error.js");
|
|
5
|
+
/**
|
|
6
|
+
* gRPC typed queries never replay ledger history implicitly: transactions/events/exercises queries — and
|
|
7
|
+
* contracts queries whose predicates or includes reach archived state — require the walkHistory option, so
|
|
8
|
+
* the (potentially very expensive) replay only ever happens when the caller asked for it.
|
|
9
|
+
*/
|
|
10
|
+
class HistoryWalkRequiredError extends canton_error_js_1.CantonError {
|
|
11
|
+
relation;
|
|
12
|
+
constructor(relation) {
|
|
13
|
+
super(`The "${relation}" query requires replaying ledger history, which is disabled by default. `
|
|
14
|
+
+ "Enable the walkHistory option to permit it (pair it with incrementalHistory so repeat "
|
|
15
|
+
+ "queries fetch only new offsets), or reshape the query to active-only state served by the "
|
|
16
|
+
+ "contract cache.");
|
|
17
|
+
this.relation = relation;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.HistoryWalkRequiredError = HistoryWalkRequiredError;
|
|
@@ -4,26 +4,43 @@ exports.GrpcContractCache = void 0;
|
|
|
4
4
|
exports.normalizeParties = normalizeParties;
|
|
5
5
|
const types_1 = require("node:util/types");
|
|
6
6
|
const validation_error_js_1 = require("../../core/errors/validation-error.js");
|
|
7
|
+
const transaction_filter_js_1 = require("../../transports/grpc/generated/canton/com/daml/ledger/api/v2/transaction_filter.js");
|
|
7
8
|
const contracts_mapper_js_1 = require("../../transports/grpc/mappers/contracts-mapper.js");
|
|
8
9
|
const query_source_js_1 = require("../query-source.js");
|
|
9
10
|
const grpc_relation_mapper_js_1 = require("./grpc-relation-mapper.js");
|
|
10
11
|
const grpc_query_snapshot_reader_js_1 = require("./grpc-query-snapshot-reader.js");
|
|
12
|
+
const DELTA_DEFAULT_MAX_OFFSET_GAP = 1_000_000;
|
|
13
|
+
const DELTA_MAX_PAGES = 10_000;
|
|
11
14
|
class GrpcContractCache {
|
|
12
15
|
stateService;
|
|
13
16
|
store;
|
|
14
17
|
ttlMs;
|
|
15
18
|
endpointScope;
|
|
16
19
|
now;
|
|
20
|
+
maxPageSize;
|
|
21
|
+
updateService;
|
|
22
|
+
delta;
|
|
17
23
|
inflight = new Map();
|
|
18
|
-
constructor(stateService, store, ttlMs, endpointScope, now = Date.now) {
|
|
24
|
+
constructor(stateService, store, ttlMs, endpointScope, now = Date.now, maxPageSize, updateService, delta = {}) {
|
|
19
25
|
this.stateService = stateService;
|
|
20
26
|
this.store = store;
|
|
21
27
|
this.ttlMs = ttlMs;
|
|
22
28
|
this.endpointScope = endpointScope;
|
|
23
29
|
this.now = now;
|
|
30
|
+
this.maxPageSize = maxPageSize;
|
|
31
|
+
this.updateService = updateService;
|
|
32
|
+
this.delta = delta;
|
|
24
33
|
if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
|
|
25
34
|
throw new validation_error_js_1.ValidationError("Contract cache ttlMs must be a positive finite number.");
|
|
26
35
|
}
|
|
36
|
+
else if (maxPageSize !== undefined && (!Number.isFinite(maxPageSize) || !Number.isInteger(maxPageSize) || maxPageSize <= 0)) {
|
|
37
|
+
throw new validation_error_js_1.ValidationError("Contract cache maxPageSize must be a positive integer.");
|
|
38
|
+
}
|
|
39
|
+
for (const [name, value] of Object.entries({ maxOffsetGap: delta.maxOffsetGap, maxUpdates: delta.maxUpdates })) {
|
|
40
|
+
if (value !== undefined && (!Number.isFinite(value) || !Number.isInteger(value) || value < 0)) {
|
|
41
|
+
throw new validation_error_js_1.ValidationError(`Contract cache delta ${name} must be a non-negative integer.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
27
44
|
}
|
|
28
45
|
async cacheContracts(args) {
|
|
29
46
|
const parties = normalizeParties(args);
|
|
@@ -49,7 +66,7 @@ class GrpcContractCache {
|
|
|
49
66
|
if (snapshot === undefined) {
|
|
50
67
|
return undefined;
|
|
51
68
|
}
|
|
52
|
-
return Object.freeze({ activeAtOffset: snapshot.activeAtOffset, contracts: snapshot.contracts });
|
|
69
|
+
return Object.freeze({ activeAtOffset: snapshot.activeAtOffset, contracts: snapshot.contracts, creationMetadata: snapshot.creationMetadata });
|
|
53
70
|
}
|
|
54
71
|
async invalidateContractsCache(args) {
|
|
55
72
|
const parties = normalizeParties(args);
|
|
@@ -62,7 +79,186 @@ class GrpcContractCache {
|
|
|
62
79
|
}
|
|
63
80
|
await this.store.deleteAsync(key);
|
|
64
81
|
}
|
|
82
|
+
/** Measures the cached snapshot against the current ledger end, without changing anything. */
|
|
83
|
+
async inspectContractsCacheAsync(args) {
|
|
84
|
+
const parties = normalizeParties(args);
|
|
85
|
+
const base = asCompatibleSnapshot(await this.store.getAsync(cacheKey(this.endpointScope, parties)), this.endpointScope, parties, currentEpochMs(this.now), true);
|
|
86
|
+
if (base === undefined || this.stateService.getLedgerEndAsync === undefined) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const ledgerEnd = parseCacheOffset((await this.stateService.getLedgerEndAsync({})).offset);
|
|
90
|
+
const activeAt = parseCacheOffset(base.activeAtOffset);
|
|
91
|
+
if (ledgerEnd === undefined || activeAt === undefined || ledgerEnd < activeAt) {
|
|
92
|
+
return undefined;
|
|
93
|
+
}
|
|
94
|
+
return Object.freeze({
|
|
95
|
+
activeAtOffset: base.activeAtOffset,
|
|
96
|
+
ledgerEndOffset: ledgerEnd.toString(),
|
|
97
|
+
offsetGap: (ledgerEnd - activeAt).toString(),
|
|
98
|
+
contractCount: base.contracts.length,
|
|
99
|
+
expiresAt: new Date(base.expiresAtEpochMs),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
65
102
|
async populateAsync(parties, key) {
|
|
103
|
+
// An expired snapshot is still a valid delta base: expiry gates queries, not refreshes.
|
|
104
|
+
const base = asCompatibleSnapshot(await this.store.getAsync(key), this.endpointScope, parties, currentEpochMs(this.now), true);
|
|
105
|
+
if (base !== undefined && this.updateService !== undefined && this.delta.enabled === true) {
|
|
106
|
+
const delta = await this.tryDeltaRefreshAsync(parties, key, base);
|
|
107
|
+
if (delta !== undefined) {
|
|
108
|
+
return delta;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return this.fullRefreshAsync(parties, key, base);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Patches the base snapshot forward from the update stream instead of re-downloading the ACS. Returns
|
|
115
|
+
* undefined — meaning "do the full download instead" — whenever correctness cannot be proven cheaply:
|
|
116
|
+
* pruning past the base offset, an offset gap or update count beyond the configured budgets, any
|
|
117
|
+
* reassignment or topology change in the window (multi-synchronizer moves cannot be patched safely), an
|
|
118
|
+
* exercised event where ACS_DELTA promises none, or an archive/create inconsistent with the base rows.
|
|
119
|
+
*/
|
|
120
|
+
async tryDeltaRefreshAsync(parties, key, base) {
|
|
121
|
+
if (this.stateService.getLedgerEndAsync === undefined || this.stateService.getLatestPrunedOffsetsAsync === undefined || this.updateService === undefined) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const baseOffset = parseCacheOffset(base.activeAtOffset);
|
|
126
|
+
const pruned = parseCacheOffset((await this.stateService.getLatestPrunedOffsetsAsync({})).participantPrunedUpToInclusive);
|
|
127
|
+
const ledgerEnd = parseCacheOffset((await this.stateService.getLedgerEndAsync({})).offset);
|
|
128
|
+
if (baseOffset === undefined || pruned === undefined || pruned > baseOffset || ledgerEnd === undefined || ledgerEnd < baseOffset) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
const offsetGap = ledgerEnd - baseOffset;
|
|
132
|
+
if (offsetGap === 0n) {
|
|
133
|
+
return this.writeSnapshotAsync(parties, key, base.activeAtOffset, base.contracts, base.creationMetadata, { refresh: "noop", offsetGap: "0" });
|
|
134
|
+
}
|
|
135
|
+
const maxOffsetGap = BigInt(this.delta.maxOffsetGap ?? DELTA_DEFAULT_MAX_OFFSET_GAP);
|
|
136
|
+
if (offsetGap > maxOffsetGap) {
|
|
137
|
+
return undefined;
|
|
138
|
+
}
|
|
139
|
+
const window = await this.readDeltaWindowAsync(parties, base, ledgerEnd.toString());
|
|
140
|
+
if (window === undefined) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
const baseIds = new Set(base.contracts.map((row) => row.contractId));
|
|
144
|
+
const adds = new Map();
|
|
145
|
+
const removes = new Set();
|
|
146
|
+
for (const entry of window.entries) {
|
|
147
|
+
if (entry.kind === "created") {
|
|
148
|
+
if (baseIds.has(entry.event.contractId) || adds.has(entry.event.contractId)) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
adds.set(entry.event.contractId, { event: entry.event, synchronizerId: entry.synchronizerId });
|
|
152
|
+
}
|
|
153
|
+
else if (adds.has(entry.contractId)) {
|
|
154
|
+
adds.delete(entry.contractId);
|
|
155
|
+
}
|
|
156
|
+
else if (baseIds.has(entry.contractId)) {
|
|
157
|
+
removes.add(entry.contractId);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const survivors = [...adds.values()].map(({ event, synchronizerId }) => ({
|
|
164
|
+
contractEntry: {
|
|
165
|
+
oneofKind: "activeContract",
|
|
166
|
+
activeContract: { createdEvent: event, synchronizerId, reassignmentCounter: "0" },
|
|
167
|
+
},
|
|
168
|
+
}));
|
|
169
|
+
const fragment = (0, grpc_relation_mapper_js_1.mapGrpcQueryRelationFragment)([], survivors);
|
|
170
|
+
const contracts = [...base.contracts.filter((row) => !removes.has(row.contractId)), ...fragment.contracts]
|
|
171
|
+
.sort((left, right) => left.contractId.localeCompare(right.contractId));
|
|
172
|
+
const addedMetadata = fragment.creationIdentities.map((identity) => ({
|
|
173
|
+
contractId: identity.contractId,
|
|
174
|
+
packageName: identity.packageName,
|
|
175
|
+
representativePackageId: identity.representativePackageId,
|
|
176
|
+
}));
|
|
177
|
+
const creationMetadata = [...base.creationMetadata.filter((entry) => !removes.has(entry.contractId)), ...addedMetadata]
|
|
178
|
+
.sort((left, right) => left.contractId.localeCompare(right.contractId));
|
|
179
|
+
return await this.writeSnapshotAsync(parties, key, ledgerEnd.toString(), contracts, creationMetadata, {
|
|
180
|
+
refresh: "delta",
|
|
181
|
+
offsetGap: offsetGap.toString(),
|
|
182
|
+
deltaUpdateCount: window.entries.length,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
// Any validation failure in the window falls back to the full download, which re-validates from scratch.
|
|
187
|
+
return undefined;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
async readDeltaWindowAsync(parties, base, endInclusive) {
|
|
191
|
+
const eventFormat = (0, contracts_mapper_js_1.mapGrpcQueryContractsRequest)(parties === undefined ? { allParties: true } : { parties }).eventFormat;
|
|
192
|
+
const maxUpdates = this.delta.maxUpdates ?? Math.max(1_000, 2 * base.contracts.length);
|
|
193
|
+
const entries = [];
|
|
194
|
+
const seenPageTokens = new Set();
|
|
195
|
+
const end = parseCacheOffset(endInclusive);
|
|
196
|
+
let expectedLowestExclusive = parseCacheOffset(base.activeAtOffset);
|
|
197
|
+
let pageToken;
|
|
198
|
+
let updatesSeen = 0;
|
|
199
|
+
for (let pagesRead = 0; pagesRead < DELTA_MAX_PAGES; pagesRead += 1) {
|
|
200
|
+
const request = {
|
|
201
|
+
beginOffsetExclusive: base.activeAtOffset,
|
|
202
|
+
endOffsetInclusive: endInclusive,
|
|
203
|
+
updateFormat: { includeTransactions: { eventFormat, transactionShape: transaction_filter_js_1.TransactionShape.ACS_DELTA } },
|
|
204
|
+
descendingOrder: false,
|
|
205
|
+
pageToken: pageToken === undefined ? undefined : Uint8Array.from(pageToken),
|
|
206
|
+
};
|
|
207
|
+
const response = await this.updateService.getUpdatesPageAsync(request);
|
|
208
|
+
const lowest = parseCacheOffset(response.lowestPageOffsetExclusive);
|
|
209
|
+
const highest = parseCacheOffset(response.highestPageOffsetInclusive);
|
|
210
|
+
if (lowest === undefined || highest === undefined || lowest !== expectedLowestExclusive || highest < lowest || highest > end) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
updatesSeen += response.updates.length;
|
|
214
|
+
if (updatesSeen > maxUpdates) {
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
for (const update of response.updates) {
|
|
218
|
+
const collected = collectDeltaEntries(update, entries);
|
|
219
|
+
if (!collected) {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const nextPageToken = response.nextPageToken;
|
|
224
|
+
if (nextPageToken === undefined || nextPageToken.length === 0) {
|
|
225
|
+
return highest === end ? { entries } : undefined;
|
|
226
|
+
}
|
|
227
|
+
else if (highest >= end || highest <= lowest) {
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
230
|
+
const tokenKey = Array.from(nextPageToken).join(",");
|
|
231
|
+
if (seenPageTokens.has(tokenKey)) {
|
|
232
|
+
return undefined;
|
|
233
|
+
}
|
|
234
|
+
seenPageTokens.add(tokenKey);
|
|
235
|
+
expectedLowestExclusive = highest;
|
|
236
|
+
pageToken = Uint8Array.from(nextPageToken);
|
|
237
|
+
}
|
|
238
|
+
return undefined;
|
|
239
|
+
}
|
|
240
|
+
async writeSnapshotAsync(parties, key, activeAtOffset, contracts, creationMetadata, outcome) {
|
|
241
|
+
const expiresAtEpochMs = effectiveExpiryEpochMs(this.now, this.ttlMs);
|
|
242
|
+
const snapshot = {
|
|
243
|
+
version: 2,
|
|
244
|
+
endpointScope: this.endpointScope,
|
|
245
|
+
parties,
|
|
246
|
+
activeAtOffset,
|
|
247
|
+
expiresAtEpochMs,
|
|
248
|
+
contracts: copyRows(contracts),
|
|
249
|
+
creationMetadata: creationMetadata.map((entry) => ({ ...entry })),
|
|
250
|
+
};
|
|
251
|
+
await this.store.setAsync(key, snapshot, this.ttlMs);
|
|
252
|
+
return {
|
|
253
|
+
source: query_source_js_1.QuerySource.grpc,
|
|
254
|
+
cached: true,
|
|
255
|
+
activeAtOffset,
|
|
256
|
+
contractCount: contracts.length,
|
|
257
|
+
expiresAt: new Date(expiresAtEpochMs),
|
|
258
|
+
...outcome,
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async fullRefreshAsync(parties, key, base) {
|
|
66
262
|
const activeContracts = [];
|
|
67
263
|
const seenPageTokens = new Set();
|
|
68
264
|
// Every continuation request must be identical to the first page's request apart from the page
|
|
@@ -70,7 +266,9 @@ class GrpcContractCache {
|
|
|
70
266
|
// the response's now-explicit activeAtOffset into page 2+ makes it reject the token
|
|
71
267
|
// (INVALID_ACS_PAGE_TOKEN) — the token was prepared for a request with the field absent. The token
|
|
72
268
|
// itself pins the snapshot offset; the echoed offset is only tracked to validate it stays constant.
|
|
73
|
-
const baseRequest = (0, contracts_mapper_js_1.mapGrpcQueryContractsRequest)(parties === undefined
|
|
269
|
+
const baseRequest = (0, contracts_mapper_js_1.mapGrpcQueryContractsRequest)(parties === undefined
|
|
270
|
+
? { allParties: true, maxPageSize: this.maxPageSize }
|
|
271
|
+
: { parties, maxPageSize: this.maxPageSize });
|
|
74
272
|
let activeAtOffset;
|
|
75
273
|
let pageToken;
|
|
76
274
|
do {
|
|
@@ -94,22 +292,35 @@ class GrpcContractCache {
|
|
|
94
292
|
seenPageTokens.add(tokenKey);
|
|
95
293
|
}
|
|
96
294
|
} while (pageToken !== undefined && pageToken.length > 0);
|
|
97
|
-
const
|
|
295
|
+
const fragment = (0, grpc_relation_mapper_js_1.mapGrpcQueryRelationFragment)([], activeContracts);
|
|
296
|
+
const contracts = fragment.contracts;
|
|
297
|
+
const creationMetadata = fragment.creationIdentities.map((identity) => ({
|
|
298
|
+
contractId: identity.contractId,
|
|
299
|
+
packageName: identity.packageName,
|
|
300
|
+
representativePackageId: identity.representativePackageId,
|
|
301
|
+
}));
|
|
98
302
|
const expiresAtEpochMs = effectiveExpiryEpochMs(this.now, this.ttlMs);
|
|
303
|
+
const baseOffset = base === undefined ? undefined : parseCacheOffset(base.activeAtOffset);
|
|
304
|
+
const newOffset = parseCacheOffset(activeAtOffset);
|
|
99
305
|
const result = {
|
|
100
306
|
source: query_source_js_1.QuerySource.grpc,
|
|
101
307
|
cached: true,
|
|
102
308
|
activeAtOffset: activeAtOffset,
|
|
103
309
|
contractCount: contracts.length,
|
|
104
310
|
expiresAt: new Date(expiresAtEpochMs),
|
|
311
|
+
refresh: "full",
|
|
312
|
+
...(baseOffset !== undefined && newOffset !== undefined && newOffset >= baseOffset
|
|
313
|
+
? { offsetGap: (newOffset - baseOffset).toString() }
|
|
314
|
+
: {}),
|
|
105
315
|
};
|
|
106
316
|
const snapshot = {
|
|
107
|
-
version:
|
|
317
|
+
version: 2,
|
|
108
318
|
endpointScope: this.endpointScope,
|
|
109
319
|
parties,
|
|
110
320
|
activeAtOffset: activeAtOffset,
|
|
111
321
|
expiresAtEpochMs,
|
|
112
322
|
contracts: copyRows(contracts),
|
|
323
|
+
creationMetadata,
|
|
113
324
|
};
|
|
114
325
|
await this.store.setAsync(key, snapshot, this.ttlMs);
|
|
115
326
|
return result;
|
|
@@ -155,7 +366,43 @@ function normalizeParties(args) {
|
|
|
155
366
|
function cacheKey(endpointScope, parties) {
|
|
156
367
|
return `grpc-contract-cache:v1:${JSON.stringify([endpointScope, parties])}`;
|
|
157
368
|
}
|
|
158
|
-
|
|
369
|
+
/** Collects created/archived events from one ACS_DELTA update; false means the window cannot be patched. */
|
|
370
|
+
function collectDeltaEntries(update, entries) {
|
|
371
|
+
if (update === null || typeof update !== "object") {
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
const oneof = update.update;
|
|
375
|
+
if (oneof === undefined || oneof.oneofKind === "offsetCheckpoint") {
|
|
376
|
+
return oneof !== undefined;
|
|
377
|
+
}
|
|
378
|
+
else if (oneof.oneofKind !== "transaction") {
|
|
379
|
+
// Reassignments and topology changes move contracts between synchronizers/visibility in ways a
|
|
380
|
+
// row-level patch cannot represent safely.
|
|
381
|
+
return false;
|
|
382
|
+
}
|
|
383
|
+
const transaction = oneof.transaction;
|
|
384
|
+
const synchronizerId = transaction?.synchronizerId;
|
|
385
|
+
if (typeof synchronizerId !== "string" || synchronizerId.length === 0 || !Array.isArray(transaction?.events)) {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
for (const wrapped of transaction.events) {
|
|
389
|
+
const event = wrapped?.event;
|
|
390
|
+
if (event?.oneofKind === "created" && event.created !== undefined) {
|
|
391
|
+
entries.push({ kind: "created", event: event.created, synchronizerId });
|
|
392
|
+
}
|
|
393
|
+
else if (event?.oneofKind === "archived" && typeof event.archived?.contractId === "string" && event.archived.contractId.length > 0) {
|
|
394
|
+
entries.push({ kind: "archived", contractId: event.archived.contractId });
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return true;
|
|
401
|
+
}
|
|
402
|
+
function parseCacheOffset(value) {
|
|
403
|
+
return (0, grpc_query_snapshot_reader_js_1.isCanonicalGrpcOffset)(value) ? BigInt(value) : undefined;
|
|
404
|
+
}
|
|
405
|
+
function asCompatibleSnapshot(value, endpointScope, parties, nowEpochMs, ignoreExpiry = false) {
|
|
159
406
|
try {
|
|
160
407
|
if (value === null || typeof value !== "object") {
|
|
161
408
|
return undefined;
|
|
@@ -168,7 +415,8 @@ function asCompatibleSnapshot(value, endpointScope, parties, nowEpochMs) {
|
|
|
168
415
|
const activeAtOffset = candidate.activeAtOffset;
|
|
169
416
|
const expiresAtEpochMs = candidate.expiresAtEpochMs;
|
|
170
417
|
const contracts = materializeContractRows(candidate.contracts);
|
|
171
|
-
|
|
418
|
+
const creationMetadata = materializeCreationMetadata(candidate.creationMetadata, contracts);
|
|
419
|
+
if (version !== 2
|
|
172
420
|
|| storedEndpointScope !== endpointScope
|
|
173
421
|
|| (rawParties !== undefined && storedParties === undefined)
|
|
174
422
|
|| !sameParties(storedParties, parties)
|
|
@@ -179,8 +427,9 @@ function asCompatibleSnapshot(value, endpointScope, parties, nowEpochMs) {
|
|
|
179
427
|
|| typeof expiresAtEpochMs !== "number"
|
|
180
428
|
|| !Number.isFinite(expiresAtEpochMs)
|
|
181
429
|
|| !Number.isFinite(new Date(expiresAtEpochMs).getTime())
|
|
182
|
-
|| expiresAtEpochMs <= nowEpochMs
|
|
183
|
-
|| contracts === undefined
|
|
430
|
+
|| (!ignoreExpiry && expiresAtEpochMs <= nowEpochMs)
|
|
431
|
+
|| contracts === undefined
|
|
432
|
+
|| creationMetadata === undefined) {
|
|
184
433
|
return undefined;
|
|
185
434
|
}
|
|
186
435
|
return {
|
|
@@ -190,6 +439,7 @@ function asCompatibleSnapshot(value, endpointScope, parties, nowEpochMs) {
|
|
|
190
439
|
activeAtOffset,
|
|
191
440
|
expiresAtEpochMs,
|
|
192
441
|
contracts,
|
|
442
|
+
creationMetadata,
|
|
193
443
|
};
|
|
194
444
|
}
|
|
195
445
|
catch {
|
|
@@ -253,6 +503,40 @@ function materializePageToken(value) {
|
|
|
253
503
|
throw new Error("Active-contracts response nextPageToken is invalid.");
|
|
254
504
|
}
|
|
255
505
|
}
|
|
506
|
+
function materializeCreationMetadata(value, contracts) {
|
|
507
|
+
if (contracts === undefined) {
|
|
508
|
+
return undefined;
|
|
509
|
+
}
|
|
510
|
+
const entries = materializeIndexedValues(value, materializeCreationMetadataEntry);
|
|
511
|
+
if (entries === undefined || entries.some((entry) => entry === undefined)) {
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|
|
514
|
+
const materialized = entries;
|
|
515
|
+
// Coherence: exactly one metadata entry per contract row, so the contractType join can never dangle.
|
|
516
|
+
const metadataContractIds = new Set(materialized.map((entry) => entry.contractId));
|
|
517
|
+
if (metadataContractIds.size !== materialized.length
|
|
518
|
+
|| materialized.length !== contracts.length
|
|
519
|
+
|| !contracts.every((row) => metadataContractIds.has(row.contractId))) {
|
|
520
|
+
return undefined;
|
|
521
|
+
}
|
|
522
|
+
return materialized;
|
|
523
|
+
}
|
|
524
|
+
function materializeCreationMetadataEntry(value) {
|
|
525
|
+
if (value === null || typeof value !== "object") {
|
|
526
|
+
return undefined;
|
|
527
|
+
}
|
|
528
|
+
const candidate = value;
|
|
529
|
+
const contractId = candidate.contractId;
|
|
530
|
+
const packageName = candidate.packageName;
|
|
531
|
+
const representativePackageId = candidate.representativePackageId;
|
|
532
|
+
if (typeof contractId !== "string"
|
|
533
|
+
|| typeof packageName !== "string"
|
|
534
|
+
|| packageName.length === 0
|
|
535
|
+
|| (typeof representativePackageId !== "string" && representativePackageId !== null)) {
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
return { contractId, packageName, representativePackageId };
|
|
539
|
+
}
|
|
256
540
|
function materializeContractRows(value) {
|
|
257
541
|
const rows = materializeIndexedValues(value, materializeContractRow);
|
|
258
542
|
if (rows === undefined) {
|