contracts, node: Port CommercialPaperTests, TwoPartyTradeProtocolTests and GroupToGraphConversion to use new dsl

This commit is contained in:
Andras Slemmer
2016-07-04 17:15:02 +01:00
parent 9b36df607e
commit cde315aca9
3 changed files with 122 additions and 125 deletions

View File

@ -17,10 +17,10 @@ import kotlin.test.assertFailsWith
import kotlin.test.assertTrue import kotlin.test.assertTrue
interface ICommercialPaperTestTemplate { interface ICommercialPaperTestTemplate {
open fun getPaper(): ICommercialPaperState fun getPaper(): ICommercialPaperState
open fun getIssueCommand(): CommandData fun getIssueCommand(): CommandData
open fun getRedeemCommand(): CommandData fun getRedeemCommand(): CommandData
open fun getMoveCommand(): CommandData fun getMoveCommand(): CommandData
} }
class JavaCommercialPaperTest() : ICommercialPaperTestTemplate { class JavaCommercialPaperTest() : ICommercialPaperTestTemplate {
@ -63,79 +63,120 @@ class CommercialPaperTestsGeneric {
val issuer = MEGA_CORP.ref(123) val issuer = MEGA_CORP.ref(123)
@Test @Test
fun ok() { fun `trade lifecycle test`() {
trade().verify() val someProfits = 1200.DOLLARS `issued by` issuer
ledger {
nonVerifiedTransaction {
output("alice's $900", 900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY)
output("some profits", someProfits.STATE `owned by` MEGA_CORP_PUBKEY)
} }
@Test // Some CP is issued onto the ledger by MegaCorp.
fun `not matured at redemption`() { transaction("Issuance") {
trade(redemptionTime = TEST_TX_TIME + 2.days).expectFailureOfTx(3, "must have matured") output("paper") { thisTest.getPaper() }
command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME)
}
// The CP is sold to alice for her $900, $100 less than the face value. At 10% interest after only 7 days,
// that sounds a bit too good to be true!
transaction("Trade") {
input("paper")
input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output("alice's paper") { "paper".output<ICommercialPaperState>().data `owned by` ALICE_PUBKEY }
command(ALICE_PUBKEY) { Cash.Commands.Move() }
command(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
}
// Time passes, and Alice redeem's her CP for $1000, netting a $100 profit. MegaCorp has received $1200
// as a single payment from somewhere and uses it to pay Alice off, keeping the remaining $200 as change.
transaction("Redemption") {
input("alice's paper")
input("some profits")
fun TransactionDsl<TransactionDslInterpreter>.outputs(aliceGetsBack: Amount<Issued<Currency>>) {
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
}
command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
command(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
tweak {
outputs(700.DOLLARS `issued by` issuer)
timestamp(TEST_TX_TIME + 8.days)
this `fails with` "received amount equals the face value"
}
outputs(1000.DOLLARS `issued by` issuer)
tweak {
timestamp(TEST_TX_TIME + 2.days)
this `fails with` "must have matured"
}
timestamp(TEST_TX_TIME + 8.days)
tweak {
output { "paper".output<ICommercialPaperState>().data }
this `fails with` "must be destroyed"
}
verifies()
}
}
} }
@Test @Test
fun `key mismatch at issue`() { fun `key mismatch at issue`() {
transactionGroup { ledger {
transaction { transaction {
output { thisTest.getPaper() } output { thisTest.getPaper() }
arg(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() } command(DUMMY_PUBKEY_1) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
this `fails with` "signed by the claimed issuer"
} }
expectFailureOfTx(1, "signed by the claimed issuer")
} }
} }
@Test @Test
fun `face value is not zero`() { fun `face value is not zero`() {
transactionGroup { ledger {
transaction { transaction {
output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) } output { thisTest.getPaper().withFaceValue(0.DOLLARS `issued by` issuer) }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
this `fails with` "face value is not zero"
} }
expectFailureOfTx(1, "face value is not zero")
} }
} }
@Test @Test
fun `maturity date not in the past`() { fun `maturity date not in the past`() {
transactionGroup { ledger {
transaction { transaction {
output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) } output { thisTest.getPaper().withMaturityDate(TEST_TX_TIME - 10.days) }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
this `fails with` "maturity date is not in the past"
} }
expectFailureOfTx(1, "maturity date is not in the past")
} }
} }
@Test @Test
fun `issue cannot replace an existing state`() { fun `issue cannot replace an existing state`() {
transactionGroup { ledger {
roots { nonVerifiedTransaction {
transaction(thisTest.getPaper() `with notary` DUMMY_NOTARY label "paper") output("paper") { thisTest.getPaper() }
} }
transaction { transaction {
input("paper") input("paper")
output { thisTest.getPaper() } output { thisTest.getPaper() }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() } command(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
} this `fails with` "there is no input state"
expectFailureOfTx(1, "there is no input state")
} }
} }
@Test
fun `did not receive enough money at redemption`() {
trade(aliceGetsBack = 700.DOLLARS `issued by` issuer).expectFailureOfTx(3, "received amount equals the face value")
}
@Test
fun `paper must be destroyed by redemption`() {
trade(destroyPaperAtRedemption = false).expectFailureOfTx(3, "must be destroyed")
} }
fun <T : ContractState> cashOutputsToWallet(vararg outputs: TransactionState<T>): Pair<LedgerTransaction, List<StateAndRef<T>>> { fun <T : ContractState> cashOutputsToWallet(vararg outputs: TransactionState<T>): Pair<LedgerTransaction, List<StateAndRef<T>>> {
@ -199,52 +240,4 @@ class CommercialPaperTestsGeneric {
TransactionGroup(setOf(issueTX, moveTX, validRedemption), setOf(corpWalletTX, alicesWalletTX)).verify() TransactionGroup(setOf(issueTX, moveTX, validRedemption), setOf(corpWalletTX, alicesWalletTX)).verify()
} }
// Generate a trade lifecycle with various parameters.
fun trade(redemptionTime: Instant = TEST_TX_TIME + 8.days,
aliceGetsBack: Amount<Issued<Currency>> = 1000.DOLLARS `issued by` issuer,
destroyPaperAtRedemption: Boolean = true): TransactionGroupDSL<ICommercialPaperState> {
val someProfits = 1200.DOLLARS `issued by` issuer
return transactionGroupFor() {
roots {
transaction(900.DOLLARS.CASH `issued by` issuer `owned by` ALICE_PUBKEY `with notary` DUMMY_NOTARY label "alice's $900")
transaction(someProfits.STATE `owned by` MEGA_CORP_PUBKEY `with notary` DUMMY_NOTARY label "some profits")
}
// Some CP is issued onto the ledger by MegaCorp.
transaction("Issuance") {
output("paper") { thisTest.getPaper() }
arg(MEGA_CORP_PUBKEY) { thisTest.getIssueCommand() }
timestamp(TEST_TX_TIME)
}
// The CP is sold to alice for her $900, $100 less than the face value. At 10% interest after only 7 days,
// that sounds a bit too good to be true!
transaction("Trade") {
input("paper")
input("alice's $900")
output("borrowed $900") { 900.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output("alice's paper") { "paper".output.data `owned by` ALICE_PUBKEY }
arg(ALICE_PUBKEY) { Cash.Commands.Move() }
arg(MEGA_CORP_PUBKEY) { thisTest.getMoveCommand() }
}
// Time passes, and Alice redeem's her CP for $1000, netting a $100 profit. MegaCorp has received $1200
// as a single payment from somewhere and uses it to pay Alice off, keeping the remaining $200 as change.
transaction("Redemption") {
input("alice's paper")
input("some profits")
output("Alice's profit") { aliceGetsBack.STATE `owned by` ALICE_PUBKEY }
output("Change") { (someProfits - aliceGetsBack).STATE `owned by` MEGA_CORP_PUBKEY }
if (!destroyPaperAtRedemption)
output { "paper".output.data }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
arg(ALICE_PUBKEY) { thisTest.getRedeemCommand() }
timestamp(redemptionTime)
}
}
}
} }

