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

net.corda.contracts.asset.OnLedgerAsset.kt Maven / Gradle / Ivy

package net.corda.contracts.asset

import net.corda.contracts.clause.AbstractConserveAmount
import net.corda.core.contracts.*
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.trace
import java.security.PublicKey
import java.util.*

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Generic contract for assets on a ledger
//

/**
 * An asset transaction may split and merge assets represented by a set of (issuer, depositRef) pairs, across multiple
 * input and output states. Imagine a Bitcoin transaction but in which all UTXOs had a colour (a blend of
 * issuer+depositRef) and you couldn't merge outputs of two colours together, but you COULD put them in the same
 * transaction.
 *
 * The goal of this design is to ensure that assets can be withdrawn from the ledger easily: if you receive some asset
 * via this contract, you always know where to go in order to extract it from the R3 ledger, no matter how many hands
 * it has passed through in the intervening time.
 *
 * At the same time, other contracts that just want assets and don't care much who is currently holding it can ignore
 * the issuer/depositRefs and just examine the amount fields.
 */
abstract class OnLedgerAsset> : Contract {
    companion object {
        val log = loggerFor>()

        /**
         * Generate a transaction that moves an amount of currency to the given pubkey.
         *
         * Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
         *
         * @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
         *           to move the cash will be added on top.
         * @param amount How much currency to send.
         * @param to a key of the recipient.
         * @param acceptableStates a list of acceptable input states to use.
         * @param deriveState a function to derive an output state based on an input state, amount for the output
         * and public key to pay to.
         * @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
         *         the resulting transaction for it to be valid.
         * @throws InsufficientBalanceException when a cash spending transaction fails because
         *         there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
         */
        @Throws(InsufficientBalanceException::class)
        @JvmStatic
        fun , T: Any> generateSpend(tx: TransactionBuilder,
                                                         amount: Amount,
                                                         to: PublicKey,
                                                         acceptableStates: List>,
                                                         deriveState: (TransactionState, Amount>, PublicKey) -> TransactionState,
                                                         generateMoveCommand: () -> CommandData): Pair> {
            // Discussion
            //
            // This code is analogous to the Wallet.send() set of methods in bitcoinj, and has the same general outline.
            //
            // First we must select a set of asset states (which for convenience we will call 'coins' here, as in bitcoinj).
            // The input states can be considered our "vault", and may consist of different products, and with different
            // issuers and deposits.
            //
            // Coin selection is a complex problem all by itself and many different approaches can be used. It is easily
            // possible for different actors to use different algorithms and approaches that, for example, compete on
            // privacy vs efficiency (number of states created). Some spends may be artificial just for the purposes of
            // obfuscation and so on.
            //
            // Having selected input states of the correct asset, we must craft output states for the amount we're sending and
            // the "change", which goes back to us. The change is required to make the amounts balance. We may need more
            // than one change output in order to avoid merging assets from different deposits. The point of this design
            // is to ensure that ledger entries are immutable and globally identifiable.
            //
            // Finally, we add the states to the provided partial transaction.

            // TODO: We should be prepared to produce multiple transactions spending inputs from
            // different notaries, or at least group states by notary and take the set with the
            // highest total value.

            // notary may be associated with locked state only
            tx.notary = acceptableStates.firstOrNull()?.state?.notary

            val (gathered, gatheredAmount) = gatherCoins(acceptableStates, amount)

            val takeChangeFrom = gathered.firstOrNull()
            val change = if (takeChangeFrom != null && gatheredAmount > amount) {
                Amount(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.data.amount.token)
            } else {
                null
            }
            val keysUsed = gathered.map { it.state.data.owner }

            val states = gathered.groupBy { it.state.data.amount.token.issuer }.map {
                val coins = it.value
                val totalAmount = coins.map { it.state.data.amount }.sumOrThrow()
                deriveState(coins.first().state, totalAmount, to)
            }.sortedBy { it.data.amount.quantity }

            val outputs = if (change != null) {
                // Just copy a key across as the change key. In real life of course, this works but leaks private data.
                // In bitcoinj we derive a fresh key here and then shuffle the outputs to ensure it's hard to follow
                // value flows through the transaction graph.
                val existingOwner = gathered.first().state.data.owner
                // Add a change output and adjust the last output downwards.
                states.subList(0, states.lastIndex) +
                        states.last().let {
                            val spent = it.data.amount.withoutIssuer() - change.withoutIssuer()
                            deriveState(it, Amount(spent.quantity, it.data.amount.token), it.data.owner)
                        } +
                        states.last().let {
                            deriveState(it, Amount(change.quantity, it.data.amount.token), existingOwner)
                        }
            } else states

            for (state in gathered) tx.addInputState(state)
            for (state in outputs) tx.addOutputState(state)

            // What if we already have a move command with the right keys? Filter it out here or in platform code?
            tx.addCommand(generateMoveCommand(), keysUsed)

            return Pair(tx, keysUsed)
        }

        /**
         * Gather assets from the given list of states, sufficient to match or exceed the given amount.
         *
         * @param acceptableCoins list of states to use as inputs.
         * @param amount the amount to gather states up to.
         * @throws InsufficientBalanceException if there isn't enough value in the states to cover the requested amount.
         */
        @Throws(InsufficientBalanceException::class)
        private fun , T : Any> gatherCoins(acceptableCoins: Collection>,
                                                                amount: Amount): Pair>, Amount> {
            require(amount.quantity > 0) { "Cannot gather zero coins" }
            val gathered = arrayListOf>()
            var gatheredAmount = Amount(0, amount.token)
            for (c in acceptableCoins) {
                if (gatheredAmount >= amount) break
                gathered.add(c)
                gatheredAmount += Amount(c.state.data.amount.quantity, amount.token)
            }

            if (gatheredAmount < amount) {
                log.trace { "Insufficient balance: requested $amount, available $gatheredAmount" }
                throw InsufficientBalanceException(amount - gatheredAmount)
            }

            log.trace { "Gathered coins: requested $amount, available $gatheredAmount, change: ${gatheredAmount - amount}" }

            return Pair(gathered, gatheredAmount)
        }

        /**
         * Generate an transaction exiting fungible assets from the ledger.
         *
         * @param tx transaction builder to add states and commands to.
         * @param amountIssued the amount to be exited, represented as a quantity of issued currency.
         * @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is
         * the responsibility of the caller to check that they do not attempt to exit funds held by others.
         * @return the public key of the assets issuer, who must sign the transaction for it to be valid.
         */
        @Throws(InsufficientBalanceException::class)
        @JvmStatic
        fun , T: Any> generateExit(tx: TransactionBuilder, amountIssued: Amount>,
                                                        assetStates: List>,
                                                        deriveState: (TransactionState, Amount>, PublicKey) -> TransactionState,
                                                        generateMoveCommand: () -> CommandData,
                                                        generateExitCommand: (Amount>) -> CommandData): PublicKey {
            val owner = assetStates.map { it.state.data.owner }.toSet().singleOrNull() ?: throw InsufficientBalanceException(amountIssued)
            val currency = amountIssued.token.product
            val amount = Amount(amountIssued.quantity, currency)
            var acceptableCoins = assetStates.filter { ref -> ref.state.data.amount.token == amountIssued.token }
            tx.notary = acceptableCoins.firstOrNull()?.state?.notary
            // TODO: We should be prepared to produce multiple transactions exiting inputs from
            // different notaries, or at least group states by notary and take the set with the
            // highest total value
            acceptableCoins = acceptableCoins.filter { it.state.notary == tx.notary }

            val (gathered, gatheredAmount) = gatherCoins(acceptableCoins, amount)
            val takeChangeFrom = gathered.lastOrNull()
            val change = if (takeChangeFrom != null && gatheredAmount > amount) {
                Amount(gatheredAmount.quantity - amount.quantity, takeChangeFrom.state.data.amount.token)
            } else {
                null
            }

            val outputs = if (change != null) {
                // Add a change output and adjust the last output downwards.
                listOf(deriveState(gathered.last().state, change, owner))
            } else emptyList()

            for (state in gathered) tx.addInputState(state)
            for (state in outputs) tx.addOutputState(state)
            tx.addCommand(generateMoveCommand(), gathered.map { it.state.data.owner })
            tx.addCommand(generateExitCommand(amountIssued), gathered.flatMap { it.state.data.exitKeys })
            return amountIssued.token.issuer.party.owningKey
        }

        /**
         * Puts together an issuance transaction for the specified state. Normally contracts will provide convenient
         * wrappers around this function, which build the state for you, and those should be used in preference.
         */
        @JvmStatic
        fun , T: Any> generateIssue(tx: TransactionBuilder,
                                                         transactionState: TransactionState,
                                                         issueCommand: CommandData) {
            check(tx.inputStates().isEmpty())
            check(tx.outputStates().map { it.data }.filterIsInstance(transactionState.javaClass).isEmpty())
            require(transactionState.data.amount.quantity > 0)
            val at = transactionState.data.amount.token.issuer
            tx.addOutputState(transactionState)
            tx.addCommand(issueCommand, at.party.owningKey)
        }
    }

