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

io.provenance.p8e.shared.state.EnvelopeStateEngine.kt Maven / Gradle / Ivy

package io.provenance.p8e.shared.state

import io.p8e.proto.ContractScope
import io.p8e.proto.ContractScope.Envelope.Status.CREATED
import io.p8e.proto.ContractScope.Envelope.Status.CHAINCODE
import io.p8e.proto.ContractScope.Envelope.Status.COMPLETE
import io.p8e.proto.ContractScope.Envelope.Status.INDEX
import io.p8e.proto.ContractScope.Envelope.Status.SIGNED
import io.p8e.proto.ContractScope.Envelope.Status.ERROR
import io.p8e.proto.ContractScope.Envelope.Status.EXECUTED
import io.p8e.proto.ContractScope.Envelope.Status.OUTBOX
import io.p8e.proto.ContractScope.Envelope.Status.FRAGMENT
import io.p8e.proto.ContractScope.Envelope.Status.INBOX
import io.p8e.proto.ContractScope.Envelope.Status.UNRECOGNIZED
import io.p8e.proto.ContractScope.EnvelopeState
import io.p8e.proto.ContractScope.EnvelopeState.Builder
import io.p8e.proto.ContractScope.Scope
import io.p8e.util.toJavaPublicKey
import io.p8e.util.auditedProv
import io.provenance.p8e.shared.extension.logger
import io.p8e.util.toOffsetDateTimeProv
import io.p8e.util.toProtoTimestampProv
import io.provenance.p8e.shared.domain.EnvelopeRecord
import io.provenance.p8e.shared.domain.ScopeRecord
import io.provenance.p8e.shared.extension.filterByGroup
import io.provenance.p8e.shared.extension.pendingConsiderationBuilders
import io.provenance.p8e.shared.extension.record
import java.time.OffsetDateTime

class EnvelopeStateEngine {

    private val log = logger()

    /**
        CREATED = 0 [(description) = "Envelope created."];
        FRAGMENT = 1 [(description) = "Envelope sent to other parties, awaiting responses."];
        INBOX = 2 [(description) = "Envelope received."];
        EXECUTED = 3 [(description) = "Envelope executed by non-invoker."];
        OUTBOX = 4 [(description) = "Envelope response sent from non-invoker to invoker."];
        SIGNED = 5 [(description) = "Envelope is complete with all signatures."];
        CHAINCODE = 6 [(description) = "Envelope has been sent to chaincode."];
        INDEX = 7 [(description) = "Envelope has been returned from chaincode."];
        COMPLETE = 8 [(description) = "Envelope has been completed."];
        ERROR = 11 [(description) = "Envelope is in error state."];
    */

    fun onHandleCreated(record: EnvelopeRecord, state: EnvelopeState): EnvelopeRecord {
        log.info("Handling created state for envelope")

        record.isInvoker = true
        record.status = CREATED
        record.createdTime = state.auditFields.createdDate.toOffsetDateTimeProv()

        log.info("Committing envelope state data ${record.status}")
        record.data = state.toBuilder()
                .apply { inputBuilder.status = CREATED }
                .build()

        return record

    }

    fun onHandleFragment(record: EnvelopeRecord): EnvelopeRecord {
        log.info("Handling fragment state for envelope")

        val now  = OffsetDateTime.now()

        record.status = FRAGMENT
        record.fragmentTime = now
        record.data = commitEnvelopeState(record, now)
                .auditedProv()
                .build()

        return record
    }

    fun onHandleInbox(record: EnvelopeRecord, state: EnvelopeState): EnvelopeRecord {
        log.info("Handling inbox state for envelope")

        val now = OffsetDateTime.now()

        record.status = INBOX
        record.inboxTime = now

        log.info("Committing envelope state data ${record.status}")
        record.data = state.toBuilder()
                .apply { inputBuilder.status = INBOX }
                .build()

       return record
    }

    fun onHandleRead(record: EnvelopeRecord): EnvelopeRecord {
        log.info("Handling read state for envelope")

        val now = OffsetDateTime.now()

        record.readTime = now
        record.data = record.data.toBuilder()
                .setReadTime(now.toProtoTimestampProv())
                .auditedProv()
                .build()

        return record
    }

    fun onHandleExecute(record: EnvelopeRecord, result: ContractScope.Envelope): EnvelopeRecord {
        log.info("Handling execute state for envelope")

        val now = OffsetDateTime.now()

        record.status = EXECUTED
        record.executedTime = now
        record.data = commitEnvelopeState(record = record, now = now, result = result)
                .auditedProv()
                .build()

        return record
    }

    fun onHandleOutbox(record: EnvelopeRecord): EnvelopeRecord {
        log.info("Handling outbox state for envelope")

        val now = OffsetDateTime.now()

        record.status = OUTBOX
        record.outboundTime = now
        record.data = commitEnvelopeState(record, now)
                .auditedProv()
                .build()

        return record
    }

    fun onHandleSign(record: EnvelopeRecord): EnvelopeRecord {
        log.info("Handling signed state for envelope")

        val now = OffsetDateTime.now()

        if(record.errorTime != null) {
            log.info("Envelope has an error time - marking as errored.")
            return onHandleError(record)
        } else if(record.status == SIGNED && record.signedTime != null) {
            log.info("Envelope is already signed, moving to state chaincode")
            return onHandleChaincode(record)
        } else {
            log.info("Setting envelope to signed state")

            record.status = SIGNED
            record.signedTime = now
            record.data = commitEnvelopeState(record, now)
                    .auditedProv()
                    .build()

            return record
        }
    }

