From 3e996262dfe0343223900a481852ad28e5ded849 Mon Sep 17 00:00:00 2001 From: Cody Worsnop Date: Thu, 16 Jun 2022 14:23:57 -0700 Subject: [PATCH] Batching Contract Execution (#31) * Adding participants to audience keys * first pass * updates * final * cleanup * cleanup / documentation * PR comments * more pr comments --- .github/workflows/release.yml | 6 +- .../api/models/cee/ContractConfig.kt | 6 +- .../approve/ApproveContractBatchRequest.kt | 13 ++ .../cee/approve/ApproveContractRequest.kt | 4 +- .../models/cee/approve/EnvelopeApproval.kt | 8 ++ .../execute/ExecuteContractBatchRequest.kt | 13 ++ .../cee/execute/ExecuteContractRequest.kt | 3 +- .../api/models/cee/execute/ScopeInfo.kt | 8 ++ .../cee/reject/RejectContractBatchRequest.kt | 10 ++ .../models/cee/submit/EnvelopeSubmission.kt | 6 + ...bmitContractBatchExecutionResultRequest.kt | 11 ++ .../SubmitContractExecutionResultRequest.kt | 3 +- .../api/models/p8e/ProvenanceConfig.kt | 3 + .../api/domain/provenance/Provenance.kt | 2 +- .../approve/ApproveContractBatchExecution.kt | 68 ++++++++++ .../cee/approve/ApproveContractExecution.kt | 20 +-- .../ApproveContractBatchRequestWrapper.kt | 9 ++ .../usecase/cee/common/ContractUtilities.kt | 105 +++++++++++++++ .../usecase/cee/execute/ExecuteContract.kt | 88 ++---------- .../cee/execute/ExecuteContractBatch.kt | 95 +++++++++++++ .../ExecuteContractBatchRequestWrapper.kt | 9 ++ .../reject/RejectContractBatchExecution.kt | 22 +++ .../RejectContractBatchRequestWrapper.kt | 9 ++ .../SubmitContractBatchExecutionResult.kt | 68 ++++++++++ .../submit/SubmitContractExecutionResult.kt | 13 +- ...tractBatchExecutionResultRequestWrapper.kt | 9 ++ .../provenance/ProvenanceService.kt | 24 ++-- .../provenance/exceptions/Exceptions.kt | 4 +- .../extensions/ProvenanceExtensions.kt | 17 ++- .../api/frameworks/web/ErrorResponses.kt | 4 + .../api/frameworks/web/external/cee/CeeApi.kt | 127 +++++++++++++++++- .../frameworks/web/external/cee/CeeHandler.kt | 30 ++++- 32 files changed, 690 insertions(+), 127 deletions(-) create mode 100644 models/src/main/kotlin/io/provenance/api/models/cee/approve/ApproveContractBatchRequest.kt create mode 100644 models/src/main/kotlin/io/provenance/api/models/cee/approve/EnvelopeApproval.kt create mode 100644 models/src/main/kotlin/io/provenance/api/models/cee/execute/ExecuteContractBatchRequest.kt create mode 100644 models/src/main/kotlin/io/provenance/api/models/cee/execute/ScopeInfo.kt create mode 100644 models/src/main/kotlin/io/provenance/api/models/cee/reject/RejectContractBatchRequest.kt create mode 100644 models/src/main/kotlin/io/provenance/api/models/cee/submit/EnvelopeSubmission.kt create mode 100644 models/src/main/kotlin/io/provenance/api/models/cee/submit/SubmitContractBatchExecutionResultRequest.kt create mode 100644 service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/ApproveContractBatchExecution.kt create mode 100644 service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/models/ApproveContractBatchRequestWrapper.kt create mode 100644 service/src/main/kotlin/io/provenance/api/domain/usecase/cee/common/ContractUtilities.kt create mode 100644 service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/ExecuteContractBatch.kt create mode 100644 service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/model/ExecuteContractBatchRequestWrapper.kt create mode 100644 service/src/main/kotlin/io/provenance/api/domain/usecase/cee/reject/RejectContractBatchExecution.kt create mode 100644 service/src/main/kotlin/io/provenance/api/domain/usecase/cee/reject/models/RejectContractBatchRequestWrapper.kt create mode 100644 service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/SubmitContractBatchExecutionResult.kt create mode 100644 service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/models/SubmitContractBatchExecutionResultRequestWrapper.kt diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1335f07e..29b4ddd5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,14 +24,14 @@ jobs: uses: actions/setup-java@v1 with: java-version: 11 - + - name: Setup Container run: |- brew tap hashicorp/tap brew install hashicorp/tap/vault - + - name: Build with Gradle - run: ./gradlew clean build --refresh-dependencies :generateVersionFile githubRelease + run: ./gradlew clean build --refresh-dependencies :generateVersionFile githubRelease -Psemver.modifier=${{ inputs.versionModifier }} if: ${{ github.ref_name == 'main' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/ContractConfig.kt b/models/src/main/kotlin/io/provenance/api/models/cee/ContractConfig.kt index 8110b6c4..6a595a71 100644 --- a/models/src/main/kotlin/io/provenance/api/models/cee/ContractConfig.kt +++ b/models/src/main/kotlin/io/provenance/api/models/cee/ContractConfig.kt @@ -1,11 +1,7 @@ package io.provenance.api.models.cee -import java.util.UUID - data class ContractConfig( val contractName: String, - val scopeUuid: UUID, - val sessionUuid: UUID?, val scopeSpecificationName: String, - val parserConfig: ParserConfig?, + val parserConfig: ParserConfig? = null, ) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/approve/ApproveContractBatchRequest.kt b/models/src/main/kotlin/io/provenance/api/models/cee/approve/ApproveContractBatchRequest.kt new file mode 100644 index 00000000..b3dbca0a --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/cee/approve/ApproveContractBatchRequest.kt @@ -0,0 +1,13 @@ +package io.provenance.api.models.cee.approve + +import io.provenance.api.models.account.AccountInfo +import io.provenance.api.models.eos.ObjectStoreConfig +import io.provenance.api.models.p8e.ProvenanceConfig + +class ApproveContractBatchRequest( + val account: AccountInfo = AccountInfo(), + val client: ObjectStoreConfig, + val provenanceConfig: ProvenanceConfig, + val approvals: List, + val chunkSize: Int = 25, +) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/approve/ApproveContractRequest.kt b/models/src/main/kotlin/io/provenance/api/models/cee/approve/ApproveContractRequest.kt index a727dffe..463f8991 100644 --- a/models/src/main/kotlin/io/provenance/api/models/cee/approve/ApproveContractRequest.kt +++ b/models/src/main/kotlin/io/provenance/api/models/cee/approve/ApproveContractRequest.kt @@ -3,12 +3,10 @@ package io.provenance.api.models.cee.approve import io.provenance.api.models.account.AccountInfo import io.provenance.api.models.eos.ObjectStoreConfig import io.provenance.api.models.p8e.ProvenanceConfig -import java.time.OffsetDateTime data class ApproveContractRequest( val account: AccountInfo = AccountInfo(), val client: ObjectStoreConfig, val provenanceConfig: ProvenanceConfig, - val envelope: ByteArray, - val expiration: OffsetDateTime = OffsetDateTime.now().plusHours(1), + val approval: EnvelopeApproval, ) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/approve/EnvelopeApproval.kt b/models/src/main/kotlin/io/provenance/api/models/cee/approve/EnvelopeApproval.kt new file mode 100644 index 00000000..47d8efbe --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/cee/approve/EnvelopeApproval.kt @@ -0,0 +1,8 @@ +package io.provenance.api.models.cee.approve + +import java.time.OffsetDateTime + +data class EnvelopeApproval( + val envelope: ByteArray, + val expiration: OffsetDateTime = OffsetDateTime.now().plusHours(1), +) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/execute/ExecuteContractBatchRequest.kt b/models/src/main/kotlin/io/provenance/api/models/cee/execute/ExecuteContractBatchRequest.kt new file mode 100644 index 00000000..d21ba207 --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/cee/execute/ExecuteContractBatchRequest.kt @@ -0,0 +1,13 @@ +package io.provenance.api.models.cee.execute + +import io.provenance.api.models.account.Participant +import io.provenance.api.models.p8e.PermissionInfo + +data class ExecuteContractBatchRequest( + val config: ExecuteContractConfig, + val records: Map, + val participants: List = emptyList(), + val permissions: PermissionInfo?, + val chunkSize: Int = 25, + val scopes: List = emptyList(), +) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/execute/ExecuteContractRequest.kt b/models/src/main/kotlin/io/provenance/api/models/cee/execute/ExecuteContractRequest.kt index a1b7a0cb..8af4c63d 100644 --- a/models/src/main/kotlin/io/provenance/api/models/cee/execute/ExecuteContractRequest.kt +++ b/models/src/main/kotlin/io/provenance/api/models/cee/execute/ExecuteContractRequest.kt @@ -4,8 +4,9 @@ import io.provenance.api.models.account.Participant import io.provenance.api.models.p8e.PermissionInfo data class ExecuteContractRequest( + val scope: ScopeInfo, val config: ExecuteContractConfig, val records: Map, val participants: List = emptyList(), - val permissions: PermissionInfo?, + val permissions: PermissionInfo? = null, ) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/execute/ScopeInfo.kt b/models/src/main/kotlin/io/provenance/api/models/cee/execute/ScopeInfo.kt new file mode 100644 index 00000000..6e9974d3 --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/cee/execute/ScopeInfo.kt @@ -0,0 +1,8 @@ +package io.provenance.api.models.cee.execute + +import java.util.UUID + +data class ScopeInfo( + val scopeUuid: UUID, + val sessionUuid: UUID? = null, +) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/reject/RejectContractBatchRequest.kt b/models/src/main/kotlin/io/provenance/api/models/cee/reject/RejectContractBatchRequest.kt new file mode 100644 index 00000000..e9702e9a --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/cee/reject/RejectContractBatchRequest.kt @@ -0,0 +1,10 @@ +package io.provenance.api.models.cee.reject + +import io.provenance.api.models.account.AccountInfo +import io.provenance.api.models.eos.ObjectStoreConfig + +data class RejectContractBatchRequest( + val account: AccountInfo = AccountInfo(), + val client: ObjectStoreConfig, + val rejection: List, +) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/submit/EnvelopeSubmission.kt b/models/src/main/kotlin/io/provenance/api/models/cee/submit/EnvelopeSubmission.kt new file mode 100644 index 00000000..ff84c80f --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/cee/submit/EnvelopeSubmission.kt @@ -0,0 +1,6 @@ +package io.provenance.api.models.cee.submit + +data class EnvelopeSubmission( + val envelope: ByteArray, + val state: ByteArray +) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/submit/SubmitContractBatchExecutionResultRequest.kt b/models/src/main/kotlin/io/provenance/api/models/cee/submit/SubmitContractBatchExecutionResultRequest.kt new file mode 100644 index 00000000..b2385596 --- /dev/null +++ b/models/src/main/kotlin/io/provenance/api/models/cee/submit/SubmitContractBatchExecutionResultRequest.kt @@ -0,0 +1,11 @@ +package io.provenance.api.models.cee.submit + +import io.provenance.api.models.account.AccountInfo +import io.provenance.api.models.p8e.ProvenanceConfig + +data class SubmitContractBatchExecutionResultRequest( + val account: AccountInfo = AccountInfo(), + val provenance: ProvenanceConfig, + val submission: List, + val chunkSize: Int = 25, +) diff --git a/models/src/main/kotlin/io/provenance/api/models/cee/submit/SubmitContractExecutionResultRequest.kt b/models/src/main/kotlin/io/provenance/api/models/cee/submit/SubmitContractExecutionResultRequest.kt index c7177ed0..3c065d94 100644 --- a/models/src/main/kotlin/io/provenance/api/models/cee/submit/SubmitContractExecutionResultRequest.kt +++ b/models/src/main/kotlin/io/provenance/api/models/cee/submit/SubmitContractExecutionResultRequest.kt @@ -6,6 +6,5 @@ import io.provenance.api.models.p8e.ProvenanceConfig data class SubmitContractExecutionResultRequest( val account: AccountInfo = AccountInfo(), val provenance: ProvenanceConfig, - val envelope: ByteArray, - val state: ByteArray, + val submission: EnvelopeSubmission ) diff --git a/models/src/main/kotlin/io/provenance/api/models/p8e/ProvenanceConfig.kt b/models/src/main/kotlin/io/provenance/api/models/p8e/ProvenanceConfig.kt index 3a9356c3..91f2f767 100644 --- a/models/src/main/kotlin/io/provenance/api/models/p8e/ProvenanceConfig.kt +++ b/models/src/main/kotlin/io/provenance/api/models/p8e/ProvenanceConfig.kt @@ -1,7 +1,10 @@ package io.provenance.api.models.p8e +import cosmos.tx.v1beta1.ServiceOuterClass + data class ProvenanceConfig( val chainId: String, val nodeEndpoint: String, val gasAdjustment: Double? = 1.5, + val broadcastMode: ServiceOuterClass.BroadcastMode = ServiceOuterClass.BroadcastMode.BROADCAST_MODE_SYNC ) diff --git a/service/src/main/kotlin/io/provenance/api/domain/provenance/Provenance.kt b/service/src/main/kotlin/io/provenance/api/domain/provenance/Provenance.kt index 559cfb97..46ce2a13 100644 --- a/service/src/main/kotlin/io/provenance/api/domain/provenance/Provenance.kt +++ b/service/src/main/kotlin/io/provenance/api/domain/provenance/Provenance.kt @@ -16,7 +16,7 @@ import java.util.UUID interface Provenance { fun onboard(chainId: String, nodeEndpoint: String, signer: Signer, storeTxBody: TxBody): TxResponse fun executeTransaction(config: ProvenanceConfig, tx: TxOuterClass.TxBody, signer: Signer): Abci.TxResponse - fun buildContractTx(config: ProvenanceConfig, tx: ProvenanceTx): TxOuterClass.TxBody? + fun buildContractTx(config: ProvenanceConfig, tx: ProvenanceTx): TxOuterClass.TxBody fun getScope(config: ProvenanceConfig, scopeUuid: UUID, height: Long? = null): ScopeResponse fun classifyAsset(config: ProvenanceConfig, signer: Signer, contractConfig: SmartContractConfig, onboardAssetRequest: OnboardAssetExecute): TxResponse fun verifyAsset(config: ProvenanceConfig, signer: Signer, contractConfig: SmartContractConfig, verifyAssetRequest: VerifyAssetExecute): TxResponse diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/ApproveContractBatchExecution.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/ApproveContractBatchExecution.kt new file mode 100644 index 00000000..ff811d01 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/ApproveContractBatchExecution.kt @@ -0,0 +1,68 @@ +package io.provenance.api.domain.usecase.cee.approve + +import com.google.protobuf.Any +import cosmos.authz.v1beta1.Tx +import cosmos.tx.v1beta1.TxOuterClass +import io.provenance.api.domain.provenance.Provenance +import io.provenance.api.domain.usecase.AbstractUseCase +import io.provenance.api.domain.usecase.cee.approve.models.ApproveContractBatchRequestWrapper +import io.provenance.api.domain.usecase.cee.common.client.CreateClient +import io.provenance.api.domain.usecase.cee.common.client.model.CreateClientRequest +import io.provenance.api.domain.usecase.provenance.account.GetSigner +import io.provenance.api.domain.usecase.provenance.account.models.GetSignerRequest +import io.provenance.api.frameworks.provenance.exceptions.ContractExecutionBatchException +import io.provenance.scope.contract.proto.Envelopes +import io.provenance.scope.sdk.FragmentResult +import mu.KotlinLogging +import org.springframework.stereotype.Component + +@Component +class ApproveContractBatchExecution( + private val createClient: CreateClient, + private val provenance: Provenance, + private val getSigner: GetSigner, +) : AbstractUseCase() { + private val log = KotlinLogging.logger { } + + override suspend fun execute(args: ApproveContractBatchRequestWrapper) { + val errors = mutableListOf() + val executionResults = mutableListOf>>() + val signer = getSigner.execute(GetSignerRequest(args.uuid, args.request.account)) + val client = createClient.execute(CreateClientRequest(args.uuid, args.request.account, args.request.client)) + + args.request.approvals.forEach { + val envelope = Envelopes.Envelope.newBuilder().mergeFrom(it.envelope).build() + val result = client.execute(envelope) + + if (result is FragmentResult) { + client.approveScopeUpdate(result.envelopeState, it.expiration).let { grant -> + executionResults.add(Pair(result.envelopeState, grant)) + } + } + } + + val chunked = executionResults.chunked(args.request.chunkSize) + chunked.forEachIndexed { index, it -> + runCatching { + val messages = it.flatMap { grantList -> grantList.second.map { grant -> Any.pack(grant, "") } } + val txBody = TxOuterClass.TxBody.newBuilder().addAllMessages(messages).build() + val broadcast = provenance.executeTransaction(args.request.provenanceConfig, txBody, signer) + + it.forEach { executions -> + client.respondWithApproval(executions.first, broadcast.txhash) + } + }.fold( + onSuccess = { + log.info("Successfully processed batch $index of ${chunked.size}") + }, + onFailure = { + errors.add(it) + } + ) + } + + if (errors.any()) { + throw ContractExecutionBatchException(errors.joinToString(limit = 20) { it.message.toString() }) + } + } +} diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/ApproveContractExecution.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/ApproveContractExecution.kt index 0c9b03cc..7aa9ba85 100644 --- a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/ApproveContractExecution.kt +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/ApproveContractExecution.kt @@ -9,6 +9,9 @@ import io.provenance.api.domain.usecase.cee.common.client.CreateClient import io.provenance.api.domain.usecase.cee.common.client.model.CreateClientRequest import io.provenance.api.domain.usecase.provenance.account.GetSigner import io.provenance.api.domain.usecase.provenance.account.models.GetSignerRequest +import io.provenance.api.frameworks.provenance.exceptions.ContractExecutionException +import io.provenance.api.frameworks.provenance.extensions.toTxResponse +import io.provenance.api.models.p8e.TxResponse import io.provenance.scope.contract.proto.Envelopes import io.provenance.scope.sdk.FragmentResult import org.springframework.stereotype.Component @@ -18,22 +21,21 @@ class ApproveContractExecution( private val createClient: CreateClient, private val provenance: Provenance, private val getSigner: GetSigner, -) : AbstractUseCase() { - override suspend fun execute(args: ApproveContractRequestWrapper) { +) : AbstractUseCase() { + override suspend fun execute(args: ApproveContractRequestWrapper): TxResponse { val client = createClient.execute(CreateClientRequest(args.uuid, args.request.account, args.request.client)) - val envelope = Envelopes.Envelope.newBuilder().mergeFrom(args.request.envelope).build() + val envelope = Envelopes.Envelope.newBuilder().mergeFrom(args.request.approval.envelope).build() val result = client.execute(envelope) if (result is FragmentResult) { - val approvalTxHash = client.approveScopeUpdate(result.envelopeState, args.request.expiration).let { + val tx = client.approveScopeUpdate(result.envelopeState, args.request.approval.expiration).let { val signer = getSigner.execute(GetSignerRequest(args.uuid, args.request.account)) val txBody = TxOuterClass.TxBody.newBuilder().addAllMessages(it.map { msg -> Any.pack(msg, "") }).build() - val broadcast = provenance.executeTransaction(args.request.provenanceConfig, txBody, signer) - - broadcast.txhash + provenance.executeTransaction(args.request.provenanceConfig, txBody, signer) } - client.respondWithApproval(result.envelopeState, approvalTxHash) - } + client.respondWithApproval(result.envelopeState, tx.txhash) + return tx.toTxResponse() + } else throw ContractExecutionException("Attempted to approve an envelope that did not result in a fragment. Only non-approved envelopes should be sent!") } } diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/models/ApproveContractBatchRequestWrapper.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/models/ApproveContractBatchRequestWrapper.kt new file mode 100644 index 00000000..2e5c4396 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/approve/models/ApproveContractBatchRequestWrapper.kt @@ -0,0 +1,9 @@ +package io.provenance.api.domain.usecase.cee.approve.models + +import io.provenance.api.models.cee.approve.ApproveContractBatchRequest +import java.util.UUID + +data class ApproveContractBatchRequestWrapper( + val uuid: UUID, + val request: ApproveContractBatchRequest +) diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/common/ContractUtilities.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/common/ContractUtilities.kt new file mode 100644 index 00000000..27c484dc --- /dev/null +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/common/ContractUtilities.kt @@ -0,0 +1,105 @@ +package io.provenance.api.domain.usecase.cee.common + +import com.google.protobuf.Message +import io.provenance.api.domain.cee.ContractParser +import io.provenance.api.domain.cee.ContractService +import io.provenance.api.domain.provenance.Provenance +import io.provenance.api.domain.usecase.cee.common.client.CreateClient +import io.provenance.api.domain.usecase.cee.common.client.model.CreateClientRequest +import io.provenance.api.domain.usecase.common.originator.EntityManager +import io.provenance.api.domain.usecase.common.originator.models.KeyManagementConfigWrapper +import io.provenance.api.models.account.Participant +import io.provenance.api.models.cee.ParserConfig +import io.provenance.api.models.cee.execute.ExecuteContractConfig +import io.provenance.api.models.cee.execute.ScopeInfo +import io.provenance.api.models.p8e.PermissionInfo +import io.provenance.client.protobuf.extensions.isSet +import io.provenance.metadata.v1.ScopeResponse +import io.provenance.scope.contract.annotations.Input +import io.provenance.scope.contract.spec.P8eContract +import io.provenance.scope.encryption.util.toJavaPublicKey +import io.provenance.scope.sdk.Client +import io.provenance.scope.sdk.Session +import java.util.UUID +import kotlin.reflect.KType +import kotlin.reflect.full.functions +import mu.KotlinLogging +import org.springframework.stereotype.Component + +@Component +class ContractUtilities( + private val contractService: ContractService, + private val provenanceService: Provenance, + private val contractParser: ContractParser, + private val createClient: CreateClient, + private val entityManager: EntityManager +) { + private val log = KotlinLogging.logger { } + + suspend fun createClient(uuid: UUID, permissions: PermissionInfo?, participants: List, config: ExecuteContractConfig): Client { + val audiences = entityManager.hydrateKeys(permissions, participants) + return createClient.execute(CreateClientRequest(uuid, config.account, config.client, audiences)) + } + + suspend fun createSession(uuid: UUID, permissions: PermissionInfo?, participants: List, config: ExecuteContractConfig, records: Map, scopes: List): List { + val audiences = entityManager.hydrateKeys(permissions, participants) + val client = createClient.execute(CreateClientRequest(uuid, config.account, config.client, audiences)) + val contract = contractService.getContract(config.contract.contractName) + val parsedRecords = getRecords(contractParser, records, contract, config.contract.parserConfig) + + val participantsMap = participants.associate { + it.partyType to entityManager.getEntity(KeyManagementConfigWrapper(it.uuid, config.account.keyManagementConfig)) + } + + return scopes.map { + val scope = provenanceService.getScope(config.provenanceConfig, it.scopeUuid) + val scopeToUse: ScopeResponse? = if (scope.scope.scope.isSet() && !scope.scope.scope.scopeId.isEmpty) scope else null + contractService.setupContract( + client, + contract, + parsedRecords, + it.scopeUuid, + it.sessionUuid, + participantsMap, + scopeToUse, + config.contract.scopeSpecificationName, + audiences.map { it.encryptionKey.toJavaPublicKey() }.toSet() + ) + } + } + + @Suppress("TooGenericExceptionCaught") + fun getRecords(contractParser: ContractParser, records: Map, contract: Class, parserConfig: ParserConfig?): Map { + val contractRecords = mutableMapOf() + + try { + contract.kotlin.functions.forEach { func -> + func.parameters.forEach { param -> + (param.annotations.firstOrNull { it is Input } as? Input)?.let { input -> + val parameterClass = Class.forName(param.type.toClassNameString()) + records.getOrDefault(input.name, null)?.let { + + val record = when (val parser = parserConfig?.name?.let { name -> contractParser.getParser(name) }) { + null -> { + contractParser.parseInput(it, parameterClass) + } + else -> { + parser.parse(it, parameterClass, parserConfig.descriptors) + } + } + + contractRecords[input.name] = record + } + } + } + } + } catch (ex: Exception) { + log.error("Failed to get inputs for contract ${contract.simpleName}") + throw ex + } + + return contractRecords + } + + private fun KType?.toClassNameString(): String? = this?.classifier?.toString()?.drop("class ".length) +} diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/ExecuteContract.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/ExecuteContract.kt index 4d4dbb74..ae10c758 100644 --- a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/ExecuteContract.kt +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/ExecuteContract.kt @@ -1,74 +1,37 @@ package io.provenance.api.domain.usecase.cee.execute -import com.google.protobuf.Message -import io.provenance.api.domain.cee.ContractParser import io.provenance.api.domain.cee.ContractService import io.provenance.api.domain.provenance.Provenance import io.provenance.api.domain.usecase.AbstractUseCase -import io.provenance.api.domain.usecase.cee.common.client.CreateClient -import io.provenance.api.domain.usecase.cee.common.client.model.CreateClientRequest +import io.provenance.api.domain.usecase.cee.common.ContractUtilities import io.provenance.api.domain.usecase.cee.execute.model.ExecuteContractRequestWrapper -import io.provenance.api.domain.usecase.common.originator.EntityManager -import io.provenance.api.domain.usecase.common.originator.models.KeyManagementConfigWrapper import io.provenance.api.domain.usecase.provenance.account.GetSigner import io.provenance.api.domain.usecase.provenance.account.models.GetSignerRequest import io.provenance.api.frameworks.provenance.SingleTx -import io.provenance.api.models.cee.ParserConfig +import io.provenance.api.frameworks.provenance.exceptions.ContractExecutionException import io.provenance.api.models.cee.execute.ContractExecutionResponse import io.provenance.api.models.p8e.TxResponse -import io.provenance.client.protobuf.extensions.isSet -import io.provenance.metadata.v1.ScopeResponse -import io.provenance.scope.contract.annotations.Input -import io.provenance.scope.contract.spec.P8eContract -import io.provenance.scope.encryption.util.toJavaPublicKey import io.provenance.scope.sdk.FragmentResult import io.provenance.scope.sdk.SignedResult import java.util.Base64 -import kotlin.reflect.KType -import kotlin.reflect.full.functions -import mu.KotlinLogging import org.springframework.stereotype.Component -private val log = KotlinLogging.logger { } - @Component class ExecuteContract( private val contractService: ContractService, private val provenanceService: Provenance, private val getSigner: GetSigner, - private val contractParser: ContractParser, - private val createClient: CreateClient, - private val entityManager: EntityManager, + private val contractUtilities: ContractUtilities, ) : AbstractUseCase() { override suspend fun execute(args: ExecuteContractRequestWrapper): ContractExecutionResponse { val signer = getSigner.execute(GetSignerRequest(args.uuid, args.request.config.account)) - val audiences = entityManager.hydrateKeys(args.request.permissions, args.request.participants) - val client = createClient.execute(CreateClientRequest(args.uuid, args.request.config.account, args.request.config.client, audiences)) - val contract = contractService.getContract(args.request.config.contract.contractName) - val records = getRecords(args.request.records, contract, args.request.config.contract.parserConfig) - - val participants = args.request.participants.associate { - it.partyType to entityManager.getEntity(KeyManagementConfigWrapper(it.uuid, args.request.config.account.keyManagementConfig)) - } - - val scope = provenanceService.getScope(args.request.config.provenanceConfig, args.request.config.contract.scopeUuid) - val scopeToUse: ScopeResponse? = if (scope.scope.scope.isSet() && !scope.scope.scope.scopeId.isEmpty) scope else null - val session = contractService.setupContract( - client, - contract, - records, - args.request.config.contract.scopeUuid, - args.request.config.contract.sessionUuid, - participants, - scopeToUse, - args.request.config.contract.scopeSpecificationName, - audiences.map { it.encryptionKey.toJavaPublicKey() }.toSet() - ) + val client = contractUtilities.createClient(args.uuid, args.request.permissions, args.request.participants, args.request.config) + val session = contractUtilities.createSession(args.uuid, args.request.permissions, args.request.participants, args.request.config, args.request.records, listOf(args.request.scope)).single() return when (val result = contractService.executeContract(client, session)) { is SignedResult -> { - provenanceService.buildContractTx(args.request.config.provenanceConfig, SingleTx(result))?.let { + provenanceService.buildContractTx(args.request.config.provenanceConfig, SingleTx(result)).let { provenanceService.executeTransaction(args.request.config.provenanceConfig, it, signer).let { pbResponse -> ContractExecutionResponse( false, @@ -81,48 +44,13 @@ class ExecuteContract( ) ) } - } ?: throw IllegalStateException("Failed to build contract for execution output.") + } } is FragmentResult -> { client.requestAffiliateExecution(result.envelopeState) ContractExecutionResponse(true, Base64.getEncoder().encodeToString(result.envelopeState.toByteArray()), null) } - else -> throw IllegalStateException("Contract execution result was not of an expected type.") - } - } - - @Suppress("TooGenericExceptionCaught") - private fun getRecords(records: Map, contract: Class, parserConfig: ParserConfig?): Map { - val contractRecords = mutableMapOf() - - try { - contract.kotlin.functions.forEach { func -> - func.parameters.forEach { param -> - (param.annotations.firstOrNull { it is Input } as? Input)?.let { input -> - val parameterClass = Class.forName(param.type.toClassNameString()) - records.getOrDefault(input.name, null)?.let { - - val record = when (val parser = parserConfig?.name?.let { name -> contractParser.getParser(name) }) { - null -> { - contractParser.parseInput(it, parameterClass) - } - else -> { - parser.parse(it, parameterClass, parserConfig.descriptors) - } - } - - contractRecords[input.name] = record - } - } - } - } - } catch (ex: Exception) { - log.error("Failed to get inputs for contract ${contract.simpleName}") - throw ex + else -> throw ContractExecutionException("Contract execution result was not of an expected type.") } - - return contractRecords } - - private fun KType?.toClassNameString(): String? = this?.classifier?.toString()?.drop("class ".length) } diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/ExecuteContractBatch.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/ExecuteContractBatch.kt new file mode 100644 index 00000000..b10f2221 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/ExecuteContractBatch.kt @@ -0,0 +1,95 @@ +package io.provenance.api.domain.usecase.cee.execute + +import io.provenance.api.domain.cee.ContractService +import io.provenance.api.domain.provenance.Provenance +import io.provenance.api.domain.usecase.AbstractUseCase +import io.provenance.api.domain.usecase.cee.common.ContractUtilities +import io.provenance.api.domain.usecase.cee.execute.model.ExecuteContractBatchRequestWrapper +import io.provenance.api.domain.usecase.provenance.account.GetSigner +import io.provenance.api.domain.usecase.provenance.account.models.GetSignerRequest +import io.provenance.api.frameworks.provenance.BatchTx +import io.provenance.api.frameworks.provenance.exceptions.ContractExecutionBatchException +import io.provenance.api.frameworks.provenance.extensions.toTxResponse +import io.provenance.api.models.cee.execute.ContractExecutionResponse +import io.provenance.scope.sdk.ExecutionResult +import io.provenance.scope.sdk.FragmentResult +import io.provenance.scope.sdk.SignedResult +import java.util.Base64 +import mu.KotlinLogging +import org.springframework.stereotype.Component + +@Component +class ExecuteContractBatch( + private val contractService: ContractService, + private val provenanceService: Provenance, + private val getSigner: GetSigner, + private val contractUtilities: ContractUtilities, +) : AbstractUseCase>() { + + private val log = KotlinLogging.logger { } + + override suspend fun execute(args: ExecuteContractBatchRequestWrapper): List { + val responses = mutableListOf() + val errors = mutableListOf() + val results = mutableListOf() + val signer = getSigner.execute(GetSignerRequest(args.uuid, args.request.config.account)) + val client = contractUtilities.createClient(args.uuid, args.request.permissions, args.request.participants, args.request.config) + + contractUtilities.createSession(args.uuid, args.request.permissions, args.request.participants, args.request.config, args.request.records, args.request.scopes).forEach { + results.add(contractService.executeContract(client, it)) + } + + val chunkedSignedResult = results.filterIsInstance(SignedResult::class.java).chunked(args.request.chunkSize) + chunkedSignedResult.forEachIndexed { index, it -> + runCatching { + provenanceService.buildContractTx(args.request.config.provenanceConfig, BatchTx(it)).let { tx -> + provenanceService.executeTransaction(args.request.config.provenanceConfig, tx, signer).let { pbResponse -> + responses.add( + ContractExecutionResponse( + false, + null, + pbResponse.toTxResponse() + ) + ) + } + } + }.fold( + onSuccess = { + log.info("Successfully processed batch $index of ${chunkedSignedResult.size}") + }, + onFailure = { + errors.add(it) + } + ) + } + + val chunkedFragResult = results.filterIsInstance(FragmentResult::class.java).chunked(args.request.chunkSize) + chunkedFragResult.forEachIndexed { index, chunk -> + runCatching { + chunk.forEach { result -> + client.requestAffiliateExecution(result.envelopeState) + responses.add( + ContractExecutionResponse( + true, + Base64.getEncoder().encodeToString(result.envelopeState.toByteArray()), + null + ) + ) + } + }.fold( + onSuccess = { + log.info("Successfully processed batch $index of ${chunkedFragResult.size}") + }, + onFailure = { + errors.add(it) + } + ) + } + + if (errors.any()) { + throw ContractExecutionBatchException(errors.joinToString(limit = 20) { it.message.toString() }) + } + + return responses + } +} diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/model/ExecuteContractBatchRequestWrapper.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/model/ExecuteContractBatchRequestWrapper.kt new file mode 100644 index 00000000..a3181743 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/execute/model/ExecuteContractBatchRequestWrapper.kt @@ -0,0 +1,9 @@ +package io.provenance.api.domain.usecase.cee.execute.model + +import io.provenance.api.models.cee.execute.ExecuteContractBatchRequest +import java.util.UUID + +data class ExecuteContractBatchRequestWrapper( + val uuid: UUID, + val request: ExecuteContractBatchRequest, +) diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/reject/RejectContractBatchExecution.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/reject/RejectContractBatchExecution.kt new file mode 100644 index 00000000..65a85c5e --- /dev/null +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/reject/RejectContractBatchExecution.kt @@ -0,0 +1,22 @@ +package io.provenance.api.domain.usecase.cee.reject + +import io.provenance.api.domain.usecase.AbstractUseCase +import io.provenance.api.domain.usecase.cee.common.client.CreateClient +import io.provenance.api.domain.usecase.cee.common.client.model.CreateClientRequest +import io.provenance.api.domain.usecase.cee.reject.models.RejectContractBatchRequestWrapper +import io.provenance.scope.contract.proto.Envelopes +import org.springframework.stereotype.Component + +@Component +class RejectContractBatchExecution( + private val createClient: CreateClient +) : AbstractUseCase() { + override suspend fun execute(args: RejectContractBatchRequestWrapper) { + val client = createClient.execute(CreateClientRequest(args.uuid, args.request.account, args.request.client)) + + args.request.rejection.forEach { + val error = Envelopes.EnvelopeError.newBuilder().mergeFrom(it).build() + client.respondWithError(error) + } + } +} diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/reject/models/RejectContractBatchRequestWrapper.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/reject/models/RejectContractBatchRequestWrapper.kt new file mode 100644 index 00000000..dc0aec82 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/reject/models/RejectContractBatchRequestWrapper.kt @@ -0,0 +1,9 @@ +package io.provenance.api.domain.usecase.cee.reject.models + +import io.provenance.api.models.cee.reject.RejectContractBatchRequest +import java.util.UUID + +class RejectContractBatchRequestWrapper( + val uuid: UUID, + val request: RejectContractBatchRequest, +) diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/SubmitContractBatchExecutionResult.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/SubmitContractBatchExecutionResult.kt new file mode 100644 index 00000000..4750c19a --- /dev/null +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/SubmitContractBatchExecutionResult.kt @@ -0,0 +1,68 @@ +package io.provenance.api.domain.usecase.cee.submit + +import io.provenance.api.domain.provenance.Provenance +import io.provenance.api.domain.usecase.AbstractUseCase +import io.provenance.api.domain.usecase.cee.submit.models.SubmitContractBatchExecutionResultRequestWrapper +import io.provenance.api.domain.usecase.provenance.account.GetSigner +import io.provenance.api.domain.usecase.provenance.account.models.GetSignerRequest +import io.provenance.api.frameworks.provenance.BatchTx +import io.provenance.api.frameworks.provenance.exceptions.ContractExecutionBatchException +import io.provenance.api.frameworks.provenance.extensions.toTxResponse +import io.provenance.api.models.p8e.TxResponse +import io.provenance.scope.contract.proto.Envelopes +import io.provenance.scope.sdk.SignedResult +import io.provenance.scope.sdk.extensions.mergeInto +import mu.KotlinLogging +import org.springframework.stereotype.Component + +@Component +class SubmitContractBatchExecutionResult( + private val provenanceService: Provenance, + private val getSigner: GetSigner, +) : AbstractUseCase>() { + + private val log = KotlinLogging.logger { } + + override suspend fun execute(args: SubmitContractBatchExecutionResultRequestWrapper): List { + val signedResults = mutableListOf() + val response = mutableListOf() + val errors = mutableListOf() + val signer = getSigner.execute(GetSignerRequest(args.uuid, args.request.account)) + + args.request.submission.forEach { + val envelope = Envelopes.Envelope.newBuilder().mergeFrom(it.envelope).build() + val state = Envelopes.EnvelopeState.newBuilder().mergeFrom(it.state).build() + when (val result = envelope.mergeInto(state)) { + is SignedResult -> { + signedResults.add(result) + } + else -> throw IllegalStateException("Received a execution result which was not a signed result.") + } + } + + val chunked = signedResults.chunked(args.request.chunkSize) + chunked.forEachIndexed { index, resultCollection -> + runCatching { + + provenanceService.buildContractTx(args.request.provenance, BatchTx(resultCollection)).let { tx -> + provenanceService.executeTransaction(args.request.provenance, tx, signer).let { pbResponse -> + response.add(pbResponse.toTxResponse()) + } + } + }.fold( + onSuccess = { + log.info("Successfully processed batch $index of ${resultCollection.size}") + }, + onFailure = { + errors.add(it) + } + ) + } + + if (errors.any()) { + throw ContractExecutionBatchException(errors.joinToString(limit = 20) { it.message.toString() }) + } + + return response + } +} diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/SubmitContractExecutionResult.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/SubmitContractExecutionResult.kt index 2edcd5e4..b0937e91 100644 --- a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/SubmitContractExecutionResult.kt +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/SubmitContractExecutionResult.kt @@ -1,12 +1,13 @@ package io.provenance.api.domain.usecase.cee.submit -import io.provenance.api.models.p8e.TxResponse import io.provenance.api.domain.provenance.Provenance import io.provenance.api.domain.usecase.AbstractUseCase import io.provenance.api.domain.usecase.cee.submit.models.SubmitContractExecutionResultRequestWrapper import io.provenance.api.domain.usecase.provenance.account.GetSigner import io.provenance.api.domain.usecase.provenance.account.models.GetSignerRequest import io.provenance.api.frameworks.provenance.SingleTx +import io.provenance.api.frameworks.provenance.exceptions.ContractExecutionException +import io.provenance.api.models.p8e.TxResponse import io.provenance.scope.contract.proto.Envelopes import io.provenance.scope.sdk.SignedResult import io.provenance.scope.sdk.extensions.mergeInto @@ -20,18 +21,18 @@ class SubmitContractExecutionResult( override suspend fun execute(args: SubmitContractExecutionResultRequestWrapper): TxResponse { val signer = getSigner.execute(GetSignerRequest(args.uuid, args.request.account)) - val envelope = Envelopes.Envelope.newBuilder().mergeFrom(args.request.envelope).build() - val state = Envelopes.EnvelopeState.newBuilder().mergeFrom(args.request.state).build() + val envelope = Envelopes.Envelope.newBuilder().mergeFrom(args.request.submission.envelope).build() + val state = Envelopes.EnvelopeState.newBuilder().mergeFrom(args.request.submission.state).build() return when (val result = envelope.mergeInto(state)) { is SignedResult -> { - provenanceService.buildContractTx(args.request.provenance, SingleTx(result))?.let { + provenanceService.buildContractTx(args.request.provenance, SingleTx(result)).let { provenanceService.executeTransaction(args.request.provenance, it, signer).let { pbResponse -> TxResponse(pbResponse.txhash, pbResponse.gasWanted.toString(), pbResponse.gasUsed.toString(), pbResponse.height.toString()) } - } ?: throw IllegalStateException("Failed to build the contract tx with the supplied signed result.") + } } - else -> throw IllegalStateException("Received a execution result which was not a signed result.") + else -> throw ContractExecutionException("Received a execution result which was not a signed result.") } } } diff --git a/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/models/SubmitContractBatchExecutionResultRequestWrapper.kt b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/models/SubmitContractBatchExecutionResultRequestWrapper.kt new file mode 100644 index 00000000..e3195e13 --- /dev/null +++ b/service/src/main/kotlin/io/provenance/api/domain/usecase/cee/submit/models/SubmitContractBatchExecutionResultRequestWrapper.kt @@ -0,0 +1,9 @@ +package io.provenance.api.domain.usecase.cee.submit.models + +import io.provenance.api.models.cee.submit.SubmitContractBatchExecutionResultRequest +import java.util.UUID + +data class SubmitContractBatchExecutionResultRequestWrapper( + val uuid: UUID, + val request: SubmitContractBatchExecutionResultRequest +) diff --git a/service/src/main/kotlin/io/provenance/api/frameworks/provenance/ProvenanceService.kt b/service/src/main/kotlin/io/provenance/api/frameworks/provenance/ProvenanceService.kt index b4823a51..b1b442ed 100644 --- a/service/src/main/kotlin/io/provenance/api/frameworks/provenance/ProvenanceService.kt +++ b/service/src/main/kotlin/io/provenance/api/frameworks/provenance/ProvenanceService.kt @@ -12,9 +12,9 @@ import io.grpc.stub.MetadataUtils import io.provenance.api.domain.provenance.Provenance import io.provenance.api.frameworks.provenance.exceptions.ContractTxException import io.provenance.api.frameworks.provenance.extensions.getBaseAccount -import io.provenance.api.frameworks.provenance.extensions.getCurrentHeight import io.provenance.api.frameworks.provenance.extensions.getErrorResult import io.provenance.api.frameworks.provenance.extensions.isError +import io.provenance.api.frameworks.provenance.extensions.toTxBody import io.provenance.api.models.p8e.ProvenanceConfig import io.provenance.api.models.p8e.TxBody import io.provenance.api.models.p8e.TxResponse @@ -31,6 +31,7 @@ import io.provenance.client.grpc.PbClient import io.provenance.client.grpc.Signer import io.provenance.metadata.v1.ScopeRequest import io.provenance.metadata.v1.ScopeResponse +import io.provenance.scope.contract.proto.Contracts import io.provenance.scope.sdk.SignedResult import java.net.URI import java.util.UUID @@ -54,24 +55,27 @@ class ProvenanceService : Provenance { private val log = KotlinLogging.logger { } private val cachedSequenceMap = ConcurrentHashMap() - override fun buildContractTx(config: ProvenanceConfig, tx: ProvenanceTx): TxOuterClass.TxBody? = + override fun buildContractTx(config: ProvenanceConfig, tx: ProvenanceTx): TxOuterClass.TxBody = PbClient(config.chainId, URI(config.nodeEndpoint), GasEstimationMethod.MSG_FEE_CALCULATION).use { pbClient -> return when (tx) { is SingleTx -> { when (val error = tx.getErrorResult()) { null -> { - log.info("Building the tx.") val messages = tx.value.messages.map { Any.pack(it, "") } - - TxOuterClass.TxBody.newBuilder() - .setTimeoutHeight(getCurrentHeight(pbClient) + 12L) - .addAllMessages(messages) - .build() + messages.toTxBody(pbClient) } else -> throw ContractTxException(error.result.errorMessage) } } - is BatchTx -> throw IllegalArgumentException("Batched transactions are not supported.") + is BatchTx -> { + when (val error = tx.getErrorResult()) { + emptyList() -> { + val messages = tx.value.flatMap { it.messages.map { Any.pack(it, "") } } + messages.toTxBody(pbClient) + } + else -> throw ContractTxException("Tx Batch operation failed: $error") + } + } } } @@ -92,7 +96,7 @@ class ProvenanceService : Provenance { txBody = tx, signers = listOf(baseSigner), gasAdjustment = config.gasAdjustment, - mode = ServiceOuterClass.BroadcastMode.BROADCAST_MODE_BLOCK + mode = config.broadcastMode ) pbClient.close() diff --git a/service/src/main/kotlin/io/provenance/api/frameworks/provenance/exceptions/Exceptions.kt b/service/src/main/kotlin/io/provenance/api/frameworks/provenance/exceptions/Exceptions.kt index 2036691b..953b1459 100644 --- a/service/src/main/kotlin/io/provenance/api/frameworks/provenance/exceptions/Exceptions.kt +++ b/service/src/main/kotlin/io/provenance/api/frameworks/provenance/exceptions/Exceptions.kt @@ -1,7 +1,5 @@ package io.provenance.api.frameworks.provenance.exceptions class ContractExecutionException(message: String? = "", cause: Throwable? = null) : RuntimeException(message, cause) -class ResourceNotFoundException(message: String? = "", cause: Throwable? = null) : RuntimeException(message, cause) -class WontRunLoanValidationException(message: String? = "", cause: Throwable? = null) : RuntimeException(message, cause) -class ExistingScopeNotChangedException(message: String) : RuntimeException(message) +class ContractExecutionBatchException(message: String? = "", cause: Throwable? = null) : RuntimeException(message, cause) class ContractTxException(message: String) : RuntimeException(message) diff --git a/service/src/main/kotlin/io/provenance/api/frameworks/provenance/extensions/ProvenanceExtensions.kt b/service/src/main/kotlin/io/provenance/api/frameworks/provenance/extensions/ProvenanceExtensions.kt index 63192c8a..1c68e00f 100644 --- a/service/src/main/kotlin/io/provenance/api/frameworks/provenance/extensions/ProvenanceExtensions.kt +++ b/service/src/main/kotlin/io/provenance/api/frameworks/provenance/extensions/ProvenanceExtensions.kt @@ -1,12 +1,16 @@ package io.provenance.api.frameworks.provenance.extensions +import com.google.protobuf.Any import cosmos.auth.v1beta1.Auth +import cosmos.base.abci.v1beta1.Abci import cosmos.base.abci.v1beta1.Abci.TxResponse import cosmos.base.tendermint.v1beta1.Query import cosmos.tx.v1beta1.ServiceOuterClass.BroadcastTxResponse +import cosmos.tx.v1beta1.TxOuterClass +import io.provenance.api.frameworks.provenance.BatchTx +import io.provenance.api.frameworks.provenance.SingleTx import io.provenance.client.grpc.PbClient import io.provenance.client.protobuf.extensions.getBaseAccount -import io.provenance.api.frameworks.provenance.SingleTx import io.provenance.scope.contract.proto.Contracts import java.util.concurrent.TimeUnit @@ -22,6 +26,17 @@ fun TxResponse.getError(): String = fun SingleTx.getErrorResult() = this.value.envelopeState.result.contract.considerationsList.firstOrNull { it.result.result == Contracts.ExecutionResult.Result.FAIL } +fun BatchTx.getErrorResult() = + this.value.mapNotNull { it.envelopeState.result.contract.considerationsList.firstOrNull { it.result.result == Contracts.ExecutionResult.Result.FAIL } } + +fun Iterable.toTxBody(pbClient: PbClient) = + TxOuterClass.TxBody.newBuilder() + .setTimeoutHeight(getCurrentHeight(pbClient) + 12L) + .addAllMessages(this) + .build() + +fun Abci.TxResponse.toTxResponse() = io.provenance.api.models.p8e.TxResponse(this.txhash, this.gasWanted.toString(), this.gasUsed.toString(), this.height.toString()) + fun getCurrentHeight(pbClient: PbClient): Long = pbClient.tendermintService .withDeadlineAfter(10, TimeUnit.SECONDS) .getLatestBlock(Query.GetLatestBlockRequest.getDefaultInstance()).block.header.height diff --git a/service/src/main/kotlin/io/provenance/api/frameworks/web/ErrorResponses.kt b/service/src/main/kotlin/io/provenance/api/frameworks/web/ErrorResponses.kt index 87955658..913f795c 100644 --- a/service/src/main/kotlin/io/provenance/api/frameworks/web/ErrorResponses.kt +++ b/service/src/main/kotlin/io/provenance/api/frameworks/web/ErrorResponses.kt @@ -2,6 +2,8 @@ package io.provenance.api.frameworks.web import io.provenance.api.domain.usecase.common.errors.NotFoundError import io.provenance.api.domain.usecase.common.errors.ServerError +import io.provenance.api.frameworks.provenance.exceptions.ContractExecutionBatchException +import io.provenance.api.frameworks.provenance.exceptions.ContractExecutionException import mu.KotlinLogging import org.springframework.http.HttpStatus import org.springframework.web.reactive.function.server.ServerResponse @@ -33,6 +35,8 @@ object ErrorResponses { return when (cause) { is NotFoundError -> notFound(cause) is IllegalArgumentException -> badRequest(cause) + is ContractExecutionException -> badRequest(cause) + is ContractExecutionBatchException -> badRequest(cause) is ServerError -> serverError(cause) else -> unknownError(cause) } diff --git a/service/src/main/kotlin/io/provenance/api/frameworks/web/external/cee/CeeApi.kt b/service/src/main/kotlin/io/provenance/api/frameworks/web/external/cee/CeeApi.kt index 79ce808c..10aced45 100644 --- a/service/src/main/kotlin/io/provenance/api/frameworks/web/external/cee/CeeApi.kt +++ b/service/src/main/kotlin/io/provenance/api/frameworks/web/external/cee/CeeApi.kt @@ -1,13 +1,16 @@ package io.provenance.api.frameworks.web.external.cee +import io.provenance.api.frameworks.web.Routes +import io.provenance.api.frameworks.web.logging.logExchange +import io.provenance.api.models.cee.approve.ApproveContractBatchRequest import io.provenance.api.models.cee.approve.ApproveContractRequest import io.provenance.api.models.cee.execute.ContractExecutionResponse import io.provenance.api.models.cee.execute.ExecuteContractRequest +import io.provenance.api.models.cee.reject.RejectContractBatchRequest import io.provenance.api.models.cee.reject.RejectContractRequest +import io.provenance.api.models.cee.submit.SubmitContractBatchExecutionResultRequest import io.provenance.api.models.cee.submit.SubmitContractExecutionResultRequest import io.provenance.api.models.p8e.TxResponse -import io.provenance.api.frameworks.web.Routes -import io.provenance.api.frameworks.web.logging.logExchange import io.swagger.v3.oas.annotations.Operation import io.swagger.v3.oas.annotations.Parameter import io.swagger.v3.oas.annotations.enums.ParameterIn @@ -144,6 +147,120 @@ class CeeApi { ] ) ), + RouterOperation( + path = "${Routes.EXTERNAL_BASE_V1}/cee/batch/submit", + method = arrayOf(RequestMethod.POST), + produces = ["application/json"], + operation = Operation( + tags = ["Contract Execution"], + operationId = "submitContractBatch", + method = "POST", + parameters = [ + Parameter( + name = "x-uuid", + required = true, + `in` = ParameterIn.HEADER, + schema = Schema(implementation = UUID::class), + ), + ], + requestBody = RequestBody( + required = true, + content = [Content(schema = Schema(implementation = SubmitContractBatchExecutionResultRequest::class))] + ), + responses = [ + ApiResponse( + responseCode = "200", + description = "successful operation", + content = [Content(schema = Schema(implementation = TxResponse::class))] + ) + ] + ) + ), + RouterOperation( + path = "${Routes.EXTERNAL_BASE_V1}/cee/batch/reject", + method = arrayOf(RequestMethod.POST), + produces = ["application/json"], + operation = Operation( + tags = ["Contract Execution"], + operationId = "rejectContractBatch", + method = "POST", + parameters = [ + Parameter( + name = "x-uuid", + required = true, + `in` = ParameterIn.HEADER, + schema = Schema(implementation = UUID::class), + ), + ], + requestBody = RequestBody( + required = true, + content = [Content(schema = Schema(implementation = RejectContractBatchRequest::class))] + ), + responses = [ + ApiResponse( + responseCode = "204", + description = "successful operation", + ) + ] + ) + ), + RouterOperation( + path = "${Routes.EXTERNAL_BASE_V1}/cee/batch/approve", + method = arrayOf(RequestMethod.POST), + produces = ["application/json"], + operation = Operation( + tags = ["Contract Execution"], + operationId = "approveContractBatch", + method = "POST", + parameters = [ + Parameter( + name = "x-uuid", + required = true, + `in` = ParameterIn.HEADER, + schema = Schema(implementation = UUID::class), + ), + ], + requestBody = RequestBody( + required = true, + content = [Content(schema = Schema(implementation = ApproveContractBatchRequest::class))] + ), + responses = [ + ApiResponse( + responseCode = "204", + description = "successful operation", + ) + ] + ) + ), + RouterOperation( + path = "${Routes.EXTERNAL_BASE_V1}/cee/batch/execute", + method = arrayOf(RequestMethod.POST), + produces = ["application/json"], + operation = Operation( + tags = ["Contract Execution"], + operationId = "executeContractBatch", + method = "POST", + parameters = [ + Parameter( + name = "x-uuid", + required = true, + `in` = ParameterIn.HEADER, + schema = Schema(implementation = UUID::class), + ), + ], + requestBody = RequestBody( + required = true, + content = [Content(schema = Schema(implementation = SubmitContractBatchExecutionResultRequest::class))] + ), + responses = [ + ApiResponse( + responseCode = "200", + description = "successful operation", + content = [Content(schema = Schema(implementation = TxResponse::class))] + ) + ] + ) + ), ) fun externalCeeApiV1(handler: CeeHandler) = coRouter { logExchange(log) @@ -152,6 +269,12 @@ class CeeApi { POST("/execute", handler::executeContract) POST("/submit", handler::submitContractResult) POST("/reject", handler::rejectContractExecution) + "/batch".nest { + POST("/execute", handler::executeContractBatch) + POST("/submit", handler::submitContractBatchResult) + POST("/reject", handler::rejectContractBatchExecution) + POST("/approve", handler::approveContractBatchExecution) + } } } } diff --git a/service/src/main/kotlin/io/provenance/api/frameworks/web/external/cee/CeeHandler.kt b/service/src/main/kotlin/io/provenance/api/frameworks/web/external/cee/CeeHandler.kt index 8bdde6a4..782861bc 100644 --- a/service/src/main/kotlin/io/provenance/api/frameworks/web/external/cee/CeeHandler.kt +++ b/service/src/main/kotlin/io/provenance/api/frameworks/web/external/cee/CeeHandler.kt @@ -1,12 +1,20 @@ package io.provenance.api.frameworks.web.external.cee +import io.provenance.api.domain.usecase.cee.approve.ApproveContractBatchExecution import io.provenance.api.domain.usecase.cee.approve.ApproveContractExecution +import io.provenance.api.domain.usecase.cee.approve.models.ApproveContractBatchRequestWrapper import io.provenance.api.domain.usecase.cee.approve.models.ApproveContractRequestWrapper import io.provenance.api.domain.usecase.cee.execute.ExecuteContract +import io.provenance.api.domain.usecase.cee.execute.ExecuteContractBatch +import io.provenance.api.domain.usecase.cee.execute.model.ExecuteContractBatchRequestWrapper import io.provenance.api.domain.usecase.cee.execute.model.ExecuteContractRequestWrapper +import io.provenance.api.domain.usecase.cee.reject.RejectContractBatchExecution import io.provenance.api.domain.usecase.cee.reject.RejectContractExecution +import io.provenance.api.domain.usecase.cee.reject.models.RejectContractBatchRequestWrapper import io.provenance.api.domain.usecase.cee.reject.models.RejectContractExecutionRequestWrapper +import io.provenance.api.domain.usecase.cee.submit.SubmitContractBatchExecutionResult import io.provenance.api.domain.usecase.cee.submit.SubmitContractExecutionResult +import io.provenance.api.domain.usecase.cee.submit.models.SubmitContractBatchExecutionResultRequestWrapper import io.provenance.api.domain.usecase.cee.submit.models.SubmitContractExecutionResultRequestWrapper import io.provenance.api.frameworks.web.misc.foldToServerResponse import io.provenance.api.frameworks.web.misc.getUser @@ -20,7 +28,11 @@ class CeeHandler( private val executeContract: ExecuteContract, private val approveContractExecution: ApproveContractExecution, private val rejectContractExecution: RejectContractExecution, - private val submitContract: SubmitContractExecutionResult + private val submitContract: SubmitContractExecutionResult, + private val executeContractBatch: ExecuteContractBatch, + private val submitExecuteContractBatch: SubmitContractBatchExecutionResult, + private val approveContractBatchExecution: ApproveContractBatchExecution, + private val rejectContractBatchExecution: RejectContractBatchExecution, ) { suspend fun executeContract(req: ServerRequest): ServerResponse = runCatching { executeContract.execute(ExecuteContractRequestWrapper(req.getUser(), req.awaitBody())) @@ -37,4 +49,20 @@ class CeeHandler( suspend fun rejectContractExecution(req: ServerRequest): ServerResponse = runCatching { rejectContractExecution.execute(RejectContractExecutionRequestWrapper(req.getUser(), req.awaitBody())) }.foldToServerResponse() + + suspend fun executeContractBatch(req: ServerRequest): ServerResponse = runCatching { + executeContractBatch.execute(ExecuteContractBatchRequestWrapper(req.getUser(), req.awaitBody())) + }.foldToServerResponse() + + suspend fun submitContractBatchResult(req: ServerRequest): ServerResponse = runCatching { + submitExecuteContractBatch.execute(SubmitContractBatchExecutionResultRequestWrapper(req.getUser(), req.awaitBody())) + }.foldToServerResponse() + + suspend fun approveContractBatchExecution(req: ServerRequest): ServerResponse = runCatching { + approveContractBatchExecution.execute(ApproveContractBatchRequestWrapper(req.getUser(), req.awaitBody())) + }.foldToServerResponse() + + suspend fun rejectContractBatchExecution(req: ServerRequest): ServerResponse = runCatching { + rejectContractBatchExecution.execute(RejectContractBatchRequestWrapper(req.getUser(), req.awaitBody())) + }.foldToServerResponse() }