Merge branch 'master' of https://github.com/corda/enterprise into christians-jmeter-flows

This commit is contained in:
Christian Sailer 2017-11-24 10:05:35 +00:00
commit 2fa39dc7d3
27 changed files with 862 additions and 14 deletions

5
.idea/compiler.xml generated
View File

@ -12,6 +12,9 @@
<module name="bank-of-corda-demo_test" target="1.8" />
<module name="buildSrc_main" target="1.8" />
<module name="buildSrc_test" target="1.8" />
<module name="business-network-demo_integrationTest" target="1.8" />
<module name="business-network-demo_main" target="1.8" />
<module name="business-network-demo_test" target="1.8" />
<module name="client_main" target="1.8" />
<module name="client_test" target="1.8" />
<module name="confidential-identities_main" target="1.8" />
@ -115,6 +118,8 @@
<module name="rpc_main" target="1.8" />
<module name="rpc_smokeTest" target="1.8" />
<module name="rpc_test" target="1.8" />
<module name="samples-business-network-demo_main" target="1.8" />
<module name="samples-business-network-demo_test" target="1.8" />
<module name="samples_main" target="1.8" />
<module name="samples_test" target="1.8" />
<module name="sandbox_main" target="1.8" />

View File

@ -0,0 +1,32 @@
create table contract_cash_states (
output_index int4 not null,
transaction_id varchar(64) not null,
ccy_code varchar(3),
issuer_key_hash varchar(130),
issuer_ref bytea,
owner_name varchar(255),
pennies int8,
primary key (output_index, transaction_id)
);
create index ccy_code_idx on contract_cash_states (ccy_code);
create index pennies_idx on contract_cash_states (pennies);
create table cp_states (
output_index int4 not null,
transaction_id varchar(64) not null,
ccy_code varchar(3),
face_value int8,
face_value_issuer_key_hash varchar(130),
face_value_issuer_ref bytea,
issuance_key_hash varchar(130),
issuance_ref bytea,
maturity_instant timestamp,
owner_key_hash varchar(130),
primary key (output_index, transaction_id)
);
create index ccy_code_index on cp_states (ccy_code);
create index maturity_index on cp_states (maturity_instant);
create index face_value_index on cp_states (face_value);

View File

