Merged in test-dsl-docs (pull request #212)

Test dsl docs
This commit is contained in:
Andras Slemmer 2016-07-11 14:31:35 +01:00
commit ae2e6ab917
19 changed files with 1023 additions and 315 deletions

View File

@ -1,15 +1,10 @@
package com.r3corda.contracts.testing
import com.r3corda.contracts.*
import com.r3corda.contracts.asset.CASH_PROGRAM_ID
import com.r3corda.contracts.asset.Cash
import com.r3corda.contracts.asset.Obligation
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Contract
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.DUMMY_PROGRAM_ID
import com.r3corda.core.contracts.DummyContract
import com.r3corda.core.contracts.DummyState
import com.r3corda.core.contracts.PartyAndReference
import com.r3corda.core.contracts.Issued
import com.r3corda.core.contracts.TransactionState
@ -23,42 +18,10 @@ import java.security.PublicKey
import java.time.Instant
import java.util.*
// In a real system this would be a persistent map of hash to bytecode and we'd instantiate the object as needed inside
// a sandbox. For unit tests we just have a hard-coded list.
val TEST_PROGRAM_MAP: Map<Contract, Class<out Contract>> = mapOf(
CASH_PROGRAM_ID to Cash::class.java,
CP_PROGRAM_ID to CommercialPaper::class.java,
JavaCommercialPaper.JCP_PROGRAM_ID to JavaCommercialPaper::class.java,
DUMMY_PROGRAM_ID to DummyContract::class.java,
IRS_PROGRAM_ID to InterestRateSwap::class.java
)
fun generateState() = DummyState(Random().nextInt())
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
//
// Define a transaction like this:
//
// transaction {
// input { someExpression }
// output { someExpression }
// arg { someExpression }
//
// tweak {
// ... same thing but works with a copy of the parent, can add inputs/outputs/args just within this scope.
// }
//
// contract.accepts() -> should pass
// contract `fails requirement` "some substring of the error message"
// }
// For Java compatibility please define helper methods here and then define the infix notation
object JavaTestHelpers {
@JvmStatic fun ownedBy(state: Cash.State, owner: PublicKey) = state.copy(owner = owner)
@JvmStatic fun issuedBy(state: Cash.State, party: Party) = state.copy(amount = Amount<Issued<Currency>>(state.amount.quantity, state.issuanceDef.copy(issuer = state.deposit.copy(party = party))))
@JvmStatic fun issuedBy(state: Cash.State, deposit: PartyAndReference) = state.copy(amount = Amount<Issued<Currency>>(state.amount.quantity, state.issuanceDef.copy(issuer = deposit)))
@JvmStatic fun issuedBy(state: Cash.State, party: Party) = state.copy(amount = Amount(state.amount.quantity, state.issuanceDef.copy(issuer = state.deposit.copy(party = party))))
@JvmStatic fun issuedBy(state: Cash.State, deposit: PartyAndReference) = state.copy(amount = Amount(state.amount.quantity, state.issuanceDef.copy(issuer = deposit)))
@JvmStatic fun withNotary(state: Cash.State, notary: Party) = TransactionState(state, notary)
@JvmStatic fun withDeposit(state: Cash.State, deposit: PartyAndReference) = state.copy(amount = state.amount.copy(token = state.amount.token.copy(issuer = deposit)))
@ -70,12 +33,12 @@ object JavaTestHelpers {
@JvmStatic fun ownedBy(state: CommercialPaper.State, owner: PublicKey) = state.copy(owner = owner)
@JvmStatic fun withNotary(state: CommercialPaper.State, notary: Party) = TransactionState(state, notary)
@JvmStatic fun ownedBy(state: ICommercialPaperState, new_owner: PublicKey) = state.withOwner(new_owner)
@JvmStatic fun ownedBy(state: ICommercialPaperState, new_owner: PublicKey): ICommercialPaperState = state.withOwner(new_owner)
@JvmStatic fun withNotary(state: ContractState, notary: Party) = TransactionState(state, notary)
@JvmStatic fun CASH(amount: Amount<Currency>) = Cash.State(
Amount<Issued<Currency>>(amount.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, amount.token)),
Amount(amount.quantity, Issued(DUMMY_CASH_ISSUER, amount.token)),
NullPublicKey)
@JvmStatic fun STATE(amount: Amount<Issued<Currency>>) = Cash.State(amount, NullPublicKey)
@ -86,7 +49,6 @@ object JavaTestHelpers {
OBLIGATION_DEF(amount.token), amount.quantity, NullPublicKey)
}
infix fun Cash.State.`owned by`(owner: PublicKey) = JavaTestHelpers.ownedBy(this, owner)
infix fun Cash.State.`issued by`(party: Party) = JavaTestHelpers.issuedBy(this, party)
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = JavaTestHelpers.issuedBy(this, deposit)

View File

@ -14,7 +14,7 @@ import static com.r3corda.contracts.testing.JavaTestHelpers.*;
*/
public class CashTestsJava {
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});;
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});
private PartyAndReference defaultIssuer = getMEGA_CORP().ref(defaultRef);
private Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), getDUMMY_PUBKEY_1());
private Cash.State outState = new Cash.State(inState.getAmount(), getDUMMY_PUBKEY_2());

View File

@ -85,7 +85,7 @@ class CommercialPaperTestsGeneric {
input("paper")
input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output("alice's paper") { "paper".output<ICommercialPaperState>().data `owned by` ALICE_PUBKEY }
output("alice's paper") { "paper".output<ICommercialPaperState>() `owned by` ALICE_PUBKEY }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
this.verifies()
@ -97,7 +97,7 @@ class CommercialPaperTestsGeneric {
input("alice's paper")
input("some profits")
fun TransactionDSL<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.outputs(aliceGetsBack: Amount<Issued<Currency>>) {
fun TransactionDSL<TransactionDSLInterpreter>.outputs(aliceGetsBack: Amount<Issued<Currency>>) {
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
}
@ -120,7 +120,7 @@ class CommercialPaperTestsGeneric {
timestamp(TEST_TX_TIME + 8.days)
tweak {
output { "paper".output<ICommercialPaperState>().data }
output { "paper".output<ICommercialPaperState>() }
this `fails with` "must be destroyed"
}

View File

@ -360,7 +360,7 @@ class IRSTests {
/**
* Generates a typical transactional history for an IRS.
*/
fun trade(): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
fun trade(): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
val ld = LocalDate.of(2016, 3, 8)
val bd = BigDecimal("0.0063518")
@ -377,11 +377,11 @@ class IRSTests {
input("irs post agreement")
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
output("irs post first fixing") {
postAgreement.data.copy(
postAgreement.data.fixedLeg,
postAgreement.data.floatingLeg,
postAgreement.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
postAgreement.data.common
postAgreement.copy(
postAgreement.fixedLeg,
postAgreement.floatingLeg,
postAgreement.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
postAgreement.common
)
}
command(ORACLE_PUBKEY) {
@ -653,7 +653,7 @@ class IRSTests {
* result and the grouping won't work either.
* In reality, the only fields that should be in common will be the next fixing date and the reference rate.
*/
fun tradegroups(): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
fun tradegroups(): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
val ld1 = LocalDate.of(2016, 3, 8)
val bd1 = BigDecimal("0.0063518")
@ -693,20 +693,20 @@ class IRSTests {
input("irs post agreement2")
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
output("irs post first fixing1") {
postAgreement1.data.copy(
postAgreement1.data.fixedLeg,
postAgreement1.data.floatingLeg,
postAgreement1.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
postAgreement1.data.common.copy(tradeID = "t1")
postAgreement1.copy(
postAgreement1.fixedLeg,
postAgreement1.floatingLeg,
postAgreement1.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
postAgreement1.common.copy(tradeID = "t1")
)
}
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
output("irs post first fixing2") {
postAgreement2.data.copy(
postAgreement2.data.fixedLeg,
postAgreement2.data.floatingLeg,
postAgreement2.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
postAgreement2.data.common.copy(tradeID = "t2")
postAgreement2.copy(
postAgreement2.fixedLeg,
postAgreement2.floatingLeg,
postAgreement2.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
postAgreement2.common.copy(tradeID = "t2")
)
}

View File

@ -5,7 +5,6 @@ import com.r3corda.contracts.testing.*
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.testing.*
import com.r3corda.core.testing.JavaTestHelpers
import com.r3corda.core.utilities.nonEmptySetOf
import org.junit.Test
import java.security.PublicKey
@ -35,7 +34,7 @@ class ObligationTests {
val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2)
private fun obligationTestRoots(
group: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
) = group.apply {
unverifiedTransaction {
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY))

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

@ -169,4 +169,4 @@ operator fun KeyPair.component1() = this.private
operator fun KeyPair.component2() = this.public
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */
fun generateKeyPair() = EddsaKeyPairGenerator().generateKeyPair()
fun generateKeyPair(): KeyPair = EddsaKeyPairGenerator().generateKeyPair()

View File

@ -4,36 +4,159 @@ import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import java.io.InputStream
/**
* This interface defines output state lookup by label. It is split from the interpreter interfaces so that outputs may
* be looked up both in ledger{..} and transaction{..} blocks.
*/
interface OutputStateLookup {
/**
* Retrieves an output previously defined by [TransactionDSLInterpreter._output] with a label passed in.
* @param clazz The class object holding the type of the output state expected.
* @param label The label of the to-be-retrieved output state
* @return The output [StateAndRef]
*/
fun <S : ContractState> retrieveOutputStateAndRef(clazz: Class<S>, label: String): StateAndRef<S>
}
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 tweak(dsl: LedgerDSL<R, T, LedgerDSLInterpreter<R, T>>.() -> Unit)
fun attachment(attachment: InputStream): SecureHash
fun verifies()
/**
* This interface asserts that the DSL at hand is capable of verifying its underlying construct(ledger/transaction)
*/
interface Verifies {
/**
* Verifies the ledger/transaction, throws if the verification fails.
*/
fun verifies(): EnforceVerifyOrFail
/**
* Asserts that verifies() throws
* @param expectedMessage An optional string to be searched for in the raised exception.
*/
fun failsWith(expectedMessage: String?): EnforceVerifyOrFail {
val exceptionThrown = try {
verifies()
false
} catch (exception: Exception) {
if (expectedMessage != null) {
val exceptionMessage = exception.message
if (exceptionMessage == null) {
throw AssertionError(
"Expected exception containing '$expectedMessage' but raised exception had no message"
)
} else if (!exceptionMessage.toLowerCase().contains(expectedMessage.toLowerCase())) {
throw AssertionError(
"Expected exception containing '$expectedMessage' but raised exception was '$exception'"
)
}
}
true
}
if (!exceptionThrown) {
throw AssertionError("Expected exception but didn't get one")
}
return EnforceVerifyOrFail.Token
}
/**
* Asserts that [verifies] throws, with no condition on the exception message
*/
fun fails() = failsWith(null)
/**
* @see failsWith
*/
infix fun `fails with`(msg: String) = failsWith(msg)
}
/**
* This is the class the top-level primitives deal with. It delegates all other primitives to the contained interpreter.
* This way we have a decoupling of the DSL "AST" and the interpretation(s) of it. Note how the delegation forces
* covariance of the TransactionInterpreter parameter.
* This interface defines the bare bone functionality that a Ledger DSL interpreter should implement.
*
* TODO (Kotlin 1.1): Use type synonyms to make the type params less unwieldy
*/
class LedgerDSL<R, out T : TransactionDSLInterpreter<R>, out L : LedgerDSLInterpreter<R, T>> (val interpreter: L) :
LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>> by interpreter {
interface LedgerDSLInterpreter<out T : TransactionDSLInterpreter> : Verifies, OutputStateLookup {
/**
* Creates and adds a transaction to the ledger.
* @param transactionLabel Optional label of the transaction, to be used in diagnostic messages.
* @param transactionBuilder The base transactionBuilder that will be used to build the transaction.
* @param dsl The dsl that should be interpreted for building the transaction.
* @return The final [WireTransaction] of the built transaction.
*/
fun _transaction(transactionLabel: String?, transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<T>.() -> EnforceVerifyOrFail): WireTransaction
fun transaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R) =
transaction(null, dsl)
fun unverifiedTransaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> Unit) =
unverifiedTransaction(null, dsl)
/**
* Creates and adds a transaction to the ledger that will not be verified by [verifies].
* @param transactionLabel Optional label of the transaction, to be used in diagnostic messages.
* @param transactionBuilder The base transactionBuilder that will be used to build the transaction.
* @param dsl The dsl that should be interpreted for building the transaction.
* @return The final [WireTransaction] of the built transaction.
*/
fun _unverifiedTransaction(transactionLabel: String?, transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<T>.() -> Unit): WireTransaction
/**
* Creates a local scoped copy of the ledger.
* @param dsl The ledger DSL to be interpreted using the copy.
*/
fun tweak(dsl: LedgerDSL<T, LedgerDSLInterpreter<T>>.() -> Unit)
/**
* Adds an attachment to the ledger.
* @param attachment The [InputStream] defining the contents of the attachment.
* @return The [SecureHash] that identifies the attachment, to be used in transactions.
*/
fun attachment(attachment: InputStream): SecureHash
}
/**
* This is the class that defines the syntactic sugar of the ledger Test DSL and delegates to the contained interpreter,
* and what is actually used in `ledger { (...) }`. Add convenience functions here, or if you want to extend the DSL
* functionality then first add your primitive to [LedgerDSLInterpreter] and then add the convenience defaults/extension
* methods here.
*/
class LedgerDSL<out T : TransactionDSLInterpreter, out L : LedgerDSLInterpreter<T>> (val interpreter: L) :
LedgerDSLInterpreter<TransactionDSLInterpreter> by interpreter {
/**
* @see LedgerDSLInterpreter._transaction
*/
@JvmOverloads
fun transaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(),
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) =
_transaction(label, transactionBuilder, dsl)
/**
* @see LedgerDSLInterpreter._unverifiedTransaction
*/
@JvmOverloads
fun unverifiedTransaction(label: String? = null, transactionBuilder: TransactionBuilder = TransactionBuilder(),
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> Unit) =
_unverifiedTransaction(label, transactionBuilder, dsl)
/**
* @see OutputStateLookup.retrieveOutputStateAndRef
*/
inline fun <reified S : ContractState> String.outputStateAndRef(): StateAndRef<S> =
retrieveOutputStateAndRef(S::class.java, this)
inline fun <reified S : ContractState> String.output(): TransactionState<S> =
outputStateAndRef<S>().state
/**
* Retrieves the output [TransactionState] based on the label.
* @see OutputStateLookup.retrieveOutputStateAndRef
*/
inline fun <reified S : ContractState> String.output(): S =
outputStateAndRef<S>().state.data
/**
* Retrieves the output [StateRef] based on the label.
* @see OutputStateLookup.retrieveOutputStateAndRef
*/
fun String.outputRef(): StateRef = outputStateAndRef<ContractState>().ref
/**
* @see OutputStateLookup.retrieveOutputStateAndRef
*/
fun <S : ContractState> retrieveOutput(clazz: Class<S>, label: String) =
retrieveOutputStateAndRef(clazz, label).state.data
}

View File

@ -14,18 +14,49 @@ import java.security.KeyPair
import java.security.PublicKey
import java.util.*
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Here is a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
//
// Define a transaction like this:
//
// ledger {
// transaction {
// input { someExpression }
// output { someExpression }
// command { someExpression }
//
// tweak {
// ... same thing but works with a copy of the parent, can add inputs/outputs/commands just within this scope.
// }
//
// contract.verifies() -> verify() should pass
// contract `fails with` "some substring of the error message"
// }
// }
//
/**
* Here follows implementations of the [LedgerDSLInterpreter] and [TransactionDSLInterpreter] interfaces to be used in
* tests. Top level primitives [ledger] and [transaction] that bind the interpreter types are also defined here.
*/
/**
* @see JavaTestHelpers.transaction
*/
fun transaction(
transactionLabel: String? = null,
dsl: TransactionDSL<
EnforceVerifyOrFail,
TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> EnforceVerifyOrFail
) = JavaTestHelpers.transaction(transactionLabel, dsl)
transactionBuilder: TransactionBuilder = TransactionBuilder(),
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = JavaTestHelpers.transaction(transactionLabel, transactionBuilder, dsl)
/**
* @see JavaTestHelpers.ledger
*/
fun ledger(
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
storageService: StorageService = MockStorageService(),
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
) = JavaTestHelpers.ledger(identityService, storageService, dsl)
@Deprecated(
@ -33,8 +64,8 @@ fun ledger(
replaceWith = ReplaceWith("tweak"),
level = DeprecationLevel.ERROR)
@Suppress("UNUSED_PARAMETER")
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
fun TransactionDSLInterpreter.ledger(
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
}
@Deprecated(
@ -42,11 +73,8 @@ fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
replaceWith = ReplaceWith("tweak"),
level = DeprecationLevel.ERROR)
@Suppress("UNUSED_PARAMETER")
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
dsl: TransactionDSL<
EnforceVerifyOrFail,
TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> EnforceVerifyOrFail) {
fun TransactionDSLInterpreter.transaction(
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) {
}
@Deprecated(
@ -54,8 +82,8 @@ fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
replaceWith = ReplaceWith("tweak"),
level = DeprecationLevel.ERROR)
@Suppress("UNUSED_PARAMETER")
fun LedgerDSLInterpreter<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.ledger(
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
fun LedgerDSLInterpreter<TransactionDSLInterpreter>.ledger(
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
}
/**
@ -68,57 +96,56 @@ sealed class EnforceVerifyOrFail {
internal object Token: EnforceVerifyOrFail()
}
class DuplicateOutputLabel(label: String) : Exception("Output label '$label' already used")
class AttachmentResolutionException(attachmentId: SecureHash) : Exception("Attachment with id $attachmentId not found")
/**
* 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()
) : TransactionDSLInterpreter<EnforceVerifyOrFail>, OutputStateLookup by ledgerInterpreter {
val transactionBuilder: TransactionBuilder,
internal val labelToIndexMap: HashMap<String, Int>
) : TransactionDSLInterpreter, OutputStateLookup by ledgerInterpreter {
constructor(
ledgerInterpreter: TestLedgerDSLInterpreter,
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 (label in labelToIndexMap) {
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 {
@ -127,52 +154,18 @@ data class TestTransactionDSLInterpreter(
return EnforceVerifyOrFail.Token
}
override fun failsWith(expectedMessage: String?): EnforceVerifyOrFail {
val exceptionThrown = try {
this.verifies()
false
} catch (exception: Exception) {
if (expectedMessage != null) {
val exceptionMessage = exception.message
if (exceptionMessage == null) {
throw AssertionError(
"Expected exception containing '$expectedMessage' but raised exception had no message"
)
} else if (!exceptionMessage.toLowerCase().contains(expectedMessage.toLowerCase())) {
throw AssertionError(
"Expected exception containing '$expectedMessage' but raised exception was '$exception'"
)
}
}
true
}
if (!exceptionThrown) {
throw AssertionError("Expected exception but didn't get one")
}
return EnforceVerifyOrFail.Token
}
override fun tweak(
dsl: TransactionDSL<
EnforceVerifyOrFail,
TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> EnforceVerifyOrFail
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = dsl(TransactionDSL(copy()))
}
class AttachmentResolutionException(attachmentId: SecureHash) :
Exception("Attachment with id $attachmentId not found")
data class TestLedgerDSLInterpreter private constructor (
private val identityService: IdentityService,
private val storageService: StorageService,
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(),
private val nonVerifiedTransactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
) : LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter> {
) : LedgerDSLInterpreter<TestTransactionDSLInterpreter> {
val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
// We specify [labelToOutputStateAndRefs] just so that Kotlin picks the primary constructor instead of cycling
@ -181,19 +174,25 @@ data class TestLedgerDSLInterpreter private constructor (
)
companion object {
private fun getCallerLocation(offset: Int): String {
val stackTraceElement = Thread.currentThread().stackTrace[3 + offset]
return stackTraceElement.toString()
private fun getCallerLocation(): String? {
val stackTrace = Thread.currentThread().stackTrace
for (i in 1 .. stackTrace.size) {
val stackTraceElement = stackTrace[i]
if (!stackTraceElement.fileName.contains("DSL")) {
return stackTraceElement.toString()
}
}
return null
}
}
internal data class WireTransactionWithLocation(
val label: String?,
val transaction: WireTransaction,
val location: String
val location: String?
)
class VerifiesFailed(transactionLocation: String, cause: Throwable) :
Exception("Transaction defined at ($transactionLocation) didn't verify: $cause", cause)
class VerifiesFailed(transactionName: String, cause: Throwable) :
Exception("Transaction ($transactionName) didn't verify: $cause", cause)
class TypeMismatch(requested: Class<*>, actual: Class<*>) :
Exception("Actual type $actual is not a subtype of requested type $requested")
@ -242,10 +241,11 @@ data class TestLedgerDSLInterpreter private constructor (
internal fun resolveAttachment(attachmentId: SecureHash): Attachment =
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
private fun <Return> interpretTransactionDsl(
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Return
private fun <R> interpretTransactionDsl(
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> R
): 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?,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> R,
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> R,
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
): WireTransaction {
val transactionLocation = getCallerLocation(3)
val transactionInterpreter = interpretTransactionDsl(dsl)
val transactionLocation = getCallerLocation()
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 (label in labelToOutputStateAndRefs) {
throw DuplicateOutputLabel(label)
}
labelToOutputStateAndRefs[label] = wireTransaction.outRef(index)
}
transactionMap[wireTransaction.serialized.hash] =
@ -294,32 +296,38 @@ data class TestLedgerDSLInterpreter private constructor (
return wireTransaction
}
override fun transaction(
override fun _transaction(
transactionLabel: String?,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations)
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, transactionWithLocations)
override fun unverifiedTransaction(
override fun _unverifiedTransaction(
transactionLabel: String?,
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Unit
) = recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations)
transactionBuilder: TransactionBuilder,
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> Unit
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, nonVerifiedTransactionWithLocations)
override fun tweak(
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter,
LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter>>.() -> Unit) =
dsl: LedgerDSL<TestTransactionDSLInterpreter,
LedgerDSLInterpreter<TestTransactionDSLInterpreter>>.() -> Unit) =
dsl(LedgerDSL(copy()))
override fun attachment(attachment: InputStream): SecureHash {
return storageService.attachments.importAttachment(attachment)
}
override fun verifies() {
override fun verifies(): EnforceVerifyOrFail {
val transactionGroup = toTransactionGroup()
try {
transactionGroup.verify()
} catch (exception: TransactionVerificationException) {
throw VerifiesFailed(transactionWithLocations[exception.tx.origHash]?.location ?: "<unknown>", exception)
val transactionWithLocation = transactionWithLocations[exception.tx.origHash]
val transactionName = transactionWithLocation?.label ?: transactionWithLocation?.location ?: "<unknown>"
throw VerifiesFailed(transactionName, exception)
}
return EnforceVerifyOrFail.Token
}
override fun <S : ContractState> retrieveOutputStateAndRef(clazz: Class<S>, label: String): StateAndRef<S> {
@ -335,13 +343,19 @@ data class TestLedgerDSLInterpreter private constructor (
}
}
/**
* Signs all transactions passed in.
* @param transactionsToSign Transactions to be signed.
* @param extraKeys extra keys to sign transactions with.
* @return List of [SignedTransaction]s.
*/
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyPair>) = transactionsToSign.map { wtx ->
val allPubKeys = wtx.signers.toMutableSet()
val bits = wtx.serialize()
require(bits == wtx.serialized)
val signatures = ArrayList<DigitalSignature.WithKey>()
for (key in ALL_TEST_KEYS + extraKeys) {
if (allPubKeys.contains(key.public)) {
if (key.public in allPubKeys) {
signatures += key.signWithECDSA(bits)
allPubKeys -= key.public
}
@ -349,6 +363,10 @@ fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyP
SignedTransaction(bits, signatures)
}
fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll(
transactionsToSign: List<WireTransaction> = this.interpreter.wireTransactions, vararg extraKeys: KeyPair) =
signAll(transactionsToSign, extraKeys)
/**
* Signs all transactions in the ledger.
* @param extraKeys extra keys to sign transactions with.
* @return List of [SignedTransaction]s.
*/
fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll(
vararg extraKeys: KeyPair) = signAll(this.interpreter.wireTransactions, extraKeys)

View File

@ -15,26 +15,6 @@ import java.security.KeyPair
import java.security.PublicKey
import java.time.Instant
/** If an exception is thrown by the body, rethrows the root cause exception. */
inline fun <R> rootCauseExceptions(body: () -> R): R {
try {
return body()
} catch(e: Exception) {
throw Throwables.getRootCause(e)
}
}
fun freeLocalHostAndPort(): HostAndPort {
val freePort = ServerSocket(0).use { it.localPort }
return HostAndPort.fromParts("localhost", freePort)
}
object TestUtils {
val keypair = generateKeyPair()
val keypair2 = generateKeyPair()
val keypair3 = generateKeyPair()
}
/**
* JAVA INTEROP. Please keep the following points in mind when extending the Kotlin DSL
*
@ -58,18 +38,22 @@ object JavaTestHelpers {
@JvmStatic val TEST_TX_TIME: Instant get() = Instant.parse("2015-04-17T12:00:00.00Z")
// A few dummy values for testing.
@JvmStatic val MEGA_CORP_KEY: KeyPair get() = TestUtils.keypair
@JvmStatic val MEGA_CORP_KEY: KeyPair by lazy { generateKeyPair() }
@JvmStatic val MEGA_CORP_PUBKEY: PublicKey get() = MEGA_CORP_KEY.public
@JvmStatic val MINI_CORP_KEY: KeyPair get() = TestUtils.keypair2
@JvmStatic val MINI_CORP_KEY: KeyPair by lazy { generateKeyPair() }
@JvmStatic val MINI_CORP_PUBKEY: PublicKey get() = MINI_CORP_KEY.public
@JvmStatic val ORACLE_KEY: KeyPair get() = TestUtils.keypair3
@JvmStatic val ORACLE_KEY: KeyPair by lazy { generateKeyPair() }
@JvmStatic val ORACLE_PUBKEY: PublicKey get() = ORACLE_KEY.public
@JvmStatic val DUMMY_PUBKEY_1: PublicKey get() = DummyPublicKey("x1")
@JvmStatic val DUMMY_PUBKEY_2: PublicKey get() = DummyPublicKey("x2")
@JvmStatic val DUMMY_KEY_1: KeyPair by lazy { generateKeyPair() }
@JvmStatic val DUMMY_KEY_2: KeyPair by lazy { generateKeyPair() }
@JvmStatic val DUMMY_KEY_3: KeyPair by lazy { generateKeyPair() }
@JvmStatic val ALICE_KEY: KeyPair by lazy { generateKeyPair() }
@JvmStatic val ALICE_PUBKEY: PublicKey get() = ALICE_KEY.public
@JvmStatic val ALICE: Party get() = Party("Alice", ALICE_PUBKEY)
@ -90,23 +74,45 @@ object JavaTestHelpers {
@JvmStatic fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
/** If an exception is thrown by the body, rethrows the root cause exception. */
@JvmStatic inline fun <R> rootCauseExceptions(body: () -> R): R {
try {
return body()
} catch(e: Exception) {
throw Throwables.getRootCause(e)
}
}
@JvmStatic fun freeLocalHostAndPort(): HostAndPort {
val freePort = ServerSocket(0).use { it.localPort }
return HostAndPort.fromParts("localhost", freePort)
}
/**
* Creates and tests a ledger built by the passed in dsl.
* @param identityService The [IdentityService] to be used while building the ledger.
* @param storageService The [StorageService] to be used for storing e.g. [Attachment]s.
* @param dsl The dsl building the ledger.
*/
@JvmStatic @JvmOverloads fun ledger(
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
storageService: StorageService = MockStorageService(),
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService))
dsl(ledgerDsl)
return ledgerDsl
}
/**
* Creates a ledger with a single transaction, built by the passed in dsl.
* @see LedgerDSLInterpreter._transaction
*/
@JvmStatic @JvmOverloads fun transaction(
transactionLabel: String? = null,
dsl: TransactionDSL<
EnforceVerifyOrFail,
TransactionDSLInterpreter<EnforceVerifyOrFail>
>.() -> EnforceVerifyOrFail
) = ledger { transaction(transactionLabel, dsl) }
transactionBuilder: TransactionBuilder = TransactionBuilder(),
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
) = ledger { this.transaction(transactionLabel, transactionBuilder, dsl) }
}
val TEST_TX_TIME = JavaTestHelpers.TEST_TX_TIME
@ -118,6 +124,9 @@ val ORACLE_KEY = JavaTestHelpers.ORACLE_KEY
val ORACLE_PUBKEY = JavaTestHelpers.ORACLE_PUBKEY
val DUMMY_PUBKEY_1 = JavaTestHelpers.DUMMY_PUBKEY_1
val DUMMY_PUBKEY_2 = JavaTestHelpers.DUMMY_PUBKEY_2
val DUMMY_KEY_1 = JavaTestHelpers.DUMMY_KEY_1
val DUMMY_KEY_2 = JavaTestHelpers.DUMMY_KEY_2
val DUMMY_KEY_3 = JavaTestHelpers.DUMMY_KEY_3
val ALICE_KEY = JavaTestHelpers.ALICE_KEY
val ALICE_PUBKEY = JavaTestHelpers.ALICE_PUBKEY
val ALICE = JavaTestHelpers.ALICE
@ -132,12 +141,5 @@ val ALL_TEST_KEYS = JavaTestHelpers.ALL_TEST_KEYS
val MOCK_IDENTITY_SERVICE = JavaTestHelpers.MOCK_IDENTITY_SERVICE
fun generateStateRef() = JavaTestHelpers.generateStateRef()
class LabeledOutput(val label: String?, val state: TransactionState<*>) {
override fun toString() = state.toString() + (if (label != null) " ($label)" else "")
override fun equals(other: Any?) = other is LabeledOutput && state.equals(other.state)
override fun hashCode(): Int = state.hashCode()
}
infix fun TransactionState<*>.label(label: String) = LabeledOutput(label, this)
fun freeLocalHostAndPort() = JavaTestHelpers.freeLocalHostAndPort()
inline fun <R> rootCauseExceptions(body: () -> R) = JavaTestHelpers.rootCauseExceptions(body)

View File

@ -5,84 +5,115 @@ import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.seconds
import java.security.PublicKey
import java.time.Duration
import java.time.Instant
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Defines a simple DSL for building pseudo-transactions (not the same as the wire protocol) for testing purposes.
//
// Define a transaction like this:
//
// ledger {
// transaction {
// input { someExpression }
// output { someExpression }
// command { someExpression }
//
// tweak {
// ... same thing but works with a copy of the parent, can add inputs/outputs/commands just within this scope.
// }
//
// contract.verifies() -> verify() should pass
// contract `fails with` "some substring of the error message"
// }
// }
//
/**
* The [TransactionDSLInterpreter] defines the interface DSL interpreters should satisfy. No
* overloading/default valuing should be done here, only the basic functions that are required to implement everything.
* Same goes for functions requiring reflection e.g. [OutputStateLookup.retrieveOutputStateAndRef]
* Put convenience functions in [TransactionDSL] instead. There are some cases where the overloads would clash with the
* Interpreter interface, in these cases define a "backing" function in the interface instead (e.g. [_command]).
*
* This way the responsibility of providing a nice frontend DSL and the implementation(s) are separated.
* This interface defines the bare bone functionality that a Transaction DSL interpreter should implement.
* @param <R> The return type of [verifies]/[failsWith] and the like. It is generic so that we have control over whether
* we want to enforce users to call these methods (@see [EnforceVerifyOrFail]) or not.
*/
interface TransactionDSLInterpreter<R> : OutputStateLookup {
val ledgerInterpreter: LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>>
interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
/**
* A reference to the enclosing ledger{..}'s interpreter.
*/
val ledgerInterpreter: LedgerDSLInterpreter<TransactionDSLInterpreter>
/**
* Adds an input reference to the transaction. Note that [verifies] will resolve this reference.
* @param stateRef The input [StateRef].
*/
fun input(stateRef: StateRef)
/**
* Adds an output to the transaction.
* @param label An optional label that may be later used to retrieve the output probably in other transactions.
* @param notary The associated notary.
* @param contractState The state itself.
*/
fun _output(label: String?, notary: Party, contractState: ContractState)
/**
* Adds an [Attachment] reference to the transaction.
* @param attachmentId The hash of the attachment, possibly returned by [LedgerDSLInterpreter.attachment]
*/
fun attachment(attachmentId: SecureHash)
/**
* Adds a command to the transaction.
* @param signers The signer public keys.
* @param commandData The contents of the command.
*/
fun _command(signers: List<PublicKey>, commandData: CommandData)
fun verifies(): R
fun failsWith(expectedMessage: String?): R
fun tweak(
dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R
): R
/**
* Creates a local scoped copy of the transaction.
* @param dsl The transaction DSL to be interpreted using the copy.
*/
fun tweak(dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail): EnforceVerifyOrFail
}
class TransactionDSL<R, out T : TransactionDSLInterpreter<R>> (val interpreter: T) :
TransactionDSLInterpreter<R> by interpreter {
class TransactionDSL<out T : TransactionDSLInterpreter> (val interpreter: T) :
TransactionDSLInterpreter by interpreter {
fun input(stateLabel: String) = input(retrieveOutputStateAndRef(ContractState::class.java, stateLabel).ref)
/**
* Adds the passed in state as a non-verified transaction output to the ledger and adds that as an input.
* Looks up the output label and adds the found state as an input.
* @param stateLabel The label of the output state specified when calling [TransactionDSLInterpreter._output] and friends.
*/
fun input(stateLabel: String) = input(retrieveOutputStateAndRef(ContractState::class.java, stateLabel).ref)
/**
* Creates an [LedgerDSLInterpreter._unverifiedTransaction] with a single output state and adds it's reference as an
* input to the current transaction.
* @param state The state to be added.
*/
fun input(state: ContractState) {
val transaction = ledgerInterpreter.unverifiedTransaction(null) {
val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder()) {
output { state }
}
input(transaction.outRef<ContractState>(0).ref)
}
fun input(stateClosure: () -> ContractState) = input(stateClosure())
/**
* @see TransactionDSLInterpreter._output
*/
@JvmOverloads
fun output(label: String? = null, notary: Party = DUMMY_NOTARY, contractStateClosure: () -> ContractState) =
_output(label, notary, contractStateClosure())
@JvmOverloads
fun output(label: String? = null, contractState: ContractState) =
/**
* @see TransactionDSLInterpreter._output
*/
fun output(label: String, contractState: ContractState) =
_output(label, DUMMY_NOTARY, contractState)
fun output(contractState: ContractState) =
_output(null, DUMMY_NOTARY, contractState)
/**
* @see TransactionDSLInterpreter._command
*/
fun command(vararg signers: PublicKey, commandDataClosure: () -> CommandData) =
_command(listOf(*signers), commandDataClosure())
/**
* @see TransactionDSLInterpreter._command
*/
fun command(signer: PublicKey, commandData: CommandData) = _command(listOf(signer), commandData)
/**
* Adds a timestamp command to the transaction.
* @param time The [Instant] of the [TimestampCommand].
* @param tolerance The tolerance of the [TimestampCommand].
* @param notary The notary to sign the command.
*/
@JvmOverloads
fun timestamp(time: Instant, notary: PublicKey = DUMMY_NOTARY.owningKey) =
timestamp(TimestampCommand(time, 30.seconds), notary)
fun timestamp(time: Instant, tolerance: Duration = 30.seconds, notary: PublicKey = DUMMY_NOTARY.owningKey) =
timestamp(TimestampCommand(time, tolerance), notary)
/**
* Adds a timestamp command to the transaction.
* @param data The [TimestampCommand].
* @param notary The notary to sign the command.
*/
@JvmOverloads
fun timestamp(data: TimestampCommand, notary: PublicKey = DUMMY_NOTARY.owningKey) = command(notary, data)
fun fails() = failsWith(null)
infix fun `fails with`(msg: String) = failsWith(msg)
}

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

@ -47,7 +47,7 @@ class TransactionSerializationTests {
val fakeStateRef = generateStateRef()
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
val outputState = TransactionState(TestCash.State(depositRef, 600.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, TestUtils.keypair.public), DUMMY_NOTARY)
val changeState = TransactionState(TestCash.State(depositRef, 400.POUNDS, DUMMY_KEY_1.public), DUMMY_NOTARY)
lateinit var tx: TransactionBuilder
@ -55,14 +55,14 @@ class TransactionSerializationTests {
@Before
fun setup() {
tx = TransactionType.General.Builder().withItems(
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(TestUtils.keypair.public))
inputState, outputState, changeState, Command(TestCash.Commands.Move(), arrayListOf(DUMMY_KEY_1.public))
)
}
@Test
fun signWireTX() {
tx.signWith(DUMMY_NOTARY_KEY)
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_KEY_1)
val signedTX = tx.toSignedTransaction()
// Now check that the signature we just made verifies.
@ -82,7 +82,7 @@ class TransactionSerializationTests {
tx.toSignedTransaction()
}
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_KEY_1)
tx.signWith(DUMMY_NOTARY_KEY)
val signedTX = tx.toSignedTransaction()
@ -94,9 +94,9 @@ class TransactionSerializationTests {
// If the signature was replaced in transit, we don't like it.
assertFailsWith(SignatureException::class) {
val tx2 = TransactionType.General.Builder().withItems(inputState, outputState, changeState,
Command(TestCash.Commands.Move(), TestUtils.keypair2.public))
Command(TestCash.Commands.Move(), DUMMY_KEY_2.public))
tx2.signWith(DUMMY_NOTARY_KEY)
tx2.signWith(TestUtils.keypair2)
tx2.signWith(DUMMY_KEY_2)
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
}
@ -105,7 +105,7 @@ class TransactionSerializationTests {
@Test
fun timestamp() {
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
tx.signWith(TestUtils.keypair)
tx.signWith(DUMMY_KEY_1)
tx.signWith(DUMMY_NOTARY_KEY)
val stx = tx.toSignedTransaction()
val ltx = stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments)

View File

@ -39,6 +39,7 @@ Read on to learn:
where-to-start
tutorial-contract
tutorial-test-dsl
protocol-state-machines
oracles
event-scheduling

View File

@ -0,0 +1,557 @@
.. highlight:: kotlin
.. role:: kotlin(code)
:language: kotlin
.. raw:: html
<script type="text/javascript" src="_static/jquery.js"></script>
<script type="text/javascript" src="_static/codesets.js"></script>
Writing a contract test
=======================
This tutorial will take you through the steps required to write a contract test using Kotlin and/or Java.
The testing DSL allows one to define a piece of the ledger with transactions referring to each other, and ways of
verifying their correctness.
Testing single transactions
---------------------------
We start with the empty ledger:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun emptyLedger() {
ledger {
}
}
.. sourcecode:: java
import static com.r3corda.core.testing.JavaTestHelpers.*;
import static com.r3corda.core.contracts.JavaTestHelpers.*;
@Test
public void emptyLedger() {
ledger(l -> {
return Unit.INSTANCE; // We need to return this explicitly
});
}
The DSL keyword ``ledger`` takes a closure that can build up several transactions and may verify their overall
correctness.
Let's add a Cash transaction:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun simpleCashDoesntCompile() {
val inState = Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = DUMMY_PUBKEY_1
)
ledger {
transaction {
input(inState)
}
}
}
.. sourcecode:: java
@Test
public void simpleCashDoesntCompile() {
Cash.State inState = new Cash.State(
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
getDUMMY_PUBKEY_1()
);
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
});
return Unit.INSTANCE;
});
}
We can add a transaction to the ledger using the ``transaction`` primitive. The transaction in turn may be defined by
specifying ``input``-s, ``output``-s, ``command``-s and ``attachment``-s.
The above ``input`` call is a bit special: Transactions don't actually contain input states, just references
to output states of other transactions. Under the hood the above ``input`` call creates a dummy transaction in the
ledger (that won't be verified) which outputs the specified state, and references that from this transaction.
The above code however doesn't compile:
.. container:: codeset
.. sourcecode:: kotlin
Error:(26, 21) Kotlin: Type mismatch: inferred type is Unit but EnforceVerifyOrFail was expected
.. sourcecode:: java
Error:(26, 31) java: incompatible types: bad return type in lambda expression missing return value
This is deliberate: The DSL forces us to specify either ``this.verifies()`` or ``this `fails with` "some text"`` on the
last line of ``transaction``:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun simpleCash() {
val inState = Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = DUMMY_PUBKEY_1
)
ledger {
transaction {
input(inState)
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void simpleCash() {
Cash.State inState = new Cash.State(
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
getDUMMY_PUBKEY_1()
);
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
return tx.verifies();
});
return Unit.INSTANCE;
});
}
The code finally compiles. When run, it produces the following error::
com.r3corda.core.contracts.TransactionVerificationException$ContractRejection: java.lang.IllegalArgumentException: Failed requirement: for deposit [0101] at issuer MegaCorp the amounts balance
The transaction verification failed, because the sum of inputs does not equal the sum of outputs. We can specify that
this is intended behaviour by changing ``this.verifies()`` to ``this `fails with` "the amounts balance"``:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun simpleCashFailsWith() {
val inState = Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = DUMMY_PUBKEY_1
)
ledger {
transaction {
input(inState)
this `fails with` "the amounts balance"
}
}
}
.. sourcecode:: java
@Test
public void simpleCashFailsWith() {
Cash.State inState = new Cash.State(
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
getDUMMY_PUBKEY_1()
);
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
return tx.failsWith("the amounts balance");
});
return Unit.INSTANCE;
});
}
We can continue to build the transaction until it ``verifies``:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun simpleCashSuccess() {
val inState = Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = DUMMY_PUBKEY_1
)
ledger {
transaction {
input(inState)
this `fails with` "the amounts balance"
output(inState.copy(owner = DUMMY_PUBKEY_2))
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void simpleCashSuccess() {
Cash.State inState = new Cash.State(
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
getDUMMY_PUBKEY_1()
);
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
tx.failsWith("the amounts balance");
tx.output(inState.copy(inState.getAmount(), getDUMMY_PUBKEY_2()));
tx.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
``output`` specifies that we want the input state to be transferred to ``DUMMY_PUBKEY_2`` and ``command`` adds the
``Move`` command itself, signed by the current owner of the input state, ``DUMMY_PUBKEY_1``.
We constructed a complete signed cash transaction from ``DUMMY_PUBKEY_1`` to ``DUMMY_PUBKEY_2`` and verified it. Note
how we left in the ``fails with`` line - this is fine, the failure will be tested on the partially constructed
transaction.
What should we do if we wanted to test what happens when the wrong party signs the transaction? If we simply add a
``command`` it will ruin the transaction for good... Enter ``tweak``:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun simpleCashTweakSuccess() {
val inState = Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = DUMMY_PUBKEY_1
)
ledger {
transaction {
input(inState)
this `fails with` "the amounts balance"
output(inState.copy(owner = DUMMY_PUBKEY_2))
tweak {
command(DUMMY_PUBKEY_2) { Cash.Commands.Move() }
this `fails with` "the owning keys are the same as the signing keys"
}
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void simpleCashTweakSuccess() {
Cash.State inState = new Cash.State(
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
getDUMMY_PUBKEY_1()
);
ledger(l -> {
l.transaction(tx -> {
tx.input(inState);
tx.failsWith("the amounts balance");
tx.output(inState.copy(inState.getAmount(), getDUMMY_PUBKEY_2()));
tx.tweak(tw -> {
tw.command(getDUMMY_PUBKEY_2(), new Cash.Commands.Move());
return tw.failsWith("the owning keys are the same as the signing keys");
});
tx.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
``tweak`` creates a local copy of the transaction. This allows the local "ruining" of the transaction allowing testing
of different error conditions.
We now have a neat little test that tests a single transaction. This is already useful, and in fact testing of a single
transaction in this way is very common. There is even a shorthand toplevel ``transaction`` primitive that creates a
ledger with a single transaction:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun simpleCashTweakSuccessTopLevelTransaction() {
val inState = Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = DUMMY_PUBKEY_1
)
transaction {
input(inState)
this `fails with` "the amounts balance"
output(inState.copy(owner = DUMMY_PUBKEY_2))
tweak {
command(DUMMY_PUBKEY_2) { Cash.Commands.Move() }
this `fails with` "the owning keys are the same as the signing keys"
}
command(DUMMY_PUBKEY_1) { Cash.Commands.Move() }
this.verifies()
}
}
.. sourcecode:: java
@Test
public void simpleCashTweakSuccessTopLevelTransaction() {
Cash.State inState = new Cash.State(
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
getDUMMY_PUBKEY_1()
);
transaction(tx -> {
tx.input(inState);
tx.failsWith("the amounts balance");
tx.output(inState.copy(inState.getAmount(), getDUMMY_PUBKEY_2()));
tx.tweak(tw -> {
tw.command(getDUMMY_PUBKEY_2(), new Cash.Commands.Move());
return tw.failsWith("the owning keys are the same as the signing keys");
});
tx.command(getDUMMY_PUBKEY_1(), new Cash.Commands.Move());
return tx.verifies();
});
}
Chaining transactions
---------------------
Now that we know how to define a single transaction, let's look at how to define a chain of them:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun chainCash() {
ledger {
unverifiedTransaction {
output("MEGA_CORP cash") {
Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MEGA_CORP_PUBKEY
)
}
}
transaction {
input("MEGA_CORP cash")
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void chainCash() {
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output("MEGA_CORP cash",
new Cash.State(
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
getMEGA_CORP_PUBKEY()
)
);
return Unit.INSTANCE;
});
l.transaction(tx -> {
tx.input("MEGA_CORP cash");
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_1()));
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
In this example we declare that ``MEGA_CORP`` has a thousand dollars but we don't care where from, for this we can use
``unverifiedTransaction``. Note how we don't need to specify ``this.verifies()``.
The ``output`` cash was labelled with ``"MEGA_CORP cash"``, we can subsequently referred to this other transactions, e.g.
by ``input("MEGA_CORP cash")`` or ``"MEGA_CORP cash".output<Cash.State>()``.
What happens if we reuse the output cash twice?
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun chainCashDoubleSpend() {
ledger {
unverifiedTransaction {
output("MEGA_CORP cash") {
Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MEGA_CORP_PUBKEY
)
}
}
transaction {
input("MEGA_CORP cash")
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this.verifies()
}
transaction {
input("MEGA_CORP cash")
// We send it to another pubkey so that the transaction is not identical to the previous one
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_2))
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this.verifies()
}
}
}
.. sourcecode:: java
@Test
public void chainCashDoubleSpend() {
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output("MEGA_CORP cash",
new Cash.State(
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
getMEGA_CORP_PUBKEY()
)
);
return Unit.INSTANCE;
});
l.transaction(tx -> {
tx.input("MEGA_CORP cash");
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_1()));
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
return tx.verifies();
});
l.transaction(tx -> {
tx.input("MEGA_CORP cash");
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
// We send it to another pubkey so that the transaction is not identical to the previous one
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_2()));
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
return tx.verifies();
});
return Unit.INSTANCE;
});
}
The transactions ``verifies()`` individually, however the state was spent twice!
We can also verify the complete ledger by calling ``verifies``/``fails`` on the ledger level. We can also use
``tweak`` to create a local copy of the whole ledger:
.. container:: codeset
.. sourcecode:: kotlin
@Test
fun chainCashDoubleSpendFailsWith() {
ledger {
unverifiedTransaction {
output("MEGA_CORP cash") {
Cash.State(
amount = 1000.DOLLARS `issued by` MEGA_CORP.ref(1, 1),
owner = MEGA_CORP_PUBKEY
)
}
}
transaction {
input("MEGA_CORP cash")
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this.verifies()
}
tweak {
transaction {
input("MEGA_CORP cash")
// We send it to another pubkey so that the transaction is not identical to the previous one
output("MEGA_CORP cash".output<Cash.State>().copy(owner = DUMMY_PUBKEY_1))
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
this.verifies()
}
this.fails()
}
this.verifies()
}
}
.. sourcecode:: java
@Test
public void chainCashDoubleSpendFailsWith() {
ledger(l -> {
l.unverifiedTransaction(tx -> {
tx.output("MEGA_CORP cash",
new Cash.State(
issuedBy(DOLLARS(1000), getMEGA_CORP().ref((byte)1, (byte)1)),
getMEGA_CORP_PUBKEY()
)
);
return Unit.INSTANCE;
});
l.transaction(tx -> {
tx.input("MEGA_CORP cash");
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_1()));
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
return tx.verifies();
});
l.tweak(lw -> {
lw.transaction(tx -> {
tx.input("MEGA_CORP cash");
Cash.State inputCash = l.retrieveOutput(Cash.State.class, "MEGA_CORP cash");
// We send it to another pubkey so that the transaction is not identical to the previous one
tx.output(inputCash.copy(inputCash.getAmount(), getDUMMY_PUBKEY_2()));
tx.command(getMEGA_CORP_PUBKEY(), new Cash.Commands.Move());
return tx.verifies();
});
lw.fails();
return Unit.INSTANCE;
});
l.verifies();
return Unit.INSTANCE;
});
}

View File

@ -88,7 +88,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
// Nothing to do
}
override fun generateKeyPair(): KeyPair? = keyPair ?: super.generateKeyPair()
override fun generateKeyPair(): KeyPair = keyPair ?: super.generateKeyPair()
// It's OK to not have a network map service in the mock network.
override fun noNetworkMapConfigured() = Futures.immediateFuture(Unit)

View File

@ -17,7 +17,6 @@ import com.r3corda.core.node.services.ServiceType
import com.r3corda.core.node.services.TransactionStorage
import com.r3corda.core.node.services.Wallet
import com.r3corda.core.random63BitValue
import com.r3corda.core.seconds
import com.r3corda.core.testing.*
import com.r3corda.core.utilities.BriefLogFormatter
import com.r3corda.node.internal.testing.MockNetwork
@ -366,7 +365,7 @@ class TwoPartyTradeProtocolTests {
}
}
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError(
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.runWithError(
bobError: Boolean,
aliceError: Boolean,
expectedMessageSubstring: String
@ -431,7 +430,7 @@ class TwoPartyTradeProtocolTests {
return signed.associateBy { it.id }
}
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
withError: Boolean,
owner: PublicKey = BOB_PUBKEY,
issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair<Wallet, List<WireTransaction>> {
@ -472,7 +471,7 @@ class TwoPartyTradeProtocolTests {
return Pair(wallet, listOf(eb1, bc1, bc2))
}
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
withError: Boolean,
owner: PublicKey,
amount: Amount<Issued<Currency>>,
@ -484,7 +483,7 @@ class TwoPartyTradeProtocolTests {
}
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
if (!withError)
command(notary.owningKey) { TimestampCommand(TEST_TX_TIME, 30.seconds) }
timestamp(time = TEST_TX_TIME, notary = notary.owningKey)
if (attachmentID != null)
attachment(attachmentID)
if (withError) {

View File

@ -9,7 +9,7 @@ import org.graphstream.graph.Node
import org.graphstream.graph.implementations.SingleGraph
import kotlin.reflect.memberProperties
class GraphVisualiser(val dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>) {
class GraphVisualiser(val dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>) {
companion object {
val css = GraphVisualiser::class.java.getResourceAsStream("graph.css").bufferedReader().readText()
}

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)