Moves to builder syntax for TxBuilder. Adds attachments and time-windows to withItems.

This commit is contained in:
Joel Dudley 2017-07-06 14:23:43 +01:00 committed by GitHub
parent c6e165947b
commit 3063debd98
4 changed files with 44 additions and 40 deletions

View File

@ -147,9 +147,10 @@ sealed class TransactionType {
* and adds the list of participants to the signers set for every input state. * and adds the list of participants to the signers set for every input state.
*/ */
class Builder(notary: Party) : TransactionBuilder(NotaryChange, notary) { class Builder(notary: Party) : TransactionBuilder(NotaryChange, notary) {
override fun addInputState(stateAndRef: StateAndRef<*>) { override fun addInputState(stateAndRef: StateAndRef<*>): TransactionBuilder {
signers.addAll(stateAndRef.state.data.participants.map { it.owningKey }) signers.addAll(stateAndRef.state.data.participants.map { it.owningKey })
super.addInputState(stateAndRef) super.addInputState(stateAndRef)
return this
} }
} }

View File

@ -1,11 +1,10 @@
package net.corda.core.transactions package net.corda.core.transactions
import co.paralleluniverse.strands.Strand import co.paralleluniverse.strands.Strand
import com.google.common.annotations.VisibleForTesting
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.internal.FlowStateMachine
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import java.security.KeyPair import java.security.KeyPair
@ -40,23 +39,22 @@ open class TransactionBuilder(
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(), protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
protected val commands: MutableList<Command> = arrayListOf(), protected val commands: MutableList<Command> = arrayListOf(),
protected val signers: MutableSet<PublicKey> = mutableSetOf(), protected val signers: MutableSet<PublicKey> = mutableSetOf(),
window: TimeWindow? = null) { protected var window: TimeWindow? = null) {
constructor(type: TransactionType, notary: Party) : this(type, notary, (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID()) constructor(type: TransactionType, notary: Party) : this(type, notary, (Strand.currentStrand() as? FlowStateMachine<*>)?.id?.uuid ?: UUID.randomUUID())
/** /**
* Creates a copy of the builder. * Creates a copy of the builder.
*/ */
fun copy(): TransactionBuilder = fun copy() = TransactionBuilder(
TransactionBuilder( type = type,
type = type, notary = notary,
notary = notary, inputs = ArrayList(inputs),
inputs = ArrayList(inputs), attachments = ArrayList(attachments),
attachments = ArrayList(attachments), outputs = ArrayList(outputs),
outputs = ArrayList(outputs), commands = ArrayList(commands),
commands = ArrayList(commands), signers = LinkedHashSet(signers),
signers = LinkedHashSet(signers), window = window
window = timeWindow )
)
// DOCSTART 1 // DOCSTART 1
/** A more convenient way to add items to this transaction that calls the add* methods for you based on type */ /** A more convenient way to add items to this transaction that calls the add* methods for you based on type */
@ -64,10 +62,12 @@ open class TransactionBuilder(
for (t in items) { for (t in items) {
when (t) { when (t) {
is StateAndRef<*> -> addInputState(t) is StateAndRef<*> -> addInputState(t)
is SecureHash -> addAttachment(t)
is TransactionState<*> -> addOutputState(t) is TransactionState<*> -> addOutputState(t)
is ContractState -> addOutputState(t) is ContractState -> addOutputState(t)
is Command -> addCommand(t) is Command -> addCommand(t)
is CommandData -> throw IllegalArgumentException("You passed an instance of CommandData, but that lacks the pubkey. You need to wrap it in a Command object first.") is CommandData -> throw IllegalArgumentException("You passed an instance of CommandData, but that lacks the pubkey. You need to wrap it in a Command object first.")
is TimeWindow -> setTimeWindow(t)
else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}") else -> throw IllegalArgumentException("Wrong argument type: ${t.javaClass}")
} }
} }
@ -76,7 +76,7 @@ open class TransactionBuilder(
// DOCEND 1 // DOCEND 1
fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments), fun toWireTransaction() = WireTransaction(ArrayList(inputs), ArrayList(attachments),
ArrayList(outputs), ArrayList(commands), notary, signers.toList(), type, timeWindow) ArrayList(outputs), ArrayList(commands), notary, signers.toList(), type, window)
@Throws(AttachmentResolutionException::class, TransactionResolutionException::class) @Throws(AttachmentResolutionException::class, TransactionResolutionException::class)
fun toLedgerTransaction(services: ServiceHub) = toWireTransaction().toLedgerTransaction(services) fun toLedgerTransaction(services: ServiceHub) = toWireTransaction().toLedgerTransaction(services)
@ -86,51 +86,55 @@ open class TransactionBuilder(
toLedgerTransaction(services).verify() toLedgerTransaction(services).verify()
} }
open fun addInputState(stateAndRef: StateAndRef<*>) { open fun addInputState(stateAndRef: StateAndRef<*>): TransactionBuilder {
val notary = stateAndRef.state.notary val notary = stateAndRef.state.notary
require(notary == this.notary) { "Input state requires notary \"$notary\" which does not match the transaction notary \"${this.notary}\"." } require(notary == this.notary) { "Input state requires notary \"$notary\" which does not match the transaction notary \"${this.notary}\"." }
signers.add(notary.owningKey) signers.add(notary.owningKey)
inputs.add(stateAndRef.ref) inputs.add(stateAndRef.ref)
return this
} }
fun addAttachment(attachmentId: SecureHash) { fun addAttachment(attachmentId: SecureHash): TransactionBuilder {
attachments.add(attachmentId) attachments.add(attachmentId)
return this
} }
fun addOutputState(state: TransactionState<*>): Int { fun addOutputState(state: TransactionState<*>): TransactionBuilder {
outputs.add(state) outputs.add(state)
return outputs.size - 1 return this
} }
@JvmOverloads @JvmOverloads
fun addOutputState(state: ContractState, notary: Party, encumbrance: Int? = null) = addOutputState(TransactionState(state, notary, encumbrance)) fun addOutputState(state: ContractState, notary: Party, encumbrance: Int? = null) = addOutputState(TransactionState(state, notary, encumbrance))
/** A default notary must be specified during builder construction to use this method */ /** A default notary must be specified during builder construction to use this method */
fun addOutputState(state: ContractState): Int { fun addOutputState(state: ContractState): TransactionBuilder {
checkNotNull(notary) { "Need to specify a notary for the state, or set a default one on TransactionBuilder initialisation" } checkNotNull(notary) { "Need to specify a notary for the state, or set a default one on TransactionBuilder initialisation" }
return addOutputState(state, notary!!) addOutputState(state, notary!!)
return this
} }
fun addCommand(arg: Command) { fun addCommand(arg: Command): TransactionBuilder {
// TODO: replace pubkeys in commands with 'pointers' to keys in signers // TODO: replace pubkeys in commands with 'pointers' to keys in signers
signers.addAll(arg.signers) signers.addAll(arg.signers)
commands.add(arg) commands.add(arg)
return this
} }
fun addCommand(data: CommandData, vararg keys: PublicKey) = addCommand(Command(data, listOf(*keys))) fun addCommand(data: CommandData, vararg keys: PublicKey) = addCommand(Command(data, listOf(*keys)))
fun addCommand(data: CommandData, keys: List<PublicKey>) = addCommand(Command(data, keys)) fun addCommand(data: CommandData, keys: List<PublicKey>) = addCommand(Command(data, keys))
var timeWindow: TimeWindow? = window /**
/** * Sets the [TimeWindow] for this transaction, replacing the existing [TimeWindow] if there is one. To be valid, the
* Sets the [TimeWindow] for this transaction, replacing the existing [TimeWindow] if there is one. To be valid, the * transaction must then be signed by the notary service within this window of time. In this way, the notary acts as
* transaction must then be signed by the notary service within this window of time. In this way, the notary acts as * the Timestamp Authority.
* the Timestamp Authority. */
*/ fun setTimeWindow(timeWindow: TimeWindow): TransactionBuilder {
set(value) { check(notary != null) { "Only notarised transactions can have a time-window" }
check(notary != null) { "Only notarised transactions can have a time-window" } signers.add(notary!!.owningKey)
signers.add(notary!!.owningKey) window = timeWindow
field = value return this
} }
/** /**
* The [TimeWindow] for the transaction can also be defined as [time] +/- [timeTolerance]. The tolerance should be * The [TimeWindow] for the transaction can also be defined as [time] +/- [timeTolerance]. The tolerance should be
@ -139,9 +143,7 @@ open class TransactionBuilder(
* collaborating parties may therefore require a higher time tolerance than a transaction being built by a single * collaborating parties may therefore require a higher time tolerance than a transaction being built by a single
* node. * node.
*/ */
fun setTimeWindow(time: Instant, timeTolerance: Duration) { fun setTimeWindow(time: Instant, timeTolerance: Duration) = setTimeWindow(TimeWindow.withTolerance(time, timeTolerance))
timeWindow = TimeWindow.withTolerance(time, timeTolerance)
}
// Accessors that yield immutable snapshots. // Accessors that yield immutable snapshots.
fun inputStates(): List<StateRef> = ArrayList(inputs) fun inputStates(): List<StateRef> = ArrayList(inputs)

View File

@ -289,7 +289,7 @@ object FlowCookbook {
regTxBuilder.addAttachment(ourAttachment) regTxBuilder.addAttachment(ourAttachment)
// We set the time-window within which the transaction must be notarised using either of: // We set the time-window within which the transaction must be notarised using either of:
regTxBuilder.timeWindow = ourTimeWindow regTxBuilder.setTimeWindow(ourTimeWindow)
regTxBuilder.setTimeWindow(serviceHub.clock.instant(), Duration.ofSeconds(30)) regTxBuilder.setTimeWindow(serviceHub.clock.instant(), Duration.ofSeconds(30))
/**---------------------- /**----------------------

View File

@ -116,11 +116,12 @@ data class TestTransactionDSLInterpreter private constructor(
} }
override fun _output(label: String?, notary: Party, encumbrance: Int?, contractState: ContractState) { override fun _output(label: String?, notary: Party, encumbrance: Int?, contractState: ContractState) {
val outputIndex = transactionBuilder.addOutputState(contractState, notary, encumbrance) transactionBuilder.addOutputState(contractState, notary, encumbrance)
if (label != null) { if (label != null) {
if (label in labelToIndexMap) { if (label in labelToIndexMap) {
throw DuplicateOutputLabel(label) throw DuplicateOutputLabel(label)
} else { } else {
val outputIndex = transactionBuilder.outputStates().size - 1
labelToIndexMap[label] = outputIndex labelToIndexMap[label] = outputIndex
} }
} }
@ -145,7 +146,7 @@ data class TestTransactionDSLInterpreter private constructor(
} }
override fun timeWindow(data: TimeWindow) { override fun timeWindow(data: TimeWindow) {
transactionBuilder.timeWindow = data transactionBuilder.setTimeWindow(data)
} }
override fun tweak( override fun tweak(