@ -0,0 +1,238 @@
create table link_nodeinfo_party (
node_info_id int4 not null,
party_name varchar(255) not null
);
create table node_attachments (
att_id varchar(255) not null,
content oid,
filename varchar(255),
insertion_date timestamp not null,
uploader varchar(255),
primary key (att_id)
);
create table node_bft_committed_states (
output_index int4 not null,
transaction_id varchar(64) not null,
consuming_input_index int4,
consuming_transaction_id varchar(255),
requesting_party_name varchar(255),
requesting_party_key bytea,
primary key (output_index, transaction_id)
);
create table node_checkpoints (
checkpoint_id varchar(64) not null,
checkpoint_value oid,
primary key (checkpoint_id)
);
create table node_contract_upgrades (
state_ref varchar(96) not null,
contract_class_name varchar(255),
primary key (state_ref)
);
create table node_identities (
pk_hash varchar(130) not null,
identity_value oid,
primary key (pk_hash)
);
create table node_info_hosts (
host varchar(255) not null,
port int4 not null,
node_info_id int4,
primary key (host, port)
);
create table node_info_party_cert (
party_name varchar(255) not null,
isMain boolean not null,
owning_key_hash varchar(130),
party_cert_binary oid,
primary key (party_name)
);
create table node_infos (
node_info_id int4 not null,
node_info_hash varchar(64),
platform_version int4,
serial int8,
primary key (node_info_id)
);
create table node_message_ids (
message_id varchar(36) not null,
insertion_time timestamp,
primary key (message_id)
);
create table node_message_retry (
message_id int8 not null,
message oid,
recipients oid,
primary key (message_id)
);
create table node_named_identities (
name varchar(128) not null,
pk_hash varchar(130),
primary key (name)
);
create table node_notary_commit_log (
output_index int4 not null,
transaction_id varchar(64) not null,
consuming_input_index int4,
consuming_transaction_id varchar(255),
requesting_party_name varchar(255),
requesting_party_key bytea,
primary key (output_index, transaction_id)
);
create table node_our_key_pairs (
public_key_hash varchar(130) not null,
private_key oid,
public_key oid,
primary key (public_key_hash)
);
create table node_raft_committed_states (
id varchar(255) not null,
state_index int8,
state_value oid,
primary key (id)
);
create table node_scheduled_states (
output_index int4 not null,
transaction_id varchar(64) not null,
scheduled_at timestamp not null,
primary key (output_index, transaction_id)
);
create table node_transaction_mappings (
tx_id varchar(64) not null,
state_machine_run_id varchar(36),
primary key (tx_id)
);
create table node_transactions (
tx_id varchar(64) not null,
transaction_value oid,
primary key (tx_id)
);
create table vault_fungible_states (
output_index int4 not null,
transaction_id varchar(64) not null,
issuer_name varchar(255),
issuer_ref bytea,
owner_name varchar(255),
quantity int8,
primary key (output_index, transaction_id)
);
create table vault_fungible_states_parts (
output_index int4 not null,
transaction_id varchar(64) not null,
participants varchar(255)
);
create table vault_linear_states (
output_index int4 not null,
transaction_id varchar(64) not null,
external_id varchar(255),
uuid bytea not null,
primary key (output_index, transaction_id)
);
create table vault_linear_states_parts (
output_index int4 not null,
transaction_id varchar(64) not null,
participants varchar(255)
);
create table vault_states (
output_index int4 not null,
transaction_id varchar(64) not null,
consumed_timestamp timestamp,
contract_state_class_name varchar(255),
lock_id varchar(255),
lock_timestamp timestamp,
notary_name varchar(255),
recorded_timestamp timestamp,
state_status int4,
primary key (output_index, transaction_id)
);
create table vault_transaction_notes (
seq_no int4 not null,
note varchar(255),
transaction_id varchar(64),
primary key (seq_no)
);
create index att_id_idx on node_attachments (att_id);
create index external_id_index on vault_linear_states (external_id);
create index uuid_index on vault_linear_states (uuid);
create index state_status_idx on vault_states (state_status);
create index lock_id_idx on vault_states (lock_id, state_status);
create index transaction_id_index on vault_transaction_notes (transaction_id);
create sequence hibernate_sequence start 1 increment 1;
alter table link_nodeinfo_party
add constraint FK1ua3h6nwwfji0mn23c5d1xx8e
foreign key (party_name)
references node_info_party_cert;
alter table link_nodeinfo_party
add constraint FK544l9wsec35ph7hxrtwfd2lws
foreign key (node_info_id)
references node_infos;
alter table node_info_hosts
add constraint FK5ie46htdrkftmwe6rpwrnp0mp
foreign key (node_info_id)
references node_infos;
alter table vault_fungible_states_parts
add constraint FKchmfeq1ldqnoq9idv9ogxauqm
foreign key (output_index, transaction_id)
references vault_fungible_states;
alter table vault_linear_states_parts
add constraint FKhafsv733d0bo9j1tg352koq3y
foreign key (output_index, transaction_id)
references vault_linear_states;

View File

@ -201,6 +201,7 @@ create index att_id_idx on node_attachments (att_id);
create index external_id_index on vault_linear_states (external_id);
create index uuid_index on vault_linear_states (uuid);
create index state_status_idx on vault_states (state_status);
create index lock_id_idx on vault_states (lock_id, state_status);
create index transaction_id_index on vault_transaction_notes (transaction_id);
create sequence hibernate_sequence start with 1 increment by 1;

View File

@ -0,0 +1,49 @@
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'idea'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
apply plugin: 'net.corda.plugins.cordapp'
apply plugin: 'net.corda.plugins.cordformation'
apply plugin: 'maven-publish'
sourceSets {
integrationTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
}
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
// For CSV parsing.
compile "com.opencsv:opencsv:4.0"
// Corda integration dependencies
cordaCompile project(':core')
cordaCompile project(':client:rpc')
// Cordapp dependencies
// Specify your cordapp's dependencies below, including dependent cordapps
// Test dependencies
testCompile "junit:junit:$junit_version"
}
idea {
module {
downloadJavadoc = true // defaults to false
downloadSources = true
}
}
jar {
manifest {
attributes(
'Automatic-Module-Name': 'net.corda.samples.demos.businessnetwork'
)
}
}

View File

