Reduce occurrence of flow exception in explorer demo (#467)

* changed event generator to reduce flow exception due to wrongly generated event
This commit is contained in:
Patrick Kuo 2017-03-31 14:19:02 +01:00 committed by GitHub
parent 0bbc330a04
commit d8370a41b5
3 changed files with 76 additions and 176 deletions

View File

@ -1,10 +1,8 @@
package net.corda.client.mock
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.*
import net.corda.core.contracts.Amount
import net.corda.core.crypto.Party
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.TransactionBuilder
import net.corda.flows.CashFlowCommand
import java.util.*
@ -12,91 +10,27 @@ import java.util.*
* [Generator]s for incoming/outgoing events to/from the [WalletMonitorService]. Internally it keeps track of owned
* state/ref pairs, but it doesn't necessarily generate "correct" events!
*/
class EventGenerator(
val parties: List<Party>,
val notary: Party,
val currencies: List<Currency> = listOf(USD, GBP, CHF),
val issuers: List<Party> = parties
) {
private var vault = listOf<StateAndRef<Cash.State>>()
val issuerGenerator =
Generator.pickOne(issuers).combine(Generator.intRange(0, 1)) { party, ref -> party.ref(ref.toByte()) }
class EventGenerator(val parties: List<Party>, val currencies: List<Currency>, val notary: Party) {
private val partyGenerator = Generator.pickOne(parties)
private val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
private val amountGenerator = Generator.longRange(10000, 1000000)
private val currencyGenerator = Generator.pickOne(currencies)
val currencyGenerator = Generator.pickOne(currencies)
val issuedGenerator = issuerGenerator.combine(currencyGenerator) { issuer, currency -> Issued(issuer, currency) }
val amountIssuedGenerator = generateAmount(1, 10000, issuedGenerator)
val publicKeyGenerator = Generator.pickOne(parties.map { it.owningKey })
val partyGenerator = Generator.pickOne(parties)
val cashStateGenerator = amountIssuedGenerator.combine(publicKeyGenerator) { amount, from ->
val builder = TransactionBuilder(notary = notary)
builder.addOutputState(Cash.State(amount, from))
builder.addCommand(Command(Cash.Commands.Issue(), amount.token.issuer.party.owningKey))
builder.toWireTransaction().outRef<Cash.State>(0)
private val issueCashGenerator = amountGenerator.combine(partyGenerator, issueRefGenerator, currencyGenerator) { amount, to, issueRef, ccy ->
CashFlowCommand.IssueCash(Amount(amount, ccy), issueRef, to, notary)
}
val consumedGenerator: Generator<Set<StateRef>> = Generator.frequency(
0.7 to Generator.pure(setOf()),
0.3 to Generator.impure { vault }.bind { states ->
Generator.sampleBernoulli(states, 0.2).map { someStates ->
val consumedSet = someStates.map { it.ref }.toSet()
vault = vault.filter { it.ref !in consumedSet }
consumedSet
}
}
)
val producedGenerator: Generator<Set<StateAndRef<ContractState>>> = Generator.frequency(
// 0.1 to Generator.pure(setOf())
0.9 to Generator.impure { vault }.bind { states ->
Generator.replicate(2, cashStateGenerator).map {
vault = states + it
it.toSet()
}
}
)
private val exitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator) { amount, issueRef, ccy ->
CashFlowCommand.ExitCash(Amount(amount, ccy), issueRef)
}
val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
val moveCashGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient)
}
val amountToIssueGenerator = Generator.intRange(10000, 1000000).combine(currencyGenerator) { quantity, currency -> Amount(quantity.toLong(), currency) }
val issueCashGenerator =
amountToIssueGenerator.combine(partyGenerator, issueRefGenerator) { amount, to, issueRef ->
CashFlowCommand.IssueCash(
amount,
issueRef,
to,
notary
)
}
val moveCashGenerator =
amountIssuedGenerator.combine(partyGenerator) { amountIssued, recipient ->
CashFlowCommand.PayCash(
amount = amountIssued.withoutIssuer(),
recipient = recipient
)
}
val exitCashGenerator =
amountToIssueGenerator.combine(partyGenerator, issueRefGenerator) { amount, _, issueRef ->
CashFlowCommand.ExitCash(
amount,
issueRef
)
}
val clientCommandGenerator = Generator.frequency(
1.0 to moveCashGenerator
)
val bankOfCordaExitGenerator = Generator.frequency(
0.4 to exitCashGenerator
)
val bankOfCordaIssueGenerator = Generator.frequency(
0.6 to issueCashGenerator
)
val issuerGenerator = Generator.frequency(listOf(
0.1 to exitCashGenerator,
0.9 to issueCashGenerator
))
}

View File

