From 3c23c4f53dd8cf0042365738a242bc0cddae35b7 Mon Sep 17 00:00:00 2001 From: Mike Hearn <mike@r3cev.com> Date: Tue, 22 Dec 2015 15:30:49 +0000 Subject: [PATCH] Minor: move TransactionForVerification to be next to TransactionGroup and rename the file to be more descriptive. --- src/main/kotlin/core/TransactionGroup.kt | 60 -------- .../kotlin/core/TransactionVerification.kt | 138 ++++++++++++++++++ src/main/kotlin/core/Transactions.kt | 81 +--------- 3 files changed, 139 insertions(+), 140 deletions(-) delete mode 100644 src/main/kotlin/core/TransactionGroup.kt create mode 100644 src/main/kotlin/core/TransactionVerification.kt diff --git a/src/main/kotlin/core/TransactionGroup.kt b/src/main/kotlin/core/TransactionGroup.kt deleted file mode 100644 index 3806e7910b..0000000000 --- a/src/main/kotlin/core/TransactionGroup.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members - * pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms - * set forth therein. - * - * All other rights reserved. - */ - -package core - -import java.util.* - -class TransactionResolutionException(val hash: SecureHash) : Exception() -class TransactionConflictException(val conflictRef: ContractStateRef, val tx1: LedgerTransaction, val tx2: LedgerTransaction) : Exception() - -/** - * A TransactionGroup defines a directed acyclic graph of transactions that can be resolved with each other and then - * verified. Successful verification does not imply the non-existence of other conflicting transactions: simply that - * this subgraph does not contain conflicts and is accepted by the involved contracts. - * - * The inputs of the provided transactions must be resolvable either within the [transactions] set, or from the - * [nonVerifiedRoots] set. Transactions in the non-verified set are ignored other than for looking up input states. - */ -class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerifiedRoots: Set<LedgerTransaction>) { - - /** - * Verifies the group and returns the set of resolved transactions. - */ - fun verify(programMap: Map<SecureHash, Contract>): Set<TransactionForVerification> { - // Check that every input can be resolved to an output. - // Check that no output is referenced by more than one input. - // Cycles should be impossible due to the use of hashes as pointers. - check(transactions.intersect(nonVerifiedRoots).isEmpty()) - - val hashToTXMap: Map<SecureHash, List<LedgerTransaction>> = (transactions + nonVerifiedRoots).groupBy { it.hash } - val refToConsumingTXMap = hashMapOf<ContractStateRef, LedgerTransaction>() - - val resolved = HashSet<TransactionForVerification>(transactions.size) - for (tx in transactions) { - val inputs = ArrayList<ContractState>(tx.inStateRefs.size) - for (ref in tx.inStateRefs) { - val conflict = refToConsumingTXMap[ref] - if (conflict != null) - throw TransactionConflictException(ref, tx, conflict) - refToConsumingTXMap[ref] = tx - - // Look up the connecting transaction. - val ltx = hashToTXMap[ref.txhash]?.single() ?: throw TransactionResolutionException(ref.txhash) - // Look up the output in that transaction by index. - inputs.add(ltx.outStates[ref.index]) - } - resolved.add(TransactionForVerification(inputs, tx.outStates, tx.commands, tx.hash)) - } - - for (tx in resolved) - tx.verify(programMap) - return resolved - } - -} \ No newline at end of file diff --git a/src/main/kotlin/core/TransactionVerification.kt b/src/main/kotlin/core/TransactionVerification.kt new file mode 100644 index 0000000000..af879c47d5 --- /dev/null +++ b/src/main/kotlin/core/TransactionVerification.kt @@ -0,0 +1,138 @@ +/* + * Copyright 2015 Distributed Ledger Group LLC. Distributed as Licensed Company IP to DLG Group Members + * pursuant to the August 7, 2015 Advisory Services Agreement and subject to the Company IP License terms + * set forth therein. + * + * All other rights reserved. + */ + +package core + +import java.util.* + +class TransactionResolutionException(val hash: SecureHash) : Exception() +class TransactionConflictException(val conflictRef: ContractStateRef, val tx1: LedgerTransaction, val tx2: LedgerTransaction) : Exception() + +/** + * A TransactionGroup defines a directed acyclic graph of transactions that can be resolved with each other and then + * verified. Successful verification does not imply the non-existence of other conflicting transactions: simply that + * this subgraph does not contain conflicts and is accepted by the involved contracts. + * + * The inputs of the provided transactions must be resolvable either within the [transactions] set, or from the + * [nonVerifiedRoots] set. Transactions in the non-verified set are ignored other than for looking up input states. + */ +class TransactionGroup(val transactions: Set<LedgerTransaction>, val nonVerifiedRoots: Set<LedgerTransaction>) { + + /** + * Verifies the group and returns the set of resolved transactions. + */ + fun verify(programMap: Map<SecureHash, Contract>): Set<TransactionForVerification> { + // Check that every input can be resolved to an output. + // Check that no output is referenced by more than one input. + // Cycles should be impossible due to the use of hashes as pointers. + check(transactions.intersect(nonVerifiedRoots).isEmpty()) + + val hashToTXMap: Map<SecureHash, List<LedgerTransaction>> = (transactions + nonVerifiedRoots).groupBy { it.hash } + val refToConsumingTXMap = hashMapOf<ContractStateRef, LedgerTransaction>() + + val resolved = HashSet<TransactionForVerification>(transactions.size) + for (tx in transactions) { + val inputs = ArrayList<ContractState>(tx.inStateRefs.size) + for (ref in tx.inStateRefs) { + val conflict = refToConsumingTXMap[ref] + if (conflict != null) + throw TransactionConflictException(ref, tx, conflict) + refToConsumingTXMap[ref] = tx + + // Look up the connecting transaction. + val ltx = hashToTXMap[ref.txhash]?.single() ?: throw TransactionResolutionException(ref.txhash) + // Look up the output in that transaction by index. + inputs.add(ltx.outStates[ref.index]) + } + resolved.add(TransactionForVerification(inputs, tx.outStates, tx.commands, tx.hash)) + } + + for (tx in resolved) + tx.verify(programMap) + return resolved + } + +} + +/** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */ +data class TransactionForVerification(val inStates: List<ContractState>, + val outStates: List<ContractState>, + val commands: List<AuthenticatedObject<CommandData>>, + val origHash: SecureHash) { + override fun hashCode() = origHash.hashCode() + override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash + + /** + * @throws TransactionVerificationException if a contract throws an exception, the original is in the cause field + * @throws IllegalStateException if a state refers to an unknown contract. + */ + @Throws(TransactionVerificationException::class, IllegalStateException::class) + fun verify(programMap: Map<SecureHash, Contract>) { + // For each input and output state, locate the program to run. Then execute the verification function. If any + // throws an exception, the entire transaction is invalid. + val programHashes = (inStates.map { it.programRef } + outStates.map { it.programRef }).toSet() + for (hash in programHashes) { + val program = programMap[hash] ?: throw IllegalStateException("Unknown program hash $hash") + try { + program.verify(this) + } catch(e: Throwable) { + throw TransactionVerificationException(this, program, e) + } + } + } + + /** + * Utilities for contract writers to incorporate into their logic. + */ + + data class InOutGroup<T : ContractState>(val inputs: List<T>, val outputs: List<T>) + + // A shortcut to make IDE auto-completion more intuitive for Java users. + fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority) + + // For Java users. + fun <T : ContractState> groupStates(ofType: Class<T>, selector: (T) -> Any): List<InOutGroup<T>> { + val inputs = inStates.filterIsInstance(ofType) + val outputs = outStates.filterIsInstance(ofType) + + val inGroups = inputs.groupBy(selector) + val outGroups = outputs.groupBy(selector) + + @Suppress("DEPRECATION") + return groupStatesInternal(inGroups, outGroups) + } + + // For Kotlin users: this version has nicer syntax and avoids reflection/object creation for the lambda. + inline fun <reified T : ContractState> groupStates(selector: (T) -> Any): List<InOutGroup<T>> { + val inputs = inStates.filterIsInstance<T>() + val outputs = outStates.filterIsInstance<T>() + + val inGroups = inputs.groupBy(selector) + val outGroups = outputs.groupBy(selector) + + @Suppress("DEPRECATION") + return groupStatesInternal(inGroups, outGroups) + } + + @Deprecated("Do not use this directly: exposed as public only due to function inlining") + fun <T : ContractState> groupStatesInternal(inGroups: Map<Any, List<T>>, outGroups: Map<Any, List<T>>): List<InOutGroup<T>> { + val result = ArrayList<InOutGroup<T>>() + + for ((k, v) in inGroups.entries) + result.add(InOutGroup(v, outGroups[k] ?: emptyList())) + for ((k, v) in outGroups.entries) { + if (inGroups[k] == null) + result.add(InOutGroup(emptyList(), v)) + } + + return result + } +} + +/** Thrown if a verification fails due to a contract rejection. */ +class TransactionVerificationException(val tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : Exception(cause) \ No newline at end of file diff --git a/src/main/kotlin/core/Transactions.kt b/src/main/kotlin/core/Transactions.kt index 93dda34c73..2b805b7e38 100644 --- a/src/main/kotlin/core/Transactions.kt +++ b/src/main/kotlin/core/Transactions.kt @@ -248,83 +248,4 @@ data class LedgerTransaction( throw IllegalArgumentException("State not found in this transaction") return outRef(i) } -} - -// TODO: Move class this into TransactionGroup.kt -/** A transaction in fully resolved and sig-checked form, ready for passing as input to a verification function. */ -data class TransactionForVerification(val inStates: List<ContractState>, - val outStates: List<ContractState>, - val commands: List<AuthenticatedObject<CommandData>>, - val origHash: SecureHash) { - override fun hashCode() = origHash.hashCode() - override fun equals(other: Any?) = other is TransactionForVerification && other.origHash == origHash - - /** - * @throws TransactionVerificationException if a contract throws an exception, the original is in the cause field - * @throws IllegalStateException if a state refers to an unknown contract. - */ - @Throws(TransactionVerificationException::class, IllegalStateException::class) - fun verify(programMap: Map<SecureHash, Contract>) { - // For each input and output state, locate the program to run. Then execute the verification function. If any - // throws an exception, the entire transaction is invalid. - val programHashes = (inStates.map { it.programRef } + outStates.map { it.programRef }).toSet() - for (hash in programHashes) { - val program = programMap[hash] ?: throw IllegalStateException("Unknown program hash $hash") - try { - program.verify(this) - } catch(e: Throwable) { - throw TransactionVerificationException(this, program, e) - } - } - } - - /** - * Utilities for contract writers to incorporate into their logic. - */ - - data class InOutGroup<T : ContractState>(val inputs: List<T>, val outputs: List<T>) - - // A shortcut to make IDE auto-completion more intuitive for Java users. - fun getTimestampBy(timestampingAuthority: Party): TimestampCommand? = commands.getTimestampBy(timestampingAuthority) - - // For Java users. - fun <T : ContractState> groupStates(ofType: Class<T>, selector: (T) -> Any): List<InOutGroup<T>> { - val inputs = inStates.filterIsInstance(ofType) - val outputs = outStates.filterIsInstance(ofType) - - val inGroups = inputs.groupBy(selector) - val outGroups = outputs.groupBy(selector) - - @Suppress("DEPRECATION") - return groupStatesInternal(inGroups, outGroups) - } - - // For Kotlin users: this version has nicer syntax and avoids reflection/object creation for the lambda. - inline fun <reified T : ContractState> groupStates(selector: (T) -> Any): List<InOutGroup<T>> { - val inputs = inStates.filterIsInstance<T>() - val outputs = outStates.filterIsInstance<T>() - - val inGroups = inputs.groupBy(selector) - val outGroups = outputs.groupBy(selector) - - @Suppress("DEPRECATION") - return groupStatesInternal(inGroups, outGroups) - } - - @Deprecated("Do not use this directly: exposed as public only due to function inlining") - fun <T : ContractState> groupStatesInternal(inGroups: Map<Any, List<T>>, outGroups: Map<Any, List<T>>): List<InOutGroup<T>> { - val result = ArrayList<InOutGroup<T>>() - - for ((k, v) in inGroups.entries) - result.add(InOutGroup(v, outGroups[k] ?: emptyList())) - for ((k, v) in outGroups.entries) { - if (inGroups[k] == null) - result.add(InOutGroup(emptyList(), v)) - } - - return result - } -} - -/** Thrown if a verification fails due to a contract rejection. */ -class TransactionVerificationException(val tx: TransactionForVerification, val contract: Contract, cause: Throwable?) : Exception(cause) \ No newline at end of file +} \ No newline at end of file