Move all changes unrelated to flow view in explorer to open source.

This commit is contained in:
Katarzyna Streich 2017-06-20 14:51:28 +01:00 committed by Mike Hearn
parent b7bec90fae
commit ab1b7eb551
7 changed files with 113 additions and 11 deletions

View File

@ -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)

View File

@ -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
))
}

View File

@ -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()
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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 {

View File

@ -311,8 +311,8 @@
.scroll-bar:vertical {
-fx-background-color: transparent;
}
+/* Other */
/* Other */
.identicon {
-fx-border-radius: 2;
}