mirror of
https://github.com/corda/corda.git
synced 2025-01-14 16:59:52 +00:00
Add simulation with broken flows. Refactor of simulation code.
This commit is contained in:
parent
ab0bc8b8d0
commit
e402f4d5af
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Explorer - demo nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
<configuration default="false" name="Explorer - demo nodes" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
||||||
<option name="VM_PARAMETERS" value="" />
|
<option name="VM_PARAMETERS" value="-DAMQ_DELIVERY_DELAY_MS=15000" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="" />
|
<option name="PROGRAM_PARAMETERS" value="" />
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||||
|
15
.idea/runConfigurations/Explorer___demo_nodes__flow_triage_.xml
generated
Normal file
15
.idea/runConfigurations/Explorer___demo_nodes__flow_triage_.xml
generated
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<component name="ProjectRunConfigurationManager">
|
||||||
|
<configuration default="false" name="Explorer - demo nodes (flow triage)" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||||
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
|
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
||||||
|
<option name="VM_PARAMETERS" value="-DAMQ_DELIVERY_DELAY_MS=15000" />
|
||||||
|
<option name="PROGRAM_PARAMETERS" value="-F" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||||
|
<option name="ALTERNATIVE_JRE_PATH" value="1.8" />
|
||||||
|
<option name="PASS_PARENT_ENVS" value="true" />
|
||||||
|
<module name="explorer_main" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
</component>
|
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Explorer - demo nodes (simulation)" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
<configuration default="false" name="Explorer - demo nodes (simulation)" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
||||||
<option name="VM_PARAMETERS" value="-DAMQ_DELIVERY_DELAY_MS=5000" />
|
<option name="VM_PARAMETERS" value="-DAMQ_DELIVERY_DELAY_MS=15000" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="-S" />
|
<option name="PROGRAM_PARAMETERS" value="-S" />
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||||
|
@ -1,36 +1,92 @@
|
|||||||
package net.corda.client.mock
|
package net.corda.client.mock
|
||||||
|
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
|
import net.corda.core.contracts.GBP
|
||||||
|
import net.corda.core.contracts.USD
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.flows.CashFlowCommand
|
import net.corda.flows.CashFlowCommand
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Generator]s for incoming/outgoing events to/from the [WalletMonitorService]. Internally it keeps track of owned
|
* [Generator]s for incoming/outgoing cash flow events between parties. It doesn't necessarily generate correct events!
|
||||||
* state/ref pairs, but it doesn't necessarily generate "correct" events!
|
* Especially at the beginning of simulation there might be few insufficient spend errors.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class EventGenerator(val parties: List<Party>, val currencies: List<Currency>, val notary: Party) {
|
open class EventGenerator(val parties: List<Party>, val currencies: List<Currency>, val notary: Party) {
|
||||||
private val partyGenerator = Generator.pickOne(parties)
|
protected val partyGenerator = Generator.pickOne(parties)
|
||||||
private val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
|
protected val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
|
||||||
private val amountGenerator = Generator.longRange(10000, 1000000)
|
protected val amountGenerator = Generator.longRange(10000, 1000000)
|
||||||
private val currencyGenerator = Generator.pickOne(currencies)
|
protected val currencyGenerator = Generator.pickOne(currencies)
|
||||||
|
protected val currencyMap: MutableMap<Currency, Long> = mutableMapOf(USD to 0L, GBP to 0L) // Used for rough estimation of how much money we have in general.
|
||||||
|
|
||||||
private val issueCashGenerator = amountGenerator.combine(partyGenerator, issueRefGenerator, currencyGenerator) { amount, to, issueRef, ccy ->
|
protected fun addToMap(ccy: Currency, amount: Long) {
|
||||||
|
val value = currencyMap[ccy]
|
||||||
|
if (value != null)
|
||||||
|
currencyMap[ccy] = Math.max(0L, value + amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected val issueCashGenerator = amountGenerator.combine(partyGenerator, issueRefGenerator, currencyGenerator) { amount, to, issueRef, ccy ->
|
||||||
|
addToMap(ccy, amount)
|
||||||
CashFlowCommand.IssueCash(Amount(amount, ccy), issueRef, to, notary)
|
CashFlowCommand.IssueCash(Amount(amount, ccy), issueRef, to, notary)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val exitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator) { amount, issueRef, ccy ->
|
protected val exitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator) { amount, issueRef, ccy ->
|
||||||
|
addToMap(ccy, -amount)
|
||||||
CashFlowCommand.ExitCash(Amount(amount, ccy), issueRef)
|
CashFlowCommand.ExitCash(Amount(amount, ccy), issueRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
val moveCashGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
|
open val moveCashGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
|
||||||
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient)
|
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient)
|
||||||
}
|
}
|
||||||
|
|
||||||
val issuerGenerator = Generator.frequency(listOf(
|
open val issuerGenerator = Generator.frequency(listOf(
|
||||||
0.1 to exitCashGenerator,
|
0.1 to exitCashGenerator,
|
||||||
0.9 to issueCashGenerator
|
0.9 to issueCashGenerator
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [Generator]s for incoming/outgoing events of starting different [Cash] flows. It invokes flows that throw exceptions
|
||||||
|
* for use in explorer flow triage. Exceptions are of kind spending/exiting too much cash.
|
||||||
|
*/
|
||||||
|
class ErrorFlowsEventGenerator(parties: List<Party>, currencies: List<Currency>, notary: Party): EventGenerator(parties, currencies, notary) {
|
||||||
|
enum class IssuerEvents {
|
||||||
|
NORMAL_EXIT,
|
||||||
|
EXIT_ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
val errorGenerator = Generator.pickOne(IssuerEvents.values().toList())
|
||||||
|
|
||||||
|
val errorExitCashGenerator = amountGenerator.combine(issueRefGenerator, currencyGenerator, errorGenerator) { amount, issueRef, ccy, errorType ->
|
||||||
|
when (errorType) {
|
||||||
|
IssuerEvents.NORMAL_EXIT -> {
|
||||||
|
println("Normal exit")
|
||||||
|
if (currencyMap[ccy]!! <= amount) addToMap(ccy, -amount)
|
||||||
|
CashFlowCommand.ExitCash(Amount(amount, ccy), issueRef) // It may fail at the beginning, but we don't care.
|
||||||
|
}
|
||||||
|
IssuerEvents.EXIT_ERROR -> {
|
||||||
|
println("Exit error")
|
||||||
|
CashFlowCommand.ExitCash(Amount(currencyMap[ccy]!! * 2, ccy), issueRef)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val normalMoveGenerator = amountGenerator.combine(partyGenerator, currencyGenerator) { amountIssued, recipient, currency ->
|
||||||
|
CashFlowCommand.PayCash(Amount(amountIssued, currency), recipient)
|
||||||
|
}
|
||||||
|
|
||||||
|
val errorMoveGenerator = partyGenerator.combine(currencyGenerator) { recipient, currency ->
|
||||||
|
CashFlowCommand.PayCash(Amount(currencyMap[currency]!! * 2, currency), recipient)
|
||||||
|
}
|
||||||
|
|
||||||
|
override val moveCashGenerator = Generator.frequency(listOf(
|
||||||
|
0.2 to errorMoveGenerator,
|
||||||
|
0.8 to normalMoveGenerator
|
||||||
|
))
|
||||||
|
|
||||||
|
override val issuerGenerator = Generator.frequency(listOf(
|
||||||
|
0.3 to errorExitCashGenerator,
|
||||||
|
0.7 to issueCashGenerator
|
||||||
|
))
|
||||||
|
}
|
||||||
|
@ -68,3 +68,9 @@ task(runSimulationNodes, dependsOn: 'classes', type: JavaExec) {
|
|||||||
classpath = sourceSets.main.runtimeClasspath
|
classpath = sourceSets.main.runtimeClasspath
|
||||||
args '-S'
|
args '-S'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task(runFlowTriageNodes, dependsOn: 'classes', type: JavaExec) {
|
||||||
|
main = 'net.corda.explorer.MainKt'
|
||||||
|
classpath = sourceSets.main.runtimeClasspath
|
||||||
|
args '-F'
|
||||||
|
}
|
||||||
|
@ -0,0 +1,207 @@
|
|||||||
|
package net.corda.explorer
|
||||||
|
|
||||||
|
import joptsimple.OptionSet
|
||||||
|
import net.corda.client.mock.ErrorFlowsEventGenerator
|
||||||
|
import net.corda.client.mock.EventGenerator
|
||||||
|
import net.corda.client.mock.Generator
|
||||||
|
import net.corda.client.mock.pickOne
|
||||||
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
|
import net.corda.client.rpc.CordaRPCConnection
|
||||||
|
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.crypto.Party
|
||||||
|
import net.corda.core.failure
|
||||||
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
|
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.ALICE
|
||||||
|
import net.corda.core.utilities.BOB
|
||||||
|
import net.corda.core.utilities.DUMMY_NOTARY
|
||||||
|
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
|
||||||
|
import net.corda.node.driver.NodeHandle
|
||||||
|
import net.corda.node.driver.PortAllocation
|
||||||
|
import net.corda.node.driver.driver
|
||||||
|
import net.corda.node.services.startFlowPermission
|
||||||
|
import net.corda.node.services.transactions.SimpleNotaryService
|
||||||
|
import net.corda.nodeapi.User
|
||||||
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
|
import java.time.Instant
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class ExplorerSimulation(val options: OptionSet) {
|
||||||
|
val user = User("user1", "test", permissions = setOf(
|
||||||
|
startFlowPermission<CashPaymentFlow>()
|
||||||
|
))
|
||||||
|
val manager = User("manager", "test", permissions = setOf(
|
||||||
|
startFlowPermission<CashIssueFlow>(),
|
||||||
|
startFlowPermission<CashPaymentFlow>(),
|
||||||
|
startFlowPermission<CashExitFlow>(),
|
||||||
|
startFlowPermission<IssuerFlow.IssuanceRequester>())
|
||||||
|
)
|
||||||
|
|
||||||
|
lateinit var notaryNode: NodeHandle
|
||||||
|
lateinit var aliceNode: NodeHandle
|
||||||
|
lateinit var bobNode: NodeHandle
|
||||||
|
lateinit var issuerNodeGBP: NodeHandle
|
||||||
|
lateinit var issuerNodeUSD: NodeHandle
|
||||||
|
|
||||||
|
val RPCConnections = ArrayList<CordaRPCConnection>()
|
||||||
|
val issuers = HashMap<Currency, CordaRPCOps>()
|
||||||
|
val parties = ArrayList<Pair<Party, CordaRPCOps>>()
|
||||||
|
|
||||||
|
init {
|
||||||
|
startDemoNodes()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onEnd() {
|
||||||
|
println("Closing RPC connections")
|
||||||
|
RPCConnections.forEach { it.close() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startDemoNodes() {
|
||||||
|
val portAllocation = PortAllocation.Incremental(20000)
|
||||||
|
driver(portAllocation = portAllocation) {
|
||||||
|
// TODO : Supported flow should be exposed somehow from the node instead of set of ServiceInfo.
|
||||||
|
val notary = startNode(DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)),
|
||||||
|
customOverrides = mapOf("nearestCity" to "Zurich"))
|
||||||
|
val alice = startNode(ALICE.name, rpcUsers = arrayListOf(user),
|
||||||
|
advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("cash"))),
|
||||||
|
customOverrides = mapOf("nearestCity" to "Milan"))
|
||||||
|
val bob = startNode(BOB.name, rpcUsers = arrayListOf(user),
|
||||||
|
advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("cash"))),
|
||||||
|
customOverrides = mapOf("nearestCity" to "Madrid"))
|
||||||
|
val ukBankName = X500Name("CN=UK Bank Plc,O=UK Bank Plc,L=London,C=UK")
|
||||||
|
val usaBankName = X500Name("CN=USA Bank Corp,O=USA Bank Corp,L=New York,C=USA")
|
||||||
|
val issuerGBP = startNode(ukBankName, rpcUsers = arrayListOf(manager),
|
||||||
|
advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer.GBP"))),
|
||||||
|
customOverrides = mapOf("nearestCity" to "London"))
|
||||||
|
val issuerUSD = startNode(usaBankName, rpcUsers = arrayListOf(manager),
|
||||||
|
advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer.USD"))),
|
||||||
|
customOverrides = mapOf("nearestCity" to "New York"))
|
||||||
|
|
||||||
|
notaryNode = notary.get()
|
||||||
|
aliceNode = alice.get()
|
||||||
|
bobNode = bob.get()
|
||||||
|
issuerNodeGBP = issuerGBP.get()
|
||||||
|
issuerNodeUSD = issuerUSD.get()
|
||||||
|
|
||||||
|
arrayOf(notaryNode, aliceNode, bobNode, issuerNodeGBP, issuerNodeUSD).forEach {
|
||||||
|
println("${it.nodeInfo.legalIdentity} started on ${it.configuration.rpcAddress}")
|
||||||
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
options.has("S") -> startNormalSimulation()
|
||||||
|
options.has("F") -> starErrorFlowsSimulation()
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForAllNodesToFinish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUpRPC() {
|
||||||
|
// Register with alice to use alice's RPC proxy to create random events.
|
||||||
|
val aliceClient = aliceNode.rpcClientToNode()
|
||||||
|
val aliceConnection = aliceClient.start(user.username, user.password)
|
||||||
|
val aliceRPC = aliceConnection.proxy
|
||||||
|
|
||||||
|
val bobClient = bobNode.rpcClientToNode()
|
||||||
|
val bobConnection = bobClient.start(user.username, user.password)
|
||||||
|
val bobRPC = bobConnection.proxy
|
||||||
|
|
||||||
|
val issuerClientGBP = issuerNodeGBP.rpcClientToNode()
|
||||||
|
val issuerGBPConnection = issuerClientGBP.start(manager.username, manager.password)
|
||||||
|
val issuerRPCGBP = issuerGBPConnection.proxy
|
||||||
|
|
||||||
|
val issuerClientUSD = issuerNodeUSD.rpcClientToNode()
|
||||||
|
val issuerUSDConnection =issuerClientUSD.start(manager.username, manager.password)
|
||||||
|
val issuerRPCUSD = issuerUSDConnection.proxy
|
||||||
|
|
||||||
|
RPCConnections.addAll(listOf(aliceConnection, bobConnection, issuerGBPConnection, issuerUSDConnection))
|
||||||
|
issuers.putAll(mapOf(USD to issuerRPCUSD, GBP to issuerRPCGBP))
|
||||||
|
|
||||||
|
parties.addAll(listOf(aliceNode.nodeInfo.legalIdentity to aliceRPC,
|
||||||
|
bobNode.nodeInfo.legalIdentity to bobRPC,
|
||||||
|
issuerNodeGBP.nodeInfo.legalIdentity to issuerRPCGBP,
|
||||||
|
issuerNodeUSD.nodeInfo.legalIdentity to issuerRPCUSD))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startSimulation(eventGenerator: EventGenerator, maxIterations: Int) {
|
||||||
|
// Log to logger when flow finish.
|
||||||
|
fun FlowHandle<SignedTransaction>.log(seq: Int, name: String) {
|
||||||
|
val out = "[$seq] $name $id :"
|
||||||
|
returnValue.success {
|
||||||
|
Main.log.info("$out ${it.id} ${(it.tx.outputs.first().data as Cash.State).amount}")
|
||||||
|
}.failure {
|
||||||
|
Main.log.info("$out ${it.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0..maxIterations) {
|
||||||
|
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")
|
||||||
|
} ?: command.startFlow(issuers[USD]!!).log(i, "${command.amount.token}Issuer") // TODO workaround
|
||||||
|
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())
|
||||||
|
// 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.toString())
|
||||||
|
}.generate(SplittableRandom())
|
||||||
|
}
|
||||||
|
println("Simulation completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startNormalSimulation() {
|
||||||
|
println("Running simulation mode ...")
|
||||||
|
setUpRPC()
|
||||||
|
val eventGenerator = EventGenerator(
|
||||||
|
parties = parties.map { it.first },
|
||||||
|
notary = notaryNode.nodeInfo.notaryIdentity,
|
||||||
|
currencies = listOf(GBP, USD)
|
||||||
|
)
|
||||||
|
val maxIterations = 100_000
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
startSimulation(eventGenerator, maxIterations)
|
||||||
|
onEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun starErrorFlowsSimulation() {
|
||||||
|
println("Running flows with errors simulation mode ...")
|
||||||
|
setUpRPC()
|
||||||
|
val eventGenerator = ErrorFlowsEventGenerator(
|
||||||
|
parties = parties.map { it.first },
|
||||||
|
notary = notaryNode.nodeInfo.notaryIdentity,
|
||||||
|
currencies = listOf(GBP, USD)
|
||||||
|
)
|
||||||
|
val maxIterations = 10_000
|
||||||
|
startSimulation(eventGenerator, maxIterations)
|
||||||
|
onEnd()
|
||||||
|
}
|
||||||
|
}
|
@ -156,129 +156,7 @@ class Main : App(MainView::class) {
|
|||||||
* 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.
|
* 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>) {
|
fun main(args: Array<String>) {
|
||||||
val portAllocation = PortAllocation.Incremental(20000)
|
val parser = OptionParser("SF")
|
||||||
driver(portAllocation = portAllocation) {
|
val options = parser.parse(*args)
|
||||||
val user = User("user1", "test", permissions = setOf(
|
ExplorerSimulation(options)
|
||||||
startFlowPermission<CashPaymentFlow>()
|
|
||||||
))
|
|
||||||
val manager = User("manager", "test", permissions = setOf(
|
|
||||||
startFlowPermission<CashIssueFlow>(),
|
|
||||||
startFlowPermission<CashPaymentFlow>(),
|
|
||||||
startFlowPermission<CashExitFlow>(),
|
|
||||||
startFlowPermission<IssuanceRequester>())
|
|
||||||
)
|
|
||||||
// TODO : Supported flow should be exposed somehow from the node instead of set of ServiceInfo.
|
|
||||||
val notary = startNode(DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)),
|
|
||||||
customOverrides = mapOf("nearestCity" to "Zurich"))
|
|
||||||
val alice = startNode(ALICE.name, rpcUsers = arrayListOf(user),
|
|
||||||
advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("cash"))),
|
|
||||||
customOverrides = mapOf("nearestCity" to "Milan"))
|
|
||||||
val bob = startNode(BOB.name, rpcUsers = arrayListOf(user),
|
|
||||||
advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("cash"))),
|
|
||||||
customOverrides = mapOf("nearestCity" to "Madrid"))
|
|
||||||
val issuerGBP = startNode(X500Name("CN=UK Bank Plc,O=UK Bank Plc,L=London,C=UK"), rpcUsers = arrayListOf(manager),
|
|
||||||
advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer.GBP"))),
|
|
||||||
customOverrides = mapOf("nearestCity" to "London"))
|
|
||||||
val issuerUSD = startNode(X500Name("CN=USA Bank Corp,O=USA Bank Corp,L=New York,C=US"), rpcUsers = arrayListOf(manager),
|
|
||||||
advertisedServices = setOf(ServiceInfo(ServiceType.corda.getSubType("issuer.USD"))),
|
|
||||||
customOverrides = mapOf("nearestCity" to "New York"))
|
|
||||||
|
|
||||||
val notaryNode = notary.get()
|
|
||||||
val aliceNode = alice.get()
|
|
||||||
val bobNode = bob.get()
|
|
||||||
val issuerNodeGBP = issuerGBP.get()
|
|
||||||
val issuerNodeUSD = issuerUSD.get()
|
|
||||||
|
|
||||||
arrayOf(notaryNode, aliceNode, bobNode, issuerNodeGBP, issuerNodeUSD).forEach {
|
|
||||||
println("${it.nodeInfo.legalIdentity} started on ${it.configuration.rpcAddress}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val parser = OptionParser("S")
|
|
||||||
val options = parser.parse(*args)
|
|
||||||
if (options.has("S")) {
|
|
||||||
println("Running simulation mode ...")
|
|
||||||
|
|
||||||
// Register with alice to use alice's RPC proxy to create random events.
|
|
||||||
val aliceClient = aliceNode.rpcClientToNode()
|
|
||||||
val aliceConnection = aliceClient.start(user.username, user.password)
|
|
||||||
val aliceRPC = aliceConnection.proxy
|
|
||||||
|
|
||||||
val bobClient = bobNode.rpcClientToNode()
|
|
||||||
val bobConnection = bobClient.start(user.username, user.password)
|
|
||||||
val bobRPC = bobConnection.proxy
|
|
||||||
|
|
||||||
val issuerClientGBP = issuerNodeGBP.rpcClientToNode()
|
|
||||||
val issuerGBPConnection = issuerClientGBP.start(manager.username, manager.password)
|
|
||||||
val issuerRPCGBP = issuerGBPConnection.proxy
|
|
||||||
|
|
||||||
val issuerClientUSD = issuerNodeUSD.rpcClientToNode()
|
|
||||||
val issuerUSDConnection = issuerClientUSD.start(manager.username, manager.password)
|
|
||||||
val issuerRPCUSD = issuerUSDConnection.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 = parties.map { it.first },
|
|
||||||
notary = notaryNode.nodeInfo.notaryIdentity,
|
|
||||||
currencies = listOf(GBP, USD)
|
|
||||||
)
|
|
||||||
|
|
||||||
val maxIterations = 100_000
|
|
||||||
// Log to logger when flow finish.
|
|
||||||
fun FlowHandle<SignedTransaction>.log(seq: Int, name: String) {
|
|
||||||
val out = "[$seq] $name $id :"
|
|
||||||
returnValue.success {
|
|
||||||
Main.log.info("$out ${it.id} ${(it.tx.outputs.first().data as Cash.State).amount}")
|
|
||||||
}.failure {
|
|
||||||
Main.log.info("$out ${it.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i in 0..maxIterations) {
|
|
||||||
Thread.sleep(300) //Thread.sleep(5000) -> todo rebase
|
|
||||||
// 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())
|
|
||||||
|
|
||||||
// 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.toString())
|
|
||||||
}.generate(SplittableRandom())
|
|
||||||
|
|
||||||
}
|
|
||||||
println("Simulation completed")
|
|
||||||
|
|
||||||
aliceConnection.close()
|
|
||||||
bobConnection.close()
|
|
||||||
issuerGBPConnection.close()
|
|
||||||
issuerUSDConnection.close()
|
|
||||||
}
|
|
||||||
waitForAllNodesToFinish()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user