View File

@ -86,7 +86,9 @@ class TwoPartyTradeProtocolTests {
// we run in the unit test thread exclusively to speed things up, ensure deterministic results and // we run in the unit test thread exclusively to speed things up, ensure deterministic results and
// allow interruption half way through. // allow interruption half way through.
net = MockNetwork(false, true) net = MockNetwork(false, true)
transactionGroupFor<ContractState> {
ledger {
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY) val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY)
val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY) val bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
@ -113,7 +115,7 @@ class TwoPartyTradeProtocolTests {
aliceNode.smm, aliceNode.smm,
notaryNode.info, notaryNode.info,
bobNode.info.identity, bobNode.info.identity,
lookup("alice's paper"), "alice's paper".outputStateAndRef(),
1000.DOLLARS `issued by` issuer, 1000.DOLLARS `issued by` issuer,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
@ -133,7 +135,8 @@ class TwoPartyTradeProtocolTests {
@Test @Test
fun `shutdown and restore`() { fun `shutdown and restore`() {
transactionGroupFor<ContractState> {
ledger {
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY) val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY)
var bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY) var bobNode = net.createPartyNode(notaryNode.info, BOB.name, BOB_KEY)
@ -155,7 +158,7 @@ class TwoPartyTradeProtocolTests {
aliceNode.smm, aliceNode.smm,
notaryNode.info, notaryNode.info,
bobNode.info.identity, bobNode.info.identity,
lookup("alice's paper"), "alice's paper".outputStateAndRef(),
1000.DOLLARS `issued by` issuer, 1000.DOLLARS `issued by` issuer,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
@ -246,7 +249,7 @@ class TwoPartyTradeProtocolTests {
@Test @Test
fun `check dependencies of sale asset are resolved`() { fun `check dependencies of sale asset are resolved`() {
transactionGroupFor<ContractState> { ledger {
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = makeNodeWithTracking(notaryNode.info, ALICE.name, ALICE_KEY) val aliceNode = makeNodeWithTracking(notaryNode.info, ALICE.name, ALICE_KEY)
val bobNode = makeNodeWithTracking(notaryNode.info, BOB.name, BOB_KEY) val bobNode = makeNodeWithTracking(notaryNode.info, BOB.name, BOB_KEY)
@ -275,7 +278,7 @@ class TwoPartyTradeProtocolTests {
aliceNode.smm, aliceNode.smm,
notaryNode.info, notaryNode.info,
bobNode.info.identity, bobNode.info.identity,
lookup("alice's paper"), "alice's paper".outputStateAndRef(),
1000.DOLLARS `issued by` issuer, 1000.DOLLARS `issued by` issuer,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
@ -350,19 +353,19 @@ class TwoPartyTradeProtocolTests {
@Test @Test
fun `dependency with error on buyer side`() { fun `dependency with error on buyer side`() {
transactionGroupFor<ContractState> { ledger {
runWithError(true, false, "at least one asset input") runWithError(true, false, "at least one asset input")
} }
} }
@Test @Test
fun `dependency with error on seller side`() { fun `dependency with error on seller side`() {
transactionGroupFor<ContractState> { ledger {
runWithError(false, true, "must be timestamped") runWithError(false, true, "must be timestamped")
} }
} }
private fun TransactionGroupDSL<ContractState>.runWithError(bobError: Boolean, aliceError: Boolean, private fun LedgerDsl<TransactionDslInterpreter, LedgerDslInterpreter<TransactionDslInterpreter>>.runWithError(bobError: Boolean, aliceError: Boolean,
expectedMessageSubstring: String) { expectedMessageSubstring: String) {
val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY) val notaryNode = net.createNotaryNode(DUMMY_NOTARY.name, DUMMY_NOTARY_KEY)
val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY) val aliceNode = net.createPartyNode(notaryNode.info, ALICE.name, ALICE_KEY)
@ -385,7 +388,7 @@ class TwoPartyTradeProtocolTests {
aliceNode.smm, aliceNode.smm,
notaryNode.info, notaryNode.info,
bobNode.info.identity, bobNode.info.identity,
lookup("alice's paper"), "alice's paper".outputStateAndRef(),
1000.DOLLARS `issued by` issuer, 1000.DOLLARS `issued by` issuer,
ALICE_KEY, ALICE_KEY,
buyerSessionID buyerSessionID
@ -411,7 +414,8 @@ class TwoPartyTradeProtocolTests {
assertTrue(e.cause!!.cause!!.message!!.contains(expectedMessageSubstring)) assertTrue(e.cause!!.cause!!.message!!.contains(expectedMessageSubstring))
} }
private fun TransactionGroupDSL<ContractState>.insertFakeTransactions(wtxToSign: List<WireTransaction>, private fun insertFakeTransactions(
wtxToSign: List<WireTransaction>,
services: ServiceHub, services: ServiceHub,
vararg extraKeys: KeyPair): Map<SecureHash, SignedTransaction> { vararg extraKeys: KeyPair): Map<SecureHash, SignedTransaction> {
val signed: List<SignedTransaction> = signAll(wtxToSign, *extraKeys) val signed: List<SignedTransaction> = signAll(wtxToSign, *extraKeys)
@ -423,7 +427,8 @@ class TwoPartyTradeProtocolTests {
return signed.associateBy { it.id } return signed.associateBy { it.id }
} }
private fun TransactionGroupDSL<ContractState>.fillUpForBuyer(withError: Boolean, private fun LedgerDsl<TransactionDslInterpreter, LedgerDslInterpreter<TransactionDslInterpreter>>.fillUpForBuyer(
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>> {
// Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she // Bob (Buyer) has some cash he got from the Bank of Elbonia, Alice (Seller) has some commercial paper she
@ -434,7 +439,7 @@ class TwoPartyTradeProtocolTests {
output("elbonian money 1") { 800.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } output("elbonian money 1") { 800.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
output("elbonian money 2") { 1000.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } output("elbonian money 2") { 1000.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY }
if (!withError) if (!withError)
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() } command(MEGA_CORP_PUBKEY) { Cash.Commands.Issue() }
timestamp(TEST_TX_TIME) timestamp(TEST_TX_TIME)
} }
@ -442,21 +447,22 @@ class TwoPartyTradeProtocolTests {
val bc1 = transaction { val bc1 = transaction {
input("elbonian money 1") input("elbonian money 1")
output("bob cash 1") { 800.DOLLARS.CASH `issued by` issuer `owned by` owner } output("bob cash 1") { 800.DOLLARS.CASH `issued by` issuer `owned by` owner }
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
} }
val bc2 = transaction { val bc2 = transaction {
input("elbonian money 2") input("elbonian money 2")
output("bob cash 2") { 300.DOLLARS.CASH `issued by` issuer `owned by` owner } output("bob cash 2") { 300.DOLLARS.CASH `issued by` issuer `owned by` owner }
output { 700.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } // Change output. output { 700.DOLLARS.CASH `issued by` issuer `owned by` MEGA_CORP_PUBKEY } // Change output.
arg(MEGA_CORP_PUBKEY) { Cash.Commands.Move() } command(MEGA_CORP_PUBKEY) { Cash.Commands.Move() }
} }
val wallet = Wallet(listOf<StateAndRef<Cash.State>>(lookup("bob cash 1"), lookup("bob cash 2"))) val wallet = Wallet(listOf("bob cash 1".outputStateAndRef(), "bob cash 2".outputStateAndRef()))
return Pair(wallet, listOf(eb1, bc1, bc2)) return Pair(wallet, listOf(eb1, bc1, bc2))
} }
private fun TransactionGroupDSL<ContractState>.fillUpForSeller(withError: Boolean, private fun LedgerDsl<TransactionDslInterpreter, LedgerDslInterpreter<TransactionDslInterpreter>>.fillUpForSeller(
withError: Boolean,
owner: PublicKey, owner: PublicKey,
amount: Amount<Issued<Currency>>, amount: Amount<Issued<Currency>>,
notary: Party, notary: Party,
@ -465,21 +471,20 @@ class TwoPartyTradeProtocolTests {
output("alice's paper") { output("alice's paper") {
CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days) CommercialPaper.State(MEGA_CORP.ref(1, 2, 3), owner, amount, TEST_TX_TIME + 7.days)
} }
arg(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() } command(MEGA_CORP_PUBKEY) { CommercialPaper.Commands.Issue() }
if (!withError) if (!withError)
arg(notary.owningKey) { TimestampCommand(TEST_TX_TIME, 30.seconds) } command(notary.owningKey) { TimestampCommand(TEST_TX_TIME, 30.seconds) }
if (attachmentID != null) if (attachmentID != null)
attachment(attachmentID) attachment(attachmentID)
} }
val wallet = Wallet(listOf<StateAndRef<Cash.State>>(lookup("alice's paper"))) val wallet = Wallet(listOf("alice's paper".outputStateAndRef()))
return Pair(wallet, listOf(ap)) return Pair(wallet, listOf(ap))
} }
class RecordingTransactionStorage(val delegate: TransactionStorage) : TransactionStorage { class RecordingTransactionStorage(val delegate: TransactionStorage) : TransactionStorage {
val records = Collections.synchronizedList(ArrayList<TxRecord>()) val records: MutableList<TxRecord> = Collections.synchronizedList(ArrayList<TxRecord>())
override fun addTransaction(transaction: SignedTransaction) { override fun addTransaction(transaction: SignedTransaction) {
records.add(TxRecord.Add(transaction)) records.add(TxRecord.Add(transaction))

View File

@ -2,35 +2,34 @@ package com.r3corda.node.visualiser
import com.r3corda.core.contracts.CommandData import com.r3corda.core.contracts.CommandData
import com.r3corda.core.contracts.ContractState import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.TransactionState
import com.r3corda.core.crypto.SecureHash import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.testing.TransactionGroupDSL import com.r3corda.core.testing.*
import org.graphstream.graph.Edge import org.graphstream.graph.Edge
import org.graphstream.graph.Node 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: TransactionGroupDSL<in ContractState>) { 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()
} }
fun convert(): SingleGraph { fun convert(): SingleGraph {
val tg = dsl.toTransactionGroup() val tg = dsl.interpreter.toTransactionGroup()
val graph = createGraph("Transaction group", css) val graph = createGraph("Transaction group", css)
// Map all the transactions, including the bogus non-verified ones (with no inputs) to graph nodes. // Map all the transactions, including the bogus non-verified ones (with no inputs) to graph nodes.
for ((txIndex, tx) in (tg.transactions + tg.nonVerifiedRoots).withIndex()) { for ((txIndex, tx) in (tg.transactions + tg.nonVerifiedRoots).withIndex()) {
val txNode = graph.addNode<Node>("tx$txIndex") val txNode = graph.addNode<Node>("tx$txIndex")
if (tx !in tg.nonVerifiedRoots) if (tx !in tg.nonVerifiedRoots)
txNode.label = dsl.labelForTransaction(tx).let { it ?: "TX ${tx.id.prefixChars()}" } txNode.label = dsl.interpreter.transactionName(tx.id).let { it ?: "TX[${tx.id.prefixChars()}]" }
txNode.styleClass = "tx" txNode.styleClass = "tx"
// Now create a vertex for each output state. // Now create a vertex for each output state.
for (outIndex in tx.outputs.indices) { for (outIndex in tx.outputs.indices) {
val node = graph.addNode<Node>(tx.outRef<ContractState>(outIndex).ref.toString()) val node = graph.addNode<Node>(tx.outRef<ContractState>(outIndex).ref.toString())
val state = tx.outputs[outIndex] val state = tx.outputs[outIndex]
node.label = stateToLabel(state) node.label = stateToLabel(state.data)
node.styleClass = stateToCSSClass(state.data) + ",state" node.styleClass = stateToCSSClass(state.data) + ",state"
node.setAttribute("state", state) node.setAttribute("state", state)
val edge = graph.addEdge<Edge>("tx$txIndex-out$outIndex", txNode, node, true) val edge = graph.addEdge<Edge>("tx$txIndex-out$outIndex", txNode, node, true)
@ -56,8 +55,8 @@ class GraphVisualiser(val dsl: TransactionGroupDSL<in ContractState>) {
return graph return graph
} }
private fun stateToLabel(state: TransactionState<*>): String { private fun stateToLabel(state: ContractState): String {
return dsl.labelForState(state) ?: stateToTypeName(state.data) return dsl.interpreter.outputToLabel(state) ?: stateToTypeName(state)
} }
private fun commandToTypeName(state: CommandData) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.') private fun commandToTypeName(state: CommandData) = state.javaClass.canonicalName.removePrefix("contracts.").replace('$', '.')