Add PartiallyResolvedTransaction to client

This commit is contained in:
Andras Slemmer 2016-09-23 16:44:33 +01:00
parent adf0876b18
commit 43d18d46bb
9 changed files with 292 additions and 100 deletions

View File

@ -105,9 +105,9 @@ class NodeMonitorClientTests {
}
),
expect { tx: ServiceToClientEvent.Transaction ->
require(tx.transaction.inputs.isEmpty())
require(tx.transaction.outputs.size == 1)
val signaturePubKeys = tx.transaction.mustSign.toSet()
require(tx.transaction.tx.inputs.isEmpty())
require(tx.transaction.tx.outputs.size == 1)
val signaturePubKeys = tx.transaction.sigs.map { it.by }.toSet()
// Only Alice signed
require(signaturePubKeys.size == 1)
require(signaturePubKeys.contains(aliceNode.identity.owningKey))
@ -137,9 +137,9 @@ class NodeMonitorClientTests {
}
),
expect { tx: ServiceToClientEvent.Transaction ->
require(tx.transaction.inputs.size == 1)
require(tx.transaction.outputs.size == 1)
val signaturePubKeys = tx.transaction.mustSign.toSet()
require(tx.transaction.tx.inputs.size == 1)
require(tx.transaction.tx.outputs.size == 1)
val signaturePubKeys = tx.transaction.sigs.map { it.by }.toSet()
// Alice and Notary signed
require(signaturePubKeys.size == 2)
require(signaturePubKeys.contains(aliceNode.identity.owningKey))

View File

@ -0,0 +1,22 @@
package com.r3corda.client.fxutils
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.MapChangeListener
import javafx.collections.ObservableMap
fun <K, V> ObservableMap<K, V>.getObservableValue(key: K): ObservableValue<V?> {
val property = SimpleObjectProperty(get(key))
addListener { change: MapChangeListener.Change<out K, out V> ->
if (change.key == key) {
// This is true both when a fresh element was inserted and when an existing was updated
if (change.wasAdded()) {
property.set(change.valueAdded)
} else if (change.wasRemoved()) {
property.set(null)
}
}
}
return property
}

View File

@ -3,6 +3,7 @@ package com.r3corda.client.fxutils
import javafx.beans.binding.Bindings
import javafx.beans.property.ReadOnlyObjectWrapper
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.collections.transformation.FilteredList
import org.fxmisc.easybind.EasyBind
@ -58,8 +59,13 @@ fun <A, B, C, D, R> ((A, B, C, D) -> R).lift(
* val person: ObservableValue<Person> = (..)
* val personHeight: ObservableValue<Long> = person.bind { it.height }
*/
fun <A, B> ObservableValue<out A>.bind(function: (A) -> ObservableValue<out B>): ObservableValue<out B> =
// We cast here to enforce variance, flatMap should be covariant
fun <A, B> ObservableValue<out A>.bind(function: (A) -> ObservableValue<B>): ObservableValue<B> =
EasyBind.monadic(this).flatMap(function)
/**
* A variant of [bind] that has out variance on the output type. This is sometimes useful when kotlin is too eager to
* propagate variance constraints and type inference fails.
*/
fun <A, B> ObservableValue<out A>.bindOut(function: (A) -> ObservableValue<out B>): ObservableValue<out B> =
@Suppress("UNCHECKED_CAST")
EasyBind.monadic(this).flatMap(function as (A) -> ObservableValue<B>)
@ -71,7 +77,7 @@ fun <A, B> ObservableValue<out A>.bind(function: (A) -> ObservableValue<out B>):
*
* val filteredPeople: ObservableList<Person> = people.filter(filterCriterion.map(filterFunction))
*/
fun <A> ObservableList<out A>.filter(predicate: ObservableValue<out (A) -> Boolean>): ObservableList<out A> {
fun <A> ObservableList<out A>.filter(predicate: ObservableValue<(A) -> Boolean>): ObservableList<A> {
// We cast here to enforce variance, FilteredList should be covariant
@Suppress("UNCHECKED_CAST")
return FilteredList<A>(this as ObservableList<A>).apply {
@ -101,4 +107,10 @@ fun <A, B> ObservableList<out A>.fold(initial: B, folderFunction: (B, A) -> B):
* val people: ObservableList<Person> = (..)
* val heights: ObservableList<Long> = people.map(Person::height).flatten()
*/
fun <A> ObservableList<out ObservableValue<out A>>.flatten(): ObservableList<out A> = FlattenedList(this)
fun <A> ObservableList<out ObservableValue<out A>>.flatten(): ObservableList<A> = FlattenedList(this)
/**
* val people: List<Person> = listOf(alice, bob)
* val heights: ObservableList<Long> = people.map(Person::height).sequence()
*/
fun <A> List<ObservableValue<out A>>.sequence(): ObservableList<A> = FlattenedList(FXCollections.observableArrayList(this))

View File

@ -1,9 +1,13 @@
package com.r3corda.client.model
import com.r3corda.client.fxutils.foldToObservableList
import com.r3corda.client.fxutils.getObservableValue
import com.r3corda.core.contracts.ContractState
import com.r3corda.core.contracts.StateAndRef
import com.r3corda.core.contracts.StateRef
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.transactions.LedgerTransaction
import com.r3corda.core.protocols.StateMachineRunId
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.node.services.monitor.ServiceToClientEvent
import com.r3corda.node.services.monitor.TransactionBuildResult
import com.r3corda.node.utilities.AddOrRemove
@ -11,22 +15,60 @@ import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
import javafx.collections.ObservableList
import javafx.collections.ObservableMap
import org.fxmisc.easybind.EasyBind
import org.jetbrains.exposed.sql.transactions.transaction
import org.slf4j.LoggerFactory
import rx.Observable
import java.time.Instant
import java.util.UUID
import kotlin.reflect.KProperty1
interface GatheredTransactionData {
val stateMachineRunId: ObservableValue<StateMachineRunId?>
val uuid: ObservableValue<UUID?>
val protocolStatus: ObservableValue<ProtocolStatus?>
val stateMachineStatus: ObservableValue<StateMachineStatus?>
val transaction: ObservableValue<LedgerTransaction?>
val transaction: ObservableValue<PartiallyResolvedTransaction?>
val status: ObservableValue<TransactionCreateStatus?>
val lastUpdate: ObservableValue<Instant>
val allEvents: ObservableList<out ServiceToClientEvent>
}
/**
* [PartiallyResolvedTransaction] holds a [SignedTransaction] that has zero or more inputs resolved. The intent is
* to prepare clients for cases where an input can only be resolved in the future/cannot be resolved at all (for example
* because of permissioning)
*/
data class PartiallyResolvedTransaction(
val transaction: SignedTransaction,
val inputs: List<ObservableValue<InputResolution>>
) {
val id = transaction.id
sealed class InputResolution(val stateRef: StateRef) {
class Unresolved(stateRef: StateRef) : InputResolution(stateRef)
class Resolved(val stateAndRef: StateAndRef<ContractState>) : InputResolution(stateAndRef.ref)
}
companion object {
fun fromSignedTransaction(
transaction: SignedTransaction,
transactions: ObservableMap<SecureHash, SignedTransaction>
) = PartiallyResolvedTransaction(
transaction = transaction,
inputs = transaction.tx.inputs.map { stateRef ->
EasyBind.map(transactions.getObservableValue(stateRef.txhash)) {
if (it == null) {
InputResolution.Unresolved(stateRef)
} else {
InputResolution.Resolved(it.tx.outRef(stateRef.index))
}
}
}
)
}
}
sealed class TransactionCreateStatus(val message: String?) {
class Started(message: String?) : TransactionCreateStatus(message)
class Failed(message: String?) : TransactionCreateStatus(message)
@ -47,12 +89,14 @@ data class GatheredTransactionDataWritable(
override val uuid: SimpleObjectProperty<UUID?> = SimpleObjectProperty(null),
override val stateMachineStatus: SimpleObjectProperty<StateMachineStatus?> = SimpleObjectProperty(null),
override val protocolStatus: SimpleObjectProperty<ProtocolStatus?> = SimpleObjectProperty(null),
override val transaction: SimpleObjectProperty<LedgerTransaction?> = SimpleObjectProperty(null),
override val transaction: SimpleObjectProperty<PartiallyResolvedTransaction?> = SimpleObjectProperty(null),
override val status: SimpleObjectProperty<TransactionCreateStatus?> = SimpleObjectProperty(null),
override val lastUpdate: SimpleObjectProperty<Instant>,
override val allEvents: ObservableList<ServiceToClientEvent> = FXCollections.observableArrayList()
) : GatheredTransactionData
private val log = LoggerFactory.getLogger(GatheredTransactionDataModel::class.java)
/**
* This model provides an observable list of states relating to the creation of a transaction not yet on ledger.
*/
@ -73,24 +117,37 @@ class GatheredTransactionDataModel {
* TODO: Expose a writable stream to combine [serviceToClient] with to allow recording of transactions made locally(UUID)
*/
val gatheredTransactionDataList: ObservableList<out GatheredTransactionData> =
serviceToClient.foldToObservableList<ServiceToClientEvent, GatheredTransactionDataWritable, Unit>(
initialAccumulator = Unit,
folderFun = { serviceToClientEvent, _unit, transactionStates ->
return@foldToObservableList when (serviceToClientEvent) {
serviceToClient.foldToObservableList<ServiceToClientEvent, GatheredTransactionDataWritable, ObservableMap<SecureHash, SignedTransaction>>(
initialAccumulator = FXCollections.observableHashMap<SecureHash, SignedTransaction>(),
folderFun = { serviceToClientEvent, transactions, transactionStates ->
val _unit = when (serviceToClientEvent) {
is ServiceToClientEvent.Transaction -> {
transactions.set(serviceToClientEvent.transaction.id, serviceToClientEvent.transaction)
val somewhatResolvedTransaction = PartiallyResolvedTransaction.fromSignedTransaction(
serviceToClientEvent.transaction,
transactions
)
newTransactionIdTransactionStateOrModify(transactionStates, serviceToClientEvent,
transaction = serviceToClientEvent.transaction,
transaction = somewhatResolvedTransaction,
tweak = {}
)
}
is ServiceToClientEvent.OutputState -> {}
is ServiceToClientEvent.OutputState -> {
}
is ServiceToClientEvent.StateMachine -> {
newFiberIdTransactionStateOrModify(transactionStates, serviceToClientEvent,
stateMachineRunId = serviceToClientEvent.id,
tweak = {
stateMachineStatus.set(when (serviceToClientEvent.addOrRemove) {
AddOrRemove.ADD -> StateMachineStatus.Added(serviceToClientEvent.label)
AddOrRemove.REMOVE -> StateMachineStatus.Removed(serviceToClientEvent.label)
AddOrRemove.REMOVE -> {
val currentStatus = stateMachineStatus.value
if (currentStatus is StateMachineStatus.Added) {
StateMachineStatus.Removed(currentStatus.stateMachineName)
} else {
StateMachineStatus.Removed(serviceToClientEvent.label)
}
}
})
}
)
@ -105,6 +162,15 @@ class GatheredTransactionDataModel {
}
is ServiceToClientEvent.TransactionBuild -> {
val state = serviceToClientEvent.state
when (state) {
is TransactionBuildResult.ProtocolStarted -> {
state.transaction?.let {
transactions.set(it.id, it)
}
}
}
newUuidTransactionStateOrModify(transactionStates, serviceToClientEvent,
uuid = serviceToClientEvent.id,
stateMachineRunId = when (state) {
@ -118,7 +184,9 @@ class GatheredTransactionDataModel {
tweak = {
return@newUuidTransactionStateOrModify when (state) {
is TransactionBuildResult.ProtocolStarted -> {
transaction.set(state.transaction)
state.transaction?.let {
transaction.set(PartiallyResolvedTransaction.fromSignedTransaction(it, transactions))
}
status.set(TransactionCreateStatus.Started(state.message))
}
is TransactionBuildResult.Failed -> {
@ -129,6 +197,7 @@ class GatheredTransactionDataModel {
)
}
}
transactions
}
)
@ -137,7 +206,7 @@ class GatheredTransactionDataModel {
private fun newTransactionIdTransactionStateOrModify(
transactionStates: ObservableList<GatheredTransactionDataWritable>,
event: ServiceToClientEvent,
transaction: LedgerTransaction,
transaction: PartiallyResolvedTransaction,
tweak: GatheredTransactionDataWritable.() -> Unit
) {
val index = transactionStates.indexOfFirst { transaction.id == it.transaction.value?.id }
@ -190,28 +259,67 @@ class GatheredTransactionDataModel {
transactionId: SecureHash?,
tweak: GatheredTransactionDataWritable.() -> Unit
) {
val index = transactionStates.indexOfFirst {
val matchingStates = transactionStates.filtered {
it.uuid.value == uuid ||
(stateMachineRunId != null && it.stateMachineRunId.value == stateMachineRunId) ||
(transactionId != null && it.transaction.value?.id == transactionId)
(transactionId != null && it.transaction.value?.transaction?.id == transactionId)
}
val state = if (index < 0) {
val mergedState = mergeGatheredData(matchingStates)
for (i in 0 .. matchingStates.size - 1) {
transactionStates.removeAt(matchingStates.getSourceIndex(i))
}
val state = if (mergedState == null) {
val newState = GatheredTransactionDataWritable(
uuid = SimpleObjectProperty(uuid),
stateMachineRunId = SimpleObjectProperty(stateMachineRunId),
lastUpdate = SimpleObjectProperty(event.time)
)
tweak(newState)
transactionStates.add(newState)
newState
} else {
val existingState = transactionStates[index]
existingState.lastUpdate.set(event.time)
tweak(existingState)
existingState
mergedState.lastUpdate.set(event.time)
mergedState
}
tweak(state)
state.allEvents.add(event)
}
private fun mergeGatheredData(
gatheredDataList: List<GatheredTransactionDataWritable>
): GatheredTransactionDataWritable? {
var gathered: GatheredTransactionDataWritable? = null
// Modify the last one if we can
gatheredDataList.asReversed().forEach {
val localGathered = gathered
if (localGathered == null) {
gathered = it
} else {
mergeField(it, localGathered, GatheredTransactionDataWritable::stateMachineRunId)
mergeField(it, localGathered, GatheredTransactionDataWritable::uuid)
mergeField(it, localGathered, GatheredTransactionDataWritable::stateMachineStatus)
mergeField(it, localGathered, GatheredTransactionDataWritable::protocolStatus)
mergeField(it, localGathered, GatheredTransactionDataWritable::transaction)
mergeField(it, localGathered, GatheredTransactionDataWritable::status)
localGathered.allEvents.addAll(it.allEvents)
}
}
return gathered
}
private fun <A> mergeField(
from: GatheredTransactionDataWritable,
to: GatheredTransactionDataWritable,
field: KProperty1<GatheredTransactionDataWritable, SimpleObjectProperty<A?>>) {
val fromValue = field(from).value
if (fromValue != null) {
val toField = field(to)
val toValue = toField.value
if (toValue != null && fromValue != toValue) {
log.warn("Conflicting data for field ${field.name}: $fromValue vs $toValue")
}
toField.set(fromValue)
}
}
}
}

View File

@ -254,7 +254,7 @@ class CashViewer : View() {
* We re-display the exchanged sum amount, if we have a selection.
*/
private val noSelectionSumEquiv = reportingCurrency.map { Amount(0, it) }
private val selectedViewerNodeSumEquiv = selectedViewerNode.bind { selection ->
private val selectedViewerNodeSumEquiv = selectedViewerNode.bindOut { selection ->
when (selection) {
is SingleRowSelection.None -> noSelectionSumEquiv
is SingleRowSelection.Selected ->

View File

@ -1,16 +1,12 @@
package com.r3corda.explorer.views
import com.r3corda.client.fxutils.ChosenList
import com.r3corda.client.fxutils.bind
import com.r3corda.client.fxutils.lift
import com.r3corda.client.fxutils.map
import com.r3corda.client.fxutils.*
import com.r3corda.client.model.*
import com.r3corda.contracts.asset.Cash
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.crypto.toStringShort
import com.r3corda.core.transactions.LedgerTransaction
import com.r3corda.core.protocols.StateMachineRunId
import com.r3corda.explorer.AmountDiff
import com.r3corda.explorer.formatters.AmountFormatter
@ -33,7 +29,6 @@ import javafx.scene.layout.BackgroundFill
import javafx.scene.layout.CornerRadii
import javafx.scene.layout.VBox
import javafx.scene.paint.Color
import org.fxmisc.easybind.EasyBind
import tornadofx.View
import java.security.PublicKey
import java.time.Instant
@ -61,20 +56,20 @@ class TransactionViewer: View() {
private val contractStatesInputsCountLabel: Label by fxid()
private val contractStatesInputStatesTable: TableView<StateNode> by fxid()
private val contractStatesInputStatesId: TableColumn<StateNode, String> by fxid()
private val contractStatesInputStatesType: TableColumn<StateNode, Class<out ContractState>> by fxid()
private val contractStatesInputStatesType: TableColumn<StateNode, String> by fxid()
private val contractStatesInputStatesOwner: TableColumn<StateNode, String> by fxid()
private val contractStatesInputStatesLocalCurrency: TableColumn<StateNode, Currency?> by fxid()
private val contractStatesInputStatesAmount: TableColumn<StateNode, Long> by fxid()
private val contractStatesInputStatesEquiv: TableColumn<StateNode, Amount<Currency>> by fxid()
private val contractStatesInputStatesAmount: TableColumn<StateNode, Long?> by fxid()
private val contractStatesInputStatesEquiv: TableColumn<StateNode, Amount<Currency>?> by fxid()
private val contractStatesOutputsCountLabel: Label by fxid()
private val contractStatesOutputStatesTable: TableView<StateNode> by fxid()
private val contractStatesOutputStatesId: TableColumn<StateNode, String> by fxid()
private val contractStatesOutputStatesType: TableColumn<StateNode, Class<out ContractState>> by fxid()
private val contractStatesOutputStatesType: TableColumn<StateNode, String> by fxid()
private val contractStatesOutputStatesOwner: TableColumn<StateNode, String> by fxid()
private val contractStatesOutputStatesLocalCurrency: TableColumn<StateNode, Currency?> by fxid()
private val contractStatesOutputStatesAmount: TableColumn<StateNode, Long> by fxid()
private val contractStatesOutputStatesEquiv: TableColumn<StateNode, Amount<Currency>> by fxid()
private val contractStatesOutputStatesAmount: TableColumn<StateNode, Long?> by fxid()
private val contractStatesOutputStatesEquiv: TableColumn<StateNode, Amount<Currency>?> by fxid()
private val signaturesTitledPane: TitledPane by fxid()
private val signaturesList: ListView<PublicKey> by fxid()
@ -108,7 +103,7 @@ class TransactionViewer: View() {
val statusUpdated: ObservableValue<Instant>,
val commandTypes: ObservableValue<Collection<Class<CommandData>>>,
val totalValueEquiv: ObservableValue<AmountDiff<Currency>?>,
val transaction: ObservableValue<LedgerTransaction?>,
val transaction: ObservableValue<PartiallyResolvedTransaction?>,
val allEvents: ObservableList<out ServiceToClientEvent>
)
@ -116,7 +111,7 @@ class TransactionViewer: View() {
* Holds information about a single input/output state, to be displayed in the [contractStatesTitledPane]
*/
data class StateNode(
val transactionState: TransactionState<*>,
val state: ObservableValue<PartiallyResolvedTransaction.InputResolution>,
val stateRef: StateRef
)
@ -144,12 +139,37 @@ class TransactionViewer: View() {
statusUpdated = it.lastUpdate,
commandTypes = it.transaction.map {
val commands = mutableSetOf<Class<CommandData>>()
it?.commands?.forEach {
it?.transaction?.tx?.commands?.forEach {
commands.add(it.value.javaClass)
}
commands
},
totalValueEquiv = ::calculateTotalEquiv.lift(myIdentity, reportingExchange, it.transaction),
totalValueEquiv = it.transaction.bind { transaction ->
if (transaction == null) {
null.lift<AmountDiff<Currency>?>()
} else {
val resolvedInputs = transaction.inputs.sequence().map { resolution ->
when (resolution) {
is PartiallyResolvedTransaction.InputResolution.Unresolved -> null
is PartiallyResolvedTransaction.InputResolution.Resolved -> resolution.stateAndRef
}
}.fold(listOf()) { inputs: List<StateAndRef<ContractState>>?, state: StateAndRef<ContractState>? ->
if (inputs != null && state != null) {
inputs + state
} else {
null
}
}
::calculateTotalEquiv.lift(
myIdentity,
reportingExchange,
resolvedInputs,
transaction.transaction.tx.outputs.lift()
)
}
},
transaction = it.transaction,
allEvents = it.allEvents
)
@ -159,18 +179,20 @@ class TransactionViewer: View() {
* The detail panes are only filled out if a transaction is selected
*/
private val selectedViewerNode = transactionViewTable.singleRowSelection()
private val selectedTransaction = selectedViewerNode.bind {
private val selectedTransaction = selectedViewerNode.bindOut {
when (it) {
is SingleRowSelection.None -> null.lift()
is SingleRowSelection.Selected -> it.node.transaction
}
}
private val inputStateNodes = ChosenList(selectedTransaction.map {
if (it == null) {
private val inputStateNodes = ChosenList(selectedTransaction.map { transaction ->
if (transaction == null) {
FXCollections.emptyObservableList<StateNode>()
} else {
FXCollections.observableArrayList(it.inputs.map { StateNode(it.state, it.ref) })
FXCollections.observableArrayList(transaction.inputs.map { inputResolution ->
StateNode(inputResolution, inputResolution.value.stateRef)
})
}
})
@ -178,8 +200,9 @@ class TransactionViewer: View() {
if (it == null) {
FXCollections.emptyObservableList<StateNode>()
} else {
FXCollections.observableArrayList(it.outputs.mapIndexed { index, transactionState ->
StateNode(transactionState, StateRef(it.id, index))
FXCollections.observableArrayList(it.transaction.tx.outputs.mapIndexed { index, transactionState ->
val stateRef = StateRef(it.id, index)
StateNode(PartiallyResolvedTransaction.InputResolution.Resolved(StateAndRef(transactionState, stateRef)).lift(), stateRef)
})
}
})
@ -188,7 +211,7 @@ class TransactionViewer: View() {
if (it == null) {
FXCollections.emptyObservableList<PublicKey>()
} else {
FXCollections.observableArrayList(it.mustSign)
FXCollections.observableArrayList(it.transaction.sigs.map { it.by })
}
})
@ -228,52 +251,67 @@ class TransactionViewer: View() {
statesCountLabel: Label,
statesTable: TableView<StateNode>,
statesId: TableColumn<StateNode, String>,
statesType: TableColumn<StateNode, Class<out ContractState>>,
statesType: TableColumn<StateNode, String>,
statesOwner: TableColumn<StateNode, String>,
statesLocalCurrency: TableColumn<StateNode, Currency?>,
statesAmount: TableColumn<StateNode, Long>,
statesEquiv: TableColumn<StateNode, Amount<Currency>>
statesAmount: TableColumn<StateNode, Long?>,
statesEquiv: TableColumn<StateNode, Amount<Currency>?>
) {
statesCountLabel.textProperty().bind(Bindings.size(states).map { "$it" })
Bindings.bindContent(statesTable.items, states)
val unknownString = "???"
statesId.setCellValueFactory { it.value.stateRef.toString().lift() }
statesType.setCellValueFactory { it.value.transactionState.data.javaClass.lift() }
statesType.setCellValueFactory {
resolvedOrDefault(it.value.state, unknownString) {
it.state.data.javaClass.toString()
}
}
statesOwner.setCellValueFactory {
val state = it.value.transactionState.data
if (state is OwnableState) {
state.owner.toStringShort().lift()
} else {
"???".lift()
resolvedOrDefault(it.value.state, unknownString) {
val contractState = it.state.data
if (contractState is OwnableState) {
contractState.owner.toStringShort()
} else {
unknownString
}
}
}
statesLocalCurrency.setCellValueFactory {
val state = it.value.transactionState.data
if (state is Cash.State) {
state.amount.token.product.lift()
} else {
null.lift()
resolvedOrDefault<Currency?>(it.value.state, null) {
val contractState = it.state.data
if (contractState is Cash.State) {
contractState.amount.token.product
} else {
null
}
}
}
statesAmount.setCellValueFactory {
val state = it.value.transactionState.data
if (state is Cash.State) {
state.amount.quantity.lift()
} else {
null.lift()
resolvedOrDefault<Long?>(it.value.state, null) {
val contractState = it.state.data
if (contractState is Cash.State) {
contractState.amount.quantity
} else {
null
}
}
}
statesAmount.cellFactory = NumberFormatter.boringLong.toTableCellFactory()
statesEquiv.setCellValueFactory {
val state = it.value.transactionState.data
if (state is Cash.State) {
reportingExchange.map { exchange ->
exchange.second(state.amount.withoutIssuer())
resolvedOrDefault<ObservableValue<Amount<Currency>?>>(it.value.state, null.lift()) {
val contractState = it.state.data
if (contractState is Cash.State) {
reportingExchange.map { exchange ->
exchange.second(contractState.amount.withoutIssuer())
}
} else {
null.lift()
}
} else {
null.lift()
}
}.bind { it }
}
statesEquiv.cellFactory = AmountFormatter.boring.toTableCellFactory()
}
@ -364,7 +402,7 @@ class TransactionViewer: View() {
Math.floor(tableWidthWithoutPaddingAndBorder.toDouble() / lowLevelEventsTable.columns.size).toInt()
}
matchingTransactionsLabel.textProperty().bind(EasyBind.map(Bindings.size(viewerNodes)) {
matchingTransactionsLabel.textProperty().bind(Bindings.size(viewerNodes).map {
"$it matching transaction${if (it == 1) "" else "s"}"
})
}
@ -376,20 +414,21 @@ class TransactionViewer: View() {
private fun calculateTotalEquiv(
identity: Party,
reportingCurrencyExchange: Pair<Currency, (Amount<Currency>) -> Amount<Currency>>,
transaction: LedgerTransaction?): AmountDiff<Currency>? {
if (transaction == null) {
inputs: List<StateAndRef<ContractState>>?,
outputs: List<TransactionState<ContractState>>): AmountDiff<Currency>? {
if (inputs == null) {
return null
}
var sum = 0L
val (reportingCurrency, exchange) = reportingCurrencyExchange
val publicKey = identity.owningKey
transaction.inputs.forEach {
inputs.forEach {
val contractState = it.state.data
if (contractState is Cash.State && publicKey == contractState.owner) {
sum -= exchange(contractState.amount.withoutIssuer()).quantity
}
}
transaction.outputs.forEach {
outputs.forEach {
val contractState = it.data
if (contractState is Cash.State && publicKey == contractState.owner) {
sum += exchange(contractState.amount.withoutIssuer()).quantity
@ -398,3 +437,15 @@ private fun calculateTotalEquiv(
return AmountDiff.fromLong(sum, reportingCurrency)
}
fun <A> resolvedOrDefault(
state: ObservableValue<PartiallyResolvedTransaction.InputResolution>,
default: A,
resolved: (StateAndRef<*>) -> A
): ObservableValue<A> {
return state.map {
when (it) {
is PartiallyResolvedTransaction.InputResolution.Unresolved -> default
is PartiallyResolvedTransaction.InputResolution.Resolved -> resolved(it.stateAndRef)
}
}
}

View File

@ -1,8 +1,8 @@
package com.r3corda.node.services.monitor
import com.r3corda.core.contracts.*
import com.r3corda.core.transactions.LedgerTransaction
import com.r3corda.core.protocols.StateMachineRunId
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.node.utilities.AddOrRemove
import java.time.Instant
import java.util.*
@ -11,8 +11,8 @@ import java.util.*
* Events triggered by changes in the node, and sent to monitoring client(s).
*/
sealed class ServiceToClientEvent(val time: Instant) {
class Transaction(time: Instant, val transaction: LedgerTransaction) : ServiceToClientEvent(time) {
override fun toString() = "Transaction(${transaction.commands})"
class Transaction(time: Instant, val transaction: SignedTransaction) : ServiceToClientEvent(time) {
override fun toString() = "Transaction(${transaction.tx.commands})"
}
class OutputState(
time: Instant,
@ -26,7 +26,7 @@ sealed class ServiceToClientEvent(val time: Instant) {
val id: StateMachineRunId,
val label: String,
val addOrRemove: AddOrRemove
) : ServiceToClientEvent(time) {
) : ServiceToClientEvent(time) {
override fun toString() = "StateMachine($label, ${addOrRemove.name})"
}
class Progress(time: Instant, val id: StateMachineRunId, val message: String) : ServiceToClientEvent(time) {
@ -35,7 +35,6 @@ sealed class ServiceToClientEvent(val time: Instant) {
class TransactionBuild(time: Instant, val id: UUID, val state: TransactionBuildResult) : ServiceToClientEvent(time) {
override fun toString() = "TransactionBuild($state)"
}
}
sealed class TransactionBuildResult {
@ -47,7 +46,7 @@ sealed class TransactionBuildResult {
*
* @param transaction the transaction created as a result, in the case where the protocol has completed.
*/
class ProtocolStarted(val id: StateMachineRunId, val transaction: LedgerTransaction?, val message: String?) : TransactionBuildResult() {
class ProtocolStarted(val id: StateMachineRunId, val transaction: SignedTransaction?, val message: String?) : TransactionBuildResult() {
override fun toString() = "Started($message)"
}

View File

@ -13,7 +13,7 @@ import com.r3corda.core.node.services.Vault
import com.r3corda.core.protocols.ProtocolLogic
import com.r3corda.core.protocols.StateMachineRunId
import com.r3corda.core.serialization.serialize
import com.r3corda.core.transactions.LedgerTransaction
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.transactions.TransactionBuilder
import com.r3corda.core.utilities.loggerFor
import com.r3corda.node.services.api.AbstractNodeService
@ -61,7 +61,7 @@ class NodeMonitorService(services: ServiceHubInternal, val smm: StateMachineMana
addMessageHandler(OUT_EVENT_TOPIC) { req: ClientToServiceCommandMessage -> processEventRequest(req) }
// Notify listeners on state changes
services.storageService.validatedTransactions.updates.subscribe { tx -> notifyTransaction(tx.tx.toLedgerTransaction(services)) }
services.storageService.validatedTransactions.updates.subscribe { tx -> notifyTransaction(tx) }
services.vaultService.updates.subscribe { update -> notifyVaultUpdate(update) }
smm.changes.subscribe { change ->
val id: StateMachineRunId = change.id
@ -87,7 +87,7 @@ class NodeMonitorService(services: ServiceHubInternal, val smm: StateMachineMana
= notifyEvent(ServiceToClientEvent.OutputState(Instant.now(), update.consumed, update.produced))
@VisibleForTesting
internal fun notifyTransaction(transaction: LedgerTransaction)
internal fun notifyTransaction(transaction: SignedTransaction)
= notifyEvent(ServiceToClientEvent.Transaction(Instant.now(), transaction))
private fun processEventRequest(reqMessage: ClientToServiceCommandMessage) {
@ -170,7 +170,7 @@ class NodeMonitorService(services: ServiceHubInternal, val smm: StateMachineMana
val protocol = FinalityProtocol(tx, setOf(req), setOf(req.recipient))
return TransactionBuildResult.ProtocolStarted(
smm.add(BroadcastTransactionProtocol.TOPIC, protocol).id,
tx.tx.toLedgerTransaction(services),
tx,
"Cash payment transaction generated"
)
} catch(ex: InsufficientBalanceException) {
@ -204,7 +204,7 @@ class NodeMonitorService(services: ServiceHubInternal, val smm: StateMachineMana
val protocol = FinalityProtocol(tx, setOf(req), participants)
return TransactionBuildResult.ProtocolStarted(
smm.add(BroadcastTransactionProtocol.TOPIC, protocol).id,
tx.tx.toLedgerTransaction(services),
tx,
"Cash destruction transaction generated"
)
} catch (ex: InsufficientBalanceException) {
@ -223,7 +223,7 @@ class NodeMonitorService(services: ServiceHubInternal, val smm: StateMachineMana
val protocol = BroadcastTransactionProtocol(tx, setOf(req), setOf(req.recipient))
return TransactionBuildResult.ProtocolStarted(
smm.add(BroadcastTransactionProtocol.TOPIC, protocol).id,
tx.tx.toLedgerTransaction(services),
tx,
"Cash issuance completed"
)
}

View File

@ -141,7 +141,7 @@ class NodeMonitorServiceTests {
// Check the returned event is correct
val tx = (event.state as TransactionBuildResult.ProtocolStarted).transaction
assertNotNull(tx)
assertEquals(expectedState, tx!!.outputs.single().data)
assertEquals(expectedState, tx!!.tx.outputs.single().data)
},
expect { event: ServiceToClientEvent.OutputState ->
// Check the generated state is correct
@ -203,8 +203,8 @@ class NodeMonitorServiceTests {
}
),
expect { event: ServiceToClientEvent.Transaction ->
require(event.transaction.mustSign.size == 1)
event.transaction.mustSign.containsAll(
require(event.transaction.sigs.size == 1)
event.transaction.sigs.map { it.by }.containsAll(
listOf(
monitorServiceNode.services.storageService.myLegalIdentity.owningKey
)