    fun onHandleChaincode(record: EnvelopeRecord, now: OffsetDateTime = OffsetDateTime.now()): EnvelopeRecord {
        log.info("Handling chaincode state for envelope")

        if(record.errorTime != null) {
            log.info("Envelope has an error time marking as errored.")
            return onHandleError(record)
        } else if(record.status == CHAINCODE && record.chaincodeTime != null) {
            log.info("Envelope has been ChainCoded, moving to Index state")

            val scope = ScopeRecord.findByPublicKeyAndScopeUuid(
                    record.publicKey.toJavaPublicKey(),
                    record.scope.scopeUuid)?.data?.takeIf { it.hasUuid() }

            return if(scope == null) {
                log.error("Error on Envelope - no scope record found!!")
                onHandleError(record)
            } else {
                onHandleIndex(record = record, scope = scope)
            }

        } else {
            log.info("Setting envelope to chaincode state")

            record.status = CHAINCODE
            record.chaincodeTime = now
            record.data = commitEnvelopeState(record, now)
                    .auditedProv()
                    .build()

            return record
        }
    }

    fun onHandleIndex(record: EnvelopeRecord, scope: Scope): EnvelopeRecord {
        log.info("Handling index state for envelope")

        val now = OffsetDateTime.now()

        if(record.errorTime != null) {
            log.info("Envelope has an error time - marking as errored")
            return onHandleError(record)
        } else if (record.status == INDEX && record.indexTime != null) {
            log.info("Envelope has been Index, moving to completed state")
            return onHandleComplete(record)
        } else {
            log.info("Setting envelope to index state")

            record.status = INDEX
            record.indexTime = now
            record.data = commitEnvelopeState(record = record, now = now, scope = scope)
                    .auditedProv()
                    .build()

            return record
        }
    }

    fun onHandleComplete(record: EnvelopeRecord): EnvelopeRecord {
        log.info("Handling completed state of envelope")

        val now = OffsetDateTime.now()

        if(record.errorTime != null) {
            log.info("Envelope has an error time marking as errored")
            return onHandleError(record)
        } else if(record.data.hasCompleteTime()) {
            log.warn("Cleaning up already completed envelope.")
            record.status = COMPLETE
            record.completeTime = record.data.completeTime.toOffsetDateTimeProv()
        } else {
            log.info("Setting envelope to completed state")

            record.status = COMPLETE
            record.completeTime = now
            record.data = commitEnvelopeState(record, now)
                    .auditedProv()
                    .build()
        }

        return record
    }

    fun onHandleError(record: EnvelopeRecord): EnvelopeRecord {
        log.info("Handling errored state of envelope")

        val now = OffsetDateTime.now()

        /**
         * Error time is set in DomainExtension.EnvelopeRecord.newError
         */
        record.status = ERROR
        record.data = commitEnvelopeState(record, now)
                .auditedProv()
                .build()

        return record
    }

    private fun commitEnvelopeState(record: EnvelopeRecord, now: OffsetDateTime, scope: Scope? = null, result: ContractScope.Envelope? = null): Builder {
        log.info("Committing envelope state ${record.status}")

        return when (record.status) {
            FRAGMENT ->
                record.data.toBuilder()
                        .setFragmentTime(now.toProtoTimestampProv())
                        .apply { resultBuilder.status = FRAGMENT }
            EXECUTED ->
                record.data.toBuilder()
                        .setResult(result)
                        .setExecutedTime(now.toProtoTimestampProv())
                        .apply { resultBuilder.status = EXECUTED }
            OUTBOX ->
                record.data.toBuilder()
                        .setOutboundTime(now.toProtoTimestampProv())
                        .apply { resultBuilder.status = OUTBOX }
            SIGNED ->
                record.data.toBuilder()
                        .setSignedTime(now.toProtoTimestampProv())
                        .apply { resultBuilder.status = SIGNED }
            CHAINCODE ->
                record.data.toBuilder()
                        .setChaincodeTime(now.toProtoTimestampProv())
                        .apply { resultBuilder.status = CHAINCODE }
            INDEX ->
                record.data.toBuilder()
                        .apply { resultBuilder.status = INDEX }
                        .also { builder -> builder.resultBuilder.pendingConsiderationBuilders().record(scope!!.filterByGroup(record.groupUuid)) }
                        .setIndexTime(now.toProtoTimestampProv())
                        .clearErrorTime()
            COMPLETE ->
                record.data.toBuilder()
                        .setCompleteTime(now.toProtoTimestampProv())
                        .apply { resultBuilder.status = COMPLETE }
            ERROR ->
                record.data.toBuilder()
                        .apply {
                            if (record.data.hasResult()) {
                                resultBuilder.status = ERROR
                            } else {
                                inputBuilder.status = ERROR
                            }
                        }
            CREATED, INBOX, UNRECOGNIZED -> throw IllegalStateException("Unhandled envelope status: ${record.status}") // Handled differently
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy