mirror of
https://github.com/corda/corda.git
synced 2024-12-21 22:07:55 +00:00
Merged in mike-tx-types-refactoring-september (pull request #332)
Refactor common fields from WireTransaction/LedgerTransaction out into BaseTransaction. Put some field invariants into the constructors.
This commit is contained in:
commit
5a28b29a7e
@ -0,0 +1,53 @@
|
||||
package com.r3corda.core.transactions
|
||||
|
||||
import com.r3corda.core.contracts.*
|
||||
import com.r3corda.core.crypto.Party
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* An abstract class defining fields shared by all transaction types in the system.
|
||||
*/
|
||||
abstract class BaseTransaction(
|
||||
/** The inputs of this transaction. Note that in BaseTransaction subclasses the type of this list may change! */
|
||||
open val inputs: List<*>,
|
||||
/** Ordered list of states defined by this transaction, along with the associated notaries. */
|
||||
val outputs: List<TransactionState<ContractState>>,
|
||||
/**
|
||||
* If present, the notary for this transaction. If absent then the transaction is not notarised at all.
|
||||
* This is intended for issuance/genesis transactions that don't consume any other states and thus can't
|
||||
* double spend anything.
|
||||
*/
|
||||
val notary: Party?,
|
||||
/**
|
||||
* Keys that are required to have signed the wrapping [SignedTransaction], ordered to match the list of
|
||||
* signatures. There is nothing that forces the list to be the _correct_ list of signers for this
|
||||
* transaction until the transaction is verified by using [LedgerTransaction.verify]. It includes the
|
||||
* notary key, if the notary field is set.
|
||||
*/
|
||||
val signers: List<PublicKey>,
|
||||
/**
|
||||
* Pointer to a class that defines the behaviour of this transaction: either normal, or "notary changing".
|
||||
*/
|
||||
val type: TransactionType,
|
||||
/**
|
||||
* If specified, a time window in which this transaction may have been notarised. Contracts can check this
|
||||
* time window to find out when a transaction is deemed to have occurred, from the ledger's perspective.
|
||||
*/
|
||||
val timestamp: Timestamp?
|
||||
) : NamedByHash {
|
||||
|
||||
fun checkInvariants() {
|
||||
if (notary == null) check(inputs.isEmpty()) { "The notary must be specified explicitly for any transaction that has inputs." }
|
||||
if (timestamp != null) check(notary != null) { "If a timestamp is provided, there must be a notary." }
|
||||
}
|
||||
|
||||
override fun equals(other: Any?) =
|
||||
other is BaseTransaction &&
|
||||
notary == other.notary &&
|
||||
signers == other.signers &&
|
||||
type == other.type &&
|
||||
timestamp == other.timestamp
|
||||
|
||||
override fun hashCode() = Objects.hash(notary, signers, type, timestamp)
|
||||
}
|
@ -16,24 +16,23 @@ import java.security.PublicKey
|
||||
*
|
||||
* All the above refer to inputs using a (txhash, output index) pair.
|
||||
*/
|
||||
data class LedgerTransaction(
|
||||
/** The input states which will be consumed/invalidated by the execution of this transaction. */
|
||||
val inputs: List<StateAndRef<*>>,
|
||||
/** The states that will be generated by the execution of this transaction. */
|
||||
val outputs: List<TransactionState<*>>,
|
||||
class LedgerTransaction(
|
||||
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
|
||||
override val inputs: List<StateAndRef<*>>,
|
||||
outputs: List<TransactionState<ContractState>>,
|
||||
/** Arbitrary data passed to the program of each input state. */
|
||||
val commands: List<AuthenticatedObject<CommandData>>,
|
||||
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
||||
val attachments: List<Attachment>,
|
||||
/** The hash of the original serialised WireTransaction. */
|
||||
override val id: SecureHash,
|
||||
/** The notary for this party, may be null for transactions with no notary. */
|
||||
val notary: Party?,
|
||||
/** The notary key and the command keys together: a signed transaction must provide signatures for all of these. */
|
||||
val signers: List<PublicKey>,
|
||||
val timestamp: Timestamp?,
|
||||
val type: TransactionType
|
||||
) : NamedByHash {
|
||||
notary: Party?,
|
||||
signers: List<PublicKey>,
|
||||
timestamp: Timestamp?,
|
||||
type: TransactionType
|
||||
) : BaseTransaction(inputs, outputs, notary, signers, type, timestamp) {
|
||||
init { checkInvariants() }
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
|
||||
|
||||
@ -54,4 +53,32 @@ data class LedgerTransaction(
|
||||
* @throws TransactionVerificationException if anything goes wrong.
|
||||
*/
|
||||
fun verify() = type.verify(this)
|
||||
|
||||
// TODO: When we upgrade to Kotlin 1.1 we can make this a data class again and have the compiler generate these.
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as LedgerTransaction
|
||||
|
||||
if (inputs != other.inputs) return false
|
||||
if (outputs != other.outputs) return false
|
||||
if (commands != other.commands) return false
|
||||
if (attachments != other.attachments) return false
|
||||
if (id != other.id) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + inputs.hashCode()
|
||||
result = 31 * result + outputs.hashCode()
|
||||
result = 31 * result + commands.hashCode()
|
||||
result = 31 * result + attachments.hashCode()
|
||||
result = 31 * result + id.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
@ -20,42 +20,20 @@ import java.security.PublicKey
|
||||
* the identity of the transaction, that is, it's possible for two [SignedTransaction]s with different sets of
|
||||
* signatures to have the same identity hash.
|
||||
*/
|
||||
data class WireTransaction(
|
||||
class WireTransaction(
|
||||
/** Pointers to the input states on the ledger, identified by (tx identity hash, output index). */
|
||||
val inputs: List<StateRef>,
|
||||
override val inputs: List<StateRef>,
|
||||
/** Hashes of the ZIP/JAR files that are needed to interpret the contents of this wire transaction. */
|
||||
val attachments: List<SecureHash>,
|
||||
/** Ordered list of states defined by this transaction, along with the associated notaries. */
|
||||
val outputs: List<TransactionState<ContractState>>,
|
||||
outputs: List<TransactionState<ContractState>>,
|
||||
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
|
||||
val commands: List<Command>,
|
||||
/**
|
||||
* If present, the notary for this transaction. If absent then the transaction is not notarised at all.
|
||||
* This is intended for issuance/genesis transactions that don't consume any other states and thus can't
|
||||
* double spend anything.
|
||||
*
|
||||
* TODO: Ensure the invariant 'notary == null -> inputs.isEmpty' is enforced!
|
||||
*/
|
||||
val notary: Party?,
|
||||
/**
|
||||
* Keys that are required to have signed the wrapping [SignedTransaction], ordered to match the list of
|
||||
* signatures. There is nothing that forces the list to be the _correct_ list of signers for this
|
||||
* transaction until the transaction is verified by using [LedgerTransaction.verify]. It includes the
|
||||
* notary key, if the notary field is set.
|
||||
*/
|
||||
val signers: List<PublicKey>,
|
||||
/**
|
||||
* Pointer to a class that defines the behaviour of this transaction: either normal, or "notary changing".
|
||||
*/
|
||||
val type: TransactionType,
|
||||
/**
|
||||
* If specified, a time window in which this transaction may have been notarised. Contracts can check this
|
||||
* time window to find out when a transaction is deemed to have occurred, from the ledger's perspective.
|
||||
*
|
||||
* TODO: Ensure the invariant 'timestamp != null -> notary != null' is enforced!
|
||||
*/
|
||||
val timestamp: Timestamp?
|
||||
) : NamedByHash {
|
||||
notary: Party?,
|
||||
signers: List<PublicKey>,
|
||||
type: TransactionType,
|
||||
timestamp: Timestamp?
|
||||
) : BaseTransaction(inputs, outputs, notary, signers, type, timestamp) {
|
||||
init { checkInvariants() }
|
||||
|
||||
// Cache the serialised form of the transaction and its hash to give us fast access to it.
|
||||
@Volatile @Transient private var cachedBits: SerializedBytes<WireTransaction>? = null
|
||||
@ -80,16 +58,6 @@ data class WireTransaction(
|
||||
/** Returns a [StateAndRef] for the requested output state, or throws [IllegalArgumentException] if not found. */
|
||||
fun <T : ContractState> outRef(state: ContractState): StateAndRef<T> = outRef(outputs.map { it.data }.indexOfOrThrow(state))
|
||||
|
||||
override fun toString(): String {
|
||||
val buf = StringBuilder()
|
||||
buf.appendln("Transaction $id:")
|
||||
for (input in inputs) buf.appendln("${Emoji.rightArrow}INPUT: $input")
|
||||
for (output in outputs) buf.appendln("${Emoji.leftArrow}OUTPUT: $output")
|
||||
for (command in commands) buf.appendln("${Emoji.diamond}COMMAND: $command")
|
||||
for (attachment in attachments) buf.appendln("${Emoji.paperclip}ATTACHMENT: $attachment")
|
||||
return buf.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up identities and attachments from storage to generate a [LedgerTransaction]. A transaction is expected to
|
||||
* have been fully resolved using the resolution protocol by this point.
|
||||
@ -111,4 +79,40 @@ data class WireTransaction(
|
||||
val resolvedInputs = inputs.map { StateAndRef(services.loadState(it), it) }
|
||||
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, signers, timestamp, type)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
val buf = StringBuilder()
|
||||
buf.appendln("Transaction $id:")
|
||||
for (input in inputs) buf.appendln("${Emoji.rightArrow}INPUT: $input")
|
||||
for (output in outputs) buf.appendln("${Emoji.leftArrow}OUTPUT: $output")
|
||||
for (command in commands) buf.appendln("${Emoji.diamond}COMMAND: $command")
|
||||
for (attachment in attachments) buf.appendln("${Emoji.paperclip}ATTACHMENT: $attachment")
|
||||
return buf.toString()
|
||||
}
|
||||
|
||||
// TODO: When Kotlin 1.1 comes out we can make this class a data class again, and have these be autogenerated.
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other?.javaClass != javaClass) return false
|
||||
if (!super.equals(other)) return false
|
||||
|
||||
other as WireTransaction
|
||||
|
||||
if (inputs != other.inputs) return false
|
||||
if (attachments != other.attachments) return false
|
||||
if (outputs != other.outputs) return false
|
||||
if (commands != other.commands) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = super.hashCode()
|
||||
result = 31 * result + inputs.hashCode()
|
||||
result = 31 * result + attachments.hashCode()
|
||||
result = 31 * result + outputs.hashCode()
|
||||
result = 31 * result + commands.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ import com.r3corda.core.crypto.SecureHash
|
||||
import com.r3corda.core.transactions.LedgerTransaction
|
||||
import com.r3corda.core.utilities.DUMMY_NOTARY
|
||||
import com.r3corda.core.utilities.DUMMY_NOTARY_KEY
|
||||
import com.r3corda.testing.*
|
||||
import com.r3corda.testing.ALICE
|
||||
import com.r3corda.testing.ALICE_PUBKEY
|
||||
import com.r3corda.testing.BOB
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
|
@ -74,7 +74,7 @@ class TransactionDSL<out T : TransactionDSLInterpreter>(val interpreter: T) : Tr
|
||||
* @param state The state to be added.
|
||||
*/
|
||||
fun input(state: ContractState) {
|
||||
val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder()) {
|
||||
val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder(notary = DUMMY_NOTARY)) {
|
||||
output { state }
|
||||
}
|
||||
input(transaction.outRef<ContractState>(0).ref)
|
||||
|
Loading…
Reference in New Issue
Block a user