mirror of
https://github.com/corda/corda.git
synced 2025-01-03 03:36:48 +00:00
Merged pat-cash-creation-ui into master
This commit is contained in:
commit
3403d50168
@ -6,6 +6,7 @@ import com.r3corda.client.model.ProgressTrackingEvent
|
|||||||
import com.r3corda.core.bufferUntilSubscribed
|
import com.r3corda.core.bufferUntilSubscribed
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
|
import com.r3corda.core.node.services.NetworkMapCache
|
||||||
import com.r3corda.core.node.services.ServiceInfo
|
import com.r3corda.core.node.services.ServiceInfo
|
||||||
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
||||||
import com.r3corda.core.node.services.Vault
|
import com.r3corda.core.node.services.Vault
|
||||||
@ -17,8 +18,12 @@ import com.r3corda.node.driver.startClient
|
|||||||
import com.r3corda.node.services.messaging.NodeMessagingClient
|
import com.r3corda.node.services.messaging.NodeMessagingClient
|
||||||
import com.r3corda.node.services.messaging.StateMachineUpdate
|
import com.r3corda.node.services.messaging.StateMachineUpdate
|
||||||
import com.r3corda.node.services.transactions.SimpleNotaryService
|
import com.r3corda.node.services.transactions.SimpleNotaryService
|
||||||
import com.r3corda.testing.*
|
import com.r3corda.testing.expect
|
||||||
import org.junit.*
|
import com.r3corda.testing.expectEvents
|
||||||
|
import com.r3corda.testing.sequence
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Observer
|
import rx.Observer
|
||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
@ -37,7 +42,9 @@ class NodeMonitorModelTest {
|
|||||||
lateinit var progressTracking: Observable<ProgressTrackingEvent>
|
lateinit var progressTracking: Observable<ProgressTrackingEvent>
|
||||||
lateinit var transactions: Observable<SignedTransaction>
|
lateinit var transactions: Observable<SignedTransaction>
|
||||||
lateinit var vaultUpdates: Observable<Vault.Update>
|
lateinit var vaultUpdates: Observable<Vault.Update>
|
||||||
|
lateinit var networkMapUpdates: Observable<NetworkMapCache.MapChange>
|
||||||
lateinit var clientToService: Observer<ClientToServiceCommand>
|
lateinit var clientToService: Observer<ClientToServiceCommand>
|
||||||
|
lateinit var newNode: (String) -> NodeInfo
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun start() {
|
fun start() {
|
||||||
@ -49,7 +56,7 @@ class NodeMonitorModelTest {
|
|||||||
aliceNode = aliceNodeFuture.get()
|
aliceNode = aliceNodeFuture.get()
|
||||||
notaryNode = notaryNodeFuture.get()
|
notaryNode = notaryNodeFuture.get()
|
||||||
aliceClient = startClient(aliceNode).get()
|
aliceClient = startClient(aliceNode).get()
|
||||||
|
newNode = { nodeName -> startNode(nodeName).get() }
|
||||||
val monitor = NodeMonitorModel()
|
val monitor = NodeMonitorModel()
|
||||||
|
|
||||||
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
|
stateMachineTransactionMapping = monitor.stateMachineTransactionMapping.bufferUntilSubscribed()
|
||||||
@ -57,11 +64,13 @@ class NodeMonitorModelTest {
|
|||||||
progressTracking = monitor.progressTracking.bufferUntilSubscribed()
|
progressTracking = monitor.progressTracking.bufferUntilSubscribed()
|
||||||
transactions = monitor.transactions.bufferUntilSubscribed()
|
transactions = monitor.transactions.bufferUntilSubscribed()
|
||||||
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
|
vaultUpdates = monitor.vaultUpdates.bufferUntilSubscribed()
|
||||||
|
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
|
||||||
clientToService = monitor.clientToService
|
clientToService = monitor.clientToService
|
||||||
|
|
||||||
monitor.register(aliceNode, aliceClient.config.certificatesPath)
|
monitor.register(aliceNode, aliceClient.config.certificatesPath)
|
||||||
driverStarted.set(Unit)
|
driverStarted.set(Unit)
|
||||||
stopDriver.get()
|
stopDriver.get()
|
||||||
|
|
||||||
}
|
}
|
||||||
driverStopped.set(Unit)
|
driverStopped.set(Unit)
|
||||||
}
|
}
|
||||||
@ -74,6 +83,26 @@ class NodeMonitorModelTest {
|
|||||||
driverStopped.get()
|
driverStopped.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testNetworkMapUpdate() {
|
||||||
|
newNode("Bob")
|
||||||
|
newNode("Charlie")
|
||||||
|
networkMapUpdates.expectEvents(isStrict = false) {
|
||||||
|
sequence(
|
||||||
|
// TODO : Add test for remove when driver DSL support individual node shutdown.
|
||||||
|
expect { output: NetworkMapCache.MapChange ->
|
||||||
|
require(output.node.legalIdentity.name == "Alice") { output.node.legalIdentity.name }
|
||||||
|
},
|
||||||
|
expect { output: NetworkMapCache.MapChange ->
|
||||||
|
require(output.node.legalIdentity.name == "Bob") { output.node.legalIdentity.name }
|
||||||
|
},
|
||||||
|
expect { output: NetworkMapCache.MapChange ->
|
||||||
|
require(output.node.legalIdentity.name == "Charlie") { output.node.legalIdentity.name }
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun cashIssueWorksEndToEnd() {
|
fun cashIssueWorksEndToEnd() {
|
||||||
clientToService.onNext(ClientToServiceCommand.IssueCash(
|
clientToService.onNext(ClientToServiceCommand.IssueCash(
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
package com.r3corda.client.model
|
||||||
|
|
||||||
|
import com.r3corda.client.fxutils.foldToObservableList
|
||||||
|
import com.r3corda.core.node.NodeInfo
|
||||||
|
import com.r3corda.core.node.services.NetworkMapCache
|
||||||
|
import javafx.collections.ObservableList
|
||||||
|
import kotlinx.support.jdk8.collections.removeIf
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
class NetworkIdentityModel {
|
||||||
|
private val networkIdentityObservable: Observable<NetworkMapCache.MapChange> by observable(NodeMonitorModel::networkMap)
|
||||||
|
|
||||||
|
val networkIdentities: ObservableList<NodeInfo> =
|
||||||
|
networkIdentityObservable.foldToObservableList(Unit) { update, _accumulator, observableList ->
|
||||||
|
observableList.removeIf {
|
||||||
|
when (update.type) {
|
||||||
|
NetworkMapCache.MapChangeType.Removed -> it == update.node
|
||||||
|
NetworkMapCache.MapChangeType.Modified -> it == update.prevNodeInfo
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observableList.addAll(update.node)
|
||||||
|
}
|
||||||
|
}
|
@ -3,13 +3,16 @@ package com.r3corda.client.model
|
|||||||
import com.r3corda.client.CordaRPCClient
|
import com.r3corda.client.CordaRPCClient
|
||||||
import com.r3corda.core.contracts.ClientToServiceCommand
|
import com.r3corda.core.contracts.ClientToServiceCommand
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
|
import com.r3corda.core.node.services.NetworkMapCache
|
||||||
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
||||||
import com.r3corda.core.node.services.Vault
|
import com.r3corda.core.node.services.Vault
|
||||||
import com.r3corda.core.protocols.StateMachineRunId
|
import com.r3corda.core.protocols.StateMachineRunId
|
||||||
import com.r3corda.core.transactions.SignedTransaction
|
import com.r3corda.core.transactions.SignedTransaction
|
||||||
import com.r3corda.node.services.messaging.ArtemisMessagingComponent
|
import com.r3corda.node.services.messaging.ArtemisMessagingComponent
|
||||||
|
import com.r3corda.node.services.messaging.CordaRPCOps
|
||||||
import com.r3corda.node.services.messaging.StateMachineInfo
|
import com.r3corda.node.services.messaging.StateMachineInfo
|
||||||
import com.r3corda.node.services.messaging.StateMachineUpdate
|
import com.r3corda.node.services.messaging.StateMachineUpdate
|
||||||
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
@ -35,16 +38,20 @@ class NodeMonitorModel {
|
|||||||
private val transactionsSubject = PublishSubject.create<SignedTransaction>()
|
private val transactionsSubject = PublishSubject.create<SignedTransaction>()
|
||||||
private val stateMachineTransactionMappingSubject = PublishSubject.create<StateMachineTransactionMapping>()
|
private val stateMachineTransactionMappingSubject = PublishSubject.create<StateMachineTransactionMapping>()
|
||||||
private val progressTrackingSubject = PublishSubject.create<ProgressTrackingEvent>()
|
private val progressTrackingSubject = PublishSubject.create<ProgressTrackingEvent>()
|
||||||
|
private val networkMapSubject = PublishSubject.create<NetworkMapCache.MapChange>()
|
||||||
|
|
||||||
val stateMachineUpdates: Observable<StateMachineUpdate> = stateMachineUpdatesSubject
|
val stateMachineUpdates: Observable<StateMachineUpdate> = stateMachineUpdatesSubject
|
||||||
val vaultUpdates: Observable<Vault.Update> = vaultUpdatesSubject
|
val vaultUpdates: Observable<Vault.Update> = vaultUpdatesSubject
|
||||||
val transactions: Observable<SignedTransaction> = transactionsSubject
|
val transactions: Observable<SignedTransaction> = transactionsSubject
|
||||||
val stateMachineTransactionMapping: Observable<StateMachineTransactionMapping> = stateMachineTransactionMappingSubject
|
val stateMachineTransactionMapping: Observable<StateMachineTransactionMapping> = stateMachineTransactionMappingSubject
|
||||||
val progressTracking: Observable<ProgressTrackingEvent> = progressTrackingSubject
|
val progressTracking: Observable<ProgressTrackingEvent> = progressTrackingSubject
|
||||||
|
val networkMap: Observable<NetworkMapCache.MapChange> = networkMapSubject
|
||||||
|
|
||||||
private val clientToServiceSource = PublishSubject.create<ClientToServiceCommand>()
|
private val clientToServiceSource = PublishSubject.create<ClientToServiceCommand>()
|
||||||
val clientToService: PublishSubject<ClientToServiceCommand> = clientToServiceSource
|
val clientToService: PublishSubject<ClientToServiceCommand> = clientToServiceSource
|
||||||
|
|
||||||
|
val proxyObservable = SimpleObjectProperty<CordaRPCOps?>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register for updates to/from a given vault.
|
* Register for updates to/from a given vault.
|
||||||
* @param messagingService The messaging to use for communication.
|
* @param messagingService The messaging to use for communication.
|
||||||
@ -89,9 +96,15 @@ class NodeMonitorModel {
|
|||||||
val (smTxMappings, futureSmTxMappings) = proxy.stateMachineRecordedTransactionMapping()
|
val (smTxMappings, futureSmTxMappings) = proxy.stateMachineRecordedTransactionMapping()
|
||||||
futureSmTxMappings.startWith(smTxMappings).subscribe(stateMachineTransactionMappingSubject)
|
futureSmTxMappings.startWith(smTxMappings).subscribe(stateMachineTransactionMappingSubject)
|
||||||
|
|
||||||
|
// Parties on network
|
||||||
|
val (parties, futurePartyUpdate) = proxy.networkMapUpdates()
|
||||||
|
futurePartyUpdate.startWith(parties.map { NetworkMapCache.MapChange(it, null, NetworkMapCache.MapChangeType.Added) }).subscribe(networkMapSubject)
|
||||||
|
|
||||||
// Client -> Service
|
// Client -> Service
|
||||||
clientToServiceSource.subscribe {
|
clientToServiceSource.subscribe {
|
||||||
proxy.executeCommand(it)
|
proxy.executeCommand(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proxyObservable.set(proxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -8,8 +8,8 @@ import com.r3corda.core.messaging.MessagingService
|
|||||||
import com.r3corda.core.messaging.SingleMessageRecipient
|
import com.r3corda.core.messaging.SingleMessageRecipient
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.security.PublicKey
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A network map contains lists of nodes on the network along with information about their identity keys, services
|
* A network map contains lists of nodes on the network along with information about their identity keys, services
|
||||||
@ -23,7 +23,7 @@ interface NetworkMapCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class MapChangeType { Added, Removed, Modified }
|
enum class MapChangeType { Added, Removed, Modified }
|
||||||
data class MapChange(val node: NodeInfo, val prevNodeInfo: NodeInfo?, val type: MapChangeType )
|
data class MapChange(val node: NodeInfo, val prevNodeInfo: NodeInfo?, val type: MapChangeType)
|
||||||
|
|
||||||
/** A list of nodes that advertise a network map service */
|
/** A list of nodes that advertise a network map service */
|
||||||
val networkMapNodes: List<NodeInfo>
|
val networkMapNodes: List<NodeInfo>
|
||||||
@ -43,6 +43,12 @@ interface NetworkMapCache {
|
|||||||
*/
|
*/
|
||||||
val regulators: List<NodeInfo>
|
val regulators: List<NodeInfo>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Atomically get the current party nodes and a stream of updates. Note that the Observable buffers updates until the
|
||||||
|
* first subscriber is registered so as to avoid racing with early updates.
|
||||||
|
*/
|
||||||
|
fun track(): Pair<List<NodeInfo>, Observable<MapChange>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a copy of all nodes in the map.
|
* Get a copy of all nodes in the map.
|
||||||
*/
|
*/
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
package com.r3corda.explorer
|
package com.r3corda.explorer
|
||||||
|
|
||||||
import com.r3corda.client.mock.EventGenerator
|
|
||||||
import com.r3corda.client.model.Models
|
import com.r3corda.client.model.Models
|
||||||
import com.r3corda.client.model.NodeMonitorModel
|
import com.r3corda.client.model.NodeMonitorModel
|
||||||
import com.r3corda.client.model.subject
|
|
||||||
import com.r3corda.core.contracts.ClientToServiceCommand
|
|
||||||
import com.r3corda.core.node.services.ServiceInfo
|
import com.r3corda.core.node.services.ServiceInfo
|
||||||
import com.r3corda.explorer.model.IdentityModel
|
import com.r3corda.explorer.model.IdentityModel
|
||||||
import com.r3corda.node.driver.PortAllocation
|
import com.r3corda.node.driver.PortAllocation
|
||||||
@ -12,13 +9,10 @@ import com.r3corda.node.driver.driver
|
|||||||
import com.r3corda.node.driver.startClient
|
import com.r3corda.node.driver.startClient
|
||||||
import com.r3corda.node.services.transactions.SimpleNotaryService
|
import com.r3corda.node.services.transactions.SimpleNotaryService
|
||||||
import javafx.stage.Stage
|
import javafx.stage.Stage
|
||||||
import rx.subjects.Subject
|
|
||||||
import tornadofx.App
|
import tornadofx.App
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
class Main : App() {
|
class Main : App() {
|
||||||
override val primaryView = MainWindow::class
|
override val primaryView = MainWindow::class
|
||||||
val aliceOutStream: Subject<ClientToServiceCommand, ClientToServiceCommand> by subject(NodeMonitorModel::clientToService)
|
|
||||||
|
|
||||||
override fun start(stage: Stage) {
|
override fun start(stage: Stage) {
|
||||||
|
|
||||||
@ -32,7 +26,6 @@ class Main : App() {
|
|||||||
// start the driver on another thread
|
// start the driver on another thread
|
||||||
// TODO Change this to connecting to an actual node (specified on cli/in a config) once we're happy with the code
|
// TODO Change this to connecting to an actual node (specified on cli/in a config) once we're happy with the code
|
||||||
Thread({
|
Thread({
|
||||||
|
|
||||||
val portAllocation = PortAllocation.Incremental(20000)
|
val portAllocation = PortAllocation.Incremental(20000)
|
||||||
driver(portAllocation = portAllocation) {
|
driver(portAllocation = portAllocation) {
|
||||||
|
|
||||||
@ -44,10 +37,13 @@ class Main : App() {
|
|||||||
|
|
||||||
val aliceClient = startClient(aliceNode).get()
|
val aliceClient = startClient(aliceNode).get()
|
||||||
|
|
||||||
|
Models.get<IdentityModel>(Main::class).notary.set(notaryNode.notaryIdentity)
|
||||||
Models.get<IdentityModel>(Main::class).myIdentity.set(aliceNode.legalIdentity)
|
Models.get<IdentityModel>(Main::class).myIdentity.set(aliceNode.legalIdentity)
|
||||||
Models.get<NodeMonitorModel>(Main::class).register(aliceNode, aliceClient.config.certificatesPath)
|
Models.get<NodeMonitorModel>(Main::class).register(aliceNode, aliceClient.config.certificatesPath)
|
||||||
|
|
||||||
for (i in 0 .. 10000) {
|
startNode("Bob").get()
|
||||||
|
|
||||||
|
/* for (i in 0 .. 10000) {
|
||||||
Thread.sleep(500)
|
Thread.sleep(500)
|
||||||
|
|
||||||
val eventGenerator = EventGenerator(
|
val eventGenerator = EventGenerator(
|
||||||
@ -58,11 +54,9 @@ class Main : App() {
|
|||||||
eventGenerator.clientToServiceCommandGenerator.map { command ->
|
eventGenerator.clientToServiceCommandGenerator.map { command ->
|
||||||
aliceOutStream.onNext(command)
|
aliceOutStream.onNext(command)
|
||||||
}.generate(Random())
|
}.generate(Random())
|
||||||
}
|
}*/
|
||||||
|
|
||||||
waitForAllNodesToFinish()
|
waitForAllNodesToFinish()
|
||||||
}
|
}
|
||||||
|
|
||||||
}).start()
|
}).start()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.r3corda.explorer.components
|
||||||
|
|
||||||
|
import javafx.scene.control.Alert
|
||||||
|
import javafx.scene.control.Label
|
||||||
|
import javafx.scene.control.TextArea
|
||||||
|
import javafx.scene.layout.GridPane
|
||||||
|
import javafx.scene.layout.Priority
|
||||||
|
import java.io.PrintWriter
|
||||||
|
import java.io.StringWriter
|
||||||
|
|
||||||
|
class ExceptionDialog(ex: Throwable) : Alert(AlertType.ERROR) {
|
||||||
|
|
||||||
|
private fun Throwable.toExceptionText(): String {
|
||||||
|
return StringWriter().use {
|
||||||
|
PrintWriter(it).use {
|
||||||
|
this.printStackTrace(it)
|
||||||
|
}
|
||||||
|
it.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Create expandable Exception.
|
||||||
|
val label = Label("The exception stacktrace was:")
|
||||||
|
contentText = ex.message
|
||||||
|
|
||||||
|
val textArea = TextArea(ex.toExceptionText())
|
||||||
|
textArea.isEditable = false
|
||||||
|
textArea.isWrapText = true
|
||||||
|
|
||||||
|
textArea.maxWidth = Double.MAX_VALUE
|
||||||
|
textArea.maxHeight = Double.MAX_VALUE
|
||||||
|
GridPane.setVgrow(textArea, Priority.ALWAYS)
|
||||||
|
GridPane.setHgrow(textArea, Priority.ALWAYS)
|
||||||
|
|
||||||
|
val expContent = GridPane()
|
||||||
|
expContent.maxWidth = Double.MAX_VALUE
|
||||||
|
expContent.add(label, 0, 0)
|
||||||
|
expContent.add(textArea, 0, 1)
|
||||||
|
|
||||||
|
// Set expandable Exception into the dialog pane.
|
||||||
|
dialogPane.expandableContent = expContent
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,7 @@ package com.r3corda.explorer.model
|
|||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import javafx.beans.property.SimpleObjectProperty
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
|
|
||||||
|
|
||||||
class IdentityModel {
|
class IdentityModel {
|
||||||
val myIdentity = SimpleObjectProperty<Party>()
|
val myIdentity = SimpleObjectProperty<Party?>()
|
||||||
|
val notary = SimpleObjectProperty<Party?>()
|
||||||
}
|
}
|
@ -5,7 +5,8 @@ import javafx.beans.property.SimpleObjectProperty
|
|||||||
enum class SelectedView {
|
enum class SelectedView {
|
||||||
Home,
|
Home,
|
||||||
Cash,
|
Cash,
|
||||||
Transaction
|
Transaction,
|
||||||
|
NewTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
class TopLevelModel {
|
class TopLevelModel {
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package com.r3corda.explorer.model
|
||||||
|
|
||||||
|
enum class CashTransaction(val partyNameA: String, val partyNameB: String?) {
|
||||||
|
Issue("Issuer Bank", "Receiver Bank"),
|
||||||
|
Pay("Payer", "Payee"),
|
||||||
|
Exit("Issuer Bank", null);
|
||||||
|
}
|
@ -33,6 +33,7 @@ class Header : View() {
|
|||||||
SelectedView.Home -> "Home"
|
SelectedView.Home -> "Home"
|
||||||
SelectedView.Cash -> "Cash"
|
SelectedView.Cash -> "Cash"
|
||||||
SelectedView.Transaction -> "Transactions"
|
SelectedView.Transaction -> "Transactions"
|
||||||
|
SelectedView.NewTransaction -> "New Transaction"
|
||||||
null -> "Home"
|
null -> "Home"
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -42,6 +43,7 @@ class Header : View() {
|
|||||||
SelectedView.Home -> homeImage
|
SelectedView.Home -> homeImage
|
||||||
SelectedView.Cash -> cashImage
|
SelectedView.Cash -> cashImage
|
||||||
SelectedView.Transaction -> transactionImage
|
SelectedView.Transaction -> transactionImage
|
||||||
|
SelectedView.NewTransaction -> cashImage
|
||||||
null -> homeImage
|
null -> homeImage
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -18,11 +18,9 @@ import javafx.scene.control.Label
|
|||||||
import javafx.scene.control.TitledPane
|
import javafx.scene.control.TitledPane
|
||||||
import javafx.scene.input.MouseButton
|
import javafx.scene.input.MouseButton
|
||||||
import javafx.scene.layout.TilePane
|
import javafx.scene.layout.TilePane
|
||||||
import org.fxmisc.easybind.EasyBind
|
|
||||||
import tornadofx.View
|
import tornadofx.View
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
class Home : View() {
|
class Home : View() {
|
||||||
override val root: TilePane by fxml()
|
override val root: TilePane by fxml()
|
||||||
|
|
||||||
@ -32,6 +30,8 @@ class Home : View() {
|
|||||||
private val ourTransactionsPane: TitledPane by fxid()
|
private val ourTransactionsPane: TitledPane by fxid()
|
||||||
private val ourTransactionsLabel: Label by fxid()
|
private val ourTransactionsLabel: Label by fxid()
|
||||||
|
|
||||||
|
private val newTransaction: TitledPane by fxid()
|
||||||
|
|
||||||
private val selectedView: WritableValue<SelectedView> by writableValue(TopLevelModel::selectedView)
|
private val selectedView: WritableValue<SelectedView> by writableValue(TopLevelModel::selectedView)
|
||||||
private val cashStates: ObservableList<StateAndRef<Cash.State>> by observableList(ContractStateModel::cashStates)
|
private val cashStates: ObservableList<StateAndRef<Cash.State>> by observableList(ContractStateModel::cashStates)
|
||||||
private val gatheredTransactionDataList: ObservableList<out GatheredTransactionData>
|
private val gatheredTransactionDataList: ObservableList<out GatheredTransactionData>
|
||||||
@ -63,6 +63,11 @@ class Home : View() {
|
|||||||
selectedView.value = SelectedView.Transaction
|
selectedView.value = SelectedView.Transaction
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
newTransaction.setOnMouseClicked { clickEvent ->
|
||||||
|
if (clickEvent.button == MouseButton.PRIMARY) {
|
||||||
|
selectedView.value = SelectedView.NewTransaction
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,177 @@
|
|||||||
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
|
import com.r3corda.client.fxutils.map
|
||||||
|
import com.r3corda.client.model.NetworkIdentityModel
|
||||||
|
import com.r3corda.client.model.NodeMonitorModel
|
||||||
|
import com.r3corda.client.model.observableList
|
||||||
|
import com.r3corda.client.model.observableValue
|
||||||
|
import com.r3corda.core.contracts.*
|
||||||
|
import com.r3corda.core.crypto.Party
|
||||||
|
import com.r3corda.core.node.NodeInfo
|
||||||
|
import com.r3corda.core.serialization.OpaqueBytes
|
||||||
|
import com.r3corda.explorer.components.ExceptionDialog
|
||||||
|
import com.r3corda.explorer.model.CashTransaction
|
||||||
|
import com.r3corda.explorer.model.IdentityModel
|
||||||
|
import com.r3corda.node.services.messaging.CordaRPCOps
|
||||||
|
import com.r3corda.node.services.messaging.TransactionBuildResult
|
||||||
|
import javafx.beans.binding.Bindings
|
||||||
|
import javafx.beans.binding.BooleanBinding
|
||||||
|
import javafx.beans.value.ObservableValue
|
||||||
|
import javafx.collections.FXCollections
|
||||||
|
import javafx.collections.ObservableList
|
||||||
|
import javafx.scene.Node
|
||||||
|
import javafx.scene.Parent
|
||||||
|
import javafx.scene.control.*
|
||||||
|
import javafx.util.StringConverter
|
||||||
|
import javafx.util.converter.BigDecimalStringConverter
|
||||||
|
import tornadofx.View
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.util.*
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
class NewTransaction : View() {
|
||||||
|
override val root: Parent by fxml()
|
||||||
|
|
||||||
|
private val partyATextField: TextField by fxid()
|
||||||
|
private val partyBChoiceBox: ChoiceBox<NodeInfo> by fxid()
|
||||||
|
private val partyALabel: Label by fxid()
|
||||||
|
private val partyBLabel: Label by fxid()
|
||||||
|
private val amountLabel: Label by fxid()
|
||||||
|
|
||||||
|
private val executeButton: Button by fxid()
|
||||||
|
|
||||||
|
private val transactionTypeCB: ChoiceBox<CashTransaction> by fxid()
|
||||||
|
private val amount: TextField by fxid()
|
||||||
|
private val currency: ChoiceBox<Currency> by fxid()
|
||||||
|
|
||||||
|
private val networkIdentities: ObservableList<NodeInfo> by observableList(NetworkIdentityModel::networkIdentities)
|
||||||
|
|
||||||
|
private val rpcProxy: ObservableValue<CordaRPCOps?> by observableValue(NodeMonitorModel::proxyObservable)
|
||||||
|
private val myIdentity: ObservableValue<Party?> by observableValue(IdentityModel::myIdentity)
|
||||||
|
private val notary: ObservableValue<Party?> by observableValue(IdentityModel::notary)
|
||||||
|
|
||||||
|
private val issueRefLabel: Label by fxid()
|
||||||
|
private val issueRefTextField: TextField by fxid()
|
||||||
|
|
||||||
|
private fun ObservableValue<*>.isNotNull(): BooleanBinding {
|
||||||
|
return Bindings.createBooleanBinding({ this.value != null }, arrayOf(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun resetScreen() {
|
||||||
|
partyBChoiceBox.valueProperty().set(null)
|
||||||
|
transactionTypeCB.valueProperty().set(null)
|
||||||
|
currency.valueProperty().set(null)
|
||||||
|
amount.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Disable everything when not connected to node.
|
||||||
|
val enableProperty = myIdentity.isNotNull().and(notary.isNotNull()).and(rpcProxy.isNotNull())
|
||||||
|
root.disableProperty().bind(enableProperty.not())
|
||||||
|
transactionTypeCB.items = FXCollections.observableArrayList(CashTransaction.values().asList())
|
||||||
|
|
||||||
|
// Party A textfield always display my identity name, not editable.
|
||||||
|
partyATextField.isEditable = false
|
||||||
|
partyATextField.textProperty().bind(myIdentity.map { it?.name ?: "" })
|
||||||
|
partyALabel.textProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameA?.let { "$it : " } })
|
||||||
|
partyATextField.visibleProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameA }.isNotNull())
|
||||||
|
|
||||||
|
partyBLabel.textProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB?.let { "$it : " } })
|
||||||
|
partyBChoiceBox.visibleProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB }.isNotNull())
|
||||||
|
partyBChoiceBox.items = networkIdentities
|
||||||
|
|
||||||
|
partyBChoiceBox.converter = object : StringConverter<NodeInfo?>() {
|
||||||
|
override fun toString(node: NodeInfo?): String {
|
||||||
|
return node?.legalIdentity?.name ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fromString(string: String?): NodeInfo {
|
||||||
|
throw UnsupportedOperationException("not implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigDecimal text Formatter, restricting text box input to decimal values.
|
||||||
|
val textFormatter = Pattern.compile("-?((\\d*)|(\\d+\\.\\d*))").run {
|
||||||
|
TextFormatter<BigDecimal>(BigDecimalStringConverter(), null) { change ->
|
||||||
|
val newText = change.controlNewText
|
||||||
|
if (matcher(newText).matches()) change else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
amount.textFormatter = textFormatter
|
||||||
|
|
||||||
|
// Hide currency and amount fields when transaction type is not specified.
|
||||||
|
// TODO : Create a currency model to store these values
|
||||||
|
currency.items = FXCollections.observableList(setOf(USD, GBP, CHF).toList())
|
||||||
|
currency.visibleProperty().bind(transactionTypeCB.valueProperty().isNotNull)
|
||||||
|
amount.visibleProperty().bind(transactionTypeCB.valueProperty().isNotNull)
|
||||||
|
amountLabel.visibleProperty().bind(transactionTypeCB.valueProperty().isNotNull)
|
||||||
|
issueRefLabel.visibleProperty().bind(transactionTypeCB.valueProperty().isNotNull)
|
||||||
|
issueRefTextField.visibleProperty().bind(transactionTypeCB.valueProperty().isNotNull)
|
||||||
|
|
||||||
|
// Validate inputs.
|
||||||
|
val formValidCondition = arrayOf(
|
||||||
|
myIdentity.isNotNull(),
|
||||||
|
transactionTypeCB.valueProperty().isNotNull,
|
||||||
|
partyBChoiceBox.visibleProperty().not().or(partyBChoiceBox.valueProperty().isNotNull),
|
||||||
|
textFormatter.valueProperty().isNotNull,
|
||||||
|
textFormatter.valueProperty().isNotEqualTo(BigDecimal.ZERO),
|
||||||
|
currency.valueProperty().isNotNull
|
||||||
|
).reduce(BooleanBinding::and)
|
||||||
|
|
||||||
|
// Enable execute button when form is valid.
|
||||||
|
executeButton.disableProperty().bind(formValidCondition.not())
|
||||||
|
executeButton.setOnAction { event ->
|
||||||
|
// Null checks to ensure these observable values are set, execute button should be disabled if any of these value are null, this extra checks are for precaution and getting non-nullable values without using !!.
|
||||||
|
myIdentity.value?.let { myIdentity ->
|
||||||
|
notary.value?.let { notary ->
|
||||||
|
rpcProxy.value?.let { rpcProxy ->
|
||||||
|
Triple(myIdentity, notary, rpcProxy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}?.let {
|
||||||
|
val (myIdentity, notary, rpcProxy) = it
|
||||||
|
transactionTypeCB.value?.let {
|
||||||
|
val issueRef = OpaqueBytes(if (issueRefTextField.text.trim().isNotBlank()) issueRefTextField.text.toByteArray() else ByteArray(1, { 1 }))
|
||||||
|
val command = when (it) {
|
||||||
|
CashTransaction.Issue -> ClientToServiceCommand.IssueCash(Amount(textFormatter.value, currency.value), issueRef, partyBChoiceBox.value.legalIdentity, notary)
|
||||||
|
CashTransaction.Pay -> ClientToServiceCommand.PayCash(Amount(textFormatter.value, Issued(PartyAndReference(myIdentity, issueRef), currency.value)), partyBChoiceBox.value.legalIdentity)
|
||||||
|
CashTransaction.Exit -> ClientToServiceCommand.ExitCash(Amount(textFormatter.value, currency.value), issueRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialog = Alert(Alert.AlertType.INFORMATION).apply {
|
||||||
|
headerText = null
|
||||||
|
contentText = "Transaction Started."
|
||||||
|
dialogPane.isDisable = true
|
||||||
|
initOwner((event.target as Node).scene.window)
|
||||||
|
}
|
||||||
|
dialog.show()
|
||||||
|
runAsync {
|
||||||
|
rpcProxy.executeCommand(command)
|
||||||
|
}.ui {
|
||||||
|
dialog.contentText = when (it) {
|
||||||
|
is TransactionBuildResult.ProtocolStarted -> {
|
||||||
|
dialog.alertType = Alert.AlertType.INFORMATION
|
||||||
|
dialog.setOnCloseRequest { resetScreen() }
|
||||||
|
"Transaction Started \nTransaction ID : ${it.transaction?.id} \nMessage : ${it.message}"
|
||||||
|
}
|
||||||
|
is TransactionBuildResult.Failed -> {
|
||||||
|
dialog.alertType = Alert.AlertType.ERROR
|
||||||
|
it.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.dialogPane.isDisable = false
|
||||||
|
dialog.dialogPane.scene.window.sizeToScene()
|
||||||
|
}.setOnFailed {
|
||||||
|
dialog.close()
|
||||||
|
ExceptionDialog(it.source.exception).apply {
|
||||||
|
initOwner((event.target as Node).scene.window)
|
||||||
|
showAndWait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Remove focus from textfield when click on the blank area.
|
||||||
|
root.setOnMouseClicked { e -> root.requestFocus() }
|
||||||
|
}
|
||||||
|
}
|
@ -20,17 +20,20 @@ class TopLevel : View() {
|
|||||||
private val home: Home by inject()
|
private val home: Home by inject()
|
||||||
private val cash: CashViewer by inject()
|
private val cash: CashViewer by inject()
|
||||||
private val transaction: TransactionViewer by inject()
|
private val transaction: TransactionViewer by inject()
|
||||||
|
private val newTransaction: NewTransaction by inject()
|
||||||
|
|
||||||
// Note: this is weirdly very important, as it forces the initialisation of Views. Therefore this is the entry
|
// Note: this is weirdly very important, as it forces the initialisation of Views. Therefore this is the entry
|
||||||
// point to the top level observable/stream wiring! Any events sent before this init may be lost!
|
// point to the top level observable/stream wiring! Any events sent before this init may be lost!
|
||||||
private val homeRoot = home.root
|
private val homeRoot = home.root
|
||||||
private val cashRoot = cash.root
|
private val cashRoot = cash.root
|
||||||
private val transactionRoot = transaction.root
|
private val transactionRoot = transaction.root
|
||||||
|
private val newTransactionRoot = newTransaction.root
|
||||||
|
|
||||||
private fun getView(selection: SelectedView) = when (selection) {
|
private fun getView(selection: SelectedView) = when (selection) {
|
||||||
SelectedView.Home -> homeRoot
|
SelectedView.Home -> homeRoot
|
||||||
SelectedView.Cash -> cashRoot
|
SelectedView.Cash -> cashRoot
|
||||||
SelectedView.Transaction -> transactionRoot
|
SelectedView.Transaction -> transactionRoot
|
||||||
|
SelectedView.NewTransaction -> newTransactionRoot
|
||||||
}
|
}
|
||||||
val selectedView: ObjectProperty<SelectedView> by objectProperty(TopLevelModel::selectedView)
|
val selectedView: ObjectProperty<SelectedView> by objectProperty(TopLevelModel::selectedView)
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ class TransactionViewer: View() {
|
|||||||
by observableListReadOnly(GatheredTransactionDataModel::gatheredTransactionDataList)
|
by observableListReadOnly(GatheredTransactionDataModel::gatheredTransactionDataList)
|
||||||
private val reportingExchange: ObservableValue<Pair<Currency, (Amount<Currency>) -> Amount<Currency>>>
|
private val reportingExchange: ObservableValue<Pair<Currency, (Amount<Currency>) -> Amount<Currency>>>
|
||||||
by observableValue(ReportingCurrencyModel::reportingExchange)
|
by observableValue(ReportingCurrencyModel::reportingExchange)
|
||||||
private val myIdentity: ObservableValue<Party> by observableValue(IdentityModel::myIdentity)
|
private val myIdentity: ObservableValue<Party?> by observableValue(IdentityModel::myIdentity)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is what holds data for a single transaction node. Note how a lot of these are nullable as we often simply don't
|
* This is what holds data for a single transaction node. Note how a lot of these are nullable as we often simply don't
|
||||||
@ -363,7 +363,7 @@ class TransactionViewer: View() {
|
|||||||
* We calculate the total value by subtracting relevant input states and adding relevant output states, as long as they're cash
|
* We calculate the total value by subtracting relevant input states and adding relevant output states, as long as they're cash
|
||||||
*/
|
*/
|
||||||
private fun calculateTotalEquiv(
|
private fun calculateTotalEquiv(
|
||||||
identity: Party,
|
identity: Party?,
|
||||||
reportingCurrencyExchange: Pair<Currency, (Amount<Currency>) -> Amount<Currency>>,
|
reportingCurrencyExchange: Pair<Currency, (Amount<Currency>) -> Amount<Currency>>,
|
||||||
inputs: List<StateAndRef<ContractState>>?,
|
inputs: List<StateAndRef<ContractState>>?,
|
||||||
outputs: List<TransactionState<ContractState>>): AmountDiff<Currency>? {
|
outputs: List<TransactionState<ContractState>>): AmountDiff<Currency>? {
|
||||||
@ -372,7 +372,7 @@ private fun calculateTotalEquiv(
|
|||||||
}
|
}
|
||||||
var sum = 0L
|
var sum = 0L
|
||||||
val (reportingCurrency, exchange) = reportingCurrencyExchange
|
val (reportingCurrency, exchange) = reportingCurrencyExchange
|
||||||
val publicKey = identity.owningKey
|
val publicKey = identity?.owningKey
|
||||||
inputs.forEach {
|
inputs.forEach {
|
||||||
val contractState = it.state.data
|
val contractState = it.state.data
|
||||||
if (contractState is Cash.State && publicKey == contractState.owner) {
|
if (contractState is Cash.State && publicKey == contractState.owner) {
|
||||||
|
@ -1,30 +1,20 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.control.TitledPane?>
|
|
||||||
<?import javafx.scene.layout.TilePane?>
|
<?import javafx.scene.layout.TilePane?>
|
||||||
|
|
||||||
<TilePane prefHeight="425.0" prefWidth="425.0" tileAlignment="TOP_LEFT" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
<TilePane prefHeight="425.0" prefWidth="425.0" tileAlignment="TOP_LEFT" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<children>
|
|
||||||
<TitledPane id="tile_cash" fx:id="ourCashPane" alignment="CENTER" collapsible="false" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our cash">
|
<TitledPane id="tile_cash" fx:id="ourCashPane" alignment="CENTER" collapsible="false" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our cash">
|
||||||
<content>
|
<Label fx:id="ourCashLabel" text="USD 186.7m" textAlignment="CENTER" wrapText="true"/>
|
||||||
<Label fx:id="ourCashLabel" text="USD 186.7m" textAlignment="CENTER" wrapText="true" />
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<TitledPane id="tile_debtors" fx:id="ourDebtorsPane" alignment="CENTER" collapsible="false" layoutX="232.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our debtors">
|
<TitledPane id="tile_debtors" fx:id="ourDebtorsPane" alignment="CENTER" collapsible="false" layoutX="232.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our debtors">
|
||||||
<content>
|
<Label text="USD 71.3m" textAlignment="CENTER" wrapText="true"/>
|
||||||
<Label text="USD 71.3m" textAlignment="CENTER" wrapText="true" />
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<TitledPane id="tile_creditors" fx:id="ourCreditorsPane" alignment="CENTER" collapsible="false" layoutX="312.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our creditors">
|
<TitledPane id="tile_creditors" fx:id="ourCreditorsPane" alignment="CENTER" collapsible="false" layoutX="312.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our creditors">
|
||||||
<content>
|
<Label text="USD (29.4m)" textAlignment="CENTER" wrapText="true"/>
|
||||||
<Label text="USD (29.4m)" textAlignment="CENTER" wrapText="true" />
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<TitledPane id="tile_tx" fx:id="ourTransactionsPane" alignment="CENTER" collapsible="false" layoutX="392.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our transactions">
|
<TitledPane id="tile_tx" fx:id="ourTransactionsPane" alignment="CENTER" collapsible="false" layoutX="392.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our transactions">
|
||||||
<content>
|
<Label fx:id="ourTransactionsLabel" text="In flight: 1,315" textAlignment="CENTER" wrapText="true"/>
|
||||||
<Label fx:id="ourTransactionsLabel" text="In flight: 1,315" textAlignment="CENTER" wrapText="true" />
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
</children>
|
<TitledPane id="tile_new_tx" fx:id="newTransaction" alignment="CENTER" collapsible="false" layoutX="472.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="New Transaction">
|
||||||
|
</TitledPane>
|
||||||
</TilePane>
|
</TilePane>
|
||||||
|
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<GridPane hgap="10" vgap="10" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<!-- Row 1 -->
|
||||||
|
<Label text="Transaction Type : " GridPane.halignment="RIGHT"/>
|
||||||
|
<ChoiceBox fx:id="transactionTypeCB" maxWidth="Infinity" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.hgrow="ALWAYS"/>
|
||||||
|
|
||||||
|
<!-- Row 2 -->
|
||||||
|
<Label fx:id="partyALabel" GridPane.halignment="RIGHT" GridPane.rowIndex="1"/>
|
||||||
|
<TextField fx:id="partyATextField" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1"/>
|
||||||
|
|
||||||
|
<!-- Row 3 -->
|
||||||
|
<Label fx:id="partyBLabel" GridPane.halignment="RIGHT" GridPane.rowIndex="2"/>
|
||||||
|
<ChoiceBox fx:id="partyBChoiceBox" maxWidth="Infinity" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2"/>
|
||||||
|
|
||||||
|
<!-- Row 4 -->
|
||||||
|
<Label fx:id="amountLabel" text="Amount : " GridPane.halignment="RIGHT" GridPane.rowIndex="3"/>
|
||||||
|
<ChoiceBox fx:id="currency" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||||
|
<TextField fx:id="amount" maxWidth="Infinity" GridPane.columnIndex="2" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3"/>
|
||||||
|
|
||||||
|
<!-- Row 5 -->
|
||||||
|
<Label fx:id="issueRefLabel" text="Issue Reference : " GridPane.halignment="RIGHT" GridPane.rowIndex="4"/>
|
||||||
|
<TextField fx:id="issueRefTextField" GridPane.columnIndex="1" GridPane.rowIndex="4" GridPane.columnSpan="2"/>
|
||||||
|
|
||||||
|
<!-- Row 6 -->
|
||||||
|
<Button fx:id="executeButton" text="Execute" GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="5"/>
|
||||||
|
|
||||||
|
<Pane fx:id="mainPane" prefHeight="0.0" prefWidth="0.0"/>
|
||||||
|
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0"/>
|
||||||
|
</padding>
|
||||||
|
</GridPane>
|
@ -4,7 +4,5 @@
|
|||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
<VBox fx:id="topLevel" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
<VBox fx:id="topLevel" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<children>
|
<BorderPane fx:id="selectionBorderPane"/>
|
||||||
<BorderPane fx:id="selectionBorderPane" />
|
|
||||||
</children>
|
|
||||||
</VBox>
|
</VBox>
|
||||||
|
@ -5,7 +5,9 @@ import com.r3corda.contracts.asset.InsufficientBalanceException
|
|||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.crypto.toStringShort
|
import com.r3corda.core.crypto.toStringShort
|
||||||
|
import com.r3corda.core.node.NodeInfo
|
||||||
import com.r3corda.core.node.ServiceHub
|
import com.r3corda.core.node.ServiceHub
|
||||||
|
import com.r3corda.core.node.services.NetworkMapCache
|
||||||
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
||||||
import com.r3corda.core.node.services.Vault
|
import com.r3corda.core.node.services.Vault
|
||||||
import com.r3corda.core.transactions.SignedTransaction
|
import com.r3corda.core.transactions.SignedTransaction
|
||||||
@ -33,6 +35,10 @@ class ServerRPCOps(
|
|||||||
) : CordaRPCOps {
|
) : CordaRPCOps {
|
||||||
override val protocolVersion: Int = 0
|
override val protocolVersion: Int = 0
|
||||||
|
|
||||||
|
override fun networkMapUpdates(): Pair<List<NodeInfo>, Observable<NetworkMapCache.MapChange>> {
|
||||||
|
return services.networkMapCache.track()
|
||||||
|
}
|
||||||
|
|
||||||
override fun vaultAndUpdates(): Pair<List<StateAndRef<ContractState>>, Observable<Vault.Update>> {
|
override fun vaultAndUpdates(): Pair<List<StateAndRef<ContractState>>, Observable<Vault.Update>> {
|
||||||
return databaseTransaction(database) {
|
return databaseTransaction(database) {
|
||||||
val (vault, updates) = services.vaultService.track()
|
val (vault, updates) = services.vaultService.track()
|
||||||
@ -153,5 +159,4 @@ class ServerRPCOps(
|
|||||||
|
|
||||||
class InputStateRefResolveFailed(stateRefs: List<StateRef>) :
|
class InputStateRefResolveFailed(stateRefs: List<StateRef>) :
|
||||||
Exception("Failed to resolve input StateRefs $stateRefs")
|
Exception("Failed to resolve input StateRefs $stateRefs")
|
||||||
|
|
||||||
}
|
}
|
@ -3,6 +3,8 @@ package com.r3corda.node.services.messaging
|
|||||||
import com.r3corda.core.contracts.ClientToServiceCommand
|
import com.r3corda.core.contracts.ClientToServiceCommand
|
||||||
import com.r3corda.core.contracts.ContractState
|
import com.r3corda.core.contracts.ContractState
|
||||||
import com.r3corda.core.contracts.StateAndRef
|
import com.r3corda.core.contracts.StateAndRef
|
||||||
|
import com.r3corda.core.node.NodeInfo
|
||||||
|
import com.r3corda.core.node.services.NetworkMapCache
|
||||||
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
||||||
import com.r3corda.core.node.services.Vault
|
import com.r3corda.core.node.services.Vault
|
||||||
import com.r3corda.core.protocols.StateMachineRunId
|
import com.r3corda.core.protocols.StateMachineRunId
|
||||||
@ -103,6 +105,12 @@ interface CordaRPCOps : RPCOps {
|
|||||||
@RPCReturnsObservables
|
@RPCReturnsObservables
|
||||||
fun stateMachineRecordedTransactionMapping(): Pair<List<StateMachineTransactionMapping>, Observable<StateMachineTransactionMapping>>
|
fun stateMachineRecordedTransactionMapping(): Pair<List<StateMachineTransactionMapping>, Observable<StateMachineTransactionMapping>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all parties currently visible on the network with their advertised services and an observable of future updates to the network.
|
||||||
|
*/
|
||||||
|
@RPCReturnsObservables
|
||||||
|
fun networkMapUpdates(): Pair<List<NodeInfo>, Observable<NetworkMapCache.MapChange>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the given command, possibly triggering cash creation etc.
|
* Executes the given command, possibly triggering cash creation etc.
|
||||||
* TODO: The signature of this is weird because it's the remains of an old service call, we should have a call for each command instead.
|
* TODO: The signature of this is weird because it's the remains of an old service call, we should have a call for each command instead.
|
||||||
|
@ -5,13 +5,17 @@ import com.esotericsoftware.kryo.Registration
|
|||||||
import com.esotericsoftware.kryo.Serializer
|
import com.esotericsoftware.kryo.Serializer
|
||||||
import com.esotericsoftware.kryo.io.Input
|
import com.esotericsoftware.kryo.io.Input
|
||||||
import com.esotericsoftware.kryo.io.Output
|
import com.esotericsoftware.kryo.io.Output
|
||||||
import com.esotericsoftware.kryo.serializers.DefaultSerializers
|
import com.esotericsoftware.kryo.serializers.JavaSerializer
|
||||||
|
import com.google.common.net.HostAndPort
|
||||||
import com.r3corda.contracts.asset.Cash
|
import com.r3corda.contracts.asset.Cash
|
||||||
import com.r3corda.core.ErrorOr
|
import com.r3corda.core.ErrorOr
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.DigitalSignature
|
import com.r3corda.core.crypto.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.node.NodeInfo
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.node.PhysicalLocation
|
||||||
|
import com.r3corda.core.node.ServiceEntry
|
||||||
|
import com.r3corda.core.node.services.NetworkMapCache
|
||||||
|
import com.r3corda.core.node.services.ServiceInfo
|
||||||
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
||||||
import com.r3corda.core.node.services.Vault
|
import com.r3corda.core.node.services.Vault
|
||||||
import com.r3corda.core.protocols.StateMachineRunId
|
import com.r3corda.core.protocols.StateMachineRunId
|
||||||
@ -163,17 +167,42 @@ private class RPCKryo(private val observableSerializer: Serializer<Observable<An
|
|||||||
register(setOf(Unit).javaClass) // SingletonSet
|
register(setOf(Unit).javaClass) // SingletonSet
|
||||||
register(TransactionBuildResult.ProtocolStarted::class.java)
|
register(TransactionBuildResult.ProtocolStarted::class.java)
|
||||||
register(TransactionBuildResult.Failed::class.java)
|
register(TransactionBuildResult.Failed::class.java)
|
||||||
|
register(ServiceEntry::class.java)
|
||||||
|
register(NodeInfo::class.java)
|
||||||
|
register(PhysicalLocation::class.java)
|
||||||
|
register(NetworkMapCache.MapChange::class.java)
|
||||||
|
register(NetworkMapCache.MapChangeType::class.java)
|
||||||
|
register(ArtemisMessagingComponent.NodeAddress::class.java,
|
||||||
|
read = { kryo, input -> ArtemisMessagingComponent.NodeAddress(parsePublicKeyBase58(kryo.readObject(input, String::class.java)), kryo.readObject(input, HostAndPort::class.java)) },
|
||||||
|
write = { kryo, output, nodeAddress ->
|
||||||
|
kryo.writeObject(output, nodeAddress.identity.toBase58String())
|
||||||
|
kryo.writeObject(output, nodeAddress.hostAndPort)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
register(HostAndPort::class.java)
|
||||||
|
register(ServiceInfo::class.java, read = { kryo, input -> ServiceInfo.parse(input.readString()) }, write = Kryo::writeObject)
|
||||||
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
|
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
|
||||||
register(IllegalArgumentException::class.java)
|
register(IllegalArgumentException::class.java)
|
||||||
|
// Kryo couldn't serialize Collections.unmodifiableCollection in Throwable correctly, causing null pointer exception when try to access the deserialize object.
|
||||||
|
register(NoSuchElementException::class.java, JavaSerializer())
|
||||||
register(RPCException::class.java)
|
register(RPCException::class.java)
|
||||||
register(Array<StackTraceElement>::class.java, object : Serializer<Array<StackTraceElement>>() {
|
register(Array<StackTraceElement>::class.java, read = { kryo, input -> emptyArray() }, write = { kryo, output, o -> })
|
||||||
override fun read(kryo: Kryo, input: Input, type: Class<Array<StackTraceElement>>): Array<StackTraceElement> = emptyArray()
|
|
||||||
override fun write(kryo: Kryo, output: Output, `object`: Array<StackTraceElement>) {}
|
|
||||||
})
|
|
||||||
register(Collections.unmodifiableList(emptyList<String>()).javaClass)
|
register(Collections.unmodifiableList(emptyList<String>()).javaClass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper method, attempt to reduce boiler plate code
|
||||||
|
private fun <T> register(type: Class<T>, read: (Kryo, Input) -> T, write: (Kryo, Output, T) -> Unit) {
|
||||||
|
register(type, object : Serializer<T>() {
|
||||||
|
override fun read(kryo: Kryo, input: Input, type: Class<T>?): T {
|
||||||
|
return read(kryo, input)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun write(kryo: Kryo, output: Output, o: T) {
|
||||||
|
write(kryo, output, o)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
val observableRegistration: Registration? = if (observableSerializer != null) register(Observable::class.java, observableSerializer) else null
|
val observableRegistration: Registration? = if (observableSerializer != null) register(Observable::class.java, observableSerializer) else null
|
||||||
|
|
||||||
override fun getRegistration(type: Class<*>): Registration {
|
override fun getRegistration(type: Class<*>): Registration {
|
||||||
|
@ -3,6 +3,7 @@ package com.r3corda.node.services.network
|
|||||||
import com.google.common.annotations.VisibleForTesting
|
import com.google.common.annotations.VisibleForTesting
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
|
import com.r3corda.core.bufferUntilSubscribed
|
||||||
import com.r3corda.core.contracts.Contract
|
import com.r3corda.core.contracts.Contract
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.map
|
import com.r3corda.core.map
|
||||||
@ -23,6 +24,7 @@ import com.r3corda.node.services.network.NetworkMapService.Companion.FETCH_PROTO
|
|||||||
import com.r3corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_PROTOCOL_TOPIC
|
import com.r3corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_PROTOCOL_TOPIC
|
||||||
import com.r3corda.node.services.network.NetworkMapService.FetchMapResponse
|
import com.r3corda.node.services.network.NetworkMapService.FetchMapResponse
|
||||||
import com.r3corda.node.services.network.NetworkMapService.SubscribeResponse
|
import com.r3corda.node.services.network.NetworkMapService.SubscribeResponse
|
||||||
|
import com.r3corda.node.services.transactions.SimpleNotaryService
|
||||||
import com.r3corda.node.utilities.AddOrRemove
|
import com.r3corda.node.utilities.AddOrRemove
|
||||||
import com.r3corda.protocols.sendRequest
|
import com.r3corda.protocols.sendRequest
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -54,6 +56,18 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach
|
|||||||
private var registeredForPush = false
|
private var registeredForPush = false
|
||||||
protected var registeredNodes = Collections.synchronizedMap(HashMap<Party, NodeInfo>())
|
protected var registeredNodes = Collections.synchronizedMap(HashMap<Party, NodeInfo>())
|
||||||
|
|
||||||
|
override fun track(): Pair<List<NodeInfo>, Observable<MapChange>> {
|
||||||
|
synchronized(_changed) {
|
||||||
|
fun NodeInfo.isCordaService(): Boolean {
|
||||||
|
return advertisedServices.any { it.info.type in setOf(SimpleNotaryService.type, NetworkMapService.type) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentParties = partyNodes.filter { !it.isCordaService() }
|
||||||
|
val changes = changed.filter { !it.node.isCordaService() }
|
||||||
|
return Pair(currentParties, changes.bufferUntilSubscribed())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun get() = registeredNodes.map { it.value }
|
override fun get() = registeredNodes.map { it.value }
|
||||||
override fun get(serviceType: ServiceType) = registeredNodes.filterValues { it.advertisedServices.any { it.info.type.isSubTypeOf(serviceType) } }.map { it.value }
|
override fun get(serviceType: ServiceType) = registeredNodes.filterValues { it.advertisedServices.any { it.info.type.isSubTypeOf(serviceType) } }.map { it.value }
|
||||||
override fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo? = get(type).firstOrNull()
|
override fun getRecommended(type: ServiceType, contract: Contract, vararg party: Party): NodeInfo? = get(type).firstOrNull()
|
||||||
@ -96,17 +110,22 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun addNode(node: NodeInfo) {
|
override fun addNode(node: NodeInfo) {
|
||||||
val oldValue = registeredNodes.put(node.legalIdentity, node)
|
synchronized(_changed) {
|
||||||
if (oldValue == null) {
|
val oldValue = registeredNodes.put(node.legalIdentity, node)
|
||||||
_changed.onNext(MapChange(node, oldValue, MapChangeType.Added))
|
if (oldValue == null) {
|
||||||
} else if(oldValue != node) {
|
_changed.onNext(MapChange(node, oldValue, MapChangeType.Added))
|
||||||
_changed.onNext(MapChange(node, oldValue, MapChangeType.Modified))
|
} else if (oldValue != node) {
|
||||||
|
_changed.onNext(MapChange(node, oldValue, MapChangeType.Modified))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeNode(node: NodeInfo) {
|
override fun removeNode(node: NodeInfo) {
|
||||||
val oldValue = registeredNodes.remove(node.legalIdentity)
|
synchronized(_changed) {
|
||||||
_changed.onNext(MapChange(node, oldValue, MapChangeType.Removed))
|
val oldValue = registeredNodes.remove(node.legalIdentity)
|
||||||
|
_changed.onNext(MapChange(node, oldValue, MapChangeType.Removed))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user