    abstract fun extractCommands(commands: Collection>): Collection>

    /**
     * Generate an transaction exiting assets from the ledger.
     *
     * @param tx transaction builder to add states and commands to.
     * @param amountIssued the amount to be exited, represented as a quantity of issued currency.
     * @param changeKey the key to send any change to. This needs to be explicitly stated as the input states are not
     * necessarily owned by us.
     * @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is
     * the responsibility of the caller to check that they do not exit funds held by others.
     * @return the public key of the assets issuer, who must sign the transaction for it to be valid.
     */
    @Throws(InsufficientBalanceException::class)
    fun generateExit(tx: TransactionBuilder, amountIssued: Amount>,
                     assetStates: List>): PublicKey {
        return generateExit(
                tx,
                amountIssued,
                assetStates,
                deriveState = { state, amount, owner -> deriveState(state, amount, owner) },
                generateMoveCommand = { -> generateMoveCommand() },
                generateExitCommand = { amount -> generateExitCommand(amount) }
        )
    }

    abstract fun generateExitCommand(amount: Amount>): FungibleAsset.Commands.Exit
    abstract fun generateIssueCommand(): FungibleAsset.Commands.Issue
    abstract fun generateMoveCommand(): FungibleAsset.Commands.Move

    /**
     * Derive a new transaction state based on the given example, with amount and owner modified. This allows concrete
     * implementations to have fields in their state which we don't know about here, and we simply leave them untouched
     * when sending out "change" from spending/exiting.
     */
    abstract fun deriveState(txState: TransactionState, amount: Amount>, owner: PublicKey): TransactionState
}