testdsl: Use and expose TransactionBuilder in TestTransactionDSLInterpreter

This commit is contained in:
Andras Slemmer 2016-07-06 17:22:38 +01:00
parent 656b06f7f5
commit c3060c11c0
7 changed files with 95 additions and 68 deletions

View File

@ -20,16 +20,31 @@ import java.util.*
* an output state can be added by just passing in a [ContractState] a [TransactionState] with the
* default notary will be generated automatically.
*/
abstract class TransactionBuilder(protected val type: TransactionType = TransactionType.General(),
protected val notary: Party? = null) {
protected val inputs: MutableList<StateRef> = arrayListOf()
protected val attachments: MutableList<SecureHash> = arrayListOf()
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf()
protected val commands: MutableList<Command> = arrayListOf()
protected val signers: MutableSet<PublicKey> = mutableSetOf()
open class TransactionBuilder(
protected val type: TransactionType = TransactionType.General(),
protected val notary: Party? = null,
protected val inputs: MutableList<StateRef> = arrayListOf(),
protected val attachments: MutableList<SecureHash> = arrayListOf(),
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
protected val commands: MutableList<Command> = arrayListOf(),
protected val signers: MutableSet<PublicKey> = mutableSetOf()) {
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
/**
* Creates a copy of the builder
*/
fun copy(): TransactionBuilder =
TransactionBuilder(
type = type,
notary = notary,
inputs = ArrayList(inputs),
attachments = ArrayList(attachments),
outputs = ArrayList(outputs),
commands = ArrayList(commands),
signers = LinkedHashSet(signers)
)
/**
* Places a [TimestampCommand] in this transaction, removing any existing command if there is one.
* The command requires a signature from the Notary service, which acts as a Timestamp Authority.
@ -112,31 +127,32 @@ abstract class TransactionBuilder(protected val type: TransactionType = Transact
return SignedTransaction(toWireTransaction().serialize(), ArrayList(currentSigs))
}
open fun addInputState(stateAndRef: StateAndRef<*>) {
open fun addInputState(stateAndRef: StateAndRef<*>) = addInputState(stateAndRef.ref, stateAndRef.state.notary)
fun addInputState(stateRef: StateRef, notary: Party) {
check(currentSigs.isEmpty())
val notaryKey = stateAndRef.state.notary.owningKey
signers.add(notaryKey)
inputs.add(stateAndRef.ref)
signers.add(notary.owningKey)
inputs.add(stateRef)
}
fun addAttachment(attachment: Attachment) {
fun addAttachment(attachmentId: SecureHash) {
check(currentSigs.isEmpty())
attachments.add(attachment.id)
attachments.add(attachmentId)
}
fun addOutputState(state: TransactionState<*>) {
fun addOutputState(state: TransactionState<*>): Int {
check(currentSigs.isEmpty())
outputs.add(state)
return outputs.size - 1
}
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
/** A default notary must be specified during builder construction to use this method */
fun addOutputState(state: ContractState) {
fun addOutputState(state: ContractState): Int {
checkNotNull(notary) { "Need to specify a Notary for the state, or set a default one on TransactionBuilder initialisation" }
addOutputState(state, notary!!)
return addOutputState(state, notary!!)
}
fun addCommand(arg: Command) {
@ -155,4 +171,4 @@ abstract class TransactionBuilder(protected val type: TransactionType = Transact
fun outputStates(): List<TransactionState<*>> = ArrayList(outputs)
fun commands(): List<Command> = ArrayList(commands)
fun attachments(): List<SecureHash> = ArrayList(attachments)
}
}

View File

@ -9,8 +9,10 @@ interface OutputStateLookup {
}
interface LedgerDSLInterpreter<R, out T : TransactionDSLInterpreter<R>> : OutputStateLookup {
fun transaction(transactionLabel: String?, dsl: TransactionDSL<R, T>.() -> R): WireTransaction
fun unverifiedTransaction(transactionLabel: String?, dsl: TransactionDSL<R, T>.() -> Unit): WireTransaction
fun _transaction(transactionLabel: String?, transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<R, T>.() -> R): WireTransaction
fun _unverifiedTransaction(transactionLabel: String?, transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<R, T>.() -> Unit): WireTransaction
fun tweak(dsl: LedgerDSL<R, T, LedgerDSLInterpreter<R, T>>.() -> Unit)
fun attachment(attachment: InputStream): SecureHash
fun verifies()
@ -26,10 +28,14 @@ interface LedgerDSLInterpreter<R, out T : TransactionDSLInterpreter<R>> : Output
class LedgerDSL<R, out T : TransactionDSLInterpreter<R>, out L : LedgerDSLInterpreter<R, T>> (val interpreter: L) :
LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>> by interpreter {
fun transaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R) =
transaction(null, dsl)
fun unverifiedTransaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> Unit) =
unverifiedTransaction(null, dsl)
@JvmOverloads
fun transaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(),
dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R) =
_transaction(label, transactionBuilder, dsl)
@JvmOverloads
fun unverifiedTransaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(),
dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> Unit) =
_unverifiedTransaction(label, transactionBuilder, dsl)
inline fun <reified S : ContractState> String.outputStateAndRef(): StateAndRef<S> =
retrieveOutputStateAndRef(S::class.java, this)

View File

@ -16,11 +16,12 @@ import java.util.*
fun transaction(
transactionLabel: String? = null,
transactionBuilder: TransactionBuilder = TransactionBuilder(),
dsl: TransactionDSL<
EnforceVerifyOrFail,
TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> EnforceVerifyOrFail
) = JavaTestHelpers.transaction(transactionLabel, dsl)
) = JavaTestHelpers.transaction(transactionLabel, transactionBuilder, dsl)
fun ledger(
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
@ -68,57 +69,55 @@ sealed class EnforceVerifyOrFail {
internal object Token: EnforceVerifyOrFail()
}
class DuplicateOutputLabel(label: String) : Exception("Output label '$label' already used")
/**
* This interpreter builds a transaction, and [TransactionDSL.verifies] that the resolved transaction is correct. Note
* that transactions corresponding to input states are not verified. Use [LedgerDSL.verifies] for that.
*/
data class TestTransactionDSLInterpreter(
data class TestTransactionDSLInterpreter private constructor(
override val ledgerInterpreter: TestLedgerDSLInterpreter,
private val inputStateRefs: ArrayList<StateRef> = arrayListOf(),
internal val outputStates: ArrayList<LabeledOutput> = arrayListOf(),
private val attachments: ArrayList<SecureHash> = arrayListOf(),
private val commands: ArrayList<Command> = arrayListOf(),
private val signers: LinkedHashSet<PublicKey> = LinkedHashSet(),
private val transactionType: TransactionType = TransactionType.General()
val transactionBuilder: TransactionBuilder,
internal val labelToIndexMap: HashMap<String, Int>
) : TransactionDSLInterpreter<EnforceVerifyOrFail>, OutputStateLookup by ledgerInterpreter {
constructor(
ledgerInterpreter: TestLedgerDSLInterpreter,
transactionBuilder: TransactionBuilder // = TransactionBuilder()
) : this(ledgerInterpreter, transactionBuilder, HashMap())
private fun copy(): TestTransactionDSLInterpreter =
TestTransactionDSLInterpreter(
ledgerInterpreter = ledgerInterpreter,
inputStateRefs = ArrayList(inputStateRefs),
outputStates = ArrayList(outputStates),
attachments = ArrayList(attachments),
commands = ArrayList(commands),
signers = LinkedHashSet(signers),
transactionType = transactionType
transactionBuilder = transactionBuilder.copy(),
labelToIndexMap = HashMap(labelToIndexMap)
)
internal fun toWireTransaction(): WireTransaction =
WireTransaction(
inputs = inputStateRefs,
outputs = outputStates.map { it.state },
attachments = attachments,
commands = commands,
signers = signers.toList(),
type = transactionType
)
internal fun toWireTransaction() = transactionBuilder.toWireTransaction()
override fun input(stateRef: StateRef) {
val notary = ledgerInterpreter.resolveStateRef<ContractState>(stateRef).notary
signers.add(notary.owningKey)
inputStateRefs.add(stateRef)
transactionBuilder.addInputState(stateRef, notary)
}
override fun _output(label: String?, notary: Party, contractState: ContractState) {
outputStates.add(LabeledOutput(label, TransactionState(contractState, notary)))
val outputIndex = transactionBuilder.addOutputState(contractState, notary)
if (label != null) {
if (labelToIndexMap.contains(label)) {
throw DuplicateOutputLabel(label)
} else {
labelToIndexMap[label] = outputIndex
}
}
}
override fun attachment(attachmentId: SecureHash) {
attachments.add(attachmentId)
transactionBuilder.addAttachment(attachmentId)
}
override fun _command(signers: List<PublicKey>, commandData: CommandData) {
this.signers.addAll(signers)
commands.add(Command(commandData, signers))
val command = Command(commandData, signers)
transactionBuilder.addCommand(command)
}
override fun verifies(): EnforceVerifyOrFail {
@ -243,9 +242,10 @@ data class TestLedgerDSLInterpreter private constructor (
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
private fun <Return> interpretTransactionDsl(
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Return
): TestTransactionDSLInterpreter {
val transactionInterpreter = TestTransactionDSLInterpreter(this)
val transactionInterpreter = TestTransactionDSLInterpreter(this, transactionBuilder)
dsl(TransactionDSL(transactionInterpreter))
return transactionInterpreter
}
@ -274,18 +274,20 @@ data class TestLedgerDSLInterpreter private constructor (
private fun <R> recordTransactionWithTransactionMap(
transactionLabel: String?,
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> R,
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
): WireTransaction {
val transactionLocation = getCallerLocation(3)
val transactionInterpreter = interpretTransactionDsl(dsl)
val transactionInterpreter = interpretTransactionDsl(transactionBuilder, dsl)
// Create the WireTransaction
val wireTransaction = transactionInterpreter.toWireTransaction()
// Record the output states
transactionInterpreter.outputStates.forEachIndexed { index, labeledOutput ->
if (labeledOutput.label != null) {
labelToOutputStateAndRefs[labeledOutput.label] = wireTransaction.outRef(index)
transactionInterpreter.labelToIndexMap.forEach { label, index ->
if (labelToOutputStateAndRefs.contains(label)) {
throw DuplicateOutputLabel(label)
}
labelToOutputStateAndRefs[label] = wireTransaction.outRef(index)
}
transactionMap[wireTransaction.serialized.hash] =
@ -294,15 +296,17 @@ data class TestLedgerDSLInterpreter private constructor (
return wireTransaction
}
override fun transaction(
override fun _transaction(
transactionLabel: String?,
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations)
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, transactionWithLocations)
override fun unverifiedTransaction(
override fun _unverifiedTransaction(
transactionLabel: String?,
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Unit
) = recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations)
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, nonVerifiedTransactionWithLocations)
override fun tweak(
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter,

View File

@ -102,11 +102,12 @@ object JavaTestHelpers {
@JvmStatic @JvmOverloads fun transaction(
transactionLabel: String? = null,
transactionBuilder: TransactionBuilder = TransactionBuilder(),
dsl: TransactionDSL<
EnforceVerifyOrFail,
TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> EnforceVerifyOrFail
) = ledger { transaction(transactionLabel, dsl) }
) = ledger { this.transaction(transactionLabel, transactionBuilder, dsl) }
}
val TEST_TX_TIME = JavaTestHelpers.TEST_TX_TIME

View File

@ -59,7 +59,7 @@ class TransactionDSL<R, out T : TransactionDSLInterpreter<R>> (val interpreter:
* Adds the passed in state as a non-verified transaction output to the ledger and adds that as an input.
*/
fun input(state: ContractState) {
val transaction = ledgerInterpreter.unverifiedTransaction(null) {
val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder()) {
output { state }
}
input(transaction.outRef<ContractState>(0).ref)

View File

@ -234,7 +234,7 @@ class AttachmentClassLoaderTests {
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction()
@ -265,7 +265,7 @@ class AttachmentClassLoaderTests {
val attachmentRef = importJar(storage)
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
val wireTransaction = tx.toWireTransaction()
@ -280,4 +280,4 @@ class AttachmentClassLoaderTests {
}
assertEquals(attachmentRef, e.ids.single())
}
}
}

View File

@ -341,7 +341,7 @@ private class TraderDemoProtocolSeller(val otherSide: Party,
// TODO: Consider moving these two steps below into generateIssue.
// Attach the prospectus.
tx.addAttachment(serviceHub.storageService.attachments.openAttachment(PROSPECTUS_HASH)!!)
tx.addAttachment(serviceHub.storageService.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
// Requesting timestamping, all CP must be timestamped.
tx.setTime(Instant.now(), notaryNode.identity, 30.seconds)