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:
Ross Nicoll
2016-08-04 15:23:58 +01:00
parent 493f7f1fd1
commit 162d19deeb
11 changed files with 98 additions and 103 deletions

View File

@ -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

View File

@ -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." }
}
}

View File

@ -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 })