mirror of
https://github.com/corda/corda.git
synced 2025-06-10 19:31:46 +00:00
Refactor common fields from WireTransaction/LedgerTransaction out into BaseTransaction. Put some field invariants into the constructors.
This commit is contained in:
parent
0f29067680
commit
e398b6e17b
@ -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.
|
* All the above refer to inputs using a (txhash, output index) pair.
|
||||||
*/
|
*/
|
||||||
data class LedgerTransaction(
|
class LedgerTransaction(
|
||||||
/** The input states which will be consumed/invalidated by the execution of this transaction. */
|
/** The resolved input states which will be consumed/invalidated by the execution of this transaction. */
|
||||||
val inputs: List<StateAndRef<*>>,
|
override val inputs: List<StateAndRef<*>>,
|
||||||
/** The states that will be generated by the execution of this transaction. */
|
outputs: List<TransactionState<ContractState>>,
|
||||||
val outputs: List<TransactionState<*>>,
|
|
||||||
/** Arbitrary data passed to the program of each input state. */
|
/** Arbitrary data passed to the program of each input state. */
|
||||||
val commands: List<AuthenticatedObject<CommandData>>,
|
val commands: List<AuthenticatedObject<CommandData>>,
|
||||||
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
/** A list of [Attachment] objects identified by the transaction that are needed for this transaction to verify. */
|
||||||
val attachments: List<Attachment>,
|
val attachments: List<Attachment>,
|
||||||
/** The hash of the original serialised WireTransaction. */
|
/** The hash of the original serialised WireTransaction. */
|
||||||
override val id: SecureHash,
|
override val id: SecureHash,
|
||||||
/** The notary for this party, may be null for transactions with no notary. */
|
notary: Party?,
|
||||||
val notary: Party?,
|
signers: List<PublicKey>,
|
||||||
/** The notary key and the command keys together: a signed transaction must provide signatures for all of these. */
|
timestamp: Timestamp?,
|
||||||
val signers: List<PublicKey>,
|
type: TransactionType
|
||||||
val timestamp: Timestamp?,
|
) : BaseTransaction(inputs, outputs, notary, signers, type, timestamp) {
|
||||||
val type: TransactionType
|
init { checkInvariants() }
|
||||||
) : NamedByHash {
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun <T : ContractState> outRef(index: Int) = StateAndRef(outputs[index] as TransactionState<T>, StateRef(id, index))
|
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.
|
* @throws TransactionVerificationException if anything goes wrong.
|
||||||
*/
|
*/
|
||||||
fun verify() = type.verify(this)
|
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
|
* 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.
|
* 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). */
|
/** 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. */
|
/** Hashes of the ZIP/JAR files that are needed to interpret the contents of this wire transaction. */
|
||||||
val attachments: List<SecureHash>,
|
val attachments: List<SecureHash>,
|
||||||
/** Ordered list of states defined by this transaction, along with the associated notaries. */
|
outputs: List<TransactionState<ContractState>>,
|
||||||
val outputs: List<TransactionState<ContractState>>,
|
|
||||||
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
|
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
|
||||||
val commands: List<Command>,
|
val commands: List<Command>,
|
||||||
/**
|
notary: Party?,
|
||||||
* If present, the notary for this transaction. If absent then the transaction is not notarised at all.
|
signers: List<PublicKey>,
|
||||||
* This is intended for issuance/genesis transactions that don't consume any other states and thus can't
|
type: TransactionType,
|
||||||
* double spend anything.
|
timestamp: Timestamp?
|
||||||
*
|
) : BaseTransaction(inputs, outputs, notary, signers, type, timestamp) {
|
||||||
* TODO: Ensure the invariant 'notary == null -> inputs.isEmpty' is enforced!
|
init { checkInvariants() }
|
||||||
*/
|
|
||||||
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 {
|
|
||||||
|
|
||||||
// Cache the serialised form of the transaction and its hash to give us fast access to it.
|
// 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
|
@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. */
|
/** 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))
|
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
|
* 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.
|
* 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) }
|
val resolvedInputs = inputs.map { StateAndRef(services.loadState(it), it) }
|
||||||
return LedgerTransaction(resolvedInputs, outputs, authenticatedArgs, attachments, id, notary, signers, timestamp, type)
|
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.transactions.LedgerTransaction
|
||||||
import com.r3corda.core.utilities.DUMMY_NOTARY
|
import com.r3corda.core.utilities.DUMMY_NOTARY
|
||||||
import com.r3corda.core.utilities.DUMMY_NOTARY_KEY
|
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 org.junit.Test
|
||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
@ -74,7 +74,7 @@ class TransactionDSL<out T : TransactionDSLInterpreter>(val interpreter: T) : Tr
|
|||||||
* @param state The state to be added.
|
* @param state The state to be added.
|
||||||
*/
|
*/
|
||||||
fun input(state: ContractState) {
|
fun input(state: ContractState) {
|
||||||
val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder()) {
|
val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder(notary = DUMMY_NOTARY)) {
|
||||||
output { state }
|
output { state }
|
||||||
}
|
}
|
||||||
input(transaction.outRef<ContractState>(0).ref)
|
input(transaction.outRef<ContractState>(0).ref)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user