Wave 3 of Business Network changes. (#193)

* R3NET-546: Re-arrange independent flows into separate packages. Functionally this is a NOP change.

* R3NET-546: Start BNO as a separate Corda node and improve GUI experience for IOU.

* R3NET-546: Move all the membership checks to the Business Network Owner node side, creating "InitiatedBy" flows as necessary.

* R3NET-546: Make MembershipViolationException AMQP serializable.

* R3NET-546: Improve GUI error reporting in case of membership violation.

* R3NET-546: Code changes following review by: @shamsasari

* R3NET-546: Code changes following review by: @shamsasari

* R3NET-546: Added a dedicated InvalidMembershipListNameException.
This commit is contained in:
Viktor Kolomeyko 2017-12-20 12:01:32 +00:00 committed by GitHub
parent 246142173d
commit c0e997c1dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 181 additions and 112 deletions

View File

@ -1,4 +1,4 @@
package net.corda.sample.businessnetwork package net.corda.sample.businessnetwork.iou
import net.corda.core.contracts.CommandData import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract import net.corda.core.contracts.Contract

View File

@ -1,4 +1,4 @@
package net.corda.sample.businessnetwork package net.corda.sample.businessnetwork.iou
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
@ -9,19 +9,19 @@ import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.sample.businessnetwork.membership.MembershipAware import net.corda.sample.businessnetwork.membership.flow.CheckMembershipFlow
import net.corda.sample.businessnetwork.membership.CheckMembershipFlow import net.corda.sample.businessnetwork.membership.flow.CheckMembershipResult
import net.corda.sample.businessnetwork.membership.CheckMembershipResult
import kotlin.reflect.jvm.jvmName import kotlin.reflect.jvm.jvmName
@InitiatingFlow @InitiatingFlow
@StartableByRPC @StartableByRPC
class IOUFlow(val iouValue: Int, class IOUFlow(val iouValue: Int,
val otherParty: Party) : FlowLogic<SignedTransaction>(), MembershipAware { val otherParty: Party) : FlowLogic<SignedTransaction>() {
companion object { companion object {
// TODO: Derive membership name from CorDapp config.
val allowedMembershipName = val allowedMembershipName =
CordaX500Name("AliceBobMembershipList", "AliceBob", "Washington", "US") CordaX500Name("AliceBobMembershipList", "Oslo", "NO")
} }
/** The progress tracker provides checkpoints indicating the progress of the flow to observers. */ /** The progress tracker provides checkpoints indicating the progress of the flow to observers. */

View File

@ -1,4 +1,4 @@
package net.corda.sample.businessnetwork package net.corda.sample.businessnetwork.iou
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.requireThat import net.corda.core.contracts.requireThat
@ -7,14 +7,14 @@ import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.SignTransactionFlow import net.corda.core.flows.SignTransactionFlow
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.sample.businessnetwork.membership.MembershipAware import net.corda.sample.businessnetwork.membership.flow.CheckMembershipFlow
import net.corda.sample.businessnetwork.membership.flow.CheckMembershipResult
@InitiatedBy(IOUFlow::class) @InitiatedBy(IOUFlow::class)
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>(), MembershipAware { class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable @Suspendable
override fun call() { override fun call() {
check(subFlow(CheckMembershipFlow(otherPartySession.counterparty, IOUFlow.allowedMembershipName)) == CheckMembershipResult.PASS)
otherPartySession.counterparty.checkMembership(IOUFlow.allowedMembershipName, this)
subFlow(object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) { subFlow(object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) {
override fun checkTransaction(stx: SignedTransaction) = requireThat { override fun checkTransaction(stx: SignedTransaction) = requireThat {

View File

@ -1,4 +1,4 @@
package net.corda.sample.businessnetwork package net.corda.sample.businessnetwork.iou
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.identity.Party import net.corda.core.identity.Party

View File

@ -1,38 +0,0 @@
package net.corda.sample.businessnetwork.membership
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.unwrap
@CordaSerializable
enum class CheckMembershipResult {
PASS,
FAIL
}
@InitiatingFlow
class CheckMembershipFlow(private val otherParty: Party, private val membershipName: CordaX500Name) : FlowLogic<CheckMembershipResult>(), MembershipAware {
@Suspendable
override fun call(): CheckMembershipResult {
otherParty.checkMembership(membershipName, this)
// This will trigger CounterpartyCheckMembershipFlow
val untrustworthyData = initiateFlow(otherParty).sendAndReceive<CheckMembershipResult>(membershipName)
return untrustworthyData.unwrap { it }
}
}
@InitiatedBy(CheckMembershipFlow::class)
class CounterpartyCheckMembershipFlow(private val otherPartySession: FlowSession) : FlowLogic<Unit>(), MembershipAware {
@Suspendable
override fun call() {
val membershipName = otherPartySession.receive<CordaX500Name>().unwrap { it }
otherPartySession.counterparty.checkMembership(membershipName, this)
otherPartySession.send(CheckMembershipResult.PASS)
}
}

View File

@ -1,26 +0,0 @@
package net.corda.sample.businessnetwork.membership
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServiceHub
import net.corda.sample.businessnetwork.membership.internal.MembershipListProvider
interface MembershipAware {
/**
* Checks that party has at least one common membership list with current node.
* TODO: This functionality ought to be moved into a dedicated CordaService.
*/
fun <T> AbstractParty.checkMembership(membershipName: CordaX500Name, initiatorFlow: FlowLogic<T>) {
val membershipList = getMembershipList(membershipName, initiatorFlow.serviceHub)
if (this !in membershipList) {
val msg = "'$this' doesn't belong to membership list: ${membershipName.commonName}"
throw MembershipViolationException(msg)
}
}
fun getMembershipList(listName: CordaX500Name, serviceHub: ServiceHub): MembershipList = MembershipListProvider.obtainMembershipList(listName, serviceHub.networkMapCache)
}
class MembershipViolationException(msg: String) : FlowException(msg)

View File

@ -1,16 +0,0 @@
package net.corda.sample.businessnetwork.membership
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
/**
* Flow to obtain content of the membership lists this node belongs to.
*/
@StartableByRPC
class ObtainMembershipListContentFlow(private val membershipListName: CordaX500Name) : FlowLogic<Set<AbstractParty>>(), MembershipAware {
@Suspendable
override fun call(): Set<AbstractParty> = getMembershipList(membershipListName, serviceHub).content()
}

View File

@ -0,0 +1,42 @@
package net.corda.sample.businessnetwork.membership.flow
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.unwrap
@CordaSerializable
enum class CheckMembershipResult {
PASS,
FAIL
}
@InitiatingFlow
class CheckMembershipFlow(private val otherParty: Party, private val membershipName: CordaX500Name) : FlowLogic<CheckMembershipResult>() {
@Suspendable
override fun call(): CheckMembershipResult {
val bnoParty = serviceHub.networkMapCache.getPeerByLegalName(membershipName)
return if (bnoParty != null) {
// This will trigger CounterpartyCheckMembershipFlow
val untrustworthyData = initiateFlow(bnoParty).sendAndReceive<CheckMembershipResult>(otherParty)
untrustworthyData.unwrap { it }
} else {
throw InvalidMembershipListNameException(membershipName)
}
}
}
@InitiatedBy(CheckMembershipFlow::class)
class OwnerSideCheckMembershipFlow(private val initiatingPartySession: FlowSession) : FlowLogic<Unit>(), MembershipAware {
@Suspendable
override fun call() {
val partyToCheck = initiatingPartySession.receive<Party>().unwrap { it }
partyToCheck.checkMembership(ourIdentity.name, this)
initiatingPartySession.send(CheckMembershipResult.PASS)
}
}

View File

@ -0,0 +1,32 @@
package net.corda.sample.businessnetwork.membership.flow
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.node.ServiceHub
import net.corda.sample.businessnetwork.membership.internal.MembershipListProvider
import org.slf4j.LoggerFactory
interface MembershipAware {
/**
* Checks that party is included into the specified membership list.
*/
fun <T> AbstractParty.checkMembership(membershipName: CordaX500Name, initiatorFlow: FlowLogic<T>) {
LoggerFactory.getLogger(javaClass).debug("Checking membership of party '${this.nameOrNull()}' in membership list '$membershipName'")
val membershipList = getMembershipList(membershipName, initiatorFlow.serviceHub)
if (this !in membershipList) {
val msg = "'$this' doesn't belong to membership list: ${membershipName.organisation}"
throw MembershipViolationException(msg)
}
}
fun getMembershipList(listName: CordaX500Name, serviceHub: ServiceHub): MembershipList {
LoggerFactory.getLogger(javaClass).debug("Obtaining membership list for name '$listName'")
return MembershipListProvider.obtainMembershipList(listName, serviceHub.networkMapCache)
}
}
class MembershipViolationException(val msg: String) : FlowException(msg)
class InvalidMembershipListNameException(val membershipListName: CordaX500Name) : FlowException("Business Network owner node not found for: $membershipListName")

View File

@ -1,4 +1,4 @@
package net.corda.sample.businessnetwork.membership package net.corda.sample.businessnetwork.membership.flow
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty

View File

@ -0,0 +1,34 @@
package net.corda.sample.businessnetwork.membership.flow
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.*
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.utilities.unwrap
/**
* Flow to obtain content of the membership lists this node belongs to.
*/
@StartableByRPC
@InitiatingFlow
class ObtainMembershipListContentFlow(private val membershipListName: CordaX500Name) : FlowLogic<Set<AbstractParty>>() {
@Suspendable
override fun call(): Set<AbstractParty> {
val bnoParty = serviceHub.networkMapCache.getPeerByLegalName(membershipListName) ?:
throw InvalidMembershipListNameException(membershipListName)
val untrustworthyData = initiateFlow(bnoParty).receive<Set<AbstractParty>>()
return untrustworthyData.unwrap { it }
}
}
@InitiatedBy(ObtainMembershipListContentFlow::class)
class OwnerSideObtainMembershipListContentFlow(private val initiatingPartySession: FlowSession) : FlowLogic<Unit>(), MembershipAware {
@Suspendable
override fun call() {
// Checking whether the calling party is a member. If not it is not even in position to enquire about membership list content.
initiatingPartySession.counterparty.checkMembership(ourIdentity.name, this)
val membershipListContent: Set<AbstractParty> = getMembershipList(ourIdentity.name, serviceHub).content()
initiatingPartySession.send(membershipListContent)
}
}

View File

@ -4,7 +4,7 @@ import com.opencsv.CSVReaderBuilder
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
import net.corda.sample.businessnetwork.membership.MembershipList import net.corda.sample.businessnetwork.membership.flow.MembershipList
import java.io.InputStream import java.io.InputStream
/** /**

View File

@ -2,9 +2,9 @@ package net.corda.sample.businessnetwork.membership.internal
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.NetworkMapCache import net.corda.core.node.services.NetworkMapCache
import net.corda.sample.businessnetwork.membership.MembershipList import net.corda.sample.businessnetwork.membership.flow.MembershipList
object MembershipListProvider { object MembershipListProvider {
fun obtainMembershipList(listName: CordaX500Name, networkMapCache: NetworkMapCache): MembershipList = fun obtainMembershipList(listName: CordaX500Name, networkMapCache: NetworkMapCache): MembershipList =
CsvMembershipList(MembershipListProvider::class.java.getResourceAsStream("${listName.commonName}.csv"), networkMapCache) CsvMembershipList(MembershipListProvider::class.java.getResourceAsStream("${listName.organisation}.csv"), networkMapCache)
} }

View File

@ -14,8 +14,8 @@ import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.sample.businessnetwork.IOUFlow import net.corda.sample.businessnetwork.iou.IOUFlow
import net.corda.sample.businessnetwork.membership.ObtainMembershipListContentFlow import net.corda.sample.businessnetwork.membership.flow.ObtainMembershipListContentFlow
import net.corda.finance.GBP import net.corda.finance.GBP
import net.corda.finance.USD import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash import net.corda.finance.contracts.asset.Cash
@ -32,8 +32,14 @@ import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
import kotlin.reflect.KClass
class ExplorerSimulation(private val options: OptionSet) { class ExplorerSimulation(private val options: OptionSet) {
private companion object {
fun packagesOfClasses(vararg classes: KClass<*>): List<String> = classes.map { it.java.`package`.name }
}
private val user = User("user1", "test", permissions = setOf( private val user = User("user1", "test", permissions = setOf(
startFlow<CashPaymentFlow>(), startFlow<CashPaymentFlow>(),
startFlow<CashConfigDataFlow>(), startFlow<CashConfigDataFlow>(),
@ -52,6 +58,7 @@ class ExplorerSimulation(private val options: OptionSet) {
private lateinit var bobNode: NodeHandle private lateinit var bobNode: NodeHandle
private lateinit var issuerNodeGBP: NodeHandle private lateinit var issuerNodeGBP: NodeHandle
private lateinit var issuerNodeUSD: NodeHandle private lateinit var issuerNodeUSD: NodeHandle
private lateinit var bnoNode: NodeHandle
private lateinit var notary: Party private lateinit var notary: Party
private val RPCConnections = ArrayList<CordaRPCConnection>() private val RPCConnections = ArrayList<CordaRPCConnection>()
@ -65,7 +72,8 @@ class ExplorerSimulation(private val options: OptionSet) {
fun startDemoNodes() { fun startDemoNodes() {
val portAllocation = PortAllocation.Incremental(20000) val portAllocation = PortAllocation.Incremental(20000)
driver(portAllocation = portAllocation, extraCordappPackagesToScan = listOf("net.corda.finance", IOUFlow::class.java.`package`.name), driver(portAllocation = portAllocation,
extraCordappPackagesToScan = packagesOfClasses(CashPaymentFlow::class, IOUFlow::class, ObtainMembershipListContentFlow::class),
isDebug = true, waitForAllNodesToFinish = true, jmxPolicy = JmxPolicy(true)) { isDebug = true, waitForAllNodesToFinish = true, jmxPolicy = JmxPolicy(true)) {
// TODO : Supported flow should be exposed somehow from the node instead of set of ServiceInfo. // 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 alice = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user))
@ -76,14 +84,16 @@ class ExplorerSimulation(private val options: OptionSet) {
customOverrides = mapOf("issuableCurrencies" to listOf("GBP"))) customOverrides = mapOf("issuableCurrencies" to listOf("GBP")))
val issuerUSD = startNode(providedName = usaBankName, rpcUsers = listOf(manager), val issuerUSD = startNode(providedName = usaBankName, rpcUsers = listOf(manager),
customOverrides = mapOf("issuableCurrencies" to listOf("USD"))) customOverrides = mapOf("issuableCurrencies" to listOf("USD")))
val bno = startNode(providedName = IOUFlow.allowedMembershipName, rpcUsers = listOf(user))
notaryNode = defaultNotaryNode.get() notaryNode = defaultNotaryNode.get()
aliceNode = alice.get() aliceNode = alice.get()
bobNode = bob.get() bobNode = bob.get()
issuerNodeGBP = issuerGBP.get() issuerNodeGBP = issuerGBP.get()
issuerNodeUSD = issuerUSD.get() issuerNodeUSD = issuerUSD.get()
bnoNode = bno.get()
arrayOf(notaryNode, aliceNode, bobNode, issuerNodeGBP, issuerNodeUSD).forEach { arrayOf(notaryNode, aliceNode, bobNode, issuerNodeGBP, issuerNodeUSD, bnoNode).forEach {
println("${it.nodeInfo.legalIdentities.first()} started on ${it.configuration.rpcAddress}") println("${it.nodeInfo.legalIdentities.first()} started on ${it.configuration.rpcAddress}")
} }

View File

@ -9,8 +9,8 @@ import net.corda.client.jfx.utils.map
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.sample.businessnetwork.IOUFlow import net.corda.sample.businessnetwork.iou.IOUFlow
import net.corda.sample.businessnetwork.membership.ObtainMembershipListContentFlow import net.corda.sample.businessnetwork.membership.flow.ObtainMembershipListContentFlow
class MembershipListModel { class MembershipListModel {
private val proxy by observableValue(NodeMonitorModel::proxyObservable) private val proxy by observableValue(NodeMonitorModel::proxyObservable)

View File

@ -83,9 +83,10 @@ class Network : CordaView() {
.map { it.stateAndRef.state.data }.getParties() .map { it.stateAndRef.state.data }.getParties()
val outputParties = it.transaction.tx.outputStates.observable().getParties() val outputParties = it.transaction.tx.outputStates.observable().getParties()
val signingParties = it.transaction.sigs.map { it.by.toKnownParty() } val signingParties = it.transaction.sigs.map { it.by.toKnownParty() }
// Input parties fire a bullets to all output parties, and to the signing parties. !! This is a rough guess of how the message moves in the network. // Input parties fire a bullets to all output parties, then to the signing parties and then signing parties to output parties.
// !! This is a rough guess of how the message moves in the network.
// TODO : Expose artemis queue to get real message information. // TODO : Expose artemis queue to get real message information.
inputParties.cross(outputParties) + inputParties.cross(signingParties) inputParties.cross(outputParties) + inputParties.cross(signingParties) + signingParties.cross(outputParties)
} }
} }

View File

@ -28,7 +28,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.utilities.toBase58String import net.corda.core.utilities.toBase58String
import net.corda.sample.businessnetwork.IOUState import net.corda.sample.businessnetwork.iou.IOUState
import net.corda.explorer.AmountDiff import net.corda.explorer.AmountDiff
import net.corda.explorer.formatters.AmountFormatter import net.corda.explorer.formatters.AmountFormatter
import net.corda.explorer.formatters.Formatter import net.corda.explorer.formatters.Formatter

View File

@ -2,10 +2,17 @@ package net.corda.explorer.views.cordapps.iou
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
import javafx.beans.binding.Bindings
import javafx.geometry.Pos
import javafx.scene.input.MouseButton import javafx.scene.input.MouseButton
import javafx.scene.layout.BorderPane import javafx.scene.layout.BorderPane
import net.corda.client.jfx.model.TransactionDataModel
import net.corda.client.jfx.model.observableList
import net.corda.client.jfx.utils.map
import net.corda.core.utilities.Try import net.corda.core.utilities.Try
import net.corda.explorer.model.CordaView import net.corda.explorer.model.CordaView
import net.corda.explorer.model.CordaWidget
import net.corda.sample.businessnetwork.iou.IOUState
import net.corda.explorer.model.MembershipListModel import net.corda.explorer.model.MembershipListModel
import tornadofx.* import tornadofx.*
@ -13,6 +20,7 @@ class IOUViewer : CordaView("IOU") {
// Inject UI elements. // Inject UI elements.
override val root: BorderPane by fxml() override val root: BorderPane by fxml()
override val icon: FontAwesomeIcon = FontAwesomeIcon.CHEVRON_CIRCLE_RIGHT override val icon: FontAwesomeIcon = FontAwesomeIcon.CHEVRON_CIRCLE_RIGHT
override val widgets = listOf(CordaWidget(title, IOUWidget(), icon)).observable()
// Wire up UI // Wire up UI
init { init {
@ -32,4 +40,18 @@ class IOUViewer : CordaView("IOU") {
val allParties = MembershipListModel().allParties val allParties = MembershipListModel().allParties
allParties[0] allParties[0]
}.isSuccess }.isSuccess
private class IOUWidget : BorderPane() {
private val partiallyResolvedTransactions by observableList(TransactionDataModel::partiallyResolvedTransactions)
private val iouTransactions = partiallyResolvedTransactions.filtered { t -> t.transaction.tx.outputs.any({ ts -> ts.data is IOUState }) }
init {
right {
label {
textProperty().bind(Bindings.size(iouTransactions).map(Number::toString))
BorderPane.setAlignment(this, Pos.BOTTOM_RIGHT)
}
}
}
}
} }

View File

@ -1,6 +1,7 @@
package net.corda.explorer.views.cordapps.iou package net.corda.explorer.views.cordapps.iou
import com.google.common.base.Splitter import com.google.common.base.Splitter
import com.sun.javafx.collections.ImmutableObservableList
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.beans.binding.BooleanBinding import javafx.beans.binding.BooleanBinding
import javafx.collections.FXCollections import javafx.collections.FXCollections
@ -15,13 +16,15 @@ import net.corda.client.jfx.model.*
import net.corda.client.jfx.utils.isNotNull import net.corda.client.jfx.utils.isNotNull
import net.corda.client.jfx.utils.map import net.corda.client.jfx.utils.map
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.messaging.FlowHandle import net.corda.core.messaging.FlowHandle
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.sample.businessnetwork.IOUFlow import net.corda.core.utilities.loggerFor
import net.corda.sample.businessnetwork.iou.IOUFlow
import net.corda.explorer.formatters.PartyNameFormatter import net.corda.explorer.formatters.PartyNameFormatter
import net.corda.explorer.model.MembershipListModel import net.corda.explorer.model.MembershipListModel
import net.corda.explorer.views.bigDecimalFormatter import net.corda.explorer.views.bigDecimalFormatter
@ -46,7 +49,12 @@ class NewTransaction : Fragment() {
fun show(window: Window) { fun show(window: Window) {
// Every time re-query from the server side // Every time re-query from the server side
val elementsFromServer = MembershipListModel().allParties val elementsFromServer = try {
MembershipListModel().allParties
} catch (ex: Exception) {
loggerFor<NewTransaction>().error("Unexpected error fetching membership list content", ex)
ImmutableObservableList<AbstractParty>()
}
partyBChoiceBox.apply { partyBChoiceBox.apply {
items = FXCollections.observableList(parties.map { it.chooseIdentityAndCert() }).filtered { elementsFromServer.contains(it.party) }.sorted() items = FXCollections.observableList(parties.map { it.chooseIdentityAndCert() }).filtered { elementsFromServer.contains(it.party) }.sorted()