mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +00:00
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:
parent
246142173d
commit
c0e997c1dd
@ -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.Contract
|
@ -1,4 +1,4 @@
|
||||
package net.corda.sample.businessnetwork
|
||||
package net.corda.sample.businessnetwork.iou
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
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.TransactionBuilder
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import net.corda.sample.businessnetwork.membership.MembershipAware
|
||||
import net.corda.sample.businessnetwork.membership.CheckMembershipFlow
|
||||
import net.corda.sample.businessnetwork.membership.CheckMembershipResult
|
||||
import net.corda.sample.businessnetwork.membership.flow.CheckMembershipFlow
|
||||
import net.corda.sample.businessnetwork.membership.flow.CheckMembershipResult
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
|
||||
@InitiatingFlow
|
||||
@StartableByRPC
|
||||
class IOUFlow(val iouValue: Int,
|
||||
val otherParty: Party) : FlowLogic<SignedTransaction>(), MembershipAware {
|
||||
val otherParty: Party) : FlowLogic<SignedTransaction>() {
|
||||
|
||||
companion object {
|
||||
// TODO: Derive membership name from CorDapp config.
|
||||
val allowedMembershipName =
|
||||
CordaX500Name("AliceBobMembershipList", "AliceBob", "Washington", "US")
|
||||
CordaX500Name("AliceBobMembershipList", "Oslo", "NO")
|
||||
}
|
||||
|
||||
/** The progress tracker provides checkpoints indicating the progress of the flow to observers. */
|
@ -1,4 +1,4 @@
|
||||
package net.corda.sample.businessnetwork
|
||||
package net.corda.sample.businessnetwork.iou
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
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.SignTransactionFlow
|
||||
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)
|
||||
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>(), MembershipAware {
|
||||
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
|
||||
otherPartySession.counterparty.checkMembership(IOUFlow.allowedMembershipName, this)
|
||||
check(subFlow(CheckMembershipFlow(otherPartySession.counterparty, IOUFlow.allowedMembershipName)) == CheckMembershipResult.PASS)
|
||||
|
||||
subFlow(object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) {
|
||||
override fun checkTransaction(stx: SignedTransaction) = requireThat {
|
@ -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.identity.Party
|
@ -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)
|
||||
}
|
||||
}
|
@ -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)
|
@ -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()
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
@ -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")
|
@ -1,4 +1,4 @@
|
||||
package net.corda.sample.businessnetwork.membership
|
||||
package net.corda.sample.businessnetwork.membership.flow
|
||||
|
||||
import net.corda.core.identity.AbstractParty
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ import com.opencsv.CSVReaderBuilder
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
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
|
||||
|
||||
/**
|
||||
|
@ -2,9 +2,9 @@ package net.corda.sample.businessnetwork.membership.internal
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.sample.businessnetwork.membership.MembershipList
|
||||
import net.corda.sample.businessnetwork.membership.flow.MembershipList
|
||||
|
||||
object MembershipListProvider {
|
||||
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)
|
||||
}
|
@ -14,8 +14,8 @@ import net.corda.core.messaging.FlowHandle
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.sample.businessnetwork.IOUFlow
|
||||
import net.corda.sample.businessnetwork.membership.ObtainMembershipListContentFlow
|
||||
import net.corda.sample.businessnetwork.iou.IOUFlow
|
||||
import net.corda.sample.businessnetwork.membership.flow.ObtainMembershipListContentFlow
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
@ -32,8 +32,14 @@ import net.corda.testing.driver.PortAllocation
|
||||
import net.corda.testing.driver.driver
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
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(
|
||||
startFlow<CashPaymentFlow>(),
|
||||
startFlow<CashConfigDataFlow>(),
|
||||
@ -52,6 +58,7 @@ class ExplorerSimulation(private val options: OptionSet) {
|
||||
private lateinit var bobNode: NodeHandle
|
||||
private lateinit var issuerNodeGBP: NodeHandle
|
||||
private lateinit var issuerNodeUSD: NodeHandle
|
||||
private lateinit var bnoNode: NodeHandle
|
||||
private lateinit var notary: Party
|
||||
|
||||
private val RPCConnections = ArrayList<CordaRPCConnection>()
|
||||
@ -65,7 +72,8 @@ class ExplorerSimulation(private val options: OptionSet) {
|
||||
|
||||
fun startDemoNodes() {
|
||||
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)) {
|
||||
// TODO : Supported flow should be exposed somehow from the node instead of set of ServiceInfo.
|
||||
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")))
|
||||
val issuerUSD = startNode(providedName = usaBankName, rpcUsers = listOf(manager),
|
||||
customOverrides = mapOf("issuableCurrencies" to listOf("USD")))
|
||||
val bno = startNode(providedName = IOUFlow.allowedMembershipName, rpcUsers = listOf(user))
|
||||
|
||||
notaryNode = defaultNotaryNode.get()
|
||||
aliceNode = alice.get()
|
||||
bobNode = bob.get()
|
||||
issuerNodeGBP = issuerGBP.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}")
|
||||
}
|
||||
|
||||
|
@ -9,8 +9,8 @@ import net.corda.client.jfx.utils.map
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.sample.businessnetwork.IOUFlow
|
||||
import net.corda.sample.businessnetwork.membership.ObtainMembershipListContentFlow
|
||||
import net.corda.sample.businessnetwork.iou.IOUFlow
|
||||
import net.corda.sample.businessnetwork.membership.flow.ObtainMembershipListContentFlow
|
||||
|
||||
class MembershipListModel {
|
||||
private val proxy by observableValue(NodeMonitorModel::proxyObservable)
|
||||
|
@ -83,9 +83,10 @@ class Network : CordaView() {
|
||||
.map { it.stateAndRef.state.data }.getParties()
|
||||
val outputParties = it.transaction.tx.outputStates.observable().getParties()
|
||||
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.
|
||||
inputParties.cross(outputParties) + inputParties.cross(signingParties)
|
||||
inputParties.cross(outputParties) + inputParties.cross(signingParties) + signingParties.cross(outputParties)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
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.formatters.AmountFormatter
|
||||
import net.corda.explorer.formatters.Formatter
|
||||
|
@ -2,10 +2,17 @@ package net.corda.explorer.views.cordapps.iou
|
||||
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.input.MouseButton
|
||||
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.explorer.model.CordaView
|
||||
import net.corda.explorer.model.CordaWidget
|
||||
import net.corda.sample.businessnetwork.iou.IOUState
|
||||
import net.corda.explorer.model.MembershipListModel
|
||||
import tornadofx.*
|
||||
|
||||
@ -13,6 +20,7 @@ class IOUViewer : CordaView("IOU") {
|
||||
// Inject UI elements.
|
||||
override val root: BorderPane by fxml()
|
||||
override val icon: FontAwesomeIcon = FontAwesomeIcon.CHEVRON_CIRCLE_RIGHT
|
||||
override val widgets = listOf(CordaWidget(title, IOUWidget(), icon)).observable()
|
||||
|
||||
// Wire up UI
|
||||
init {
|
||||
@ -28,8 +36,22 @@ class IOUViewer : CordaView("IOU") {
|
||||
}
|
||||
|
||||
fun isEnabledForNode(): Boolean = Try.on {
|
||||
// Assuming if the model can be initialized - the CorDapp is installed
|
||||
val allParties = MembershipListModel().allParties
|
||||
allParties[0]
|
||||
}.isSuccess
|
||||
// Assuming if the model can be initialized - the CorDapp is installed
|
||||
val allParties = MembershipListModel().allParties
|
||||
allParties[0]
|
||||
}.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
package net.corda.explorer.views.cordapps.iou
|
||||
|
||||
import com.google.common.base.Splitter
|
||||
import com.sun.javafx.collections.ImmutableObservableList
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.beans.binding.BooleanBinding
|
||||
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.map
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.messaging.FlowHandle
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
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.model.MembershipListModel
|
||||
import net.corda.explorer.views.bigDecimalFormatter
|
||||
@ -46,7 +49,12 @@ class NewTransaction : Fragment() {
|
||||
fun show(window: Window) {
|
||||
|
||||
// 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 {
|
||||
items = FXCollections.observableList(parties.map { it.chooseIdentityAndCert() }).filtered { elementsFromServer.contains(it.party) }.sorted()
|
||||
|
Loading…
Reference in New Issue
Block a user