mirror of
https://github.com/corda/corda.git
synced 2025-04-07 11:27:01 +00:00
Change how clause verification is called
Change away from extending ClauseVerifier for contracts which support clauses, and explicitely call clause verification code in the verify() function. This should make the flow of control easier to understand.
This commit is contained in:
parent
493f7f1fd1
commit
162d19deeb
@ -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