@ -49,8 +49,8 @@ The Demo Nodes can be started in one of two modes:
2. Simulation
In this mode Nodes will automatically commence executing commands as part of a random generation process.
Issuer nodes will randomly issue, move and exit cash.
Participant nodes will randomly generate spends (eg. move cash to other nodes, including issuers)
The simulation start with pre-allocating chunks of cash to each of the party in 2 currencies (USD, GBP), then it enter a loop to generate random events.
In each iteration, the issuers will execute a Cash Issue or Cash Exit command (at a 9:1 ratio) and a random party will execute a move of cash to another random party.
**Windows**::

View File

@ -12,13 +12,19 @@ import joptsimple.OptionParser
import net.corda.client.jfx.model.Models
import net.corda.client.jfx.model.observableValue
import net.corda.client.mock.EventGenerator
import net.corda.client.mock.Generator
import net.corda.client.mock.pickOne
import net.corda.client.rpc.notUsed
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount
import net.corda.core.contracts.GBP
import net.corda.core.contracts.USD
import net.corda.core.failure
import net.corda.core.messaging.FlowHandle
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.success
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.loggerFor
import net.corda.explorer.model.CordaViewModel
@ -26,6 +32,7 @@ import net.corda.explorer.model.SettingsModel
import net.corda.explorer.views.*
import net.corda.explorer.views.cordapps.cash.CashViewer
import net.corda.flows.CashExitFlow
import net.corda.flows.CashFlowCommand
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.flows.IssuerFlow.IssuanceRequester
@ -37,10 +44,8 @@ import net.corda.nodeapi.User
import org.apache.commons.lang.SystemUtils
import org.controlsfx.dialog.ExceptionDialog
import tornadofx.*
import java.time.Instant
import java.util.*
import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.ExecutionException
import kotlin.concurrent.thread
/**
* Main class for Explorer, you will need Tornado FX to run the explorer.
@ -140,6 +145,9 @@ class Main : App(MainView::class) {
/**
* This main method will starts 5 nodes (Notary, Alice, Bob, UK Bank and USA Bank) locally for UI testing,
* they will be on localhost ports 20003, 20006, 20009, 20012 and 20015 respectively.
*
* The simulation start with pre-allocating chunks of cash to each of the party in 2 currencies (USD, GBP), then it enter a loop to generate random events.
* On each iteration, the issuers will execute a Cash Issue or Cash Exit flow (at a 9:1 ratio) and a random party will execute a move of cash to another random party.
*/
fun main(args: Array<String>) {
val portAllocation = PortAllocation.Incremental(20000)
@ -201,107 +209,65 @@ fun main(args: Array<String>) {
issuerClientUSD.start(manager.username, manager.password)
val issuerRPCUSD = issuerClientUSD.proxy()
val issuers = mapOf(USD to issuerRPCUSD, GBP to issuerRPCGBP)
val parties = listOf(aliceNode.nodeInfo.legalIdentity to aliceRPC,
bobNode.nodeInfo.legalIdentity to bobRPC,
issuerNodeGBP.nodeInfo.legalIdentity to issuerRPCGBP,
issuerNodeUSD.nodeInfo.legalIdentity to issuerRPCUSD)
val eventGenerator = EventGenerator(
parties = listOf(aliceNode.nodeInfo.legalIdentity, bobNode.nodeInfo.legalIdentity),
parties = parties.map { it.first },
notary = notaryNode.nodeInfo.notaryIdentity,
issuers = listOf(issuerNodeGBP.nodeInfo.legalIdentity, issuerNodeUSD.nodeInfo.legalIdentity)
)
val issuerGBPEventGenerator = EventGenerator(
parties = listOf(issuerNodeGBP.nodeInfo.legalIdentity, aliceNode.nodeInfo.legalIdentity, bobNode.nodeInfo.legalIdentity),
notary = notaryNode.nodeInfo.notaryIdentity,
currencies = listOf(GBP)
)
val issuerUSDEventGenerator = EventGenerator(
parties = listOf(issuerNodeUSD.nodeInfo.legalIdentity, aliceNode.nodeInfo.legalIdentity, bobNode.nodeInfo.legalIdentity),
notary = notaryNode.nodeInfo.notaryIdentity,
currencies = listOf(USD)
currencies = listOf(GBP, USD)
)
val maxIterations = 100000
val flowHandles = mapOf(
"GBPIssuer" to ArrayBlockingQueue<FlowHandle<SignedTransaction>>(maxIterations + 1),
"USDIssuer" to ArrayBlockingQueue<FlowHandle<SignedTransaction>>(maxIterations + 1),
"Alice" to ArrayBlockingQueue<FlowHandle<SignedTransaction>>(maxIterations + 1),
"Bob" to ArrayBlockingQueue<FlowHandle<SignedTransaction>>(maxIterations + 1),
"GBPExit" to ArrayBlockingQueue<FlowHandle<SignedTransaction>>(maxIterations + 1),
"USDExit" to ArrayBlockingQueue<FlowHandle<SignedTransaction>>(maxIterations + 1)
)
val maxIterations = 100_000
// Log to logger when flow finish.
fun FlowHandle<SignedTransaction>.log(seq: Int, name: String) {
val out = "[$seq] $name $id :"
progress.notUsed()
returnValue.success {
Main.log.info("$out ${it.id} ${(it.tx.outputs.first().data as Cash.State).amount}")
}.failure {
Main.log.info("$out ${it.message}")
}
}
flowHandles.forEach {
thread {
for (i in 0..maxIterations) {
val item = it.value.take()
val out = "[$i] ${it.key} ${item.id} :"
try {
val result = item.returnValue.get()
Main.log.info("$out ${result.id} ${(result.tx.outputs.first().data as Cash.State).amount}")
} catch(e: ExecutionException) {
Main.log.info("$out ${e.cause!!.message}")
}
// Pre allocate some money to each party.
eventGenerator.parties.forEach {
for (ref in 0..1) {
for ((currency, issuer) in issuers) {
CashFlowCommand.IssueCash(Amount(1_000_000, currency), OpaqueBytes(ByteArray(1, { ref.toByte() })), it, notaryNode.nodeInfo.notaryIdentity).startFlow(issuer)
.progress.notUsed()
}
}
}
for (i in 0..maxIterations) {
Thread.sleep(500)
// Issuer requests
if ((i % 5) == 0) {
issuerGBPEventGenerator.bankOfCordaIssueGenerator.map { command ->
println("[$i] ISSUING ${command.amount} with ref ${command.issueRef} to ${command.recipient}")
val cmd = command.startFlow(issuerRPCGBP)
flowHandles["GBPIssuer"]?.add(cmd)
cmd.progress.notUsed()
Unit
}.generate(SplittableRandom())
issuerUSDEventGenerator.bankOfCordaIssueGenerator.map { command ->
println("[$i] ISSUING ${command.amount} with ref ${command.issueRef} to ${command.recipient}")
val cmd = command.startFlow(issuerRPCUSD)
flowHandles["USDIssuer"]?.add(cmd)
cmd.progress.notUsed()
Unit
}.generate(SplittableRandom())
}
// Exit requests
if ((i % 10) == 0) {
issuerGBPEventGenerator.bankOfCordaExitGenerator.map { command ->
println("[$i] EXITING ${command.amount} with ref ${command.issueRef}")
val cmd = command.startFlow(issuerRPCGBP)
flowHandles["GBPExit"]?.add(cmd)
cmd.progress.notUsed()
Unit
}.generate(SplittableRandom())
issuerUSDEventGenerator.bankOfCordaExitGenerator.map { command ->
println("[$i] EXITING ${command.amount} with ref ${command.issueRef}")
val cmd = command.startFlow(issuerRPCUSD)
flowHandles["USDExit"]?.add(cmd)
cmd.progress.notUsed()
Unit
}.generate(SplittableRandom())
}
// Party pay requests
// Alice
eventGenerator.clientCommandGenerator.map { command ->
println("[$i] SENDING ${command.amount} from ${aliceRPC.nodeIdentity().legalIdentity} to ${command.recipient}")
val cmd = command.startFlow(aliceRPC)
flowHandles["Alice"]?.add(cmd)
cmd.progress.notUsed()
Unit
Thread.sleep(300)
// Issuer requests.
eventGenerator.issuerGenerator.map { command ->
when (command) {
is CashFlowCommand.IssueCash -> issuers[command.amount.token]?.let {
println("${Instant.now()} [$i] ISSUING ${command.amount} with ref ${command.issueRef} to ${command.recipient}")
command.startFlow(it).log(i, "${command.amount.token}Issuer")
}
is CashFlowCommand.ExitCash -> issuers[command.amount.token]?.let {
println("${Instant.now()} [$i] EXITING ${command.amount} with ref ${command.issueRef}")
command.startFlow(it).log(i, "${command.amount.token}Exit")
}
else -> throw IllegalArgumentException("Unsupported command: $command")
}
}.generate(SplittableRandom())
// Bob
eventGenerator.clientCommandGenerator.map { command ->
println("[$i] SENDING ${command.amount} from ${bobRPC.nodeIdentity().legalIdentity} to ${command.recipient}")
val cmd = command.startFlow(bobRPC)
flowHandles["Bob"]?.add(cmd)
cmd.progress.notUsed()
Unit
// Party pay requests.
eventGenerator.moveCashGenerator.combine(Generator.pickOne(parties)) { command, (party, rpc) ->
println("${Instant.now()} [$i] SENDING ${command.amount} from $party to ${command.recipient}")
command.startFlow(rpc).log(i, party.name)
}.generate(SplittableRandom())
}
println("Simulation completed")
aliceClient.close()