@ -0,0 +1,32 @@
package net.corda.sample.businessnetwork
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract
import net.corda.core.contracts.requireSingleCommand
import net.corda.core.contracts.requireThat
import net.corda.core.transactions.LedgerTransaction
class IOUContract : Contract {
// Our Create command.
class Create : CommandData
override fun verify(tx: LedgerTransaction) {
val command = tx.commands.requireSingleCommand<Create>()
requireThat {
// Constraints on the shape of the transaction.
"No inputs should be consumed when issuing an IOU." using (tx.inputs.isEmpty())
"There should be one output state of type IOUState." using (tx.outputs.size == 1)
// IOU-specific constraints.
val out = tx.outputsOfType<IOUState>().single()
"The IOU's value must be non-negative." using (out.value > 0)
"The lender and the borrower cannot be the same entity." using (out.lender != out.borrower)
// Constraints on the signers.
"There must be two signers." using (command.signers.toSet().size == 2)
"The borrower and lender must be signers." using (command.signers.containsAll(listOf(
out.borrower.owningKey, out.lender.owningKey)))
}
}
}

View File

@ -0,0 +1,71 @@
package net.corda.sample.businessnetwork
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract
import net.corda.core.flows.*
import net.corda.core.identity.CordaX500Name
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 kotlin.reflect.jvm.jvmName
@InitiatingFlow
@StartableByRPC
class IOUFlow(val iouValue: Int,
val otherParty: Party) : FlowLogic<SignedTransaction>(), MembershipAware {
companion object {
val allowedMembershipName =
CordaX500Name("AliceBobMembershipList", "AliceBob", "Washington", "US")
}
/** The progress tracker provides checkpoints indicating the progress of the flow to observers. */
override val progressTracker = ProgressTracker()
/** The flow logic is encapsulated within the call() method. */
@Suspendable
override fun call(): SignedTransaction {
// Check whether the other party belongs to the membership list important for us.
otherParty.checkMembership(allowedMembershipName, this)
// Prior to creating any state - obtain consent from [otherParty] to borrow from us.
// This is done early enough in the flow such that if the other party rejects - do not do any unnecessary processing in this flow.
// Even if this is not done, later on upon signatures collection phase membership will be checked on the other side and
// transaction rejected if this doesn't hold. See [IOUFlowResponder] for more information.
otherParty.checkSharesSameMembershipWithUs(allowedMembershipName, this)
// We retrieve the notary identity from the network map.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// We create a transaction builder
val txBuilder = TransactionBuilder(notary = notary)
// We create the transaction components.
val outputState = IOUState(iouValue, ourIdentity, otherParty)
val outputContract = IOUContract::class.jvmName
val outputContractAndState = StateAndContract(outputState, outputContract)
val cmd = Command(IOUContract.Create(), listOf(ourIdentity.owningKey, otherParty.owningKey))
// We add the items to the builder.
txBuilder.withItems(outputContractAndState, cmd)
// Verifying the transaction.
txBuilder.verify(serviceHub)
// Signing the transaction.
val signedTx = serviceHub.signInitialTransaction(txBuilder)
// Creating a session with the other party.
val otherpartySession = initiateFlow(otherParty)
// Obtaining the counterparty's signature.
val fullySignedTx = subFlow(CollectSignaturesFlow(signedTx, listOf(otherpartySession), CollectSignaturesFlow.tracker()))
// Finalising the transaction.
return subFlow(FinalityFlow(fullySignedTx))
}
}

View File

@ -0,0 +1,27 @@
package net.corda.sample.businessnetwork
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.requireThat
import net.corda.core.flows.FlowLogic
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.CheckMembershipFlow
@InitiatedBy(IOUFlow::class)
class IOUFlowResponder(val otherPartySession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
subFlow(CheckMembershipFlow(IOUFlow.allowedMembershipName, otherPartySession.counterparty))
subFlow(object : SignTransactionFlow(otherPartySession, SignTransactionFlow.tracker()) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
val output = stx.tx.outputs.single().data
"This must be an IOU transaction." using (output is IOUState)
val iou = output as IOUState
"The IOU's value can't be too high." using (iou.value < 100)
}
})
}
}

View File

@ -0,0 +1,11 @@
package net.corda.sample.businessnetwork.membership
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
class CheckMembershipFlow(private val membershipName: CordaX500Name, private val counterParty: AbstractParty) : FlowLogic<Unit>(), MembershipAware {
override fun call() {
counterParty.checkMembership(membershipName, this)
}
}

