Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,27 @@ abstract class ValidatorAppReference(
)
}
}

def getHoldingsSummaryAt(
at: CantonTimestamp,
migrationId: Long,
ownerPartyIds: Vector[PartyId],
recordTimeMatch: Option[definitions.HoldingsSummaryRequest.RecordTimeMatch] = None,
asOfRound: Option[Long] = None,
): Option[definitions.HoldingsSummaryResponse] = {
consoleEnvironment.run {
httpCommand(
HttpScanProxyAppClient.GetHoldingsSummaryAt(
at,
migrationId,
ownerPartyIds,
recordTimeMatch,
asOfRound,
)
)
}
}

def getDsoInfo(): definitions.GetDsoInfoResponse = {
consoleEnvironment.run {
httpCommand(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,17 @@ class ValidatorIntegrationTest extends IntegrationTest with WalletTestUtil {
// dso info is available on the scan proxy
aliceValidatorBackend.scanProxy.getDsoInfo() shouldBe sv1ScanBackend.getDsoInfo()

val now = env.environment.clock.now
aliceValidatorBackend.scanProxy.getHoldingsSummaryAt(
now,
0L,
Vector(aliceValidatorParty),
) shouldBe sv1ScanBackend.getHoldingsSummaryAt(
now,
0L,
Vector(aliceValidatorParty),
)

// check that the dsoGovernance are not vetted
aliceValidatorBackend.participantClient.topology.vetted_packages
.list(filterParticipant = aliceValidatorBackend.participantClient.id.toProtoPrimitive)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import org.lfdecentralizedtrust.splice.http.HttpClient
import org.lfdecentralizedtrust.splice.http.v0.definitions.{
AnsEntry,
GetDsoInfoResponse,
HoldingsSummaryResponse,
LookupTransferCommandStatusResponse,
MigrationSchedule,
}
Expand Down Expand Up @@ -91,6 +92,7 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.{
DsoRules_CloseVoteRequestResult,
VoteRequest,
}
import org.lfdecentralizedtrust.splice.http.v0.definitions.HoldingsSummaryRequest.RecordTimeMatch
import org.lfdecentralizedtrust.tokenstandard.{
allocation,
allocationinstruction,
Expand Down Expand Up @@ -171,6 +173,16 @@ class BftScanConnection(
_.getDsoInfo()
)

override def getHoldingsSummaryAt(
at: CantonTimestamp,
migrationId: Long,
ownerPartyIds: Vector[PartyId],
recordTimeMatch: Option[RecordTimeMatch],
asOfRound: Option[Long],
)(implicit tc: TraceContext): Future[Option[HoldingsSummaryResponse]] = {
bftCall(_.getHoldingsSummaryAt(at, migrationId, ownerPartyIds, recordTimeMatch, asOfRound))
}

override protected def runGetAmuletRulesWithState(
cachedAmuletRules: Option[ContractWithState[AmuletRules.ContractId, AmuletRules]]
)(implicit tc: TraceContext): Future[ContractWithState[AmuletRules.ContractId, AmuletRules]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import org.lfdecentralizedtrust.splice.environment.*
import org.lfdecentralizedtrust.splice.http.HttpClient
import org.lfdecentralizedtrust.splice.http.v0.definitions.{
GetDsoInfoResponse,
HoldingsSummaryResponse,
LookupTransferCommandStatusResponse,
MigrationSchedule,
}
Expand All @@ -35,14 +36,15 @@ import com.digitalasset.canton.data.CantonTimestamp
import com.digitalasset.canton.lifecycle.FlagCloseableAsync
import com.digitalasset.canton.logging.{NamedLoggerFactory, TracedLogger}
import com.digitalasset.canton.time.Clock
import com.digitalasset.canton.topology.{SynchronizerId, PartyId}
import com.digitalasset.canton.topology.{PartyId, SynchronizerId}
import com.digitalasset.canton.tracing.TraceContext
import io.grpc.Status
import org.apache.pekko.stream.Materializer
import org.lfdecentralizedtrust.splice.codegen.java.splice.dsorules.{
DsoRules_CloseVoteRequestResult,
VoteRequest,
}
import org.lfdecentralizedtrust.splice.http.v0.definitions.HoldingsSummaryRequest.RecordTimeMatch

import scala.concurrent.{ExecutionContext, ExecutionContextExecutor, Future}
import scala.jdk.OptionConverters.*
Expand Down Expand Up @@ -75,6 +77,14 @@ trait ScanConnection
logger,
)

def getHoldingsSummaryAt(
at: CantonTimestamp,
migrationId: Long,
ownerPartyIds: Vector[PartyId],
recordTimeMatch: Option[RecordTimeMatch],
asOfRound: Option[Long],
)(implicit tc: TraceContext): Future[Option[HoldingsSummaryResponse]]

def getAmuletRulesWithState()(implicit
ec: ExecutionContext,
tc: TraceContext,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ import org.lfdecentralizedtrust.splice.environment.{
}
import org.lfdecentralizedtrust.splice.http.HttpClient
import org.lfdecentralizedtrust.splice.http.v0.definitions.{
HoldingsSummaryResponse,
LookupTransferCommandStatusResponse,
MigrationSchedule,
}
import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.{HttpScanAppClient}
import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient
import org.lfdecentralizedtrust.splice.scan.config.ScanAppClientConfig
import org.lfdecentralizedtrust.splice.scan.store.db.ScanAggregator
import org.lfdecentralizedtrust.splice.store.HistoryBackfilling.SourceMigrationInfo
Expand Down Expand Up @@ -71,6 +72,7 @@ import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.transferins
import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.transferinstructionv1.TransferInstruction
import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.allocationv1.Allocation
import org.lfdecentralizedtrust.splice.codegen.java.splice.api.token.allocationinstructionv1
import org.lfdecentralizedtrust.splice.http.v0.definitions.HoldingsSummaryRequest.RecordTimeMatch
import org.lfdecentralizedtrust.splice.scan.admin.api.client.commands.HttpScanAppClient.BftSequencer
import org.lfdecentralizedtrust.tokenstandard.transferinstruction.v1.definitions.TransferFactoryWithChoiceContext

Expand Down Expand Up @@ -131,6 +133,25 @@ class SingleScanConnection private[client] (
runHttpCmd(config.adminApi.url, HttpScanAppClient.GetDsoInfo(List()))
}

override def getHoldingsSummaryAt(
at: CantonTimestamp,
migrationId: Long,
ownerPartyIds: Vector[PartyId],
recordTimeMatch: Option[RecordTimeMatch],
asOfRound: Option[Long],
)(implicit tc: TraceContext): Future[Option[HoldingsSummaryResponse]] = {
runHttpCmd(
config.adminApi.url,
HttpScanAppClient.GetHoldingsSummaryAt(
at.toInstant.atOffset(java.time.ZoneOffset.UTC),
migrationId,
ownerPartyIds,
recordTimeMatch,
asOfRound,
),
)
}

override def getAmuletRulesWithState()(implicit
ec: ExecutionContext,
tc: TraceContext,
Expand Down
28 changes: 27 additions & 1 deletion apps/validator/src/main/openapi/scan-proxy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,33 @@ paths:
"404":
description: No TransferCommand exists with this contract id within the last 24h
$ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/404"

/v0/scan-proxy/holdings/summary:
post:
tags: [ scan-proxy ]
x-jvm-package: scanproxy
operationId: "getHoldingsSummaryAt"
description: |
Returns the summary of active amulet contracts for a given migration id and record time, for the given parties.
This is an aggregate of `/v0/holdings/state` by owner party ID with better performance than client-side computation.
requestBody:
required: true
content:
application/json:
schema:
$ref: "../../../../scan/src/main/openapi/scan.yaml#/components/schemas/HoldingsSummaryRequest"
responses:
"200":
description: ok
content:
application/json:
schema:
$ref: "../../../../scan/src/main/openapi/scan.yaml#/components/schemas/HoldingsSummaryResponse"
"400":
$ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/400"
"404":
$ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/404"
"500":
$ref: "../../../../common/src/main/openapi/common-external.yaml#/components/responses/500"
components:
schemas:
# does not include the TTL, and the contracts are not-MaybeCached
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package org.lfdecentralizedtrust.splice.validator.admin.api.client.commands

import cats.data.EitherT
import cats.syntax.either.*
import com.digitalasset.canton.data.CantonTimestamp
import org.lfdecentralizedtrust.splice.admin.api.client.commands.HttpCommand
import org.lfdecentralizedtrust.splice.codegen.java.splice.ans.AnsRules
import org.apache.pekko.http.scaladsl.model.{HttpHeader, HttpResponse, StatusCodes}
Expand Down Expand Up @@ -57,6 +58,34 @@ object HttpScanProxyAppClient {
}
}

case class GetHoldingsSummaryAt(
at: CantonTimestamp,
migrationId: Long,
ownerPartyIds: Vector[PartyId],
recordTimeMatch: Option[definitions.HoldingsSummaryRequest.RecordTimeMatch],
asOfRound: Option[Long],
) extends ScanProxyBaseCommand[scanProxy.GetHoldingsSummaryAtResponse, Option[
definitions.HoldingsSummaryResponse
]] {

override def submitRequest(client: ScanproxyClient, headers: List[HttpHeader]) =
client.getHoldingsSummaryAt(
definitions.HoldingsSummaryRequest(
migrationId,
at.toInstant.atOffset(java.time.ZoneOffset.UTC),
recordTimeMatch,
ownerPartyIds.map(_.toProtoPrimitive),
asOfRound,
),
headers,
)

override def handleOk()(implicit decoder: TemplateJsonDecoder) = {
case scanProxy.GetHoldingsSummaryAtResponse.OK(response) => Right(Some(response))
case scanProxy.GetHoldingsSummaryAtResponse.NotFound(_) => Right(None)
}
}

case object GetAnsRules
extends ScanProxyBaseCommand[
scanProxy.GetAnsRulesResponse,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package org.lfdecentralizedtrust.splice.validator.admin.http

import com.digitalasset.canton.data.CantonTimestamp
import org.lfdecentralizedtrust.splice.http.v0.definitions.MaybeCachedContractWithState
import org.lfdecentralizedtrust.splice.http.v0.{definitions, scanproxy as v0}
import org.lfdecentralizedtrust.splice.scan.admin.api.client.BftScanConnection
Expand Down Expand Up @@ -99,6 +100,31 @@ class HttpScanProxyHandler(
}
}

override def getHoldingsSummaryAt(
respond: v0.ScanproxyResource.GetHoldingsSummaryAtResponse.type
)(
body: definitions.HoldingsSummaryRequest
)(tUser: AuthenticatedRequest): Future[v0.ScanproxyResource.GetHoldingsSummaryAtResponse] = {
implicit val AuthenticatedRequest(_, traceContext) = tUser
withSpan(s"$workflowId.getHoldingsSummaryAt") { implicit traceContext => _ =>
for {
summaryOpt <- scanConnection.getHoldingsSummaryAt(
CantonTimestamp.assertFromInstant(body.recordTime.toInstant),
body.migrationId,
body.ownerPartyIds.map(PartyId.tryFromProtoPrimitive),
body.recordTimeMatch,
body.asOfRound,
)
} yield {
summaryOpt match {
case Some(summary) => respond.OK(summary)
case None =>
respond.NotFound(definitions.ErrorResponse("Summary not found for given parameters"))
}
}
}
}

override def getAmuletRules(respond: v0.ScanproxyResource.GetAmuletRulesResponse.type)()(
tUser: AuthenticatedRequest
): Future[v0.ScanproxyResource.GetAmuletRulesResponse] = {
Expand Down
4 changes: 4 additions & 0 deletions docs/src/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@
- The default logger has been switched to use an asynchronous appender, for all the nodes, for better performance.
The behavior can be switched back to synchronous logging by setting the environment variable `LOG_IMMEDIATE_FLUSH=true`.

- Validator

- Expose ``/v0/holdings/summary`` endpoint from scan proxy.

.. release-notes:: 0.5.6

- Sequencer
Expand Down