From f454b949e85961738292dc422730ffae918023aa Mon Sep 17 00:00:00 2001 From: Andrius Dagys Date: Fri, 9 Mar 2018 14:17:51 +0000 Subject: [PATCH 1/3] CORDA-1142: Fix explorer to support all transaction types --- .../client/jfx/model/NodeMonitorModel.kt | 13 +++-- .../client/jfx/model/TransactionDataModel.kt | 50 ++++++++++++++--- .../net/corda/explorer/views/Network.kt | 7 ++- .../corda/explorer/views/TransactionViewer.kt | 53 ++++++++++++------- 4 files changed, 88 insertions(+), 35 deletions(-) diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt index 0b866332f9..ad2900fd53 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/NodeMonitorModel.kt @@ -88,14 +88,13 @@ class NodeMonitorModel { stateMachineUpdates.startWith(currentStateMachines).subscribe(stateMachineUpdatesSubject) // Vault snapshot (force single page load with MAX_PAGE_SIZE) + updates - val (_, vaultUpdates) = proxy.vaultTrackBy(QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL), + val (statesSnapshot, vaultUpdates) = proxy.vaultTrackBy(QueryCriteria.VaultQueryCriteria(Vault.StateStatus.ALL), PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE)) - - val vaultSnapshot = proxy.vaultQueryBy(QueryCriteria.VaultQueryCriteria(Vault.StateStatus.UNCONSUMED), - PageSpecification(DEFAULT_PAGE_NUM, MAX_PAGE_SIZE)) - // We have to fetch the snapshot separately since vault query API doesn't allow different criteria for snapshot and updates. - // TODO : This will create a small window of opportunity for inconsistent updates, might need to change the vault API to handle this case. - val initialVaultUpdate = Vault.Update(setOf(), vaultSnapshot.states.toSet()) + val unconsumedStates = statesSnapshot.states.filterIndexed { index, _ -> + statesSnapshot.statesMetadata[index].status == Vault.StateStatus.UNCONSUMED + }.toSet() + val consumedStates = statesSnapshot.states.toSet() - unconsumedStates + val initialVaultUpdate = Vault.Update(consumedStates, unconsumedStates) vaultUpdates.startWith(initialVaultUpdate).subscribe(vaultUpdatesSubject) // Transactions diff --git a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt index e82e529262..b2a76661e1 100644 --- a/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt +++ b/client/jfx/src/main/kotlin/net/corda/client/jfx/model/TransactionDataModel.kt @@ -1,13 +1,14 @@ package net.corda.client.jfx.model import javafx.beans.value.ObservableValue +import javafx.collections.FXCollections import javafx.collections.ObservableMap import net.corda.client.jfx.utils.* import net.corda.core.contracts.ContractState import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateRef -import net.corda.core.crypto.SecureHash import net.corda.core.transactions.SignedTransaction +import net.corda.core.transactions.WireTransaction import org.fxmisc.easybind.EasyBind /** @@ -17,7 +18,8 @@ import org.fxmisc.easybind.EasyBind */ data class PartiallyResolvedTransaction( val transaction: SignedTransaction, - val inputs: List>) { + val inputs: List>, + val outputs: List>) { val id = transaction.id sealed class InputResolution { @@ -29,21 +31,49 @@ data class PartiallyResolvedTransaction( } } + sealed class OutputResolution { + abstract val stateRef: StateRef + + data class Unresolved(override val stateRef: StateRef) : OutputResolution() + data class Resolved(val stateAndRef: StateAndRef) : OutputResolution() { + override val stateRef: StateRef get() = stateAndRef.ref + } + } + companion object { fun fromSignedTransaction( transaction: SignedTransaction, - transactions: ObservableMap + stateMap: ObservableMap> ) = PartiallyResolvedTransaction( transaction = transaction, - inputs = transaction.tx.inputs.map { stateRef -> - EasyBind.map(transactions.getObservableValue(stateRef.txhash)) { + inputs = transaction.inputs.map { stateRef -> + EasyBind.map(stateMap.getObservableValue(stateRef)) { if (it == null) { InputResolution.Unresolved(stateRef) } else { - InputResolution.Resolved(it.tx.outRef(stateRef.index)) + InputResolution.Resolved(it) + } + } + }, + outputs = if (transaction.coreTransaction is WireTransaction) { + transaction.tx.outRefsOfType().map { + OutputResolution.Resolved(it).lift() + } + } else { + // Transaction will have the same number of outputs as inputs + val outputCount = transaction.coreTransaction.inputs.size + val stateRefs = (0 until outputCount).map { StateRef(transaction.id, it) } + stateRefs.map { stateRef -> + EasyBind.map(stateMap.getObservableValue(stateRef)) { + if (it == null) { + OutputResolution.Unresolved(stateRef) + } else { + OutputResolution.Resolved(it) + } } } } + ) } } @@ -54,9 +84,13 @@ data class PartiallyResolvedTransaction( class TransactionDataModel { private val transactions by observable(NodeMonitorModel::transactions) private val collectedTransactions = transactions.recordInSequence() - private val transactionMap = transactions.recordAsAssociation(SignedTransaction::id) + private val vaultUpdates by observable(NodeMonitorModel::vaultUpdates) + private val stateMap = vaultUpdates.fold(FXCollections.observableHashMap>()) { map, update -> + val states = update.consumed + update.produced + states.forEach { map[it.ref] = it } + } val partiallyResolvedTransactions = collectedTransactions.map { - PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap) + PartiallyResolvedTransaction.fromSignedTransaction(it, stateMap) } } diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt index 8970e7b1e1..dc01a1e6ac 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/Network.kt @@ -30,6 +30,7 @@ import net.corda.client.jfx.utils.* import net.corda.core.contracts.ContractState import net.corda.core.identity.Party import net.corda.core.node.NodeInfo +import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.toBase58String import net.corda.explorer.formatters.PartyNameFormatter import net.corda.explorer.model.CordaView @@ -81,7 +82,11 @@ class Network : CordaView() { .map { it as? PartiallyResolvedTransaction.InputResolution.Resolved } .filterNotNull() .map { it.stateAndRef.state.data }.getParties() - val outputParties = it.transaction.tx.outputStates.observable().getParties() + val outputParties = it.transaction.coreTransaction.let { + if (it is WireTransaction) it.outputStates.observable().getParties() + // For ContractUpgradeWireTransaction and NotaryChangeWireTransaction the output parties are the same as input parties + else inputParties + } 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. // TODO : Expose artemis queue to get real message information. diff --git a/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt b/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt index 128c2f221b..e529a99a29 100644 --- a/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt +++ b/tools/explorer/src/main/kotlin/net/corda/explorer/views/TransactionViewer.kt @@ -26,6 +26,8 @@ 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.transactions.SignedTransaction +import net.corda.core.transactions.WireTransaction import net.corda.core.utilities.toBase58String import net.corda.explorer.AmountDiff import net.corda.explorer.formatters.AmountFormatter @@ -69,7 +71,7 @@ class TransactionViewer : CordaView("Transactions") { val tx: PartiallyResolvedTransaction, val id: SecureHash, val inputs: Inputs, - val outputs: ObservableList>, + val outputs: Outputs, val inputParties: ObservableList>>, val outputParties: ObservableList>>, val commandTypes: List>, @@ -77,6 +79,7 @@ class TransactionViewer : CordaView("Transactions") { ) data class Inputs(val resolved: ObservableList>, val unresolved: ObservableList) + data class Outputs(val resolved: ObservableList>, val unresolved: ObservableList) override fun onDock() { txIdToScroll?.let { @@ -103,38 +106,42 @@ class TransactionViewer : CordaView("Transactions") { */ init { val transactions = transactions.map { - val resolved = it.inputs.sequence() + val resolvedInputs = it.inputs.sequence() .map { it as? PartiallyResolvedTransaction.InputResolution.Resolved } .filterNotNull() .map { it.stateAndRef } - val unresolved = it.inputs.sequence() + val unresolvedInputs = it.inputs.sequence() .map { it as? PartiallyResolvedTransaction.InputResolution.Unresolved } .filterNotNull() .map { it.stateRef } - val outputs = it.transaction.tx.outputs - .mapIndexed { index, transactionState -> - val stateRef = StateRef(it.id, index) - StateAndRef(transactionState, stateRef) - }.observable() + val resolvedOutputs = it.outputs.sequence() + .map { it as? PartiallyResolvedTransaction.OutputResolution.Resolved } + .filterNotNull() + .map { it.stateAndRef } + val unresolvedOutputs = it.inputs.sequence() + .map { it as? PartiallyResolvedTransaction.InputResolution.Unresolved } + .filterNotNull() + .map { it.stateRef } + val commands = if (it.transaction.coreTransaction is WireTransaction) it.transaction.tx.commands else emptyList() Transaction( tx = it, id = it.id, - inputs = Inputs(resolved, unresolved), - outputs = outputs, - inputParties = resolved.getParties(), - outputParties = outputs.getParties(), - commandTypes = it.transaction.tx.commands.map { it.value.javaClass }, + inputs = Inputs(resolvedInputs, unresolvedInputs), + outputs = Outputs(resolvedOutputs, unresolvedOutputs), + inputParties = resolvedInputs.getParties(), + outputParties = resolvedOutputs.getParties(), + commandTypes = commands.map { it.value.javaClass }, totalValueEquiv = ::calculateTotalEquiv.lift(myIdentity, reportingExchange, - resolved.map { it.state.data }.lift(), - it.transaction.tx.outputStates.lift()) + resolvedInputs.map { it.state.data }.lift(), + resolvedOutputs.map { it.state.data }.lift()) ) } val searchField = SearchField(transactions, "Transaction ID" to { tx, s -> "${tx.id}".contains(s, true) }, "Input" to { tx, s -> tx.inputs.resolved.any { it.state.contract.contains(s, true) } }, - "Output" to { tx, s -> tx.outputs.any { it.state.contract.contains(s, true) } }, + "Output" to { tx, s -> tx.outputs.resolved.any { it.state.contract.contains(s, true) } }, "Input Party" to { tx, s -> tx.inputParties.any { it.any { it.value?.name?.organisation?.contains(s, true) == true } } }, "Output Party" to { tx, s -> tx.outputParties.any { it.any { it.value?.name?.organisation?.contains(s, true) == true } } }, "Command Type" to { tx, s -> tx.commandTypes.any { it.simpleName.contains(s, true) } } @@ -161,7 +168,15 @@ class TransactionViewer : CordaView("Transactions") { text += "Unresolved(${it.unresolved.size})" } } - column("Output", Transaction::outputs).cellFormat { text = it.toText() } + column("Output", Transaction::outputs).cellFormat { + text = it.resolved.toText() + if (!it.unresolved.isEmpty()) { + if (!text.isBlank()) { + text += ", " + } + text += "Unresolved(${it.unresolved.size})" + } + } column("Input Party", Transaction::inputParties).setCustomCellFactory { label { text = it.formatJoinPartyNames(formatter = PartyNameFormatter.short) @@ -238,14 +253,14 @@ class TransactionViewer : CordaView("Transactions") { val signatureData = transaction.tx.transaction.sigs.map { it.by } // Bind count to TitlePane inputPane.text = "Input (${transaction.inputs.resolved.count()})" - outputPane.text = "Output (${transaction.outputs.count()})" + outputPane.text = "Output (${transaction.outputs.resolved.count()})" signaturesPane.text = "Signatures (${signatureData.count()})" inputs.cellCache { getCell(it) } outputs.cellCache { getCell(it) } inputs.items = transaction.inputs.resolved - outputs.items = transaction.outputs.observable() + outputs.items = transaction.outputs.resolved signatures.children.addAll(signatureData.map { signature -> val party = signature.toKnownParty() From 27e45bc865520561096cf8d0793149106b4fde03 Mon Sep 17 00:00:00 2001 From: Michal Kit Date: Thu, 15 Mar 2018 08:42:36 +0000 Subject: [PATCH 2/3] Adding public key verification to the X509Utilities.createCertificateSigningRequest (#2784) --- .../net/corda/core/internal/InternalUtils.kt | 5 +++-- .../nodeapi/internal/crypto/X509Utilities.kt | 19 +++++++++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt index d3a924dfef..cfde75e90a 100644 --- a/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt +++ b/core/src/main/kotlin/net/corda/core/internal/InternalUtils.kt @@ -326,13 +326,14 @@ val KClass<*>.packageName: String get() = java.`package`.name fun URL.openHttpConnection(): HttpURLConnection = openConnection() as HttpURLConnection -fun URL.post(serializedData: OpaqueBytes) { - openHttpConnection().apply { +fun URL.post(serializedData: OpaqueBytes): ByteArray { + return openHttpConnection().run { doOutput = true requestMethod = "POST" setRequestProperty("Content-Type", "application/octet-stream") outputStream.use { serializedData.open().copyTo(it) } checkOkResponse() + inputStream.use { it.readBytes() } } } diff --git a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt index e7ff717bf2..b23a8fc861 100644 --- a/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt +++ b/node-api/src/main/kotlin/net/corda/nodeapi/internal/crypto/X509Utilities.kt @@ -4,7 +4,10 @@ import net.corda.core.CordaOID import net.corda.core.crypto.Crypto import net.corda.core.crypto.SignatureScheme import net.corda.core.crypto.random63BitValue -import net.corda.core.internal.* +import net.corda.core.internal.CertRole +import net.corda.core.internal.reader +import net.corda.core.internal.uncheckedCast +import net.corda.core.internal.writer import net.corda.core.utilities.days import net.corda.core.utilities.millis import org.bouncycastle.asn1.* @@ -26,6 +29,7 @@ import java.math.BigInteger import java.nio.file.Path import java.security.KeyPair import java.security.PublicKey +import java.security.SignatureException import java.security.cert.* import java.security.cert.Certificate import java.time.Duration @@ -265,7 +269,11 @@ object X509Utilities { return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public) .addAttribute(BCStyle.E, DERUTF8String(email)) .addAttribute(ASN1ObjectIdentifier(CordaOID.X509_EXTENSION_CORDA_ROLE), certRole) - .build(signer) + .build(signer).apply { + if (!isSignatureValid()) { + throw SignatureException("The certificate signing request signature validation failed.") + } + } } fun createCertificateSigningRequest(subject: X500Principal, email: String, keyPair: KeyPair, certRole: CertRole = CertRole.NODE_CA): PKCS10CertificationRequest { @@ -311,6 +319,13 @@ val Certificate.x509: X509Certificate get() = requireNotNull(this as? X509Certif val Array.x509: List get() = map { it.x509 } +/** + * Validates the signature of the CSR + */ +fun PKCS10CertificationRequest.isSignatureValid(): Boolean { + return this.isSignatureValid(JcaContentVerifierProviderBuilder().build(this.subjectPublicKeyInfo)) +} + /** * Wraps a [CertificateFactory] to remove boilerplate. It's unclear whether [CertificateFactory] is threadsafe so best * so assume this class is not. From a64474181971e79638d6c23cb677a91a6c0f1bb5 Mon Sep 17 00:00:00 2001 From: Chris Rankin Date: Thu, 15 Mar 2018 08:59:45 +0000 Subject: [PATCH 3/3] Expose two static fields on SecureHash for Java. (#2822) --- .../net/corda/core/crypto/SecureHash.kt | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt index cf253903e2..cc0d35131a 100644 --- a/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt +++ b/core/src/main/kotlin/net/corda/core/crypto/SecureHash.kt @@ -82,13 +82,31 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) { /** * A SHA-256 hash value consisting of 32 0x00 bytes. + * This field provides more intuitive access from Java. */ - val zeroHash = SecureHash.SHA256(ByteArray(32, { 0.toByte() })) + @JvmField + val zeroHash: SHA256 = SecureHash.SHA256(ByteArray(32, { 0.toByte() })) + + /** + * A SHA-256 hash value consisting of 32 0x00 bytes. + * This function is provided for API stability. + */ + @Suppress("Unused") + fun getZeroHash(): SHA256 = zeroHash /** * A SHA-256 hash value consisting of 32 0xFF bytes. + * This field provides more intuitive access from Java. */ - val allOnesHash = SecureHash.SHA256(ByteArray(32, { 255.toByte() })) + @JvmField + val allOnesHash: SHA256 = SecureHash.SHA256(ByteArray(32, { 255.toByte() })) + + /** + * A SHA-256 hash value consisting of 32 0xFF bytes. + * This function is provided for API stability. + */ + @Suppress("Unused") + fun getAllOnesHash(): SHA256 = allOnesHash } // In future, maybe SHA3, truncated hashes etc.