mirror of
https://github.com/corda/corda.git
synced 2025-03-23 12:35:23 +00:00
Merged in rnicoll-clause-cleanup (pull request #266)
Clean up how clauses are implemented
This commit is contained in:
commit
84c6989b8b
@ -22,7 +22,7 @@ import static kotlin.collections.CollectionsKt.*;
|
||||
* This is a Java version of the CommercialPaper contract (chosen because it's simple). This demonstrates how the
|
||||
* use of Kotlin for implementation of the framework does not impose the same language choice on contract developers.
|
||||
*/
|
||||
public class JavaCommercialPaper extends ClauseVerifier {
|
||||
public class JavaCommercialPaper implements Contract {
|
||||
//public static SecureHash JCP_PROGRAM_ID = SecureHash.sha256("java commercial paper (this should be a bytecode hash)");
|
||||
private static final Contract JCP_PROGRAM_ID = new JavaCommercialPaper();
|
||||
|
||||
@ -161,7 +161,7 @@ public class JavaCommercialPaper extends ClauseVerifier {
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<InOutGroup<State, State>> extractGroups(@NotNull TransactionForContract tx) {
|
||||
public List<InOutGroup<State, State>> groupStates(@NotNull TransactionForContract tx) {
|
||||
return tx.groupStates(State.class, State::withoutOwner);
|
||||
}
|
||||
}
|
||||
@ -316,20 +316,18 @@ public class JavaCommercialPaper extends ClauseVerifier {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<SingleClause> getClauses() {
|
||||
return Collections.singletonList(new Clause.Group());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Collection<AuthenticatedObject<CommandData>> extractCommands(@NotNull TransactionForContract tx) {
|
||||
private Collection<AuthenticatedObject<CommandData>> extractCommands(@NotNull TransactionForContract tx) {
|
||||
return tx.getCommands()
|
||||
.stream()
|
||||
.filter((AuthenticatedObject<CommandData> command) -> command.getValue() instanceof Commands)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(@NotNull TransactionForContract tx) throws IllegalArgumentException {
|
||||
ClauseVerifier.verifyClauses(tx, Collections.singletonList(new Clause.Group()), extractCommands(tx));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public SecureHash getLegalContractReference() {
|
||||
|
@ -40,7 +40,7 @@ import java.util.*
|
||||
val CP_PROGRAM_ID = CommercialPaper()
|
||||
|
||||
// TODO: Generalise the notion of an owned instrument into a superclass/supercontract. Consider composition vs inheritance.
|
||||
class CommercialPaper : ClauseVerifier() {
|
||||
class CommercialPaper : Contract {
|
||||
// TODO: should reference the content of the legal agreement, not its URI
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper")
|
||||
|
||||
@ -49,11 +49,11 @@ class CommercialPaper : ClauseVerifier() {
|
||||
val maturityDate: Instant
|
||||
)
|
||||
|
||||
override val clauses = listOf(Clauses.Group())
|
||||
|
||||
override fun extractCommands(tx: TransactionForContract): List<AuthenticatedObject<CommandData>>
|
||||
private fun extractCommands(tx: TransactionForContract): List<AuthenticatedObject<CommandData>>
|
||||
= tx.commands.select<Commands>()
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClauses(tx, listOf(Clauses.Group()), extractCommands(tx))
|
||||
|
||||
data class State(
|
||||
val issuance: PartyAndReference,
|
||||
override val owner: PublicKey,
|
||||
@ -88,7 +88,7 @@ class CommercialPaper : ClauseVerifier() {
|
||||
Issue()
|
||||
)
|
||||
|
||||
override fun extractGroups(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, Issued<Terms>>>
|
||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, Issued<Terms>>>
|
||||
= tx.groupStates<State, Issued<Terms>> { it.token }
|
||||
}
|
||||
|
||||
|
@ -183,7 +183,7 @@ class FloatingRatePaymentEvent(date: LocalDate,
|
||||
* Currently, we are not interested (excuse pun) in valuing the swap, calculating the PVs, DFs and all that good stuff (soon though).
|
||||
* This is just a representation of a vanilla Fixed vs Floating (same currency) IRS in the R3 prototype model.
|
||||
*/
|
||||
class InterestRateSwap() : ClauseVerifier() {
|
||||
class InterestRateSwap() : Contract {
|
||||
override val legalContractReference = SecureHash.sha256("is_this_the_text_of_the_contract ? TBD")
|
||||
|
||||
/**
|
||||
@ -447,10 +447,11 @@ class InterestRateSwap() : ClauseVerifier() {
|
||||
fixingCalendar, index, indexSource, indexTenor)
|
||||
}
|
||||
|
||||
override val clauses: List<SingleClause> = listOf(Clause.Timestamped(), Clause.Group())
|
||||
override fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
|
||||
private fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
|
||||
= tx.commands.select<Commands>() + tx.commands.select<TimestampCommand>()
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClauses(tx, listOf(Clause.Timestamped(), Clause.Group()), extractCommands(tx))
|
||||
|
||||
interface Clause {
|
||||
/**
|
||||
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides
|
||||
@ -505,7 +506,7 @@ class InterestRateSwap() : ClauseVerifier() {
|
||||
override val ifMatched = MatchBehaviour.END
|
||||
override val ifNotMatched = MatchBehaviour.ERROR
|
||||
|
||||
override fun extractGroups(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, String>>
|
||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, String>>
|
||||
// Group by Trade ID for in / out states
|
||||
= tx.groupStates() { state -> state.common.tradeID }
|
||||
|
||||
|
@ -60,7 +60,7 @@ class Cash : OnLedgerAsset<Currency, Cash.State>() {
|
||||
Issue(),
|
||||
ConserveAmount())
|
||||
|
||||
override fun extractGroups(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, Issued<Currency>>>
|
||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<State, Issued<Currency>>>
|
||||
= tx.groupStates<State, Issued<Currency>> { it.issuanceDef }
|
||||
}
|
||||
|
||||
|
@ -80,7 +80,7 @@ class CommodityContract : OnLedgerAsset<Commodity, CommodityContract.State>() {
|
||||
/**
|
||||
* Group commodity states by issuance definition (issuer and underlying commodity).
|
||||
*/
|
||||
override fun extractGroups(tx: TransactionForContract)
|
||||
override fun groupStates(tx: TransactionForContract)
|
||||
= tx.groupStates<State, Issued<Commodity>> { it.issuanceDef }
|
||||
}
|
||||
|
||||
|
@ -30,7 +30,7 @@ val OBLIGATION_PROGRAM_ID = Obligation<Currency>()
|
||||
*
|
||||
* @param P the product the obligation is for payment of.
|
||||
*/
|
||||
class Obligation<P> : ClauseVerifier() {
|
||||
class Obligation<P> : Contract {
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
@ -43,7 +43,7 @@ class Obligation<P> : ClauseVerifier() {
|
||||
* that is inconsistent with the legal contract.
|
||||
*/
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://www.big-book-of-banking-law.example.gov/cash-settlement.html")
|
||||
override val clauses = listOf(InterceptorClause(Clauses.VerifyLifecycle<P>(), Clauses.Net<P>()),
|
||||
private val clauses = listOf(InterceptorClause(Clauses.VerifyLifecycle<P>(), Clauses.Net<P>()),
|
||||
Clauses.Group<P>())
|
||||
|
||||
interface Clauses {
|
||||
@ -62,7 +62,7 @@ class Obligation<P> : ClauseVerifier() {
|
||||
ConserveAmount()
|
||||
)
|
||||
|
||||
override fun extractGroups(tx: TransactionForContract): List<TransactionForContract.InOutGroup<Obligation.State<P>, Issued<Terms<P>>>>
|
||||
override fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<Obligation.State<P>, Issued<Terms<P>>>>
|
||||
= tx.groupStates<Obligation.State<P>, Issued<Terms<P>>> { it.issuanceDef }
|
||||
}
|
||||
|
||||
@ -377,8 +377,9 @@ class Obligation<P> : ClauseVerifier() {
|
||||
data class Exit<P>(override val amount: Amount<Issued<Terms<P>>>) : Commands, FungibleAsset.Commands.Exit<Terms<P>>
|
||||
}
|
||||
|
||||
override fun extractCommands(tx: TransactionForContract): List<AuthenticatedObject<FungibleAsset.Commands>>
|
||||
private fun extractCommands(tx: TransactionForContract): List<AuthenticatedObject<FungibleAsset.Commands>>
|
||||
= tx.commands.select<Obligation.Commands>()
|
||||
override fun verify(tx: TransactionForContract) = verifyClauses(tx, clauses, extractCommands(tx))
|
||||
|
||||
/**
|
||||
* A default command mutates inputs and produces identical outputs, except that the lifecycle changes.
|
||||
|
@ -2,7 +2,8 @@ package com.r3corda.contracts.asset
|
||||
|
||||
import com.r3corda.contracts.clause.AbstractConserveAmount
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.contracts.clauses.ClauseVerifier
|
||||
import com.r3corda.core.contracts.clauses.SingleClause
|
||||
import com.r3corda.core.contracts.clauses.verifyClauses
|
||||
import com.r3corda.core.crypto.Party
|
||||
import java.security.PublicKey
|
||||
|
||||
@ -24,9 +25,13 @@ import java.security.PublicKey
|
||||
* 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<T : Any, S : FungibleAsset<T>> : ClauseVerifier() {
|
||||
abstract class OnLedgerAsset<T : Any, S : FungibleAsset<T>> : Contract {
|
||||
abstract val clauses: List<SingleClause>
|
||||
abstract fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
|
||||
abstract val conserveClause: AbstractConserveAmount<S, T>
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClauses(tx, clauses, extractCommands(tx))
|
||||
|
||||
/**
|
||||
* Generate an transaction exiting assets from the ledger.
|
||||
*
|
||||
|
@ -0,0 +1,44 @@
|
||||
package com.r3corda.core.contracts.clauses
|
||||
|
||||
import com.r3corda.core.contracts.AuthenticatedObject
|
||||
import com.r3corda.core.contracts.CommandData
|
||||
import com.r3corda.core.contracts.TransactionForContract
|
||||
|
||||
/**
|
||||
* A clause that can be matched as part of execution of a contract.
|
||||
*/
|
||||
// TODO: ifNotMatched/ifMatched should be dropped, and replaced by logic in the calling code that understands
|
||||
// "or", "and", "single" etc. composition of sets of clauses.
|
||||
interface Clause {
|
||||
/** Classes for commands which must ALL be present in transaction for this clause to be triggered */
|
||||
val requiredCommands: Set<Class<out CommandData>>
|
||||
/** Behaviour if this clause is matched */
|
||||
val ifNotMatched: MatchBehaviour
|
||||
/** Behaviour if this clause is not matches */
|
||||
val ifMatched: MatchBehaviour
|
||||
}
|
||||
|
||||
enum class MatchBehaviour {
|
||||
CONTINUE,
|
||||
END,
|
||||
ERROR
|
||||
}
|
||||
|
||||
interface SingleVerify {
|
||||
/**
|
||||
* Verify the transaction matches the conditions from this clause. For example, a "no zero amount output" clause
|
||||
* would check each of the output states that it applies to, looking for a zero amount, and throw IllegalStateException
|
||||
* if any matched.
|
||||
*
|
||||
* @return the set of commands that are consumed IF this clause is matched, and cannot be used to match a
|
||||
* later clause. This would normally be all commands matching "requiredCommands" for this clause, but some
|
||||
* verify() functions may do further filtering on possible matches, and return a subset. This may also include
|
||||
* commands that were not required (for example the Exit command for fungible assets is optional).
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
fun verify(tx: TransactionForContract,
|
||||
commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData>
|
||||
|
||||
}
|
||||
|
||||
interface SingleClause : Clause, SingleVerify
|
@ -1,64 +1,10 @@
|
||||
@file:JvmName("ClauseVerifier")
|
||||
package com.r3corda.core.contracts.clauses
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import java.util.*
|
||||
|
||||
interface Clause {
|
||||
/** Classes for commands which must ALL be present in transaction for this clause to be triggered */
|
||||
val requiredCommands: Set<Class<out CommandData>>
|
||||
/** Behaviour if this clause is matched */
|
||||
val ifNotMatched: MatchBehaviour
|
||||
/** Behaviour if this clause is not matches */
|
||||
val ifMatched: MatchBehaviour
|
||||
}
|
||||
|
||||
enum class MatchBehaviour {
|
||||
CONTINUE,
|
||||
END,
|
||||
ERROR
|
||||
}
|
||||
|
||||
interface SingleVerify {
|
||||
/**
|
||||
* Verify the transaction matches the conditions from this clause. For example, a "no zero amount output" clause
|
||||
* would check each of the output states that it applies to, looking for a zero amount, and throw IllegalStateException
|
||||
* if any matched.
|
||||
*
|
||||
* @return the set of commands that are consumed IF this clause is matched, and cannot be used to match a
|
||||
* later clause. This would normally be all commands matching "requiredCommands" for this clause, but some
|
||||
* verify() functions may do further filtering on possible matches, and return a subset. This may also include
|
||||
* commands that were not required (for example the Exit command for fungible assets is optional).
|
||||
*/
|
||||
@Throws(IllegalStateException::class)
|
||||
fun verify(tx: TransactionForContract,
|
||||
commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData>
|
||||
|
||||
}
|
||||
|
||||
|
||||
interface SingleClause : Clause, SingleVerify
|
||||
|
||||
/**
|
||||
* Abstract superclass for clause-based contracts to extend, which provides a verify() function
|
||||
* that delegates to the supplied list of clauses.
|
||||
*/
|
||||
abstract class ClauseVerifier : Contract {
|
||||
abstract val clauses: List<SingleClause>
|
||||
abstract fun extractCommands(tx: TransactionForContract): Collection<AuthenticatedObject<CommandData>>
|
||||
override fun verify(tx: TransactionForContract) = verifyClauses(tx, clauses, extractCommands(tx))
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify a transaction against the given list of clauses.
|
||||
*
|
||||
* @param tx transaction to be verified.
|
||||
* @param clauses the clauses to verify.
|
||||
* @param T common supertype of commands to extract from the transaction, which are of relevance to these clauses.
|
||||
*/
|
||||
inline fun <reified T : CommandData> verifyClauses(tx: TransactionForContract,
|
||||
clauses: List<SingleClause>)
|
||||
= verifyClauses(tx, clauses, tx.commands.select<T>())
|
||||
|
||||
// Wrapper object for exposing a JVM friend version of the clause verifier
|
||||
/**
|
||||
* Verify a transaction against the given list of clauses.
|
||||
*
|
||||
@ -89,4 +35,5 @@ fun verifyClauses(tx: TransactionForContract,
|
||||
}
|
||||
|
||||
require(unmatchedCommands.isEmpty()) { "All commands must be matched at end of execution." }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -26,10 +26,10 @@ abstract class GroupClauseVerifier<S : ContractState, T : Any> : SingleClause {
|
||||
override val requiredCommands: Set<Class<out CommandData>>
|
||||
get() = emptySet()
|
||||
|
||||
abstract fun extractGroups(tx: TransactionForContract): List<TransactionForContract.InOutGroup<S, T>>
|
||||
abstract fun groupStates(tx: TransactionForContract): List<TransactionForContract.InOutGroup<S, T>>
|
||||
|
||||
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> {
|
||||
val groups = extractGroups(tx)
|
||||
val groups = groupStates(tx)
|
||||
val matchedCommands = HashSet<CommandData>()
|
||||
val unmatchedCommands = ArrayList(commands.map { it.value })
|
||||
|
||||
|
@ -10,7 +10,7 @@ Writing a contract using clauses
|
||||
This tutorial will take you through restructuring the commercial paper contract to use clauses. You should have
|
||||
already completed ":doc:`tutorial-contract`".
|
||||
|
||||
Clauses are essentially "mini-contracts" which contain verification logic, and are composed together to form
|
||||
Clauses are essentially micro-contracts which contain independent verification logic, and are composed together to form
|
||||
a contract. With appropriate design, they can be made to be reusable, for example issuing contract state objects is
|
||||
generally the same for all fungible contracts, so a single issuance clause can be shared. This cuts down on scope for
|
||||
error, and improves consistency of behaviour.
|
||||
@ -27,25 +27,24 @@ In the case of commercial paper, we have a "Grouping" outermost clause, which wi
|
||||
Commercial paper class
|
||||
----------------------
|
||||
|
||||
First we need to change the class from implementing ``Contract``, to extend ``ClauseVerifier``. This is an abstract
|
||||
class which provides a verify() function for us, and requires we provide a property (``clauses``) for the clauses to test,
|
||||
and a function (``extractCommands``) to extract the applicable commands from the transaction. This is important because
|
||||
``ClauseVerifier`` checks that no commands applicable to the contract are left unprocessed at the end. The following
|
||||
examples are trimmed to the modified class definition and added elements, for brevity:
|
||||
To use the clause verification logic, the contract needs to call the ``verifyClauses()`` function, passing in the transaction,
|
||||
a list of clauses to verify, and a collection of commands the clauses are expected to handle all of. This list of
|
||||
commands is important because ``verifyClauses()`` checks that none of the commands are left unprocessed at the end, and
|
||||
raises an error if they are. The following examples are trimmed to the modified class definition and added elements, for
|
||||
brevity:
|
||||
|
||||
.. container:: codeset
|
||||
|
||||
.. sourcecode:: kotlin
|
||||
|
||||
class CommercialPaper : ClauseVerifier {
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper");
|
||||
class CommercialPaper : Contract {
|
||||
override val legalContractReference: SecureHash = SecureHash.sha256("https://en.wikipedia.org/wiki/Commercial_paper")
|
||||
|
||||
override val clauses: List<SingleClause>
|
||||
get() = throw UnsupportedOperationException("not implemented")
|
||||
|
||||
override fun extractCommands(tx: TransactionForContract): List<AuthenticatedObject<CommandData>>
|
||||
private fun extractCommands(tx: TransactionForContract): List<AuthenticatedObject<CommandData>>
|
||||
= tx.commands.select<Commands>()
|
||||
|
||||
override fun verify(tx: TransactionForContract) = verifyClauses(tx, listOf(Clauses.Group()), extractCommands(tx))
|
||||
|
||||
.. sourcecode:: java
|
||||
|
||||
public class CommercialPaper implements Contract {
|
||||
@ -54,11 +53,6 @@ examples are trimmed to the modified class definition and added elements, for br
|
||||
return SecureHash.Companion.sha256("https://en.wikipedia.org/wiki/Commercial_paper");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SingleClause> getClauses() {
|
||||
throw UnsupportedOperationException("not implemented");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<AuthenticatedObject<CommandData>> extractCommands(@NotNull TransactionForContract tx) {
|
||||
return tx.getCommands()
|
||||
@ -67,6 +61,11 @@ examples are trimmed to the modified class definition and added elements, for br
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(@NotNull TransactionForContract tx) throws IllegalArgumentException {
|
||||
ClauseVerifier.verifyClauses(tx, Collections.singletonList(new Clause.Group()), extractCommands(tx));
|
||||
}
|
||||
|
||||
Clauses
|
||||
-------
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user