mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Move all changes unrelated to flow view in explorer to open source.
This commit is contained in:
parent
b7bec90fae
commit
ab1b7eb551
@ -19,8 +19,7 @@ import rx.subjects.PublishSubject
|
||||
data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String) {
|
||||
companion object {
|
||||
fun createStreamFromStateMachineInfo(stateMachine: StateMachineInfo): Observable<ProgressTrackingEvent>? {
|
||||
return stateMachine.progressTrackerStepAndUpdates?.let { pair ->
|
||||
val (current, future) = pair
|
||||
return stateMachine.progressTrackerStepAndUpdates?.let { (current, future) ->
|
||||
future.map { ProgressTrackingEvent(stateMachine.id, it) }.startWith(ProgressTrackingEvent(stateMachine.id, current))
|
||||
}
|
||||
}
|
||||
@ -75,6 +74,7 @@ class NodeMonitorModel {
|
||||
Observable.empty<ProgressTrackingEvent>()
|
||||
}
|
||||
}
|
||||
|
||||
// We need to retry, because when flow errors, we unsubscribe from progressTrackingSubject. So we end up with stream of state machine updates and no progress trackers.
|
||||
futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.retry().subscribe(progressTrackingSubject)
|
||||
|
||||
|
@ -13,7 +13,7 @@ import java.util.*
|
||||
* 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) {
|
||||
protected val partyGenerator = Generator.pickOne(parties)
|
||||
protected val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
|
||||
protected val amountGenerator = Generator.longRange(10000, 1000000)
|
||||
@ -34,12 +34,57 @@ class EventGenerator(val parties: List<Party>, val currencies: List<Currency>, v
|
||||
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)
|
||||
}
|
||||
|
||||
val issuerGenerator = Generator.frequency(listOf(
|
||||
open val issuerGenerator = Generator.frequency(listOf(
|
||||
0.1 to exitCashGenerator,
|
||||
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
|
||||
))
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
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
|
||||
@ -99,6 +100,7 @@ class ExplorerSimulation(val options: OptionSet) {
|
||||
|
||||
when {
|
||||
options.has("S") -> startNormalSimulation()
|
||||
options.has("F") -> startErrorFlowsSimulation()
|
||||
}
|
||||
|
||||
waitForAllNodesToFinish()
|
||||
@ -188,4 +190,17 @@ class ExplorerSimulation(val options: OptionSet) {
|
||||
startSimulation(eventGenerator, maxIterations)
|
||||
onEnd()
|
||||
}
|
||||
|
||||
private fun startErrorFlowsSimulation() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,9 @@ import net.corda.explorer.views.*
|
||||
import net.corda.explorer.views.cordapps.cash.CashViewer
|
||||
import org.apache.commons.lang.SystemUtils
|
||||
import org.controlsfx.dialog.ExceptionDialog
|
||||
import tornadofx.*
|
||||
import tornadofx.App
|
||||
import tornadofx.addStageIcon
|
||||
import tornadofx.find
|
||||
|
||||
/**
|
||||
* Main class for Explorer, you will need Tornado FX to run the explorer.
|
||||
@ -123,7 +125,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.
|
||||
*/
|
||||
fun main(args: Array<String>) {
|
||||
val parser = OptionParser("S")
|
||||
val parser = OptionParser("SF")
|
||||
val options = parser.parse(*args)
|
||||
ExplorerSimulation(options)
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import javafx.animation.FadeTransition
|
||||
import javafx.animation.TranslateTransition
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.beans.property.SimpleObjectProperty
|
||||
import javafx.beans.value.ObservableValue
|
||||
import javafx.collections.FXCollections
|
||||
import javafx.geometry.Bounds
|
||||
import javafx.geometry.Point2D
|
||||
@ -41,6 +42,9 @@ class Network : CordaView() {
|
||||
val notaries by observableList(NetworkIdentityModel::notaries)
|
||||
val peers by observableList(NetworkIdentityModel::parties)
|
||||
val transactions by observableList(TransactionDataModel::partiallyResolvedTransactions)
|
||||
var centralPeer: String? = null
|
||||
private var centralLabel: ObservableValue<Label?>
|
||||
|
||||
// UI components
|
||||
private val myIdentityPane by fxid<BorderPane>()
|
||||
private val notaryList by fxid<VBox>()
|
||||
@ -61,6 +65,7 @@ class Network : CordaView() {
|
||||
private val peerButtons = peerComponents.filtered { it.nodeInfo != myIdentity.value }.map { it.button }
|
||||
private val allComponents = FXCollections.observableArrayList(notaryComponents, peerComponents).concatenate()
|
||||
private val allComponentMap = allComponents.associateBy { it.nodeInfo.legalIdentity }
|
||||
private val mapLabels = allComponents.map { it.label }
|
||||
|
||||
private data class MapViewComponents(val nodeInfo: NodeInfo, val button: Button, val label: Label)
|
||||
|
||||
@ -133,19 +138,29 @@ class Network : CordaView() {
|
||||
return MapViewComponents(this, button, mapLabel)
|
||||
}
|
||||
|
||||
override fun onDock() {
|
||||
centralLabel = mapLabels.firstOrDefault(SimpleObjectProperty(myLabel), { centralPeer?.contains(it.text, true) ?: false })
|
||||
centralLabel.value?.let { mapScrollPane.centerLabel(it) }
|
||||
}
|
||||
|
||||
override fun onUndock() {
|
||||
centralPeer = null
|
||||
centralLabel = SimpleObjectProperty(myLabel)
|
||||
}
|
||||
|
||||
init {
|
||||
centralLabel = mapLabels.firstOrDefault(SimpleObjectProperty(myLabel), { centralPeer?.contains(it.text, true) ?: false })
|
||||
Bindings.bindContent(notaryList.children, notaryButtons)
|
||||
Bindings.bindContent(peerList.children, peerButtons)
|
||||
// Run once when the screen is ready.
|
||||
// TODO : Find a better way to do this.
|
||||
mapPane.heightProperty().addListener { _, old, _ ->
|
||||
if (old == 0.0) myLabel?.let {
|
||||
if (old == 0.0) centralLabel.value?.let {
|
||||
mapPane.applyCss()
|
||||
mapPane.layout()
|
||||
mapScrollPane.centerLabel(it)
|
||||
}
|
||||
}
|
||||
|
||||
// Listen on zooming gesture, if device has gesture support.
|
||||
mapPane.setOnZoom { zoom(it.zoomFactor, Point2D(it.x, it.y)) }
|
||||
|
||||
@ -164,6 +179,7 @@ class Network : CordaView() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO It doesn't work as expected.
|
||||
private fun ScrollPane.centerLabel(label: Label) {
|
||||
this.hvalue = (label.boundsInParent.width / 2 + label.boundsInParent.minX) / mapImageView.layoutBounds.width
|
||||
this.vvalue = (label.boundsInParent.height / 2 + label.boundsInParent.minY) / mapImageView.layoutBounds.height
|
||||
|
@ -55,6 +55,10 @@ class TransactionViewer : CordaView("Transactions") {
|
||||
|
||||
override val widgets = listOf(CordaWidget(title, TransactionWidget(), icon)).observable()
|
||||
|
||||
private var scrollPosition: Int = 0
|
||||
private lateinit var expander: ExpanderColumn<TransactionViewer.Transaction>
|
||||
var txIdToScroll: SecureHash? = null // Passed as param.
|
||||
|
||||
/**
|
||||
* This is what holds data for a single transaction node. Note how a lot of these are nullable as we often simply don't
|
||||
* have the data.
|
||||
@ -72,6 +76,26 @@ class TransactionViewer : CordaView("Transactions") {
|
||||
|
||||
data class Inputs(val resolved: ObservableList<StateAndRef<ContractState>>, val unresolved: ObservableList<StateRef>)
|
||||
|
||||
override fun onDock() {
|
||||
txIdToScroll?.let {
|
||||
scrollPosition = transactionViewTable.items.indexOfFirst { it.id == txIdToScroll }
|
||||
if (scrollPosition > 0) {
|
||||
expander.toggleExpanded(scrollPosition)
|
||||
val tx = transactionViewTable.items[scrollPosition]
|
||||
transactionViewTable.scrollTo(tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onUndock() {
|
||||
if (scrollPosition != 0) {
|
||||
val isExpanded = expander.getExpandedProperty(transactionViewTable.items[scrollPosition])
|
||||
if (isExpanded.value) expander.toggleExpanded(scrollPosition)
|
||||
scrollPosition = 0
|
||||
}
|
||||
txIdToScroll = null
|
||||
}
|
||||
|
||||
/**
|
||||
* We map the gathered data about transactions almost one-to-one to the nodes.
|
||||
*/
|
||||
@ -158,7 +182,7 @@ class TransactionViewer : CordaView("Transactions") {
|
||||
titleProperty.bind(reportingCurrency.map { "Total value ($it equiv)" })
|
||||
}
|
||||
|
||||
rowExpander {
|
||||
expander = rowExpander {
|
||||
add(ContractStatesView(it).root)
|
||||
prefHeight = 400.0
|
||||
}.apply {
|
||||
|
@ -311,8 +311,8 @@
|
||||
.scroll-bar:vertical {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
+/* Other */
|
||||
|
||||
/* Other */
|
||||
.identicon {
|
||||
-fx-border-radius: 2;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user