mirror of
https://github.com/corda/corda.git
synced 2025-02-20 17:33:15 +00:00
CORDA-2671: Remove broken ExplorerSimulator from Node Explorer. (#4811)
* CORDA-2671: Remove broken ExplorerSimulator from Node Explorer. * CORDA-2671: Document that Node Explorer is included with DemoBench.
This commit is contained in:
parent
7b93cc8477
commit
111acc5d6e
@ -26,68 +26,29 @@ Running the UI
|
||||
Running demo nodes
|
||||
------------------
|
||||
|
||||
A demonstration Corda network topology is configured with 5 nodes playing the following roles:
|
||||
Node Explorer is included with the :doc:`demobench` application, which allows
|
||||
you to create local Corda networks on your desktop. For example:
|
||||
|
||||
1. Notary
|
||||
2. Issuer nodes, representing two fictional central banks (UK Bank Plc issuer of GBP and USA Bank Corp issuer of USD)
|
||||
3. Participant nodes, representing two users (Alice and Bob)
|
||||
* Notary
|
||||
* Bank of Breakfast Tea (*Issuer node* for GBP)
|
||||
* Bank of Big Apples (*Issuer node* for USD)
|
||||
* Alice (Participant node, for user Alice)
|
||||
* Bob (Participant node, for user Bob)
|
||||
|
||||
DemoBench will deploy all nodes with Corda's Finance CorDapp automatically, and
|
||||
allow you to launch an instance of Node Explorer for each. You will be logged
|
||||
into the Node Explorer automatically.
|
||||
|
||||
When connected to an *Issuer* node, a user can execute cash transaction commands to issue and move cash to itself or other
|
||||
parties on the network or to exit cash (for itself only).
|
||||
|
||||
When connected to a *Participant* node a user can only execute cash transaction commands to move cash to other parties on the network.
|
||||
|
||||
The Demo Nodes can be started in one of two modes:
|
||||
The Node Explorer is also available as a stand-alone JavaFX application. It is
|
||||
available from the Corda repositories as ``corda-tools-explorer``, and can be
|
||||
run as
|
||||
|
||||
1. Normal
|
||||
|
||||
Fresh clean environment empty of transactions.
|
||||
Firstly, launch an Explorer instance to login to one of the Issuer nodes and issue some cash to the other participants (Bob and Alice).
|
||||
Then launch another Explorer instance to login to a participant node and start making payments (eg. move cash).
|
||||
You will only be able to exit (eg. redeem from the ledger) cash as an issuer node.
|
||||
|
||||
**Windows**::
|
||||
|
||||
gradlew.bat tools:explorer:runDemoNodes
|
||||
|
||||
**Other**::
|
||||
|
||||
./gradlew tools:explorer:runDemoNodes
|
||||
|
||||
2. Simulation
|
||||
|
||||
In this mode Nodes will automatically commence executing commands as part of a random generation process.
|
||||
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**::
|
||||
|
||||
gradlew.bat tools:explorer:runSimulationNodes
|
||||
|
||||
**Other**::
|
||||
|
||||
./gradlew tools:explorer:runSimulationNodes
|
||||
|
||||
|
||||
.. note:: 5 Corda nodes will be created on the following port on localhost by default.
|
||||
|
||||
* Notary -> 20005 (Does not accept logins)
|
||||
* UK Bank Plc -> 20011 (*Issuer node*)
|
||||
* USA Bank Corp -> 20008 (*Issuer node*)
|
||||
* Alice -> 20017
|
||||
* Bob -> 20014
|
||||
|
||||
Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``.
|
||||
Explorer login credentials to the Participants nodes are defaulted to ``user1`` and ``test``.
|
||||
Please note you are not allowed to login to the notary.
|
||||
|
||||
.. note:: When you start the nodes in Windows using the command prompt, they might not be killed when you close the
|
||||
window or terminate the task. If that happens you need to manually terminate the Java processes running the nodes.
|
||||
|
||||
.. note:: Alternatively, you may start the demo nodes from within IntelliJ using either of the run configurations
|
||||
``Explorer - demo nodes`` or ``Explorer - demo nodes (simulation)``
|
||||
|
||||
.. note:: It is also possible to start the Explorer GUI from IntelliJ via ``Explorer - GUI`` run configuration, provided that the optional TornadoFX plugin has been installed first.
|
||||
java -jar corda-tools-explorer.jar
|
||||
|
||||
.. note:: Use the Explorer in conjunction with the Trader Demo and Bank of Corda samples to use other *Issuer* nodes.
|
||||
|
||||
|
@ -13,36 +13,19 @@ The user can execute cash transaction commands to issue and move cash to other p
|
||||
|
||||
./gradlew tools:explorer:run
|
||||
|
||||
|
||||
## Running Demo Nodes
|
||||
|
||||
A demonstration Corda network topology is configured with 5 nodes playing the following roles:
|
||||
1. Notary
|
||||
2. Issuer nodes (representing two fictional central banks - UK Bank Plc issuer of GBP and USA Bank Corp issuer of USD)
|
||||
3. Participant nodes (representing two users - Alice and Bob)
|
||||
Node Explorer is included with the [DemoBench](https://docs.corda.net/demobench.html) application,
|
||||
which allows you to create local Corda networks on your desktop. For example:
|
||||
|
||||
The Issuer nodes have the ability to issue, move and exit cash amounts.
|
||||
The Participant nodes are only able to spend cash (eg. move cash).
|
||||
* Notary
|
||||
* Bank of Breakfast Tea (*Issuer node* for GBP)
|
||||
* Bank of Big Apples (*Issuer node* for USD)
|
||||
* Alice
|
||||
* Bob
|
||||
|
||||
**Windows:**
|
||||
|
||||
gradlew.bat tools:explorer:runDemoNodes
|
||||
|
||||
**Other:**
|
||||
|
||||
./gradlew tools:explorer:runDemoNodes
|
||||
|
||||
**These Corda nodes will be created on the following port on localhost.**
|
||||
|
||||
* Notary -> 20005 (Does not accept logins)
|
||||
* UK Bank Plc -> 20011 (*Issuer node*)
|
||||
* USA Bank Corp -> 20008 (*Issuer node*)
|
||||
* Alice -> 20017
|
||||
* Bob -> 20014
|
||||
|
||||
Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``.
|
||||
Explorer login credentials to the Participants nodes are defaulted to ``user1`` and ``test``.
|
||||
Please note you are not allowed to login to the notary.
|
||||
DemoBench will deploy all nodes with Corda's Finance CorDapp automatically, and allow you to launch an
|
||||
instance of Node Explorer for each.
|
||||
|
||||
## TODOs:
|
||||
- Shows more useful information in the dashboard.
|
||||
|
@ -22,12 +22,13 @@ dependencies {
|
||||
// Corda Core: Data structures and basic types needed to work with Corda.
|
||||
compile project(':core')
|
||||
compile project(':client:jfx')
|
||||
compile project(':client:mock')
|
||||
compile project(':node-driver')
|
||||
compile project(':finance:contracts')
|
||||
compile project(':finance:workflows')
|
||||
compile project(':tools:worldmap')
|
||||
|
||||
// Log4J: logging framework (with SLF4J bindings)
|
||||
compile "org.apache.logging.log4j:log4j-slf4j-impl:$log4j_version"
|
||||
|
||||
// Capsule is a library for building independently executable fat JARs.
|
||||
// We only need this dependency to compile our Caplet against.
|
||||
compileOnly "co.paralleluniverse:capsule:$capsule_version"
|
||||
@ -60,21 +61,10 @@ tasks.withType(JavaCompile) {
|
||||
options.compilerArgs << '-proc:none'
|
||||
}
|
||||
|
||||
task runDemoNodes(dependsOn: 'classes', type: JavaExec) {
|
||||
main = 'net.corda.explorer.MainKt'
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
}
|
||||
|
||||
task runSimulationNodes(dependsOn: 'classes', type: JavaExec) {
|
||||
main = 'net.corda.explorer.MainKt'
|
||||
classpath = sourceSets.main.runtimeClasspath
|
||||
args '-S'
|
||||
}
|
||||
|
||||
jar {
|
||||
manifest {
|
||||
attributes(
|
||||
'Automatic-Module-Name': 'net.corda.tools.explorer'
|
||||
'Automatic-Module-Name': 'net.corda.tools.explorer'
|
||||
)
|
||||
}
|
||||
}
|
@ -1,212 +0,0 @@
|
||||
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.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.CordaRPCConnection
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.thenMatch
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.FlowHandle
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.flows.AbstractCashFlow
|
||||
import net.corda.finance.flows.CashExitFlow
|
||||
import net.corda.finance.flows.CashExitFlow.ExitRequest
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.finance.internal.CashConfigDataFlow
|
||||
import net.corda.node.services.Permissions.Companion.startFlow
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.driver.*
|
||||
import net.corda.testing.driver.internal.incrementalPortAllocation
|
||||
import net.corda.testing.node.User
|
||||
import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
||||
import net.corda.testing.node.internal.FINANCE_WORKFLOWS_CORDAPP
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
|
||||
class ExplorerSimulation(private val options: OptionSet) {
|
||||
private val user = User("user1", "test", permissions = setOf(
|
||||
startFlow<CashPaymentFlow>(),
|
||||
startFlow<CashConfigDataFlow>()
|
||||
))
|
||||
private val manager = User("manager", "test", permissions = setOf(
|
||||
startFlow<CashIssueAndPaymentFlow>(),
|
||||
startFlow<CashPaymentFlow>(),
|
||||
startFlow<CashExitFlow>(),
|
||||
startFlow<CashConfigDataFlow>())
|
||||
)
|
||||
|
||||
private lateinit var notaryNode: NodeHandle
|
||||
private lateinit var aliceNode: NodeHandle
|
||||
private lateinit var bobNode: NodeHandle
|
||||
private lateinit var issuerNodeGBP: NodeHandle
|
||||
private lateinit var issuerNodeUSD: NodeHandle
|
||||
private lateinit var notary: Party
|
||||
|
||||
private val RPCConnections = ArrayList<CordaRPCConnection>()
|
||||
private val issuers = HashMap<Currency, CordaRPCOps>()
|
||||
private val parties = ArrayList<Pair<Party, CordaRPCOps>>()
|
||||
|
||||
init {
|
||||
startDemoNodes()
|
||||
}
|
||||
|
||||
private fun onEnd() {
|
||||
println("Closing RPC connections")
|
||||
RPCConnections.forEach { it.close() }
|
||||
}
|
||||
|
||||
private fun startDemoNodes() {
|
||||
val portAllocation = incrementalPortAllocation(20000)
|
||||
driver(DriverParameters(
|
||||
portAllocation = portAllocation,
|
||||
cordappsForAllNodes = FINANCE_CORDAPPS,
|
||||
waitForAllNodesToFinish = true,
|
||||
jmxPolicy = JmxPolicy.defaultEnabled()
|
||||
)) {
|
||||
// TODO : Supported flow should be exposed somehow from the node instead of set of ServiceInfo.
|
||||
val alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user))
|
||||
val bob = startNode(providedName = BOB_NAME, rpcUsers = listOf(user))
|
||||
val ukBankName = CordaX500Name(organisation = "UK Bank Plc", locality = "London", country = "GB")
|
||||
val usaBankName = CordaX500Name(organisation = "USA Bank Corp", locality = "New York", country = "US")
|
||||
val issuerGBP = startNode(NodeParameters(
|
||||
providedName = ukBankName,
|
||||
rpcUsers = listOf(manager),
|
||||
additionalCordapps = listOf(FINANCE_WORKFLOWS_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("GBP"))))
|
||||
))
|
||||
val issuerUSD = startNode(NodeParameters(
|
||||
providedName = usaBankName,
|
||||
rpcUsers = listOf(manager),
|
||||
additionalCordapps = listOf(FINANCE_WORKFLOWS_CORDAPP.copy(config = mapOf("issuableCurrencies" to listOf("USD"))))
|
||||
))
|
||||
|
||||
notaryNode = defaultNotaryNode.get()
|
||||
aliceNode = alice.get()
|
||||
bobNode = bob.get()
|
||||
issuerNodeGBP = issuerGBP.get()
|
||||
issuerNodeUSD = issuerUSD.get()
|
||||
|
||||
arrayOf(notaryNode, aliceNode, bobNode, issuerNodeGBP, issuerNodeUSD).forEach {
|
||||
println("${it.nodeInfo.legalIdentities.first()} started on ${it.rpcAddress}")
|
||||
}
|
||||
|
||||
when {
|
||||
options.has("S") -> startNormalSimulation()
|
||||
options.has("F") -> startErrorFlowsSimulation()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setUpRPC() {
|
||||
// Register with alice to use alice's RPC proxy to create random events.
|
||||
val aliceClient = CordaRPCClient(aliceNode.rpcAddress)
|
||||
val aliceConnection = aliceClient.start(user.username, user.password)
|
||||
val aliceRPC = aliceConnection.proxy
|
||||
|
||||
val bobClient = CordaRPCClient(bobNode.rpcAddress)
|
||||
val bobConnection = bobClient.start(user.username, user.password)
|
||||
val bobRPC = bobConnection.proxy
|
||||
|
||||
val issuerClientGBP = CordaRPCClient(issuerNodeGBP.rpcAddress)
|
||||
val issuerGBPConnection = issuerClientGBP.start(manager.username, manager.password)
|
||||
val issuerRPCGBP = issuerGBPConnection.proxy
|
||||
|
||||
val issuerClientUSD = CordaRPCClient(issuerNodeUSD.rpcAddress)
|
||||
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.legalIdentities.first() to aliceRPC,
|
||||
bobNode.nodeInfo.legalIdentities.first() to bobRPC,
|
||||
issuerNodeGBP.nodeInfo.legalIdentities.first() to issuerRPCGBP,
|
||||
issuerNodeUSD.nodeInfo.legalIdentities.first() to issuerRPCUSD))
|
||||
}
|
||||
|
||||
private fun startSimulation(eventGenerator: EventGenerator, maxIterations: Int) {
|
||||
// Log to logger when flow finish.
|
||||
fun FlowHandle<AbstractCashFlow.Result>.log(seq: Int, name: String) {
|
||||
val out = "[$seq] $name $id :"
|
||||
returnValue.thenMatch({ (stx) ->
|
||||
Main.log.info("$out ${stx.id} ${(stx.tx.outputs.first().data as Cash.State).amount}") // XXX: Why Main's log?
|
||||
}, {
|
||||
Main.log.info("$out ${it.message}")
|
||||
})
|
||||
}
|
||||
|
||||
for (i in 0..maxIterations) {
|
||||
Thread.sleep(1000)
|
||||
// Issuer requests.
|
||||
eventGenerator.issuerGenerator.map { request ->
|
||||
when (request) {
|
||||
is IssueAndPaymentRequest -> issuers[request.amount.token]?.let {
|
||||
println("${Instant.now()} [$i] ISSUING ${request.amount} with ref ${request.issueRef} to ${request.recipient}")
|
||||
it.startFlow(::CashIssueAndPaymentFlow, request).log(i, "${request.amount.token}Issuer")
|
||||
}
|
||||
is ExitRequest -> issuers[request.amount.token]?.let {
|
||||
println("${Instant.now()} [$i] EXITING ${request.amount} with ref ${request.issuerRef}")
|
||||
it.startFlow(::CashExitFlow, request).log(i, "${request.amount.token}Exit")
|
||||
}
|
||||
else -> throw IllegalArgumentException("Unsupported command: $request")
|
||||
}
|
||||
}.generate(SplittableRandom())
|
||||
// Party pay requests.
|
||||
eventGenerator.moveCashGenerator.combine(Generator.pickOne(parties)) { request, (party, rpc) ->
|
||||
println("${Instant.now()} [$i] SENDING ${request.amount} from $party to ${request.recipient}")
|
||||
rpc.startFlow(::CashPaymentFlow, request).log(i, party.name.toString())
|
||||
}.generate(SplittableRandom())
|
||||
}
|
||||
println("Simulation completed")
|
||||
}
|
||||
|
||||
private fun startNormalSimulation() {
|
||||
println("Running simulation mode ...")
|
||||
setUpRPC()
|
||||
notary = aliceNode.rpc.notaryIdentities().first()
|
||||
val eventGenerator = EventGenerator(
|
||||
parties = parties.map { it.first },
|
||||
notary = notary,
|
||||
currencies = listOf(GBP, USD)
|
||||
)
|
||||
val maxIterations = 100_000
|
||||
val anonymous = true
|
||||
// Pre allocate some money to each party.
|
||||
eventGenerator.parties.forEach {
|
||||
for (ref in 0..1) {
|
||||
for ((currency, issuer) in issuers) {
|
||||
val amount = Amount(1_000_000, currency)
|
||||
issuer.startFlow(::CashIssueAndPaymentFlow, amount, OpaqueBytes.of( ref.toByte() ),
|
||||
it, anonymous, notary).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
startSimulation(eventGenerator, maxIterations)
|
||||
onEnd()
|
||||
}
|
||||
|
||||
private fun startErrorFlowsSimulation() {
|
||||
println("Running flows with errors simulation mode ...")
|
||||
setUpRPC()
|
||||
val eventGenerator = ErrorFlowsEventGenerator(
|
||||
parties = parties.map { it.first },
|
||||
notary = notary,
|
||||
currencies = listOf(GBP, USD)
|
||||
)
|
||||
val maxIterations = 10_000
|
||||
startSimulation(eventGenerator, maxIterations)
|
||||
onEnd()
|
||||
}
|
||||
}
|
@ -8,11 +8,9 @@ import javafx.scene.control.ButtonType
|
||||
import javafx.scene.image.Image
|
||||
import javafx.stage.Stage
|
||||
import jfxtras.resources.JFXtrasFontRoboto
|
||||
import joptsimple.OptionParser
|
||||
import net.corda.client.jfx.model.Models
|
||||
import net.corda.client.jfx.model.NodeMonitorModel
|
||||
import net.corda.client.jfx.model.observableValue
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.explorer.model.CordaViewModel
|
||||
import net.corda.explorer.model.SettingsModel
|
||||
import net.corda.explorer.views.*
|
||||
@ -31,10 +29,6 @@ class Main : App(MainView::class) {
|
||||
private val loginView by inject<LoginView>()
|
||||
private val fullscreen by observableValue(SettingsModel::fullscreenProperty)
|
||||
|
||||
companion object {
|
||||
internal val log = contextLogger()
|
||||
}
|
||||
|
||||
override fun start(stage: Stage) {
|
||||
var nodeModel: NodeMonitorModel? = null
|
||||
|
||||
@ -122,16 +116,3 @@ class Main : App(MainView::class) {
|
||||
FontAwesomeIconFactory.get() // Force initialisation.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This main method will start 5 nodes (Notary, USA Bank, UK Bank, Bob and Alice) locally for UI testing,
|
||||
* which will bind to ports 20005, 20008, 20011, 20014 and 20017 locally.
|
||||
*
|
||||
* The simulation starts by pre-allocating chunks of cash to each of the parties in 2 currencies (USD, GBP), then it enters a loop which generates 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 parser = OptionParser("SF")
|
||||
val options = parser.parse(*args)
|
||||
ExplorerSimulation(options)
|
||||
}
|
||||
|
@ -39,7 +39,6 @@ import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow.PaymentRequest
|
||||
import net.corda.testing.core.singleIdentityAndCert
|
||||
import org.controlsfx.dialog.ExceptionDialog
|
||||
import tornadofx.*
|
||||
import java.math.BigDecimal
|
||||
@ -183,7 +182,7 @@ class NewTransaction : Fragment() {
|
||||
partyBLabel.textProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB?.let { "$it : " } })
|
||||
partyBChoiceBox.apply {
|
||||
visibleProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB }.isNotNull())
|
||||
items = FXCollections.observableList(parties.map { it.singleIdentityAndCert() }).sorted()
|
||||
items = FXCollections.observableList(parties.map { it.legalIdentitiesAndCerts.single() }).sorted()
|
||||
converter = stringConverter { it?.let { PartyNameFormatter.short.format(it.name) } ?: "" }
|
||||
}
|
||||
// Issuer
|
||||
|
Loading…
x
Reference in New Issue
Block a user