Merged in rnicoll-clause-default (pull request #290)

Add default values for ifMatched/ifNotMatched/requiredCommands
This commit is contained in:
Ross Nicoll 2016-08-23 11:49:59 +01:00
commit 88da5ba942
11 changed files with 39 additions and 58 deletions

View File

@ -517,11 +517,7 @@ class InterestRateSwap() : Contract {
override val clauses = listOf(Agree(), Fix(), Pay(), Mature()) override val clauses = listOf(Agree(), Fix(), Pay(), Mature())
} }
class Timestamped : SingleClause { class Timestamped : SingleClause() {
override val ifMatched = MatchBehaviour.CONTINUE
override val ifNotMatched = MatchBehaviour.ERROR
override val requiredCommands = emptySet<Class<out CommandData>>()
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> { override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> {
require(tx.timestamp?.midpoint != null) { "must be timestamped" } require(tx.timestamp?.midpoint != null) { "must be timestamped" }
// We return an empty set because we don't process any commands // We return an empty set because we don't process any commands

View File

@ -197,11 +197,7 @@ class Obligation<P> : Contract {
* any lifecycle change clause, which is the only clause that involve * any lifecycle change clause, which is the only clause that involve
* non-standard lifecycle states on input/output. * non-standard lifecycle states on input/output.
*/ */
class VerifyLifecycle<P> : SingleClause, GroupClause<State<P>, Issued<Terms<P>>> { class VerifyLifecycle<P> : SingleClause(), GroupClause<State<P>, Issued<Terms<P>>> {
override val requiredCommands: Set<Class<out CommandData>> = emptySet()
override val ifMatched: MatchBehaviour = MatchBehaviour.CONTINUE
override val ifNotMatched: MatchBehaviour = MatchBehaviour.ERROR
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData>
= verify( = verify(
tx.inputs.filterIsInstance<State<P>>(), tx.inputs.filterIsInstance<State<P>>(),

View File

@ -43,13 +43,10 @@ data class MultilateralNetState<P>(
* Clause for netting contract states. Currently only supports obligation contract. * Clause for netting contract states. Currently only supports obligation contract.
*/ */
// TODO: Make this usable for any nettable contract states // TODO: Make this usable for any nettable contract states
open class NetClause<P> : SingleClause { open class NetClause<P> : SingleClause() {
override val ifNotMatched: MatchBehaviour override val ifMatched: MatchBehaviour = MatchBehaviour.END
get() = MatchBehaviour.CONTINUE override val ifNotMatched: MatchBehaviour = MatchBehaviour.CONTINUE
override val ifMatched: MatchBehaviour override val requiredCommands: Set<Class<out CommandData>> = setOf(Obligation.Commands.Net::class.java)
get() = MatchBehaviour.END
override val requiredCommands: Set<Class<out CommandData>>
get() = setOf(Obligation.Commands.Net::class.java)
@Suppress("ConvertLambdaToReference") @Suppress("ConvertLambdaToReference")
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> { override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> {

View File

@ -218,11 +218,7 @@ interface LinearState: ContractState {
/** /**
* Standard clause to verify the LinearState safety properties. * Standard clause to verify the LinearState safety properties.
*/ */
class ClauseVerifier<S: LinearState>(val stateClass: Class<S>) : SingleClause { class ClauseVerifier<S: LinearState>(val stateClass: Class<S>) : SingleClause() {
override val ifMatched = MatchBehaviour.CONTINUE
override val ifNotMatched = MatchBehaviour.ERROR
override val requiredCommands = emptySet<Class<out CommandData>>()
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> { override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> {
val inputs = tx.inputs.filterIsInstance(stateClass) val inputs = tx.inputs.filterIsInstance(stateClass)
val inputIds = inputs.map { it.linearId }.distinct() val inputIds = inputs.map { it.linearId }.distinct()

View File

@ -41,4 +41,12 @@ interface SingleVerify {
} }
interface SingleClause : Clause, SingleVerify /**
* A single verifiable clause. By default always matches, continues to the next clause when matched and errors
* if not matched.
*/
abstract class SingleClause : Clause, SingleVerify {
override val ifMatched: MatchBehaviour = MatchBehaviour.CONTINUE
override val ifNotMatched: MatchBehaviour = MatchBehaviour.ERROR
override val requiredCommands: Set<Class<out CommandData>> = emptySet()
}

View File

@ -27,7 +27,7 @@ fun verifyClauses(tx: TransactionForContract,
} }
when (matchBehaviour) { when (matchBehaviour) {
MatchBehaviour.ERROR -> throw IllegalStateException() MatchBehaviour.ERROR -> throw IllegalStateException("Error due to matching/not matching ${clause}")
MatchBehaviour.CONTINUE -> { MatchBehaviour.CONTINUE -> {
} }
MatchBehaviour.END -> break@verify MatchBehaviour.END -> break@verify

View File

@ -21,7 +21,7 @@ interface GroupVerify<in S, in T : Any> {
interface GroupClause<in S : ContractState, in T : Any> : Clause, GroupVerify<S, T> interface GroupClause<in S : ContractState, in T : Any> : Clause, GroupVerify<S, T>
abstract class GroupClauseVerifier<S : ContractState, T : Any> : SingleClause { abstract class GroupClauseVerifier<S : ContractState, T : Any> : SingleClause() {
abstract val clauses: List<GroupClause<S, T>> abstract val clauses: List<GroupClause<S, T>>
override val requiredCommands: Set<Class<out CommandData>> override val requiredCommands: Set<Class<out CommandData>>
get() = emptySet() get() = emptySet()

View File

@ -9,10 +9,10 @@ import java.util.*
* A clause which intercepts calls to a wrapped clause, and passes them through verification * A clause which intercepts calls to a wrapped clause, and passes them through verification
* only from a pre-clause. This is similar to an inceptor in aspect orientated programming. * only from a pre-clause. This is similar to an inceptor in aspect orientated programming.
*/ */
data class InterceptorClause( class InterceptorClause(
val preclause: SingleVerify, val preclause: SingleVerify,
val clause: SingleClause val clause: SingleClause
) : SingleClause { ) : SingleClause() {
override val ifNotMatched: MatchBehaviour override val ifNotMatched: MatchBehaviour
get() = clause.ifNotMatched get() = clause.ifNotMatched
override val ifMatched: MatchBehaviour override val ifMatched: MatchBehaviour
@ -25,4 +25,6 @@ data class InterceptorClause(
consumed.addAll(clause.verify(tx, commands)) consumed.addAll(clause.verify(tx, commands))
return consumed return consumed
} }
override fun toString(): String = "Interceptor clause [${clause}]"
} }

View File

@ -19,11 +19,7 @@ class VerifyClausesTests {
/** Very simple check that the function doesn't error when given any clause */ /** Very simple check that the function doesn't error when given any clause */
@Test @Test
fun minimal() { fun minimal() {
val clause = object : SingleClause { val clause = object : SingleClause() {
override val requiredCommands: Set<Class<out CommandData>>
get() = emptySet()
override val ifMatched: MatchBehaviour
get() = MatchBehaviour.CONTINUE
override val ifNotMatched: MatchBehaviour override val ifNotMatched: MatchBehaviour
get() = MatchBehaviour.CONTINUE get() = MatchBehaviour.CONTINUE
@ -36,14 +32,7 @@ class VerifyClausesTests {
/** Check that when there are no required commands, a clause always matches */ /** Check that when there are no required commands, a clause always matches */
@Test @Test
fun emptyAlwaysMatches() { fun emptyAlwaysMatches() {
val clause = object : SingleClause { val clause = object : SingleClause() {
override val requiredCommands: Set<Class<out CommandData>>
get() = emptySet()
override val ifMatched: MatchBehaviour
get() = MatchBehaviour.CONTINUE
override val ifNotMatched: MatchBehaviour
get() = MatchBehaviour.ERROR
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> = emptySet() override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> = emptySet()
} }
val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256()) val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256())
@ -53,9 +42,7 @@ class VerifyClausesTests {
@Test @Test
fun errorSuperfluousCommands() { fun errorSuperfluousCommands() {
val clause = object : SingleClause { val clause = object : SingleClause() {
override val requiredCommands: Set<Class<out CommandData>>
get() = emptySet()
override val ifMatched: MatchBehaviour override val ifMatched: MatchBehaviour
get() = MatchBehaviour.ERROR get() = MatchBehaviour.ERROR
override val ifNotMatched: MatchBehaviour override val ifNotMatched: MatchBehaviour
@ -73,7 +60,7 @@ class VerifyClausesTests {
/** Check triggering of error if matched */ /** Check triggering of error if matched */
@Test @Test
fun errorMatched() { fun errorMatched() {
val clause = object : SingleClause { val clause = object : SingleClause() {
override val requiredCommands: Set<Class<out CommandData>> override val requiredCommands: Set<Class<out CommandData>>
get() = setOf(DummyContract.Commands.Create::class.java) get() = setOf(DummyContract.Commands.Create::class.java)
override val ifMatched: MatchBehaviour override val ifMatched: MatchBehaviour
@ -98,13 +85,9 @@ class VerifyClausesTests {
/** Check triggering of error if unmatched */ /** Check triggering of error if unmatched */
@Test @Test
fun errorUnmatched() { fun errorUnmatched() {
val clause = object : SingleClause { val clause = object : SingleClause() {
override val requiredCommands: Set<Class<out CommandData>> override val requiredCommands: Set<Class<out CommandData>>
get() = setOf(DummyContract.Commands.Create::class.java) get() = setOf(DummyContract.Commands.Create::class.java)
override val ifMatched: MatchBehaviour
get() = MatchBehaviour.CONTINUE
override val ifNotMatched: MatchBehaviour
get() = MatchBehaviour.ERROR
override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> = emptySet() override fun verify(tx: TransactionForContract, commands: Collection<AuthenticatedObject<CommandData>>): Set<CommandData> = emptySet()
} }

View File

@ -54,6 +54,7 @@ API changes:
* The ``arg`` method in the test DSL is now called ``command`` to be consistent with the rest of the data model. * The ``arg`` method in the test DSL is now called ``command`` to be consistent with the rest of the data model.
* The messaging APIs have changed somewhat to now use a new ``TopicSession`` object. These APIs will continue to change * The messaging APIs have changed somewhat to now use a new ``TopicSession`` object. These APIs will continue to change
in the upcoming releases. in the upcoming releases.
* Clauses now have default values provided for ``ifMatched``, ``ifNotMatched`` and ``requiredCommands``.
New documentation: New documentation:

View File

@ -16,13 +16,13 @@ generally the same for all fungible contracts, so a single issuance clause can b
error, and improves consistency of behaviour. error, and improves consistency of behaviour.
Clauses can be composed of subclauses, either to combine clauses in different ways, or to apply specialised clauses. Clauses can be composed of subclauses, either to combine clauses in different ways, or to apply specialised clauses.
In the case of commercial paper, we have a "Grouping" outermost clause, which will contain the "Issue", "Move" and In the case of commercial paper, we have a ``Group`` outermost clause, which will contain the ``Issue``, ``Move`` and
"Redeem" clauses. The result is a contract that looks something like this: ``Redeem`` clauses. The result is a contract that looks something like this:
1. Group input and output states together, and then apply the following clauses on each group: 1. Group input and output states together, and then apply the following clauses on each group:
a. If an Issue command is present, run appropriate tests and end processing this group. a. If an ``Issue`` command is present, run appropriate tests and end processing this group.
b. If a Move command is present, run appropriate tests and end processing this group. b. If a ``Move`` command is present, run appropriate tests and end processing this group.
c. If a Redeem command is present, run appropriate tests and end processing this group. c. If a ``Redeem`` command is present, run appropriate tests and end processing this group.
Commercial paper class Commercial paper class
---------------------- ----------------------
@ -71,9 +71,9 @@ Clauses
We'll tackle the inner clauses that contain the bulk of the verification logic, first, and the clause which handles We'll tackle the inner clauses that contain the bulk of the verification logic, first, and the clause which handles
grouping of input/output states later. The inner clauses need to implement the ``GroupClause`` interface, which defines grouping of input/output states later. The inner clauses need to implement the ``GroupClause`` interface, which defines
the verify() function, and properties for key information on how the clause is processed. These properties specify the the verify() function, and properties (``ifMatched``, ``ifNotMatched`` and ``requiredCommands``) defining how the clause
command(s) which must be present in order for the clause to be matched, and what to do after processing the clause is processed. These properties specify the command(s) which must be present in order for the clause to be matched,
depending on whether it was matched or not. and what to do after processing the clause depending on whether it was matched or not.
The ``verify()`` functions defined in the ``SingleClause`` and ``GroupClause`` interfaces is similar to the conventional The ``verify()`` functions defined in the ``SingleClause`` and ``GroupClause`` interfaces is similar to the conventional
``Contract`` verification function, although it adds new parameters and returns the set of commands which it has processed. ``Contract`` verification function, although it adds new parameters and returns the set of commands which it has processed.
@ -157,7 +157,9 @@ The post-processing ``MatchBehaviour`` options are:
* ERROR * ERROR
In this case we process commands against each group, until the first matching clause is found, so we ``END`` on a match In this case we process commands against each group, until the first matching clause is found, so we ``END`` on a match
and ``CONTINUE`` otherwise. ``ERROR`` can be used as a part of a clause which must always/never be matched. and ``CONTINUE`` otherwise. ``ERROR`` can be used as a part of a clause which must always/never be matched. By default
clauses are always matched (``requiredCommands`` is an empty set), execution continues after a clause is matched, and an
error is raised if a clause is not matched.
Group Clause Group Clause
------------ ------------