View File

@ -0,0 +1,31 @@
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.identity.Party
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)
fun <T> Party.checkSharesSameMembershipWithUs(membershipName: CordaX500Name, initiatorFlow: FlowLogic<T>) {
initiatorFlow.stateMachine.initiateFlow(this, CheckMembershipFlow(membershipName, initiatorFlow.ourIdentity))
}
}
class MembershipViolationException(msg: String) : FlowException(msg)

View File

@ -0,0 +1,19 @@
package net.corda.sample.businessnetwork.membership
import net.corda.core.identity.AbstractParty
/**
* Represents a concept of a parties member list.
* Nodes or other parties can be grouped into membership lists to represent business network relationship among them
*/
interface MembershipList {
/**
* @return true if a particular party belongs to a list, false otherwise.
*/
operator fun contains(party: AbstractParty): Boolean = content().contains(party)
/**
* Obtains a full content of a membership list.
*/
fun content(): Set<AbstractParty>
}

View File

@ -0,0 +1,16 @@
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,30 @@
package net.corda.sample.businessnetwork.membership.internal
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 java.io.InputStream
/**
* Implementation of a MembershipList that reads the content from CSV file.
*/
class CsvMembershipList(private val inputStream: InputStream, private val networkMapCache: NetworkMapCache) : MembershipList {
private val allParties by lazy {
fun lookUpParty(name: CordaX500Name): AbstractParty? = networkMapCache.getPeerByLegalName(name)
inputStream.use {
val reader = CSVReaderBuilder(it.reader()).withSkipLines(1).build()
reader.use {
val linesRead = reader.readAll()
val commentsRemoved = linesRead.filterNot { line -> line.isEmpty() || line[0].startsWith("#") }
val partiesList = commentsRemoved.mapNotNull { line -> lookUpParty(CordaX500Name.parse(line[0])) }
partiesList.toSet()
}
}
}
override fun content(): Set<AbstractParty> = allParties
}

View File

@ -0,0 +1,10 @@
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
object MembershipListProvider {
fun obtainMembershipList(listName: CordaX500Name, networkMapCache: NetworkMapCache): MembershipList =
CsvMembershipList(MembershipListProvider::class.java.getResourceAsStream("${listName.commonName}.csv"), networkMapCache)
}

View File

@ -0,0 +1,10 @@
package net.corda.sample.businessnetwork
import net.corda.core.contracts.ContractState
import net.corda.core.identity.Party
class IOUState(val value: Int,
val lender: Party,
val borrower: Party) : ContractState {
override val participants get() = listOf(lender, borrower)
}

View File

@ -0,0 +1,3 @@
Party name, Custom Field
"C=ES,L=Madrid,O=Alice Corp",UserCustomValue1
"C=IT,L=Rome,O=Bob Plc",UserCustomValue2
1 Party name Custom Field
2 C=ES,L=Madrid,O=Alice Corp UserCustomValue1
3 C=IT,L=Rome,O=Bob Plc UserCustomValue2

View File

@ -46,6 +46,7 @@ include 'samples:network-visualiser'
include 'samples:simm-valuation-demo'
include 'samples:notary-demo'
include 'samples:bank-of-corda-demo'
include 'samples:business-network-demo'
include 'cordform-common'
include 'network-management'
include 'verify-enclave'

View File

