XYOracleNetwork/sdk-core-kotlin

View on GitHub
core-android-library/src/main/kotlin/network/xyo/sdkcorekotlin/node/XyoOriginChainCreator.kt

Summary

Maintainability
A
2 hrs
Test Coverage
package network.xyo.sdkcorekotlin.node

import network.xyo.sdkcorekotlin.XyoException
import network.xyo.sdkcorekotlin.log.XyoLog
import network.xyo.sdkcorekotlin.boundWitness.*
import network.xyo.sdkcorekotlin.hashing.XyoHash
import network.xyo.sdkcorekotlin.heuristics.XyoHeuristicGetter
import network.xyo.sdkcorekotlin.network.*
import network.xyo.sdkcorekotlin.origin.XyoOriginBoundWitnessUtil
import network.xyo.sdkcorekotlin.origin.XyoOriginChainStateManager
import network.xyo.sdkcorekotlin.schemas.XyoSchemas.BRIDGE_BLOCK_SET
import network.xyo.sdkcorekotlin.repositories.XyoOriginBlockRepository
import network.xyo.sdkcorekotlin.repositories.XyoOriginChainStateRepository
import network.xyo.sdkobjectmodelkotlin.exceptions.XyoObjectException
import network.xyo.sdkobjectmodelkotlin.structure.XyoIterableStructure
import network.xyo.sdkobjectmodelkotlin.structure.XyoObjectStructure
import java.nio.ByteBuffer
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.collections.ArrayList
import kotlin.experimental.and
import kotlin.math.min

/**
 * A base class for all things creating an managing an origin chain (e.g. Sentinel, Bridge).
 *
 * @param storageProvider A place to store all origin blocks.
 * @property hashingProvider A hashing provider to use hashing utilities.
 */
