All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.provenance.p8e.shared.domain.Envelope.kt Maven / Gradle / Ivy

package io.provenance.p8e.shared.domain

import com.fasterxml.jackson.databind.ObjectMapper
import io.p8e.proto.ContractScope.Envelope.Status
import io.p8e.proto.ContractScope.EnvelopeState
import io.p8e.proto.ContractScope.Scope
import io.p8e.util.toHex
import io.p8e.util.toPublicKeyProto
import io.provenance.p8e.shared.sql.offsetDatetime
import io.provenance.p8e.shared.util.proto
import io.p8e.util.toUuidProv
import io.p8e.util.configureProvenance
import io.p8e.util.getExecUuid
import io.p8e.util.getPrevExecUuidNullable
import io.p8e.util.getUuid
import io.provenance.p8e.shared.sql.jsonValue
import io.provenance.p8e.shared.sql.jsonb
import org.jetbrains.exposed.dao.*
import io.provenance.p8e.shared.state.EnvelopeStateEngine
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.UUIDEntity
import org.jetbrains.exposed.dao.UUIDEntityClass
import org.jetbrains.exposed.dao.id.UUIDTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.isNull
import org.jetbrains.exposed.sql.transactions.TransactionManager
import java.security.PublicKey
import java.sql.ResultSet
import java.time.OffsetDateTime
import java.util.*

object EnvelopeTable : UUIDTable(name = "envelope", columnName = "uuid") {
    val groupUuid = uuid("group_uuid").index()
    val executionUuid = uuid("execution_uuid").index()
    val prevExecutionUuid = uuid("prev_execution_uuid").index().nullable()
    val scope = reference("scope_uuid", ScopeTable).index()
    val publicKey = text("public_key")
    val data = proto("data", EnvelopeState.getDefaultInstance())
    val chaincodeTransaction = jsonb("chaincode_transaction", ObjectMapper().configureProvenance()).nullable()
    val scopeSnapshot = proto("scope_snapshot", Scope.getDefaultInstance()).nullable()
    val expirationTime = offsetDatetime("expiration_time").nullable()
    val errorTime = offsetDatetime("error_time").nullable()
    val fragmentTime = offsetDatetime("fragment_time").nullable()
    val executedTime = offsetDatetime("executed_time").nullable()
    val chaincodeTime = offsetDatetime("chaincode_time").nullable()
    val outboundTime = offsetDatetime("outbound_time").nullable()
    val inboxTime = offsetDatetime("inbox_time").nullable()
    val indexTime = offsetDatetime("index_time").nullable()
    val readTime = offsetDatetime("read_time").nullable()
    val completeTime = offsetDatetime("complete_time").nullable()
    val signedTime = offsetDatetime("signed_time").nullable()
    val createdTime = offsetDatetime("created_time").clientDefault { OffsetDateTime.now() }
    val isInvoker = bool("is_invoker").nullable()
    val status = enumerationByName("status", 256, Status::class)
    val transactionHash = varchar("transaction_hash", 64).nullable()
    val blockHeight = long("block_height").nullable()

    val contractName = data.jsonValue("input", "contract", "spec", "name")
    val contractClassname = data.jsonValue("contractClassname")
    val actualScopeUuid = data.jsonValue("input", "ref", "scopeUuid", "value")
}

open class EnvelopeEntityClass : UUIDEntityClass(
    EnvelopeTable
) {
    private val objectMapper = ObjectMapper().configureProvenance()

    fun findByGroupUuid(groupUuid: UUID) = find { EnvelopeTable.groupUuid eq groupUuid }.toList()

    fun findByGroupAndExecutionUuid(groupUuid: UUID, executionUuid: UUID) = find {
        EnvelopeTable.groupUuid eq groupUuid and (EnvelopeTable.executionUuid eq executionUuid)
    }.toList()

    fun findByExecutionUuid(executionUuid: UUID) = find {
        EnvelopeTable.executionUuid eq executionUuid
    }.toList()

    fun findByPublicKeyAndExecutionUuid(publicKey: PublicKey, executionUuid: UUID) =
        find { EnvelopeTable.executionUuid eq executionUuid }.toList()
            .firstOrNull { it.scope.publicKey.toPublicKeyProto() == publicKey.toPublicKeyProto() }

    fun findByPublicKeyAndExecutionUuidBeforeScope(publicKey: PublicKey, executionUuid: UUID) =
        find { EnvelopeTable.executionUuid eq executionUuid }.toList()
            .firstOrNull { it.publicKey == publicKey.toHex() }

    fun findForUpdate(uuid: UUID): EnvelopeRecord? = find { EnvelopeTable.id eq uuid }.forUpdate().firstOrNull()

    fun findForUpdate(publicKey: PublicKey, executionUuid: UUID): EnvelopeRecord? =
        EnvelopeTable
            .innerJoin(ScopeTable)
            .slice(EnvelopeTable.columns)
            .select { (EnvelopeTable.executionUuid eq executionUuid) and (ScopeTable.publicKey eq publicKey.toHex()) }
            .forUpdate()
            .firstOrNull()
            ?.let { EnvelopeRecord.wrapRow(it) }

    fun insert(scopeRecord: ScopeRecord, proto: EnvelopeState, publicKey: PublicKey, envelopeStateEngine: EnvelopeStateEngine): EnvelopeRecord =
        new {
            groupUuid = proto.input.getUuid()
            executionUuid = proto.input.getExecUuid()
            prevExecutionUuid = proto.input.getPrevExecUuidNullable()
            scope = scopeRecord
            this.publicKey = publicKey.toHex()

            proto.takeIf { it.hasInboxTime() }
                ?.let {
                    envelopeStateEngine.onHandleInbox(this, it)
                }

            proto.takeIf { it.isInvoker }
                ?.let {
                    envelopeStateEngine.onHandleCreated(this, it)
                }
        }

    fun findUuids(query: String, columnLabel: String = "uuid"): List =
        TransactionManager.current().exec(query) { it.toUuids(columnLabel) }!!

    private fun ResultSet.toByteArray(columnLabel: String = "public_key"): List {
        val publicKey = mutableListOf()
        while (next()) {
            publicKey.add(getString(columnLabel).toByteArray())
        }
        return publicKey
    }

    private fun ResultSet.toUuids(columnLabel: String = "uuid"): List {
        val uuids = mutableListOf()

        while (next()) {
            uuids.add(getString(columnLabel).toUuidProv())
        }

        return uuids
    }

    private val indexQuery = """
        SELECT e.uuid
        FROM envelope e
        INNER JOIN scope s ON s.uuid = e.scope_uuid
        WHERE s.public_key = ?::text
        AND e.index_time IS NOT NULL
        AND e.complete_time IS NULL
        AND e.error_time IS NULL
        AND e.data->>? = ?
        limit 500
    """.trimIndent()

    fun findIndexesNotRead(publicKey: PublicKey, className: String) =
        findUuidsByPublicKeyAndClass(publicKey, className, indexQuery)

    private fun findUuidsByPublicKeyAndClass(publicKey: PublicKey, className: String, query: String) =
        TransactionManager.current()
        .connection
        .prepareStatement(query, false).also { statement ->
            statement.set(1, publicKey.toHex())
            statement.set(2, "contractClassname")
            statement.set(3, className)
        }.executeQuery().toUuids()

    fun search(limit: Int, q: String?, publicKey: String?, type: String?, eagerLoadScope: Boolean = true): List = mutableListOf>()
            .let { expressions ->
                val columns = EnvelopeTable.columns.filter { !it.equals(EnvelopeTable.data) }
                    .plus(EnvelopeTable.contractName)
                    .plus(EnvelopeTable.actualScopeUuid)
                var query = EnvelopeTable.slice(columns)
                if (q != null) {
                    try {
                        val uuid = UUID.fromString(q)
                        query = EnvelopeTable
                                .join(ScopeTable, JoinType.INNER)
                                .slice(columns)
                        expressions.add((EnvelopeTable.scope eq uuid) or (ScopeTable.scopeUuid eq uuid) or (EnvelopeTable.id eq uuid) or (EnvelopeTable.executionUuid eq uuid) or (EnvelopeTable.groupUuid eq uuid))
                    } catch (e: IllegalArgumentException) {
                        // invalid uuid
                    }
                }

                if (publicKey != null) {
                    expressions.add(EnvelopeTable.publicKey eq publicKey)
                }

                if (type != null) {
                    when (type.toLowerCase()) {
                        "fragment" -> expressions.add(EnvelopeTable.isInvoker.isNull())
                        else -> expressions.add(EnvelopeTable.isInvoker eq true)
                    }
                }

                when {
                    expressions.isEmpty() -> query.selectAll()
                    else -> query.select(AndOp(expressions))
                }.orderBy(EnvelopeTable.createdTime to SortOrder.DESC).limit(limit)
                        .let {EnvelopeRecord.wrapRows(it)}
                        .let {
                            when {
                                eagerLoadScope -> it.with(EnvelopeRecord::scope)
                                else -> it.toList()
                            }
                        }
            }
}

class EnvelopeRecord(uuid: EntityID) : UUIDEntity(uuid) {
    companion object : EnvelopeEntityClass()

    var uuid by EnvelopeTable.id
    var groupUuid by EnvelopeTable.groupUuid
    var executionUuid by EnvelopeTable.executionUuid
    var prevExecutionUuid by EnvelopeTable.prevExecutionUuid
    var scope by ScopeRecord referencedOn EnvelopeTable.scope
    var scopeUuid by EnvelopeTable.scope
    var publicKey by EnvelopeTable.publicKey
    var data: EnvelopeState by EnvelopeTable.data
    var chaincodeTransaction by EnvelopeTable.chaincodeTransaction
    var scopeSnapshot: Scope? by EnvelopeTable.scopeSnapshot
    var expirationTime by EnvelopeTable.expirationTime
    var errorTime by EnvelopeTable.errorTime
    var fragmentTime by EnvelopeTable.fragmentTime
    var executedTime by EnvelopeTable.executedTime
    var chaincodeTime by EnvelopeTable.chaincodeTime
    var outboundTime by EnvelopeTable.outboundTime
    var inboxTime by EnvelopeTable.inboxTime
    var indexTime by EnvelopeTable.indexTime
    var readTime by EnvelopeTable.readTime
    var completeTime by EnvelopeTable.completeTime
    var signedTime by EnvelopeTable.signedTime
    var createdTime by EnvelopeTable.createdTime
    var isInvoker by EnvelopeTable.isInvoker
    var status by EnvelopeTable.status
    var transactionHash by EnvelopeTable.transactionHash
    var blockHeight by EnvelopeTable.blockHeight

    var contractName by EnvelopeTable.contractName
    var actualScopeUuid by EnvelopeTable.actualScopeUuid

    fun isExpired() = expirationTime?.let { it <= OffsetDateTime.now() } ?: false
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy