mirror of
https://github.com/corda/corda.git
synced 2025-05-07 02:58:36 +00:00
commit
ae2e6ab917
@ -1,15 +1,10 @@
|
|||||||
package com.r3corda.contracts.testing
|
package com.r3corda.contracts.testing
|
||||||
|
|
||||||
import com.r3corda.contracts.*
|
import com.r3corda.contracts.*
|
||||||
import com.r3corda.contracts.asset.CASH_PROGRAM_ID
|
|
||||||
import com.r3corda.contracts.asset.Cash
|
import com.r3corda.contracts.asset.Cash
|
||||||
import com.r3corda.contracts.asset.Obligation
|
import com.r3corda.contracts.asset.Obligation
|
||||||
import com.r3corda.core.contracts.Amount
|
import com.r3corda.core.contracts.Amount
|
||||||
import com.r3corda.core.contracts.Contract
|
|
||||||
import com.r3corda.core.contracts.ContractState
|
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.PartyAndReference
|
||||||
import com.r3corda.core.contracts.Issued
|
import com.r3corda.core.contracts.Issued
|
||||||
import com.r3corda.core.contracts.TransactionState
|
import com.r3corda.core.contracts.TransactionState
|
||||||
@ -23,42 +18,10 @@ import java.security.PublicKey
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
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 {
|
object JavaTestHelpers {
|
||||||
@JvmStatic fun ownedBy(state: Cash.State, owner: PublicKey) = state.copy(owner = owner)
|
@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, 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<Issued<Currency>>(state.amount.quantity, state.issuanceDef.copy(issuer = deposit)))
|
@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 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)))
|
@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 ownedBy(state: CommercialPaper.State, owner: PublicKey) = state.copy(owner = owner)
|
||||||
@JvmStatic fun withNotary(state: CommercialPaper.State, notary: Party) = TransactionState(state, notary)
|
@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 withNotary(state: ContractState, notary: Party) = TransactionState(state, notary)
|
||||||
|
|
||||||
@JvmStatic fun CASH(amount: Amount<Currency>) = Cash.State(
|
@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)
|
NullPublicKey)
|
||||||
@JvmStatic fun STATE(amount: Amount<Issued<Currency>>) = Cash.State(amount, 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)
|
OBLIGATION_DEF(amount.token), amount.quantity, NullPublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
infix fun Cash.State.`owned by`(owner: PublicKey) = JavaTestHelpers.ownedBy(this, owner)
|
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`(party: Party) = JavaTestHelpers.issuedBy(this, party)
|
||||||
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = JavaTestHelpers.issuedBy(this, deposit)
|
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = JavaTestHelpers.issuedBy(this, deposit)
|
||||||
|
@ -14,7 +14,7 @@ import static com.r3corda.contracts.testing.JavaTestHelpers.*;
|
|||||||
*/
|
*/
|
||||||
public class CashTestsJava {
|
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 PartyAndReference defaultIssuer = getMEGA_CORP().ref(defaultRef);
|
||||||
private Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), getDUMMY_PUBKEY_1());
|
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());
|
private Cash.State outState = new Cash.State(inState.getAmount(), getDUMMY_PUBKEY_2());
|
||||||
|
@ -85,7 +85,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
input("paper")
|
input("paper")
|
||||||
input("alice's $900")
|
input("alice's $900")
|
||||||
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
|
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(ALICE_PUBKEY) { Cash.Commands.Move() }
|
||||||
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
|
||||||
this.verifies()
|
this.verifies()
|
||||||
@ -97,7 +97,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
input("alice's paper")
|
input("alice's paper")
|
||||||
input("some profits")
|
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("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
|
||||||
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
|
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ class CommercialPaperTestsGeneric {
|
|||||||
timestamp(TEST_TX_TIME + 8.days)
|
timestamp(TEST_TX_TIME + 8.days)
|
||||||
|
|
||||||
tweak {
|
tweak {
|
||||||
output { "paper".output<ICommercialPaperState>().data }
|
output { "paper".output<ICommercialPaperState>() }
|
||||||
this `fails with` "must be destroyed"
|
this `fails with` "must be destroyed"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,7 +360,7 @@ class IRSTests {
|
|||||||
/**
|
/**
|
||||||
* Generates a typical transactional history for an IRS.
|
* 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 ld = LocalDate.of(2016, 3, 8)
|
||||||
val bd = BigDecimal("0.0063518")
|
val bd = BigDecimal("0.0063518")
|
||||||
@ -377,11 +377,11 @@ class IRSTests {
|
|||||||
input("irs post agreement")
|
input("irs post agreement")
|
||||||
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
|
val postAgreement = "irs post agreement".output<InterestRateSwap.State>()
|
||||||
output("irs post first fixing") {
|
output("irs post first fixing") {
|
||||||
postAgreement.data.copy(
|
postAgreement.copy(
|
||||||
postAgreement.data.fixedLeg,
|
postAgreement.fixedLeg,
|
||||||
postAgreement.data.floatingLeg,
|
postAgreement.floatingLeg,
|
||||||
postAgreement.data.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
postAgreement.calculation.applyFixing(ld, FixedRate(RatioUnit(bd))),
|
||||||
postAgreement.data.common
|
postAgreement.common
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
command(ORACLE_PUBKEY) {
|
command(ORACLE_PUBKEY) {
|
||||||
@ -653,7 +653,7 @@ class IRSTests {
|
|||||||
* result and the grouping won't work either.
|
* 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.
|
* 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 ld1 = LocalDate.of(2016, 3, 8)
|
||||||
val bd1 = BigDecimal("0.0063518")
|
val bd1 = BigDecimal("0.0063518")
|
||||||
|
|
||||||
@ -693,20 +693,20 @@ class IRSTests {
|
|||||||
input("irs post agreement2")
|
input("irs post agreement2")
|
||||||
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
|
val postAgreement1 = "irs post agreement1".output<InterestRateSwap.State>()
|
||||||
output("irs post first fixing1") {
|
output("irs post first fixing1") {
|
||||||
postAgreement1.data.copy(
|
postAgreement1.copy(
|
||||||
postAgreement1.data.fixedLeg,
|
postAgreement1.fixedLeg,
|
||||||
postAgreement1.data.floatingLeg,
|
postAgreement1.floatingLeg,
|
||||||
postAgreement1.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
postAgreement1.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||||
postAgreement1.data.common.copy(tradeID = "t1")
|
postAgreement1.common.copy(tradeID = "t1")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
|
val postAgreement2 = "irs post agreement2".output<InterestRateSwap.State>()
|
||||||
output("irs post first fixing2") {
|
output("irs post first fixing2") {
|
||||||
postAgreement2.data.copy(
|
postAgreement2.copy(
|
||||||
postAgreement2.data.fixedLeg,
|
postAgreement2.fixedLeg,
|
||||||
postAgreement2.data.floatingLeg,
|
postAgreement2.floatingLeg,
|
||||||
postAgreement2.data.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
postAgreement2.calculation.applyFixing(ld1, FixedRate(RatioUnit(bd1))),
|
||||||
postAgreement2.data.common.copy(tradeID = "t2")
|
postAgreement2.common.copy(tradeID = "t2")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import com.r3corda.contracts.testing.*
|
|||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.testing.*
|
import com.r3corda.core.testing.*
|
||||||
import com.r3corda.core.testing.JavaTestHelpers
|
|
||||||
import com.r3corda.core.utilities.nonEmptySetOf
|
import com.r3corda.core.utilities.nonEmptySetOf
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
@ -35,7 +34,7 @@ class ObligationTests {
|
|||||||
val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2)
|
val outState = inState.copy(beneficiary = DUMMY_PUBKEY_2)
|
||||||
|
|
||||||
private fun obligationTestRoots(
|
private fun obligationTestRoots(
|
||||||
group: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
|
group: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>
|
||||||
) = group.apply {
|
) = group.apply {
|
||||||
unverifiedTransaction {
|
unverifiedTransaction {
|
||||||
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY))
|
output("Alice's $1,000,000 obligation to Bob", oneMillionDollars.OBLIGATION between Pair(ALICE, BOB_PUBKEY))
|
||||||
|
@ -20,16 +20,31 @@ import java.util.*
|
|||||||
* an output state can be added by just passing in a [ContractState] – a [TransactionState] with the
|
* an output state can be added by just passing in a [ContractState] – a [TransactionState] with the
|
||||||
* default notary will be generated automatically.
|
* default notary will be generated automatically.
|
||||||
*/
|
*/
|
||||||
abstract class TransactionBuilder(protected val type: TransactionType = TransactionType.General(),
|
open class TransactionBuilder(
|
||||||
protected val notary: Party? = null) {
|
protected val type: TransactionType = TransactionType.General(),
|
||||||
protected val inputs: MutableList<StateRef> = arrayListOf()
|
protected val notary: Party? = null,
|
||||||
protected val attachments: MutableList<SecureHash> = arrayListOf()
|
protected val inputs: MutableList<StateRef> = arrayListOf(),
|
||||||
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf()
|
protected val attachments: MutableList<SecureHash> = arrayListOf(),
|
||||||
protected val commands: MutableList<Command> = arrayListOf()
|
protected val outputs: MutableList<TransactionState<ContractState>> = arrayListOf(),
|
||||||
protected val signers: MutableSet<PublicKey> = mutableSetOf()
|
protected val commands: MutableList<Command> = arrayListOf(),
|
||||||
|
protected val signers: MutableSet<PublicKey> = mutableSetOf()) {
|
||||||
|
|
||||||
val time: TimestampCommand? get() = commands.mapNotNull { it.value as? TimestampCommand }.singleOrNull()
|
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.
|
* 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.
|
* 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))
|
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())
|
check(currentSigs.isEmpty())
|
||||||
|
|
||||||
val notaryKey = stateAndRef.state.notary.owningKey
|
signers.add(notary.owningKey)
|
||||||
signers.add(notaryKey)
|
inputs.add(stateRef)
|
||||||
|
|
||||||
inputs.add(stateAndRef.ref)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addAttachment(attachment: Attachment) {
|
fun addAttachment(attachmentId: SecureHash) {
|
||||||
check(currentSigs.isEmpty())
|
check(currentSigs.isEmpty())
|
||||||
attachments.add(attachment.id)
|
attachments.add(attachmentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOutputState(state: TransactionState<*>) {
|
fun addOutputState(state: TransactionState<*>): Int {
|
||||||
check(currentSigs.isEmpty())
|
check(currentSigs.isEmpty())
|
||||||
outputs.add(state)
|
outputs.add(state)
|
||||||
|
return outputs.size - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
|
fun addOutputState(state: ContractState, notary: Party) = addOutputState(TransactionState(state, notary))
|
||||||
|
|
||||||
/** 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) {
|
fun addOutputState(state: ContractState): Int {
|
||||||
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" }
|
||||||
addOutputState(state, notary!!)
|
return addOutputState(state, notary!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addCommand(arg: Command) {
|
fun addCommand(arg: Command) {
|
||||||
@ -155,4 +171,4 @@ abstract class TransactionBuilder(protected val type: TransactionType = Transact
|
|||||||
fun outputStates(): List<TransactionState<*>> = ArrayList(outputs)
|
fun outputStates(): List<TransactionState<*>> = ArrayList(outputs)
|
||||||
fun commands(): List<Command> = ArrayList(commands)
|
fun commands(): List<Command> = ArrayList(commands)
|
||||||
fun attachments(): List<SecureHash> = ArrayList(attachments)
|
fun attachments(): List<SecureHash> = ArrayList(attachments)
|
||||||
}
|
}
|
||||||
|
@ -169,4 +169,4 @@ operator fun KeyPair.component1() = this.private
|
|||||||
operator fun KeyPair.component2() = this.public
|
operator fun KeyPair.component2() = this.public
|
||||||
|
|
||||||
/** A simple wrapper that will make it easier to swap out the EC algorithm we use in future */
|
/** 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()
|
||||||
|
@ -4,36 +4,159 @@ import com.r3corda.core.contracts.*
|
|||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import java.io.InputStream
|
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 {
|
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>
|
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
|
* This interface asserts that the DSL at hand is capable of verifying its underlying construct(ledger/transaction)
|
||||||
fun unverifiedTransaction(transactionLabel: String?, dsl: TransactionDSL<R, T>.() -> Unit): WireTransaction
|
*/
|
||||||
fun tweak(dsl: LedgerDSL<R, T, LedgerDSLInterpreter<R, T>>.() -> Unit)
|
interface Verifies {
|
||||||
fun attachment(attachment: InputStream): SecureHash
|
/**
|
||||||
fun 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 interface defines the bare bone functionality that a Ledger DSL interpreter should implement.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* TODO (Kotlin 1.1): Use type synonyms to make the type params less unwieldy
|
* 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) :
|
interface LedgerDSLInterpreter<out T : TransactionDSLInterpreter> : Verifies, OutputStateLookup {
|
||||||
LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>> by interpreter {
|
/**
|
||||||
|
* 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)
|
* Creates and adds a transaction to the ledger that will not be verified by [verifies].
|
||||||
fun unverifiedTransaction(dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> Unit) =
|
* @param transactionLabel Optional label of the transaction, to be used in diagnostic messages.
|
||||||
unverifiedTransaction(null, dsl)
|
* @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> =
|
inline fun <reified S : ContractState> String.outputStateAndRef(): StateAndRef<S> =
|
||||||
retrieveOutputStateAndRef(S::class.java, this)
|
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
|
fun String.outputRef(): StateRef = outputStateAndRef<ContractState>().ref
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see OutputStateLookup.retrieveOutputStateAndRef
|
||||||
|
*/
|
||||||
|
fun <S : ContractState> retrieveOutput(clazz: Class<S>, label: String) =
|
||||||
|
retrieveOutputStateAndRef(clazz, label).state.data
|
||||||
}
|
}
|
||||||
|
@ -14,18 +14,49 @@ import java.security.KeyPair
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
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(
|
fun transaction(
|
||||||
transactionLabel: String? = null,
|
transactionLabel: String? = null,
|
||||||
dsl: TransactionDSL<
|
transactionBuilder: TransactionBuilder = TransactionBuilder(),
|
||||||
EnforceVerifyOrFail,
|
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
) = JavaTestHelpers.transaction(transactionLabel, transactionBuilder, dsl)
|
||||||
>.() -> EnforceVerifyOrFail
|
|
||||||
) = JavaTestHelpers.transaction(transactionLabel, dsl)
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see JavaTestHelpers.ledger
|
||||||
|
*/
|
||||||
fun ledger(
|
fun ledger(
|
||||||
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
||||||
storageService: StorageService = MockStorageService(),
|
storageService: StorageService = MockStorageService(),
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
||||||
) = JavaTestHelpers.ledger(identityService, storageService, dsl)
|
) = JavaTestHelpers.ledger(identityService, storageService, dsl)
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
@ -33,8 +64,8 @@ fun ledger(
|
|||||||
replaceWith = ReplaceWith("tweak"),
|
replaceWith = ReplaceWith("tweak"),
|
||||||
level = DeprecationLevel.ERROR)
|
level = DeprecationLevel.ERROR)
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
|
fun TransactionDSLInterpreter.ledger(
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
@ -42,11 +73,8 @@ fun TransactionDSLInterpreter<EnforceVerifyOrFail>.ledger(
|
|||||||
replaceWith = ReplaceWith("tweak"),
|
replaceWith = ReplaceWith("tweak"),
|
||||||
level = DeprecationLevel.ERROR)
|
level = DeprecationLevel.ERROR)
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
|
fun TransactionDSLInterpreter.transaction(
|
||||||
dsl: TransactionDSL<
|
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail) {
|
||||||
EnforceVerifyOrFail,
|
|
||||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
|
||||||
>.() -> EnforceVerifyOrFail) {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated(
|
@Deprecated(
|
||||||
@ -54,8 +82,8 @@ fun TransactionDSLInterpreter<EnforceVerifyOrFail>.transaction(
|
|||||||
replaceWith = ReplaceWith("tweak"),
|
replaceWith = ReplaceWith("tweak"),
|
||||||
level = DeprecationLevel.ERROR)
|
level = DeprecationLevel.ERROR)
|
||||||
@Suppress("UNUSED_PARAMETER")
|
@Suppress("UNUSED_PARAMETER")
|
||||||
fun LedgerDSLInterpreter<EnforceVerifyOrFail, TransactionDSLInterpreter<EnforceVerifyOrFail>>.ledger(
|
fun LedgerDSLInterpreter<TransactionDSLInterpreter>.ledger(
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -68,57 +96,56 @@ sealed class EnforceVerifyOrFail {
|
|||||||
internal object Token: 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
|
* 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.
|
* 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,
|
override val ledgerInterpreter: TestLedgerDSLInterpreter,
|
||||||
private val inputStateRefs: ArrayList<StateRef> = arrayListOf(),
|
val transactionBuilder: TransactionBuilder,
|
||||||
internal val outputStates: ArrayList<LabeledOutput> = arrayListOf(),
|
internal val labelToIndexMap: HashMap<String, Int>
|
||||||
private val attachments: ArrayList<SecureHash> = arrayListOf(),
|
) : TransactionDSLInterpreter, OutputStateLookup by ledgerInterpreter {
|
||||||
private val commands: ArrayList<Command> = arrayListOf(),
|
|
||||||
private val signers: LinkedHashSet<PublicKey> = LinkedHashSet(),
|
constructor(
|
||||||
private val transactionType: TransactionType = TransactionType.General()
|
ledgerInterpreter: TestLedgerDSLInterpreter,
|
||||||
) : TransactionDSLInterpreter<EnforceVerifyOrFail>, OutputStateLookup by ledgerInterpreter {
|
transactionBuilder: TransactionBuilder
|
||||||
|
) : this(ledgerInterpreter, transactionBuilder, HashMap())
|
||||||
|
|
||||||
private fun copy(): TestTransactionDSLInterpreter =
|
private fun copy(): TestTransactionDSLInterpreter =
|
||||||
TestTransactionDSLInterpreter(
|
TestTransactionDSLInterpreter(
|
||||||
ledgerInterpreter = ledgerInterpreter,
|
ledgerInterpreter = ledgerInterpreter,
|
||||||
inputStateRefs = ArrayList(inputStateRefs),
|
transactionBuilder = transactionBuilder.copy(),
|
||||||
outputStates = ArrayList(outputStates),
|
labelToIndexMap = HashMap(labelToIndexMap)
|
||||||
attachments = ArrayList(attachments),
|
|
||||||
commands = ArrayList(commands),
|
|
||||||
signers = LinkedHashSet(signers),
|
|
||||||
transactionType = transactionType
|
|
||||||
)
|
)
|
||||||
|
|
||||||
internal fun toWireTransaction(): WireTransaction =
|
internal fun toWireTransaction() = transactionBuilder.toWireTransaction()
|
||||||
WireTransaction(
|
|
||||||
inputs = inputStateRefs,
|
|
||||||
outputs = outputStates.map { it.state },
|
|
||||||
attachments = attachments,
|
|
||||||
commands = commands,
|
|
||||||
signers = signers.toList(),
|
|
||||||
type = transactionType
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun input(stateRef: StateRef) {
|
override fun input(stateRef: StateRef) {
|
||||||
val notary = ledgerInterpreter.resolveStateRef<ContractState>(stateRef).notary
|
val notary = ledgerInterpreter.resolveStateRef<ContractState>(stateRef).notary
|
||||||
signers.add(notary.owningKey)
|
transactionBuilder.addInputState(stateRef, notary)
|
||||||
inputStateRefs.add(stateRef)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun _output(label: String?, notary: Party, contractState: ContractState) {
|
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) {
|
override fun attachment(attachmentId: SecureHash) {
|
||||||
attachments.add(attachmentId)
|
transactionBuilder.addAttachment(attachmentId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun _command(signers: List<PublicKey>, commandData: CommandData) {
|
override fun _command(signers: List<PublicKey>, commandData: CommandData) {
|
||||||
this.signers.addAll(signers)
|
val command = Command(commandData, signers)
|
||||||
commands.add(Command(commandData, signers))
|
transactionBuilder.addCommand(command)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verifies(): EnforceVerifyOrFail {
|
override fun verifies(): EnforceVerifyOrFail {
|
||||||
@ -127,52 +154,18 @@ data class TestTransactionDSLInterpreter(
|
|||||||
return EnforceVerifyOrFail.Token
|
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(
|
override fun tweak(
|
||||||
dsl: TransactionDSL<
|
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||||
EnforceVerifyOrFail,
|
|
||||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
|
||||||
>.() -> EnforceVerifyOrFail
|
|
||||||
) = dsl(TransactionDSL(copy()))
|
) = dsl(TransactionDSL(copy()))
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttachmentResolutionException(attachmentId: SecureHash) :
|
|
||||||
Exception("Attachment with id $attachmentId not found")
|
|
||||||
|
|
||||||
data class TestLedgerDSLInterpreter private constructor (
|
data class TestLedgerDSLInterpreter private constructor (
|
||||||
private val identityService: IdentityService,
|
private val identityService: IdentityService,
|
||||||
private val storageService: StorageService,
|
private val storageService: StorageService,
|
||||||
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
|
internal val labelToOutputStateAndRefs: HashMap<String, StateAndRef<ContractState>> = HashMap(),
|
||||||
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(),
|
private val transactionWithLocations: HashMap<SecureHash, WireTransactionWithLocation> = HashMap(),
|
||||||
private val nonVerifiedTransactionWithLocations: 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 }
|
val wireTransactions: List<WireTransaction> get() = transactionWithLocations.values.map { it.transaction }
|
||||||
|
|
||||||
// We specify [labelToOutputStateAndRefs] just so that Kotlin picks the primary constructor instead of cycling
|
// 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 {
|
companion object {
|
||||||
private fun getCallerLocation(offset: Int): String {
|
private fun getCallerLocation(): String? {
|
||||||
val stackTraceElement = Thread.currentThread().stackTrace[3 + offset]
|
val stackTrace = Thread.currentThread().stackTrace
|
||||||
return stackTraceElement.toString()
|
for (i in 1 .. stackTrace.size) {
|
||||||
|
val stackTraceElement = stackTrace[i]
|
||||||
|
if (!stackTraceElement.fileName.contains("DSL")) {
|
||||||
|
return stackTraceElement.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal data class WireTransactionWithLocation(
|
internal data class WireTransactionWithLocation(
|
||||||
val label: String?,
|
val label: String?,
|
||||||
val transaction: WireTransaction,
|
val transaction: WireTransaction,
|
||||||
val location: String
|
val location: String?
|
||||||
)
|
)
|
||||||
class VerifiesFailed(transactionLocation: String, cause: Throwable) :
|
class VerifiesFailed(transactionName: String, cause: Throwable) :
|
||||||
Exception("Transaction defined at ($transactionLocation) didn't verify: $cause", cause)
|
Exception("Transaction ($transactionName) didn't verify: $cause", cause)
|
||||||
class TypeMismatch(requested: Class<*>, actual: Class<*>) :
|
class TypeMismatch(requested: Class<*>, actual: Class<*>) :
|
||||||
Exception("Actual type $actual is not a subtype of requested type $requested")
|
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 =
|
internal fun resolveAttachment(attachmentId: SecureHash): Attachment =
|
||||||
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
|
storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
|
||||||
|
|
||||||
private fun <Return> interpretTransactionDsl(
|
private fun <R> interpretTransactionDsl(
|
||||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Return
|
transactionBuilder: TransactionBuilder,
|
||||||
|
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> R
|
||||||
): TestTransactionDSLInterpreter {
|
): TestTransactionDSLInterpreter {
|
||||||
val transactionInterpreter = TestTransactionDSLInterpreter(this)
|
val transactionInterpreter = TestTransactionDSLInterpreter(this, transactionBuilder)
|
||||||
dsl(TransactionDSL(transactionInterpreter))
|
dsl(TransactionDSL(transactionInterpreter))
|
||||||
return transactionInterpreter
|
return transactionInterpreter
|
||||||
}
|
}
|
||||||
@ -274,18 +274,20 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
|
|
||||||
private fun <R> recordTransactionWithTransactionMap(
|
private fun <R> recordTransactionWithTransactionMap(
|
||||||
transactionLabel: String?,
|
transactionLabel: String?,
|
||||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> R,
|
transactionBuilder: TransactionBuilder,
|
||||||
|
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> R,
|
||||||
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
transactionMap: HashMap<SecureHash, WireTransactionWithLocation> = HashMap()
|
||||||
): WireTransaction {
|
): WireTransaction {
|
||||||
val transactionLocation = getCallerLocation(3)
|
val transactionLocation = getCallerLocation()
|
||||||
val transactionInterpreter = interpretTransactionDsl(dsl)
|
val transactionInterpreter = interpretTransactionDsl(transactionBuilder, dsl)
|
||||||
// Create the WireTransaction
|
// Create the WireTransaction
|
||||||
val wireTransaction = transactionInterpreter.toWireTransaction()
|
val wireTransaction = transactionInterpreter.toWireTransaction()
|
||||||
// Record the output states
|
// Record the output states
|
||||||
transactionInterpreter.outputStates.forEachIndexed { index, labeledOutput ->
|
transactionInterpreter.labelToIndexMap.forEach { label, index ->
|
||||||
if (labeledOutput.label != null) {
|
if (label in labelToOutputStateAndRefs) {
|
||||||
labelToOutputStateAndRefs[labeledOutput.label] = wireTransaction.outRef(index)
|
throw DuplicateOutputLabel(label)
|
||||||
}
|
}
|
||||||
|
labelToOutputStateAndRefs[label] = wireTransaction.outRef(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionMap[wireTransaction.serialized.hash] =
|
transactionMap[wireTransaction.serialized.hash] =
|
||||||
@ -294,32 +296,38 @@ data class TestLedgerDSLInterpreter private constructor (
|
|||||||
return wireTransaction
|
return wireTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun transaction(
|
override fun _transaction(
|
||||||
transactionLabel: String?,
|
transactionLabel: String?,
|
||||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
transactionBuilder: TransactionBuilder,
|
||||||
) = recordTransactionWithTransactionMap(transactionLabel, dsl, transactionWithLocations)
|
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||||
|
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, transactionWithLocations)
|
||||||
|
|
||||||
override fun unverifiedTransaction(
|
override fun _unverifiedTransaction(
|
||||||
transactionLabel: String?,
|
transactionLabel: String?,
|
||||||
dsl: TransactionDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter>.() -> Unit
|
transactionBuilder: TransactionBuilder,
|
||||||
) = recordTransactionWithTransactionMap(transactionLabel, dsl, nonVerifiedTransactionWithLocations)
|
dsl: TransactionDSL<TestTransactionDSLInterpreter>.() -> Unit
|
||||||
|
) = recordTransactionWithTransactionMap(transactionLabel, transactionBuilder, dsl, nonVerifiedTransactionWithLocations)
|
||||||
|
|
||||||
override fun tweak(
|
override fun tweak(
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter,
|
dsl: LedgerDSL<TestTransactionDSLInterpreter,
|
||||||
LedgerDSLInterpreter<EnforceVerifyOrFail, TestTransactionDSLInterpreter>>.() -> Unit) =
|
LedgerDSLInterpreter<TestTransactionDSLInterpreter>>.() -> Unit) =
|
||||||
dsl(LedgerDSL(copy()))
|
dsl(LedgerDSL(copy()))
|
||||||
|
|
||||||
override fun attachment(attachment: InputStream): SecureHash {
|
override fun attachment(attachment: InputStream): SecureHash {
|
||||||
return storageService.attachments.importAttachment(attachment)
|
return storageService.attachments.importAttachment(attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun verifies() {
|
override fun verifies(): EnforceVerifyOrFail {
|
||||||
val transactionGroup = toTransactionGroup()
|
val transactionGroup = toTransactionGroup()
|
||||||
try {
|
try {
|
||||||
transactionGroup.verify()
|
transactionGroup.verify()
|
||||||
} catch (exception: TransactionVerificationException) {
|
} 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> {
|
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 ->
|
fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyPair>) = transactionsToSign.map { wtx ->
|
||||||
val allPubKeys = wtx.signers.toMutableSet()
|
val allPubKeys = wtx.signers.toMutableSet()
|
||||||
val bits = wtx.serialize()
|
val bits = wtx.serialize()
|
||||||
require(bits == wtx.serialized)
|
require(bits == wtx.serialized)
|
||||||
val signatures = ArrayList<DigitalSignature.WithKey>()
|
val signatures = ArrayList<DigitalSignature.WithKey>()
|
||||||
for (key in ALL_TEST_KEYS + extraKeys) {
|
for (key in ALL_TEST_KEYS + extraKeys) {
|
||||||
if (allPubKeys.contains(key.public)) {
|
if (key.public in allPubKeys) {
|
||||||
signatures += key.signWithECDSA(bits)
|
signatures += key.signWithECDSA(bits)
|
||||||
allPubKeys -= key.public
|
allPubKeys -= key.public
|
||||||
}
|
}
|
||||||
@ -349,6 +363,10 @@ fun signAll(transactionsToSign: List<WireTransaction>, extraKeys: Array<out KeyP
|
|||||||
SignedTransaction(bits, signatures)
|
SignedTransaction(bits, signatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.signAll(
|
/**
|
||||||
transactionsToSign: List<WireTransaction> = this.interpreter.wireTransactions, vararg extraKeys: KeyPair) =
|
* Signs all transactions in the ledger.
|
||||||
signAll(transactionsToSign, extraKeys)
|
* @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)
|
||||||
|
@ -15,26 +15,6 @@ import java.security.KeyPair
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Instant
|
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
|
* 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")
|
@JvmStatic val TEST_TX_TIME: Instant get() = Instant.parse("2015-04-17T12:00:00.00Z")
|
||||||
|
|
||||||
// A few dummy values for testing.
|
// 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 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 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 ORACLE_PUBKEY: PublicKey get() = ORACLE_KEY.public
|
||||||
|
|
||||||
@JvmStatic val DUMMY_PUBKEY_1: PublicKey get() = DummyPublicKey("x1")
|
@JvmStatic val DUMMY_PUBKEY_1: PublicKey get() = DummyPublicKey("x1")
|
||||||
@JvmStatic val DUMMY_PUBKEY_2: PublicKey get() = DummyPublicKey("x2")
|
@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_KEY: KeyPair by lazy { generateKeyPair() }
|
||||||
@JvmStatic val ALICE_PUBKEY: PublicKey get() = ALICE_KEY.public
|
@JvmStatic val ALICE_PUBKEY: PublicKey get() = ALICE_KEY.public
|
||||||
@JvmStatic val ALICE: Party get() = Party("Alice", ALICE_PUBKEY)
|
@JvmStatic val ALICE: Party get() = Party("Alice", ALICE_PUBKEY)
|
||||||
@ -90,23 +74,45 @@ object JavaTestHelpers {
|
|||||||
|
|
||||||
@JvmStatic fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
|
@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(
|
@JvmStatic @JvmOverloads fun ledger(
|
||||||
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
identityService: IdentityService = MOCK_IDENTITY_SERVICE,
|
||||||
storageService: StorageService = MockStorageService(),
|
storageService: StorageService = MockStorageService(),
|
||||||
dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
||||||
): LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||||
val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService))
|
val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(identityService, storageService))
|
||||||
dsl(ledgerDsl)
|
dsl(ledgerDsl)
|
||||||
return ledgerDsl
|
return ledgerDsl
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a ledger with a single transaction, built by the passed in dsl.
|
||||||
|
* @see LedgerDSLInterpreter._transaction
|
||||||
|
*/
|
||||||
@JvmStatic @JvmOverloads fun transaction(
|
@JvmStatic @JvmOverloads fun transaction(
|
||||||
transactionLabel: String? = null,
|
transactionLabel: String? = null,
|
||||||
dsl: TransactionDSL<
|
transactionBuilder: TransactionBuilder = TransactionBuilder(),
|
||||||
EnforceVerifyOrFail,
|
dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail
|
||||||
TransactionDSLInterpreter<EnforceVerifyOrFail>
|
) = ledger { this.transaction(transactionLabel, transactionBuilder, dsl) }
|
||||||
>.() -> EnforceVerifyOrFail
|
|
||||||
) = ledger { transaction(transactionLabel, dsl) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val TEST_TX_TIME = JavaTestHelpers.TEST_TX_TIME
|
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 ORACLE_PUBKEY = JavaTestHelpers.ORACLE_PUBKEY
|
||||||
val DUMMY_PUBKEY_1 = JavaTestHelpers.DUMMY_PUBKEY_1
|
val DUMMY_PUBKEY_1 = JavaTestHelpers.DUMMY_PUBKEY_1
|
||||||
val DUMMY_PUBKEY_2 = JavaTestHelpers.DUMMY_PUBKEY_2
|
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_KEY = JavaTestHelpers.ALICE_KEY
|
||||||
val ALICE_PUBKEY = JavaTestHelpers.ALICE_PUBKEY
|
val ALICE_PUBKEY = JavaTestHelpers.ALICE_PUBKEY
|
||||||
val ALICE = JavaTestHelpers.ALICE
|
val ALICE = JavaTestHelpers.ALICE
|
||||||
@ -132,12 +141,5 @@ val ALL_TEST_KEYS = JavaTestHelpers.ALL_TEST_KEYS
|
|||||||
val MOCK_IDENTITY_SERVICE = JavaTestHelpers.MOCK_IDENTITY_SERVICE
|
val MOCK_IDENTITY_SERVICE = JavaTestHelpers.MOCK_IDENTITY_SERVICE
|
||||||
|
|
||||||
fun generateStateRef() = JavaTestHelpers.generateStateRef()
|
fun generateStateRef() = JavaTestHelpers.generateStateRef()
|
||||||
|
fun freeLocalHostAndPort() = JavaTestHelpers.freeLocalHostAndPort()
|
||||||
class LabeledOutput(val label: String?, val state: TransactionState<*>) {
|
inline fun <R> rootCauseExceptions(body: () -> R) = JavaTestHelpers.rootCauseExceptions(body)
|
||||||
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)
|
|
||||||
|
|
||||||
|
@ -5,84 +5,115 @@ import com.r3corda.core.crypto.Party
|
|||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.seconds
|
import com.r3corda.core.seconds
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
import java.time.Duration
|
||||||
import java.time.Instant
|
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
|
* This interface defines the bare bone functionality that a Transaction DSL interpreter should implement.
|
||||||
* overloading/default valuing should be done here, only the basic functions that are required to implement everything.
|
* @param <R> The return type of [verifies]/[failsWith] and the like. It is generic so that we have control over whether
|
||||||
* Same goes for functions requiring reflection e.g. [OutputStateLookup.retrieveOutputStateAndRef]
|
* we want to enforce users to call these methods (@see [EnforceVerifyOrFail]) or not.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
interface TransactionDSLInterpreter<R> : OutputStateLookup {
|
interface TransactionDSLInterpreter : Verifies, OutputStateLookup {
|
||||||
val ledgerInterpreter: LedgerDSLInterpreter<R, TransactionDSLInterpreter<R>>
|
/**
|
||||||
|
* 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)
|
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)
|
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)
|
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 _command(signers: List<PublicKey>, commandData: CommandData)
|
||||||
fun verifies(): R
|
|
||||||
fun failsWith(expectedMessage: String?): R
|
/**
|
||||||
fun tweak(
|
* Creates a local scoped copy of the transaction.
|
||||||
dsl: TransactionDSL<R, TransactionDSLInterpreter<R>>.() -> R
|
* @param dsl The transaction DSL to be interpreted using the copy.
|
||||||
): R
|
*/
|
||||||
|
fun tweak(dsl: TransactionDSL<TransactionDSLInterpreter>.() -> EnforceVerifyOrFail): EnforceVerifyOrFail
|
||||||
}
|
}
|
||||||
|
|
||||||
class TransactionDSL<R, out T : TransactionDSLInterpreter<R>> (val interpreter: T) :
|
class TransactionDSL<out T : TransactionDSLInterpreter> (val interpreter: T) :
|
||||||
TransactionDSLInterpreter<R> by interpreter {
|
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) {
|
fun input(state: ContractState) {
|
||||||
val transaction = ledgerInterpreter.unverifiedTransaction(null) {
|
val transaction = ledgerInterpreter._unverifiedTransaction(null, TransactionBuilder()) {
|
||||||
output { state }
|
output { state }
|
||||||
}
|
}
|
||||||
input(transaction.outRef<ContractState>(0).ref)
|
input(transaction.outRef<ContractState>(0).ref)
|
||||||
}
|
}
|
||||||
fun input(stateClosure: () -> ContractState) = input(stateClosure())
|
fun input(stateClosure: () -> ContractState) = input(stateClosure())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TransactionDSLInterpreter._output
|
||||||
|
*/
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun output(label: String? = null, notary: Party = DUMMY_NOTARY, contractStateClosure: () -> ContractState) =
|
fun output(label: String? = null, notary: Party = DUMMY_NOTARY, contractStateClosure: () -> ContractState) =
|
||||||
_output(label, notary, contractStateClosure())
|
_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)
|
_output(label, DUMMY_NOTARY, contractState)
|
||||||
|
|
||||||
|
fun output(contractState: ContractState) =
|
||||||
|
_output(null, DUMMY_NOTARY, contractState)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see TransactionDSLInterpreter._command
|
||||||
|
*/
|
||||||
fun command(vararg signers: PublicKey, commandDataClosure: () -> CommandData) =
|
fun command(vararg signers: PublicKey, commandDataClosure: () -> CommandData) =
|
||||||
_command(listOf(*signers), commandDataClosure())
|
_command(listOf(*signers), commandDataClosure())
|
||||||
|
/**
|
||||||
|
* @see TransactionDSLInterpreter._command
|
||||||
|
*/
|
||||||
fun command(signer: PublicKey, commandData: CommandData) = _command(listOf(signer), commandData)
|
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
|
@JvmOverloads
|
||||||
fun timestamp(time: Instant, notary: PublicKey = DUMMY_NOTARY.owningKey) =
|
fun timestamp(time: Instant, tolerance: Duration = 30.seconds, notary: PublicKey = DUMMY_NOTARY.owningKey) =
|
||||||
timestamp(TimestampCommand(time, 30.seconds), notary)
|
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
|
@JvmOverloads
|
||||||
fun timestamp(data: TimestampCommand, notary: PublicKey = DUMMY_NOTARY.owningKey) = command(notary, data)
|
fun timestamp(data: TimestampCommand, notary: PublicKey = DUMMY_NOTARY.owningKey) = command(notary, data)
|
||||||
|
|
||||||
fun fails() = failsWith(null)
|
|
||||||
infix fun `fails with`(msg: String) = failsWith(msg)
|
|
||||||
}
|
}
|
||||||
|
@ -234,7 +234,7 @@ class AttachmentClassLoaderTests {
|
|||||||
|
|
||||||
val attachmentRef = importJar(storage)
|
val attachmentRef = importJar(storage)
|
||||||
|
|
||||||
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
|
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
|
||||||
|
|
||||||
val wireTransaction = tx.toWireTransaction()
|
val wireTransaction = tx.toWireTransaction()
|
||||||
|
|
||||||
@ -265,7 +265,7 @@ class AttachmentClassLoaderTests {
|
|||||||
|
|
||||||
val attachmentRef = importJar(storage)
|
val attachmentRef = importJar(storage)
|
||||||
|
|
||||||
tx.addAttachment(storage.openAttachment(attachmentRef)!!)
|
tx.addAttachment(storage.openAttachment(attachmentRef)!!.id)
|
||||||
|
|
||||||
val wireTransaction = tx.toWireTransaction()
|
val wireTransaction = tx.toWireTransaction()
|
||||||
|
|
||||||
@ -280,4 +280,4 @@ class AttachmentClassLoaderTests {
|
|||||||
}
|
}
|
||||||
assertEquals(attachmentRef, e.ids.single())
|
assertEquals(attachmentRef, e.ids.single())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class TransactionSerializationTests {
|
|||||||
val fakeStateRef = generateStateRef()
|
val fakeStateRef = generateStateRef()
|
||||||
val inputState = StateAndRef(TransactionState(TestCash.State(depositRef, 100.POUNDS, DUMMY_PUBKEY_1), DUMMY_NOTARY), fakeStateRef)
|
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 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
|
lateinit var tx: TransactionBuilder
|
||||||
@ -55,14 +55,14 @@ class TransactionSerializationTests {
|
|||||||
@Before
|
@Before
|
||||||
fun setup() {
|
fun setup() {
|
||||||
tx = TransactionType.General.Builder().withItems(
|
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
|
@Test
|
||||||
fun signWireTX() {
|
fun signWireTX() {
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
tx.signWith(TestUtils.keypair)
|
tx.signWith(DUMMY_KEY_1)
|
||||||
val signedTX = tx.toSignedTransaction()
|
val signedTX = tx.toSignedTransaction()
|
||||||
|
|
||||||
// Now check that the signature we just made verifies.
|
// Now check that the signature we just made verifies.
|
||||||
@ -82,7 +82,7 @@ class TransactionSerializationTests {
|
|||||||
tx.toSignedTransaction()
|
tx.toSignedTransaction()
|
||||||
}
|
}
|
||||||
|
|
||||||
tx.signWith(TestUtils.keypair)
|
tx.signWith(DUMMY_KEY_1)
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
val signedTX = tx.toSignedTransaction()
|
val signedTX = tx.toSignedTransaction()
|
||||||
|
|
||||||
@ -94,9 +94,9 @@ class TransactionSerializationTests {
|
|||||||
// If the signature was replaced in transit, we don't like it.
|
// If the signature was replaced in transit, we don't like it.
|
||||||
assertFailsWith(SignatureException::class) {
|
assertFailsWith(SignatureException::class) {
|
||||||
val tx2 = TransactionType.General.Builder().withItems(inputState, outputState, changeState,
|
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(DUMMY_NOTARY_KEY)
|
||||||
tx2.signWith(TestUtils.keypair2)
|
tx2.signWith(DUMMY_KEY_2)
|
||||||
|
|
||||||
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
|
signedTX.copy(sigs = tx2.toSignedTransaction().sigs).verify()
|
||||||
}
|
}
|
||||||
@ -105,7 +105,7 @@ class TransactionSerializationTests {
|
|||||||
@Test
|
@Test
|
||||||
fun timestamp() {
|
fun timestamp() {
|
||||||
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
|
tx.setTime(TEST_TX_TIME, DUMMY_NOTARY, 30.seconds)
|
||||||
tx.signWith(TestUtils.keypair)
|
tx.signWith(DUMMY_KEY_1)
|
||||||
tx.signWith(DUMMY_NOTARY_KEY)
|
tx.signWith(DUMMY_NOTARY_KEY)
|
||||||
val stx = tx.toSignedTransaction()
|
val stx = tx.toSignedTransaction()
|
||||||
val ltx = stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments)
|
val ltx = stx.verifyToLedgerTransaction(MOCK_IDENTITY_SERVICE, MockStorageService().attachments)
|
||||||
|
@ -39,6 +39,7 @@ Read on to learn:
|
|||||||
|
|
||||||
where-to-start
|
where-to-start
|
||||||
tutorial-contract
|
tutorial-contract
|
||||||
|
tutorial-test-dsl
|
||||||
protocol-state-machines
|
protocol-state-machines
|
||||||
oracles
|
oracles
|
||||||
event-scheduling
|
event-scheduling
|
||||||
|
557
docs/source/tutorial-test-dsl.rst
Normal file
557
docs/source/tutorial-test-dsl.rst
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
@ -88,7 +88,7 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
|||||||
// Nothing to do
|
// 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.
|
// It's OK to not have a network map service in the mock network.
|
||||||
override fun noNetworkMapConfigured() = Futures.immediateFuture(Unit)
|
override fun noNetworkMapConfigured() = Futures.immediateFuture(Unit)
|
||||||
|
@ -17,7 +17,6 @@ import com.r3corda.core.node.services.ServiceType
|
|||||||
import com.r3corda.core.node.services.TransactionStorage
|
import com.r3corda.core.node.services.TransactionStorage
|
||||||
import com.r3corda.core.node.services.Wallet
|
import com.r3corda.core.node.services.Wallet
|
||||||
import com.r3corda.core.random63BitValue
|
import com.r3corda.core.random63BitValue
|
||||||
import com.r3corda.core.seconds
|
|
||||||
import com.r3corda.core.testing.*
|
import com.r3corda.core.testing.*
|
||||||
import com.r3corda.core.utilities.BriefLogFormatter
|
import com.r3corda.core.utilities.BriefLogFormatter
|
||||||
import com.r3corda.node.internal.testing.MockNetwork
|
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,
|
bobError: Boolean,
|
||||||
aliceError: Boolean,
|
aliceError: Boolean,
|
||||||
expectedMessageSubstring: String
|
expectedMessageSubstring: String
|
||||||
@ -431,7 +430,7 @@ class TwoPartyTradeProtocolTests {
|
|||||||
return signed.associateBy { it.id }
|
return signed.associateBy { it.id }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
|
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForBuyer(
|
||||||
withError: Boolean,
|
withError: Boolean,
|
||||||
owner: PublicKey = BOB_PUBKEY,
|
owner: PublicKey = BOB_PUBKEY,
|
||||||
issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair<Wallet, List<WireTransaction>> {
|
issuer: PartyAndReference = MEGA_CORP.ref(1)): Pair<Wallet, List<WireTransaction>> {
|
||||||
@ -472,7 +471,7 @@ class TwoPartyTradeProtocolTests {
|
|||||||
return Pair(wallet, listOf(eb1, bc1, bc2))
|
return Pair(wallet, listOf(eb1, bc1, bc2))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
|
private fun LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.fillUpForSeller(
|
||||||
withError: Boolean,
|
withError: Boolean,
|
||||||
owner: PublicKey,
|
owner: PublicKey,
|
||||||
amount: Amount<Issued<Currency>>,
|
amount: Amount<Issued<Currency>>,
|
||||||
@ -484,7 +483,7 @@ class TwoPartyTradeProtocolTests {
|
|||||||
}
|
}
|
||||||
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
|
||||||
if (!withError)
|
if (!withError)
|
||||||
command(notary.owningKey) { TimestampCommand(TEST_TX_TIME, 30.seconds) }
|
timestamp(time = TEST_TX_TIME, notary = notary.owningKey)
|
||||||
if (attachmentID != null)
|
if (attachmentID != null)
|
||||||
attachment(attachmentID)
|
attachment(attachmentID)
|
||||||
if (withError) {
|
if (withError) {
|
||||||
|
@ -9,7 +9,7 @@ import org.graphstream.graph.Node
|
|||||||
import org.graphstream.graph.implementations.SingleGraph
|
import org.graphstream.graph.implementations.SingleGraph
|
||||||
import kotlin.reflect.memberProperties
|
import kotlin.reflect.memberProperties
|
||||||
|
|
||||||
class GraphVisualiser(val dsl: LedgerDSL<EnforceVerifyOrFail, TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>) {
|
class GraphVisualiser(val dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>) {
|
||||||
companion object {
|
companion object {
|
||||||
val css = GraphVisualiser::class.java.getResourceAsStream("graph.css").bufferedReader().readText()
|
val css = GraphVisualiser::class.java.getResourceAsStream("graph.css").bufferedReader().readText()
|
||||||
}
|
}
|
||||||
|
@ -341,7 +341,7 @@ private class TraderDemoProtocolSeller(val otherSide: Party,
|
|||||||
// TODO: Consider moving these two steps below into generateIssue.
|
// TODO: Consider moving these two steps below into generateIssue.
|
||||||
|
|
||||||
// Attach the prospectus.
|
// 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.
|
// Requesting timestamping, all CP must be timestamped.
|
||||||
tx.setTime(Instant.now(), notaryNode.identity, 30.seconds)
|
tx.setTime(Instant.now(), notaryNode.identity, 30.seconds)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user