From fc000ec03ceabb0067b3c837534aaaef9a0f43eb Mon Sep 17 00:00:00 2001 From: Mike Hearn Date: Fri, 12 Feb 2016 14:55:11 +0100 Subject: [PATCH] Testing: more utilities on Transaction and TransactionGroupForTest to help with testing signed transactions. --- src/main/kotlin/core/Transactions.kt | 43 ++++++++++++++++--- src/test/kotlin/core/TransactionGroupTests.kt | 26 +++++++++++ src/test/kotlin/core/testutils/TestUtils.kt | 10 ++++- 3 files changed, 71 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/core/Transactions.kt b/src/main/kotlin/core/Transactions.kt index 4c4fc96170..fc2f0c9488 100644 --- a/src/main/kotlin/core/Transactions.kt +++ b/src/main/kotlin/core/Transactions.kt @@ -65,6 +65,11 @@ data class WireTransaction(val inputs: List, return LedgerTransaction(inputs, outputs, authenticatedArgs, originalHash) } + /** Serialises and returns this transaction as a [SignedWireTransaction] with no signatures attached. */ + fun toSignedTransaction(withSigs: List = emptyList()): SignedWireTransaction { + return SignedWireTransaction(serialize(), withSigs) + } + override fun toString(): String { val buf = StringBuilder() buf.appendln("Transaction:") @@ -155,7 +160,7 @@ class TransactionBuilder(private val inputs: MutableList = arrayListOf } /** A more convenient way to add items to this transaction that calls the add* methods for you based on type */ - public fun withItems(vararg items: Any): TransactionBuilder { + fun withItems(vararg items: Any): TransactionBuilder { for (t in items) { when (t) { is StateRef -> inputs.add(t) @@ -263,13 +268,13 @@ class TransactionBuilder(private val inputs: MutableList = arrayListOf */ data class LedgerTransaction( /** The input states which will be consumed/invalidated by the execution of this transaction. */ - val inputs: List, + val inputs: List, /** The states that will be generated by the execution of this transaction. */ - val outputs: List, + val outputs: List, /** Arbitrary data passed to the program of each input state. */ - val commands: List>, - /** The hash of the original serialised SignedTransaction */ - val hash: SecureHash + val commands: List>, + /** The hash of the original serialised WireTransaction */ + val hash: SecureHash ) { @Suppress("UNCHECKED_CAST") fun outRef(index: Int) = StateAndRef(outputs[index] as T, StateRef(hash, index)) @@ -280,4 +285,30 @@ data class LedgerTransaction( throw IllegalArgumentException("State not found in this transaction") return outRef(i) } + + fun toWireTransaction(): WireTransaction { + val wtx = WireTransaction(inputs, outputs, commands.map { Command(it.value, it.signers) }) + check(wtx.serialize().hash == hash) + return wtx + } + + /** + * Converts this transaction to [SignedWireTransaction] form, optionally using the provided keys to sign. There is + * no requirement that [andSignWithKeys] include all required keys. + * + * @throws IllegalArgumentException if a key is provided that isn't listed in any command and [allowUnusedKeys] + * is false. + */ + fun toSignedTransaction(andSignWithKeys: List = emptyList(), allowUnusedKeys: Boolean = false): SignedWireTransaction { + val allPubKeys = commands.flatMap { it.signers }.toSet() + val wtx = toWireTransaction() + val bits = wtx.serialize() + val sigs = ArrayList() + for (key in andSignWithKeys) { + if (!allPubKeys.contains(key.public) && !allowUnusedKeys) + throw IllegalArgumentException("Key provided that is not listed by any command") + sigs += key.signWithECDSA(bits) + } + return wtx.toSignedTransaction(sigs) + } } \ No newline at end of file diff --git a/src/test/kotlin/core/TransactionGroupTests.kt b/src/test/kotlin/core/TransactionGroupTests.kt index cfbfeb8502..940eb67cf4 100644 --- a/src/test/kotlin/core/TransactionGroupTests.kt +++ b/src/test/kotlin/core/TransactionGroupTests.kt @@ -127,4 +127,30 @@ class TransactionGroupTests { } } } + + @Test + fun signGroup() { + val signedTxns: List = transactionGroup { + transaction { + output("£1000") { A_THOUSAND_POUNDS } + arg(MINI_CORP_PUBKEY) { Cash.Commands.Issue() } + } + + transaction { + input("£1000") + output("alice's £1000") { A_THOUSAND_POUNDS `owned by` ALICE } + arg(MINI_CORP_PUBKEY) { Cash.Commands.Move() } + } + + transaction { + input("alice's £1000") + arg(ALICE) { Cash.Commands.Move() } + arg(MINI_CORP_PUBKEY) { Cash.Commands.Exit(1000.POUNDS) } + } + }.signAll() + + // Now go through the conversion -> verification path with them. + val ltxns = signedTxns.map { it.verifyToLedgerTransaction(MockIdentityService) }.toSet() + TransactionGroup(ltxns, emptySet()).verify(MockContractFactory) + } } \ No newline at end of file diff --git a/src/test/kotlin/core/testutils/TestUtils.kt b/src/test/kotlin/core/testutils/TestUtils.kt index 1dccf00ef3..17a67e8e3b 100644 --- a/src/test/kotlin/core/testutils/TestUtils.kt +++ b/src/test/kotlin/core/testutils/TestUtils.kt @@ -16,6 +16,7 @@ import core.crypto.DummyPublicKey import core.crypto.NullPublicKey import core.crypto.SecureHash import core.crypto.generateKeyPair +import core.serialization.serialize import core.visualiser.GraphVisualiser import java.security.PublicKey import java.time.Instant @@ -43,6 +44,8 @@ val BOB = BOB_KEY.public val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY) val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY) +val ALL_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY) + val TEST_KEYS_TO_CORP_MAP: Map = mapOf( MEGA_CORP_PUBKEY to MEGA_CORP, MINI_CORP_PUBKEY to MINI_CORP @@ -212,14 +215,13 @@ class TransactionGroupDSL(private val stateType: Class) { inStates.add(label.outputRef) } - /** * Converts to a [LedgerTransaction] with the test institution map, and just assigns a random hash * (i.e. pretend it was signed) */ fun toLedgerTransaction(): LedgerTransaction { val wtx = WireTransaction(inStates, outStates.map { it.state }, commands) - return wtx.toLedgerTransaction(MockIdentityService, SecureHash.randomSHA256()) + return wtx.toLedgerTransaction(MockIdentityService, wtx.serialize().hash) } } @@ -319,6 +321,10 @@ class TransactionGroupDSL(private val stateType: Class) { @Suppress("CAST_NEVER_SUCCEEDS") GraphVisualiser(this as TransactionGroupDSL).display() } + + fun signAll(): List { + return txns.map { it.toSignedTransaction(andSignWithKeys = ALL_KEYS, allowUnusedKeys = true) } + } } inline fun transactionGroupFor(body: TransactionGroupDSL.() -> Unit) = TransactionGroupDSL(T::class.java).apply { this.body() }