Explorer advertising service fix (#1576)

* WIP
added a helper method to convert ObservableValue<List> to ObservableList

(cherry picked from commit 75306aa)

* Fix for cash explorer after advertising service removal

(cherry picked from commit 59d0278)

* remove unused changes

* address PR issues

* fixup after rebase

* fix CashState name rendering issue
added flow permission to gradle config
This commit is contained in:
Patrick Kuo 2017-09-21 13:27:05 +01:00 committed by josecoll
parent 33421bdd44
commit 80b3411fa5
13 changed files with 97 additions and 58 deletions

View File

@ -5,19 +5,20 @@ import com.google.common.cache.CacheLoader
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import net.corda.client.jfx.utils.filterNotNull
import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.map
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.nodeapi.ServiceType
import java.security.PublicKey
class NetworkIdentityModel {
private val networkIdentityObservable by observable(NodeMonitorModel::networkMap)
val networkIdentities: ObservableList<NodeInfo> =
private val networkIdentities: ObservableList<NodeInfo> =
networkIdentityObservable.fold(FXCollections.observableArrayList()) { list, update ->
list.removeIf {
when (update) {
@ -32,13 +33,15 @@ class NetworkIdentityModel {
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
private val identityCache = CacheBuilder.newBuilder()
.build<PublicKey, ObservableValue<NodeInfo?>>(CacheLoader.from {
publicKey ->
.build<PublicKey, ObservableValue<NodeInfo?>>(CacheLoader.from { publicKey ->
publicKey?.let { rpcProxy.map { it?.nodeInfoFromParty(AnonymousParty(publicKey)) } }
})
val notaries: ObservableList<Party> = FXCollections.observableList(rpcProxy.value?.notaryIdentities())
val notaryNodes: ObservableList<NodeInfo> = FXCollections.observableList(notaries.map { rpcProxy.value?.nodeInfoFromParty(it) })
val notaries: ObservableList<Party> = networkIdentities.map {
it.legalIdentitiesAndCerts.find { it.name.commonName?.let { ServiceType.parse(it).isNotary() } ?: false }
}.map { it?.party }.filterNotNull()
val notaryNodes: ObservableList<NodeInfo> = notaries.map { rpcProxy.value?.nodeInfoFromParty(it) }.filterNotNull()
val parties: ObservableList<NodeInfo> = networkIdentities.filtered { it.legalIdentities.all { it !in notaries } }
val myIdentity = rpcProxy.map { it?.nodeInfo()?.legalIdentitiesAndCerts?.first()?.party }

View File

@ -0,0 +1,40 @@
package net.corda.finance.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StartableByRPC
import net.corda.core.serialization.CordaSerializable
import net.corda.finance.CHF
import net.corda.finance.EUR
import net.corda.finance.GBP
import net.corda.finance.USD
import java.util.*
/**
* Flow to obtain cash cordapp app configuration.
*/
@StartableByRPC
class CashConfigDataFlow : FlowLogic<CashConfiguration>() {
companion object {
private val supportedCurrencies = listOf(USD, GBP, CHF, EUR)
}
@Suspendable
override fun call(): CashConfiguration {
val issuableCurrencies = supportedCurrencies.mapNotNull {
try {
// Currently it uses checkFlowPermission to determine the list of issuable currency as a temporary hack.
// TODO: get the config from proper configuration source.
checkFlowPermission("corda.issuer.$it", emptyMap())
it
} catch (e: FlowException) {
null
}
}
return CashConfiguration(issuableCurrencies, supportedCurrencies)
}
}
@CordaSerializable
data class CashConfiguration(val issuableCurrencies: List<Currency>, val supportedCurrencies: List<Currency>)

View File

@ -23,6 +23,7 @@ import net.corda.core.utilities.*
import net.corda.node.services.api.FlowAppAuditEvent
import net.corda.node.services.api.FlowPermissionAuditEvent
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.statemachine.FlowSessionState.Initiating
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.DatabaseTransaction
@ -260,7 +261,10 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
// TODO Dummy implementation of access to application specific permission controls and audit logging
override fun checkFlowPermission(permissionName: String, extraAuditData: Map<String, String>) {
val permissionGranted = true // TODO define permission control service on ServiceHubInternal and actually check authorization.
// This is a hack to allow cash app access list of permitted issuer currency.
// TODO: replace this with cordapp configuration.
val config = serviceHub.configuration as? FullNodeConfiguration
val permissionGranted = config?.extraAdvertisedServiceIds?.contains(permissionName) ?: true
val checkPermissionEvent = FlowPermissionAuditEvent(
serviceHub.clock.instant(),
flowInitiator,

View File

@ -69,6 +69,7 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
['username' : "bankUser",
'password' : "test",
'permissions': ["StartFlow.net.corda.finance.flows.CashPaymentFlow",
"StartFlow.net.corda.finance.flows.CashConfigDataFlow",
"StartFlow.net.corda.finance.flows.CashExitFlow",
"StartFlow.net.corda.finance.flows.CashIssueAndPaymentFlow"]]
]

View File

@ -17,16 +17,13 @@ import net.corda.core.utilities.getOrThrow
import net.corda.finance.GBP
import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash
import net.corda.finance.flows.AbstractCashFlow
import net.corda.finance.flows.CashExitFlow
import net.corda.finance.flows.*
import net.corda.finance.flows.CashExitFlow.ExitRequest
import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.finance.flows.CashPaymentFlow
import net.corda.node.services.FlowPermissions.Companion.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.ServiceInfo
import net.corda.nodeapi.ServiceType
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
import net.corda.testing.ALICE
import net.corda.testing.BOB
@ -39,12 +36,14 @@ import java.util.*
class ExplorerSimulation(val options: OptionSet) {
private val user = User("user1", "test", permissions = setOf(
startFlowPermission<CashPaymentFlow>()
startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CashConfigDataFlow>()
))
private val manager = User("manager", "test", permissions = setOf(
startFlowPermission<CashIssueAndPaymentFlow>(),
startFlowPermission<CashPaymentFlow>(),
startFlowPermission<CashExitFlow>())
startFlowPermission<CashExitFlow>(),
startFlowPermission<CashConfigDataFlow>())
)
private lateinit var notaryNode: NodeHandle
@ -122,7 +121,7 @@ class ExplorerSimulation(val options: OptionSet) {
val issuerRPCGBP = issuerGBPConnection.proxy
val issuerClientUSD = issuerNodeUSD.rpcClientToNode()
val issuerUSDConnection =issuerClientUSD.start(manager.username, manager.password)
val issuerUSDConnection = issuerClientUSD.start(manager.username, manager.password)
val issuerRPCUSD = issuerUSDConnection.proxy
RPCConnections.addAll(listOf(aliceConnection, bobConnection, issuerGBPConnection, issuerUSDConnection))

View File

@ -1,34 +1,25 @@
package net.corda.explorer.model
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import net.corda.client.jfx.model.NetworkIdentityModel
import net.corda.client.jfx.model.observableList
import net.corda.client.jfx.model.NodeMonitorModel
import net.corda.client.jfx.model.observableValue
import net.corda.client.jfx.utils.ChosenList
import net.corda.client.jfx.utils.map
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.finance.flows.CashConfigDataFlow
import tornadofx.*
val ISSUER_SERVICE_TYPE = Regex("corda.issuer.(USD|GBP|CHF|EUR)")
class IssuerModel {
// TODO Explorer will be fixed as separate PR.
private val networkIdentities by observableList(NetworkIdentityModel::networkIdentities)
private val myIdentity by observableValue(NetworkIdentityModel::myIdentity)
private val supportedCurrencies by observableList(ReportingCurrencyModel::supportedCurrencies)
private val proxy by observableValue(NodeMonitorModel::proxyObservable)
private val cashAppConfiguration = proxy.map { it?.startFlow(::CashConfigDataFlow)?.returnValue?.getOrThrow() }
val supportedCurrencies = ChosenList(cashAppConfiguration.map { it?.supportedCurrencies?.observable() ?: FXCollections.emptyObservableList() })
val currencyTypes = ChosenList(cashAppConfiguration.map { it?.issuableCurrencies?.observable() ?: FXCollections.emptyObservableList() })
val issuers: ObservableList<NodeInfo> = FXCollections.observableList(networkIdentities)
val currencyTypes = ChosenList(myIdentity.map { supportedCurrencies })
val transactionTypes = ChosenList(myIdentity.map {
if (it?.isIssuerNode() ?: false)
val transactionTypes = ChosenList(cashAppConfiguration.map {
if (it?.issuableCurrencies?.isNotEmpty() == true)
CashTransaction.values().asList().observable()
else
listOf(CashTransaction.Pay).observable()
})
private fun Party.isIssuerNode() = true
}

View File

@ -16,8 +16,7 @@ import java.util.*
class ReportingCurrencyModel {
private val exchangeRate: ObservableValue<ExchangeRate> by observableValue(ExchangeRateModel::exchangeRate)
val reportingCurrency by observableValue(SettingsModel::reportingCurrencyProperty)
val supportedCurrencies = setOf(USD, GBP, CHF, EUR).toList().observable()
private val reportingCurrency by observableValue(SettingsModel::reportingCurrencyProperty)
/**
* This stream provides a stream of exchange() functions that updates when either the reporting currency or the

View File

@ -9,6 +9,7 @@ import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.geometry.Bounds
import javafx.geometry.Insets
import javafx.geometry.Point2D
import javafx.scene.Parent
import javafx.scene.control.Button
@ -35,6 +36,7 @@ import net.corda.explorer.model.CordaView
import net.corda.finance.utils.CityDatabase
import net.corda.finance.utils.ScreenCoordinate
import net.corda.finance.utils.WorldMapLocation
import net.corda.nodeapi.ServiceType
import tornadofx.*
class Network : CordaView() {
@ -91,15 +93,19 @@ class Network : CordaView() {
val node = this
val identities = node.legalIdentitiesAndCerts.sortedBy { it.owningKey.toBase58String() }
return button {
minWidth = 300.0
padding = Insets(10.0)
useMaxWidth = true
graphic = vbox {
label(PartyNameFormatter.short.format(identities[0].name)) { font = Font.font(font.family, FontWeight.BOLD, 15.0) }
gridpane { // TODO We lose node's main identity for display.
gridpane {
// TODO We lose node's main identity for display.
hgap = 5.0
vgap = 5.0
for (identity in identities) {
row(PartyNameFormatter.short.format(identity.name)) {
copyableLabel(SimpleObjectProperty(identity.owningKey.toBase58String())).apply { minWidth = 400.0 }
val isNotary = identity.name.commonName?.let { ServiceType.parse(it).isNotary() } ?: false
row("${if (isNotary) "Notary " else ""}Public Key :") {
copyableLabel(SimpleObjectProperty(identity.owningKey.toBase58String()))
}
}
node.getWorldMapLocation()?.apply { row("Location :") { label(this@apply.description) } }
@ -114,7 +120,7 @@ class Network : CordaView() {
private fun NodeInfo.render(): MapViewComponents {
val node = this
val identities = node.legalIdentitiesAndCerts.sortedBy { it.owningKey.toBase58String() }
val mapLabel = label(PartyNameFormatter.short.format(identities[0].name)) // We choose the first one for the name of the node on the map.
val mapLabel = label(PartyNameFormatter.short.format(identities.first().name)) // We choose the first one for the name of the node on the map.
mapPane.add(mapLabel)
// applyCss: This method does not normally need to be invoked directly but may be used in conjunction with Parent.layout()
// to size a Node before the next pulse, or if the Scene is not in a Stage.

View File

@ -12,6 +12,7 @@ import net.corda.client.jfx.model.objectProperty
import net.corda.client.jfx.model.observableList
import net.corda.client.jfx.utils.map
import net.corda.explorer.model.CordaView
import net.corda.explorer.model.IssuerModel
import net.corda.explorer.model.ReportingCurrencyModel
import net.corda.explorer.model.SettingsModel
import java.util.*
@ -22,7 +23,7 @@ class Settings : CordaView() {
override val icon = FontAwesomeIcon.COGS
// Inject Data.
private val currencies by observableList(ReportingCurrencyModel::supportedCurrencies)
private val currencies by observableList(IssuerModel::supportedCurrencies)
private val reportingCurrencies by objectProperty(SettingsModel::reportingCurrencyProperty)
private val rememberMe by objectProperty(SettingsModel::rememberMeProperty)
private val fullscreen by objectProperty(SettingsModel::fullscreenProperty)

View File

@ -26,7 +26,6 @@ import net.corda.core.crypto.toStringShort
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.toBase58String
import net.corda.explorer.AmountDiff
import net.corda.explorer.formatters.AmountFormatter
@ -37,6 +36,7 @@ import net.corda.explorer.identicon.identiconToolTip
import net.corda.explorer.model.CordaView
import net.corda.explorer.model.CordaWidget
import net.corda.explorer.model.ReportingCurrencyModel
import net.corda.explorer.model.SettingsModel
import net.corda.explorer.sign
import net.corda.explorer.ui.setCustomCellFactory
import net.corda.finance.contracts.asset.Cash
@ -52,7 +52,7 @@ class TransactionViewer : CordaView("Transactions") {
// Inject data
private val transactions by observableListReadOnly(TransactionDataModel::partiallyResolvedTransactions)
private val reportingExchange by observableValue(ReportingCurrencyModel::reportingExchange)
private val reportingCurrency by observableValue(ReportingCurrencyModel::reportingCurrency)
private val reportingCurrency by observableValue(SettingsModel::reportingCurrencyProperty)
private val myIdentity by observableValue(NetworkIdentityModel::myIdentity)
override val widgets = listOf(CordaWidget(title, TransactionWidget(), icon)).observable()
@ -260,7 +260,7 @@ class TransactionViewer : CordaView("Transactions") {
vgap = 10.0
hgap = 10.0
row {
label("${contractState.contract().javaClass.simpleName} (${contractState.ref.toString().substring(0, 16)}...)[${contractState.ref.index}]") {
label("${contractState.contract()} (${contractState.ref.toString().substring(0, 16)}...)[${contractState.ref.index}]") {
graphic = identicon(contractState.ref.txhash, 30.0)
tooltip = identiconToolTip(contractState.ref.txhash)
gridpaneConstraints { columnSpan = 2 }
@ -298,8 +298,7 @@ class TransactionViewer : CordaView("Transactions") {
}
}
private fun StateAndRef<ContractState>.contract() = this.state.contract
private fun StateAndRef<ContractState>.contract() = this.state.contract.split(".").last()
}
/**

View File

@ -13,10 +13,7 @@ import javafx.scene.text.Font
import javafx.scene.text.FontWeight
import javafx.stage.Window
import net.corda.client.jfx.model.*
import net.corda.client.jfx.utils.ChosenList
import net.corda.client.jfx.utils.isNotNull
import net.corda.client.jfx.utils.map
import net.corda.client.jfx.utils.unique
import net.corda.client.jfx.utils.*
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Amount.Companion.sumOrNull
import net.corda.core.contracts.withoutIssuer
@ -31,10 +28,10 @@ import net.corda.core.utilities.getOrThrow
import net.corda.explorer.formatters.PartyNameFormatter
import net.corda.explorer.model.CashTransaction
import net.corda.explorer.model.IssuerModel
import net.corda.explorer.model.ReportingCurrencyModel
import net.corda.explorer.views.bigDecimalFormatter
import net.corda.explorer.views.byteFormatter
import net.corda.explorer.views.stringConverter
import net.corda.explorer.views.toKnownParty
import net.corda.finance.flows.AbstractCashFlow
import net.corda.finance.flows.CashExitFlow
import net.corda.finance.flows.CashExitFlow.ExitRequest
@ -42,7 +39,6 @@ import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.finance.flows.CashIssueAndPaymentFlow.IssueAndPaymentRequest
import net.corda.finance.flows.CashPaymentFlow
import net.corda.finance.flows.CashPaymentFlow.PaymentRequest
import net.corda.testing.chooseIdentity
import net.corda.testing.chooseIdentityAndCert
import org.controlsfx.dialog.ExceptionDialog
import tornadofx.*
@ -71,15 +67,15 @@ class NewTransaction : Fragment() {
private val issueRef = SimpleObjectProperty<Byte>()
// Inject data
private val parties by observableList(NetworkIdentityModel::parties)
private val issuers by observableList(IssuerModel::issuers)
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
private val myIdentity by observableValue(NetworkIdentityModel::myIdentity)
private val notaries by observableList(NetworkIdentityModel::notaries)
private val cash by observableList(ContractStateModel::cash)
private val executeButton = ButtonType("Execute", ButtonBar.ButtonData.APPLY)
private val currencyTypes by observableList(IssuerModel::currencyTypes)
private val supportedCurrencies by observableList(ReportingCurrencyModel::supportedCurrencies)
private val supportedCurrencies by observableList(IssuerModel::supportedCurrencies)
private val transactionTypes by observableList(IssuerModel::transactionTypes)
private val issuers = cash.map { it.token.issuer }
private val currencyItems = ChosenList(transactionTypeCB.valueProperty().map {
when (it) {
@ -156,7 +152,7 @@ class NewTransaction : Fragment() {
val issueRef = if (issueRef.value != null) OpaqueBytes.of(issueRef.value) else defaultRef
when (it) {
executeButton -> when (transactionTypeCB.value) {
CashTransaction.Issue -> IssueAndPaymentRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.party, notaries.first(), anonymous)
CashTransaction.Issue -> IssueAndPaymentRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef, partyBChoiceBox.value.party, notaries.first().value!!, anonymous)
CashTransaction.Pay -> PaymentRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), partyBChoiceBox.value.party, anonymous = anonymous)
CashTransaction.Exit -> ExitRequest(Amount.fromDecimal(amount.value, currencyChoiceBox.value), issueRef)
else -> null
@ -192,7 +188,7 @@ class NewTransaction : Fragment() {
issuerLabel.visibleProperty().bind(transactionTypeCB.valueProperty().isNotNull)
// TODO This concept should burn (after services removal...)
issuerChoiceBox.apply {
items = issuers.map { it.chooseIdentity() }.unique().sorted()
items = issuers.map { it.party.owningKey.toKnownParty().value }.filterNotNull().unique().sorted()
converter = stringConverter { PartyNameFormatter.short.format(it.name) }
visibleProperty().bind(transactionTypeCB.valueProperty().map { it == CashTransaction.Pay })
}

View File

@ -19,7 +19,7 @@
<Insets bottom="25" left="5" right="5" top="5"/>
</StackPane.margin>
<TitledPane styleClass="networkTile" text="My Identity">
<BorderPane fx:id="myIdentityPane" minHeight="150"/>
<BorderPane fx:id="myIdentityPane"/>
</TitledPane>
<TitledPane styleClass="networkTile" text="Notaries">
<BorderPane minHeight="150">