@ -30,6 +30,9 @@ dependencies {
compile project(':node-driver')
compile project(':finance')
// Additional CorDapps that will be displayed in the GUI.
compile project(':samples:business-network-demo')
// Capsule is a library for building independently executable fat JARs.
// We only need this dependency to compile our Caplet against.
compileOnly "co.paralleluniverse:capsule:$capsule_version"

View File

@ -14,6 +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.finance.GBP
import net.corda.finance.USD
import net.corda.finance.contracts.asset.Cash
@ -33,8 +35,10 @@ import java.util.*
class ExplorerSimulation(private val options: OptionSet) {
private val user = User("user1", "test", permissions = setOf(
startFlow<CashPaymentFlow>(),
startFlow<CashConfigDataFlow>()
))
startFlow<CashConfigDataFlow>(),
startFlow<IOUFlow>(),
startFlow<ObtainMembershipListContentFlow>())
)
private val manager = User("manager", "test", permissions = setOf(
startFlow<CashIssueAndPaymentFlow>(),
startFlow<CashPaymentFlow>(),
@ -53,18 +57,15 @@ class ExplorerSimulation(private val options: OptionSet) {
private val issuers = HashMap<Currency, CordaRPCOps>()
private val parties = ArrayList<Pair<Party, CordaRPCOps>>()
init {
startDemoNodes()
}
private fun onEnd() {
println("Closing RPC connections")
RPCConnections.forEach { it.close() }
}
private fun startDemoNodes() {
fun startDemoNodes() {
val portAllocation = PortAllocation.Incremental(20000)
driver(portAllocation = portAllocation, extraCordappPackagesToScan = listOf("net.corda.finance"), waitForAllNodesToFinish = true) {
driver(portAllocation = portAllocation, extraCordappPackagesToScan = listOf("net.corda.finance", IOUFlow::class.java.`package`.name),
isDebug = true, waitForAllNodesToFinish = 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))
val bob = startNode(providedName = BOB.name, rpcUsers = listOf(user))

View File

@ -16,6 +16,7 @@ import net.corda.explorer.model.CordaViewModel
import net.corda.explorer.model.SettingsModel
import net.corda.explorer.views.*
import net.corda.explorer.views.cordapps.cash.CashViewer
import net.corda.explorer.views.cordapps.iou.IOUViewer
import org.apache.commons.lang.SystemUtils
import org.controlsfx.dialog.ExceptionDialog
import tornadofx.App
@ -104,6 +105,7 @@ class Main : App(MainView::class) {
registerView<StateMachineViewer>()
// CordApps Views.
registerView<CashViewer>()
registerView<IOUViewer>()
// Tools.
registerView<Network>()
registerView<Settings>()
@ -128,5 +130,5 @@ class Main : App(MainView::class) {
fun main(args: Array<String>) {
val parser = OptionParser("SF")
val options = parser.parse(*args)
ExplorerSimulation(options)
ExplorerSimulation(options).startDemoNodes()
}

View File

@ -0,0 +1,22 @@
package net.corda.explorer.model
import javafx.collections.FXCollections
import javafx.collections.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.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
class MembershipListModel {
private val proxy by observableValue(NodeMonitorModel::proxyObservable)
private val members = proxy.map { it?.startFlow(::ObtainMembershipListContentFlow, IOUFlow.allowedMembershipName)?.returnValue?.getOrThrow() }
private val observableValueOfParties = members.map {
FXCollections.observableList(it?.toList() ?: emptyList<AbstractParty>())
}
val allParties: ObservableList<AbstractParty> = ChosenList(observableValueOfParties)
}

View File

@ -14,6 +14,7 @@ import javafx.scene.control.ListView
import javafx.scene.control.TableView
import javafx.scene.control.TitledPane
import javafx.scene.layout.BorderPane
import javafx.scene.layout.Pane
import javafx.scene.layout.VBox
import net.corda.client.jfx.model.*
import net.corda.client.jfx.utils.filterNotNull
@ -27,9 +28,11 @@ 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.explorer.AmountDiff
import net.corda.explorer.formatters.AmountFormatter
import net.corda.explorer.formatters.Formatter
import net.corda.explorer.formatters.NumberFormatter
import net.corda.explorer.formatters.PartyNameFormatter
import net.corda.explorer.identicon.identicon
import net.corda.explorer.identicon.identiconToolTip
@ -290,6 +293,25 @@ class TransactionViewer : CordaView("Transactions") {
}
}
}
is IOUState -> {
fun Pane.partyLabel(party: Party) = label(party.nameOrNull().let { PartyNameFormatter.short.format(it) } ?: "Anonymous") {
tooltip(party.owningKey.toBase58String())
}
row {
label("Amount :") { gridpaneConstraints { hAlignment = HPos.RIGHT } }
label(NumberFormatter.boring.format(data.value))
}
row {
label("Borrower :") { gridpaneConstraints { hAlignment = HPos.RIGHT } }
val party = data.borrower
partyLabel(party)
}
row {
label("Lender :") { gridpaneConstraints { hAlignment = HPos.RIGHT } }
val party = data.lender
partyLabel(party)
}
}
// TODO : Generic view using reflection?
else -> label {}
}
@ -309,11 +331,15 @@ private fun calculateTotalEquiv(myIdentity: Party?,
inputs: List<ContractState>,
outputs: List<ContractState>): AmountDiff<Currency> {
val (reportingCurrency, exchange) = reportingCurrencyExchange
fun List<ContractState>.sum() = this.map { it as? Cash.State }
fun List<ContractState>.sum(): Long {
val cashSum: Long = map { it as? Cash.State }
.filterNotNull()
.filter { it.owner.owningKey.toKnownParty().value == myIdentity }
.map { exchange(it.amount.withoutIssuer()).quantity }
.sum()
val iouSum: Int = mapNotNull {it as? IOUState }.map { it.value }.sum() * 100
return cashSum + iouSum
}
// For issuing cash, if I am the issuer and not the owner (e.g. issuing cash to other party), count it as negative.
val issuedAmount = if (inputs.isEmpty()) outputs.map { it as? Cash.State }

View File

@ -0,0 +1,27 @@
package net.corda.explorer.views.cordapps.iou
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
import javafx.scene.input.MouseButton
import javafx.scene.layout.BorderPane
import net.corda.explorer.model.CordaView
import tornadofx.*
class IOUViewer : CordaView("IOU") {
// Inject UI elements.
override val root: BorderPane by fxml()
override val icon: FontAwesomeIcon = FontAwesomeIcon.CHEVRON_CIRCLE_RIGHT
// Wire up UI
init {
root.top = hbox(5.0) {
button("New Transaction", FontAwesomeIconView(FontAwesomeIcon.PLUS)) {
setOnMouseClicked {
if (it.button == MouseButton.PRIMARY) {
find<NewTransaction>().show(this@IOUViewer.root.scene.window)
}
}
}
}
}
}

View File

@ -0,0 +1,139 @@
package net.corda.explorer.views.cordapps.iou
import com.google.common.base.Splitter
import javafx.beans.binding.Bindings
import javafx.beans.binding.BooleanBinding
import javafx.collections.FXCollections
import javafx.geometry.Insets
import javafx.geometry.VPos
import javafx.scene.control.*
import javafx.scene.layout.GridPane
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.isNotNull
import net.corda.client.jfx.utils.map
import net.corda.core.flows.FlowException
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.explorer.formatters.PartyNameFormatter
import net.corda.explorer.model.MembershipListModel
import net.corda.explorer.views.bigDecimalFormatter
import net.corda.explorer.views.stringConverter
import net.corda.testing.chooseIdentityAndCert
import org.controlsfx.dialog.ExceptionDialog
import tornadofx.*
class NewTransaction : Fragment() {
override val root by fxml<DialogPane>()
// Components
private val partyATextField by fxid<TextField>()
private val partyBChoiceBox by fxid<ChoiceBox<PartyAndCertificate>>()
private val amountTextField by fxid<TextField>()
// Inject data
private val parties by observableList(NetworkIdentityModel::parties)
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
private val myIdentity by observableValue(NetworkIdentityModel::myIdentity)
private val notaries by observableList(NetworkIdentityModel::notaries)
private val executeButton = ButtonType("Execute", ButtonBar.ButtonData.APPLY)
fun show(window: Window) {
// Every time re-query from the server side
val elementsFromServer = MembershipListModel().allParties
partyBChoiceBox.apply {
items = FXCollections.observableList(parties.map { it.chooseIdentityAndCert() }).filtered { elementsFromServer.contains(it.party) }.sorted()
}
newTransactionDialog(window).showAndWait().ifPresent { request ->
val dialog = Alert(Alert.AlertType.INFORMATION).apply {
headerText = null
contentText = "Transaction Started."
dialogPane.isDisable = true
initOwner(window)
show()
}
val handle: FlowHandle<SignedTransaction> = rpcProxy.value!!.startFlow(::IOUFlow, request.first, request.second)
runAsync {
try {
handle.returnValue.getOrThrow()
} finally {
dialog.dialogPane.isDisable = false
}
}.ui {
val stx: SignedTransaction = it
val type = "IOU posting completed successfully"
dialog.alertType = Alert.AlertType.INFORMATION
dialog.dialogPane.content = gridpane {
padding = Insets(10.0, 40.0, 10.0, 20.0)
vgap = 10.0
hgap = 10.0
row { label(type) { font = Font.font(font.family, FontWeight.EXTRA_BOLD, font.size + 2) } }
row {
label("Transaction ID :") { GridPane.setValignment(this, VPos.TOP) }
label { text = Splitter.fixedLength(16).split("${stx.id}").joinToString("\n") }
}
}
dialog.dialogPane.scene.window.sizeToScene()
}.setOnFailed {
val ex = it.source.exception
when (ex) {
is FlowException -> {
dialog.alertType = Alert.AlertType.ERROR
dialog.contentText = ex.message
}
else -> {
dialog.close()
ExceptionDialog(ex).apply { initOwner(window) }.showAndWait()
}
}
}
}
}
private fun newTransactionDialog(window: Window) = Dialog<Pair<Int, Party>>().apply {
dialogPane = root
initOwner(window)
setResultConverter {
when (it) {
executeButton -> Pair(amountTextField.text.toInt(), partyBChoiceBox.value.party)
else -> null
}
}
}
init {
// Disable everything when not connected to node.
val notariesNotNullBinding = Bindings.createBooleanBinding({ notaries.isNotEmpty() }, arrayOf(notaries))
val enableProperty = myIdentity.isNotNull().and(rpcProxy.isNotNull()).and(notariesNotNullBinding)
root.disableProperty().bind(enableProperty.not())
// Party A text field always display my identity name, not editable.
partyATextField.textProperty().bind(myIdentity.map { it?.let { PartyNameFormatter.short.format(it.name) } ?: "" })
// Party B
partyBChoiceBox.apply {
converter = stringConverter { it?.let { PartyNameFormatter.short.format(it.name) } ?: "" }
}
// Amount
amountTextField.textFormatter = bigDecimalFormatter()
// Validate inputs.
val formValidCondition = arrayOf(
myIdentity.isNotNull(),
partyBChoiceBox.visibleProperty().not().or(partyBChoiceBox.valueProperty().isNotNull),
amountTextField.textProperty().isNotEmpty
).reduce(BooleanBinding::and)
// Enable execute button when form is valid.
root.buttonTypes.add(executeButton)
root.lookupButton(executeButton).disableProperty().bind(formValidCondition.not())
}
}

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.layout.*?>
<BorderPane stylesheets="@../../../css/corda.css" xmlns="http://javafx.com/javafx/8.0.76-ea">
<padding>
<Insets right="5" left="5" bottom="5" top="5"/>
</padding>
</BorderPane>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<DialogPane stylesheets="@../../../css/corda.css" xmlns="http://javafx.com/javafx/8.0.76-ea"
xmlns:fx="http://javafx.com/fxml/1">
<content>
<GridPane hgap="10" vgap="10">
<!-- Row 0 -->
<Label fx:id="partyALabel" GridPane.halignment="RIGHT" GridPane.rowIndex="1" text="Lender : "/>
<TextField fx:id="partyATextField" GridPane.columnIndex="1" GridPane.columnSpan="4" GridPane.rowIndex="1"
editable="false" disable="true"/>
<!-- Row 1 -->
<Label fx:id="partyBLabel" GridPane.halignment="RIGHT" GridPane.rowIndex="2" text="Borrower : "/>
<ChoiceBox fx:id="partyBChoiceBox" maxWidth="Infinity" GridPane.columnIndex="1" GridPane.columnSpan="4"
GridPane.fillWidth="true" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2"/>
<!-- Row 2 -->
<Label fx:id="amountLabel" text="Amount : " GridPane.halignment="RIGHT" GridPane.rowIndex="5"/>
<TextField fx:id="amountTextField" maxWidth="Infinity" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS"
GridPane.rowIndex="5" GridPane.columnSpan="4"/>
<padding>
<Insets bottom="20.0" left="30.0" right="30.0" top="30.0"/>
</padding>
</GridPane>
</content>
</DialogPane>

View File

@ -51,6 +51,9 @@ class Launcher {
}
jmeter.start(arrayOf("-s", "-p", (capsuleDirPath / "jmeter.properties").toString()) + extraArgs + args)
} else {
val searchPath = Files.readAllLines(Paths.get(System.getProperty("search_paths_file"))).first()
logger.info("search_paths = $searchPath")
System.setProperty("search_paths", searchPath)
jmeter.start(maybeOpenSshTunnels(args))
}
}