open class XyoOriginChainCreator (val blockRepository: XyoOriginBlockRepository,
                                  val stateRepository: XyoOriginChainStateRepository,
                                  private val hashingProvider : XyoHash.XyoHashProvider) {

    private val boundWitnessOptions = ConcurrentHashMap<String, XyoBoundWitnessOption>()
    private val heuristics = ConcurrentHashMap<String, XyoHeuristicGetter>()
    private val listeners = ConcurrentHashMap<String, XyoNodeListener>()
    private var currentBoundWitnessSession : XyoZigZagBoundWitnessSession? = null

    val originState = XyoOriginChainStateManager(stateRepository)

    /**
     * Adds a heuristic to be used when creating bound witnesses.
     *
     * @param key The key for the heuristic.
     * @param heuristic The heuristic to use in bound witnesses.
     */
    fun addHeuristic (key: String, heuristic : XyoHeuristicGetter) {
        heuristics[key] = heuristic
    }

    /**
     * Removes a heuristic from the current heuristic pool.
     *
     * @param key The key of the heuristic to use.
     */
    fun removeHeuristic (key: String) {
        heuristics.remove(key)
    }

    /**
     * Adds a Node Listener to listen for bound witness creations.
     *
     * @param key The key of the listener.
     * @param listener The XyoNodeListener to callback to.
     */
    fun addListener (key : String, listener : XyoNodeListener) {
        listeners[key] = listener
    }

    /**
     * Removes a listener from the current listener pool.
     *
     * @param key The key of the listener to remove.
     */
    fun removeListener (key : String) {
        listeners.remove(key)
    }

    /**
     * Self signs an origin block to the device's origin chain.
     *
     * @param flag The optional flag to use when self signing.
     */
    suspend fun selfSignOriginChain () {
        val boundWitness = XyoZigZagBoundWitness(
                originState.signers,
                makeSignedPayload().toTypedArray(),
                arrayOf()
        )
        boundWitness.incomingData(null, true)
        updateOriginState(boundWitness)
        onBoundWitnessEndSuccess(boundWitness)
    }

    fun addBoundWitnessOption (key: String,  boundWitnessOption: XyoBoundWitnessOption) {
        boundWitnessOptions[key] = boundWitnessOption
    }

    private class XyoOptionPayload (val unsignedOptions : Array<XyoObjectStructure>, val signedOptions : Array<XyoObjectStructure> )

    private suspend fun getBoundWitnessOptionPayloads (options: Array<XyoBoundWitnessOption>) : XyoOptionPayload {
        val signedPayloads =  ArrayList<XyoObjectStructure>()
        val unsignedPayloads = ArrayList<XyoObjectStructure>()

        for (option in options) {
            val optionPayload = option.getPayload()
            val unsignedPayload = optionPayload?.unsignedPayload
            val signedPayload = optionPayload?.signedPayload

            if (unsignedPayload != null) {
                unsignedPayloads.add(unsignedPayload)
            }

            if (signedPayload != null) {
                signedPayloads.add(signedPayload)
            }
        }

        return XyoOptionPayload(unsignedPayloads.toTypedArray(), signedPayloads.toTypedArray())
    }

    private fun getBoundWitnessOptions (flags: ByteArray): Array<XyoBoundWitnessOption> {
        val options = ArrayList<XyoBoundWitnessOption>()

        for ((_, option) in boundWitnessOptions) {
            if (min(option.flag.size, flags.size) != 0) {
                for (i in 0..(min(option.flag.size, flags.size) - 1)) {
                    val otherCatSection = option.flag[option.flag.size - i - 1]
                    val thisCatSection = flags[flags.size - i - 1]

                    if (otherCatSection and thisCatSection != 0.toByte()) {
                        options.add(option)
                    }
                }
            }
        }

        return options.toTypedArray()
    }


    private fun getHeuristics () : Array<XyoObjectStructure> {
        val list = LinkedList<XyoObjectStructure>()

        for ((_, getter) in heuristics) {
            val heuristic = getter.getHeuristic()

            if (heuristic != null) {
                list.add(heuristic)
            }

        }

       return list.toTypedArray()
    }

    private fun onBoundWitnessStart () {
        for ((_, listener) in listeners) {
            listener.onBoundWitnessStart()
        }
    }

    private suspend fun onBoundWitnessEndSuccess (boundWitness: XyoBoundWitness) {
        loadCreatedBoundWitness(boundWitness)

        for ((_, listener) in listeners) {
            listener.onBoundWitnessEndSuccess(boundWitness)
        }
    }

    private fun onBoundWitnessEndFailure(error: Exception?) {
        currentBoundWitnessSession = null
        for ((_, listener) in listeners) {
            listener.onBoundWitnessEndFailure(error)
        }
    }


    private suspend fun loadCreatedBoundWitness (boundWitness: XyoBoundWitness) {
        val hash = boundWitness.getHash(hashingProvider)

        if (!blockRepository.containsOriginBlock(hash)) {
            val subBlocks = XyoOriginBoundWitnessUtil.getBridgedBlocks(boundWitness)
            val boundWitnessWithoutBlocks = XyoBoundWitness.getInstance(
                    XyoBoundWitnessUtil.removeTypeFromUnsignedPayload(BRIDGE_BLOCK_SET.id, boundWitness).bytesCopy
            )

            blockRepository.addBoundWitness(boundWitnessWithoutBlocks)

            for ((_, listener) in listeners) {
                listener.onBoundWitnessDiscovered(boundWitnessWithoutBlocks)
            }

            if (subBlocks != null) {
                for (subBlock in subBlocks) {
                    XyoLog.logSpecial("Found Bridge Block", TAG)
                    loadCreatedBoundWitness(XyoBoundWitness.getInstance(subBlock.bytesCopy))
                }
            }
        }

    }

    @kotlin.ExperimentalUnsignedTypes
    suspend fun boundWitness (handler: XyoNetworkHandler, procedureCatalogue: XyoProcedureCatalog): XyoBoundWitness? {
        try {
            if (currentBoundWitnessSession != null) {
                onBoundWitnessEndFailure(XyoBoundWitnessCreationException("Busy - Bound witness in progress"))
                return null
            }

            onBoundWitnessStart()

            if (handler.pipe.initiationData == null) {
                // is client

                val responseWithChoice = handler.sendCataloguePacket(procedureCatalogue.getEncodedCanDo())

                if (responseWithChoice == null) {
                    onBoundWitnessEndFailure(XyoBoundWitnessCreationException("Response is null"))
                    return null
                }

                val adv = XyoChoicePacket(responseWithChoice)
                val startingData = createStartingData(adv.getResponse())

                return doBoundWitnessWithPipe(handler, startingData, adv.getChoice())
            }

            val choice = procedureCatalogue.choose(XyoProcedureCatalogFlags.flip(handler.pipe.initiationData!!.getChoice()))
            return doBoundWitnessWithPipe(handler, null, choice)
        } catch (e: XyoObjectException) {
            onBoundWitnessEndFailure(e)
        } catch (e: XyoException) {
            onBoundWitnessEndFailure(e)
        }

        return null
    }

    private suspend fun doBoundWitnessWithPipe (handler: XyoNetworkHandler,
                                                startingData: XyoIterableStructure?,
                                                choice: ByteArray): XyoBoundWitness? {

        val options = getBoundWitnessOptions(choice)
        val payloads = getBoundWitnessOptionPayloads(options)
        val signedPayload = makeSignedPayload()
        signedPayload.addAll(payloads.signedOptions)
        signedPayload.addAll(handler.pipe.getNetworkHeuristics())

        val bw = XyoZigZagBoundWitnessSession(
                handler,
                signedPayload.toTypedArray(),
                payloads.unsignedOptions,
                originState.signers,
                XyoProcedureCatalogFlags.flip(choice)
        )

        currentBoundWitnessSession = bw

        val error = currentBoundWitnessSession?.doBoundWitness(startingData)
        handler.pipe.close()

        notifyOptions(options, currentBoundWitnessSession)

        if (currentBoundWitnessSession?.completed == true && error == null) {
            XyoLog.logSpecial("Created Bound Witness", TAG)
            updateOriginState(currentBoundWitnessSession!!)
            onBoundWitnessEndSuccess(currentBoundWitnessSession!!)
            currentBoundWitnessSession = null
            return bw
        }

        onBoundWitnessEndFailure(error)
        currentBoundWitnessSession = null
        return null
    }

    private fun createStartingData (startingData : ByteArray?) : XyoIterableStructure? {
        if (startingData == null) return null

        return XyoIterableStructure(startingData, 0)
    }

    private fun notifyOptions (options: Array<XyoBoundWitnessOption>, boundWitness: XyoBoundWitness?) {
        for (option in options) {
            option.onCompleted(boundWitness)
        }
    }

    private suspend fun updateOriginState (boundWitness: XyoBoundWitness) {
        val hash = boundWitness.getHash(hashingProvider)
        originState.newOriginBlock(hash)
        originState.repo.commit()
        XyoLog.logSpecial("Updating Origin State. Awaiting Index: ${ByteBuffer.wrap(originState.index.valueCopy).int}", TAG)
    }

    private fun makeSignedPayload (): ArrayList<XyoObjectStructure> {
        val signedPayloads = ArrayList<XyoObjectStructure>(getHeuristics().asList())
        val previousHash = originState.previousHash
        val index = originState.index
        val nextPublicKey = originState.nextPublicKey

        if (previousHash != null) {
            signedPayloads.add(previousHash)
        }

        if (nextPublicKey != null) {
            signedPayloads.add(nextPublicKey)
        }

        signedPayloads.add(index)
        signedPayloads.addAll(originState.statics)

        return signedPayloads
    }

    companion object {
        const val TAG = "NOD"
    }
}