Testing: more utilities on Transaction and TransactionGroupForTest to help with testing signed transactions.

This commit is contained in:
Mike Hearn 2016-02-12 14:55:11 +01:00
parent 48192d8d9d
commit fc000ec03c
3 changed files with 71 additions and 8 deletions

View File

@ -65,6 +65,11 @@ data class WireTransaction(val inputs: List<StateRef>,
return LedgerTransaction(inputs, outputs, authenticatedArgs, originalHash)
}
/** Serialises and returns this transaction as a [SignedWireTransaction] with no signatures attached. */
fun toSignedTransaction(withSigs: List<DigitalSignature.WithKey> = 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<StateRef> = 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<StateRef> = arrayListOf
*/
data class LedgerTransaction(
/** The input states which will be consumed/invalidated by the execution of this transaction. */
val inputs: List<StateRef>,
val inputs: List<StateRef>,
/** The states that will be generated by the execution of this transaction. */
val outputs: List<ContractState>,
val outputs: List<ContractState>,
/** Arbitrary data passed to the program of each input state. */
val commands: List<AuthenticatedObject<CommandData>>,
/** The hash of the original serialised SignedTransaction */
val hash: SecureHash
val commands: List<AuthenticatedObject<CommandData>>,
/** The hash of the original serialised WireTransaction */
val hash: SecureHash
) {
@Suppress("UNCHECKED_CAST")
fun <T : ContractState> 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<KeyPair> = emptyList(), allowUnusedKeys: Boolean = false): SignedWireTransaction {
val allPubKeys = commands.flatMap { it.signers }.toSet()
val wtx = toWireTransaction()
val bits = wtx.serialize()
val sigs = ArrayList<DigitalSignature.WithKey>()
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)
}
}

View File

@ -127,4 +127,30 @@ class TransactionGroupTests {
}
}
}
@Test
fun signGroup() {
val signedTxns: List<SignedWireTransaction> = 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)
}
}

View File

@ -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<PublicKey, Party> = mapOf(
MEGA_CORP_PUBKEY to MEGA_CORP,
MINI_CORP_PUBKEY to MINI_CORP
@ -212,14 +215,13 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
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<T : ContractState>(private val stateType: Class<T>) {
@Suppress("CAST_NEVER_SUCCEEDS")
GraphVisualiser(this as TransactionGroupDSL<ContractState>).display()
}
fun signAll(): List<SignedWireTransaction> {
return txns.map { it.toSignedTransaction(andSignWithKeys = ALL_KEYS, allowUnusedKeys = true) }
}
}
inline fun <reified T : ContractState> transactionGroupFor(body: TransactionGroupDSL<T>.() -> Unit) = TransactionGroupDSL<T>(T::class.java).apply { this.body() }