Skip to content

Commit

Permalink
Batching Contract Execution (#31)
Browse files Browse the repository at this point in the history
* Adding participants to audience keys

* first pass

* updates

* final

* cleanup

* cleanup / documentation

* PR comments

* more pr comments
  • Loading branch information
cworsnop-figure authored Jun 16, 2022
1 parent f89a38d commit 3e99626
Show file tree
Hide file tree
Showing 32 changed files with 690 additions and 127 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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<EnvelopeApproval>,
val chunkSize: Int = 25,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Original file line number Diff line number Diff line change
@@ -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),
)
Original file line number Diff line number Diff line change
@@ -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<String, Any>,
val participants: List<Participant> = emptyList(),
val permissions: PermissionInfo?,
val chunkSize: Int = 25,
val scopes: List<ScopeInfo> = emptyList(),
)
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, Any>,
val participants: List<Participant> = emptyList(),
val permissions: PermissionInfo?,
val permissions: PermissionInfo? = null,
)
Original file line number Diff line number Diff line change
@@ -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,
)
Original file line number Diff line number Diff line change
@@ -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<ByteArray>,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.provenance.api.models.cee.submit

data class EnvelopeSubmission(
val envelope: ByteArray,
val state: ByteArray
)
Original file line number Diff line number Diff line change
@@ -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<EnvelopeSubmission>,
val chunkSize: Int = 25,
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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<UUID>): TxResponse
fun verifyAsset(config: ProvenanceConfig, signer: Signer, contractConfig: SmartContractConfig, verifyAssetRequest: VerifyAssetExecute<UUID>): TxResponse
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ApproveContractBatchRequestWrapper, Unit>() {
private val log = KotlinLogging.logger { }

override suspend fun execute(args: ApproveContractBatchRequestWrapper) {
val errors = mutableListOf<Throwable>()
val executionResults = mutableListOf<Pair<Envelopes.EnvelopeState, List<Tx.MsgGrant>>>()
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() })
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -18,22 +21,21 @@ class ApproveContractExecution(
private val createClient: CreateClient,
private val provenance: Provenance,
private val getSigner: GetSigner,
) : AbstractUseCase<ApproveContractRequestWrapper, Unit>() {
override suspend fun execute(args: ApproveContractRequestWrapper) {
) : AbstractUseCase<ApproveContractRequestWrapper, TxResponse>() {
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!")
}
}
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
@@ -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<Participant>, 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<Participant>, config: ExecuteContractConfig, records: Map<String, Any>, scopes: List<ScopeInfo>): List<Session> {
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<String, Any>, contract: Class<out P8eContract>, parserConfig: ParserConfig?): Map<String, Message> {
val contractRecords = mutableMapOf<String, Message>()

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)
}
Loading

0 comments on commit 3e99626

Please sign in to comment.