Merge pull request #538 from corda/andrius-merge-03-09

O/S merge 03 09
This commit is contained in:
Andrius Dagys 2018-03-09 18:58:53 +00:00 committed by GitHub
commit ac328aeb67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 586 additions and 371 deletions

View File

@ -1384,12 +1384,10 @@ public static final class net.corda.core.flows.NotarisationRequest$Companion ext
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.NotaryError extends java.lang.Object @net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.NotaryError extends java.lang.Object
## ##
@net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$Conflict extends net.corda.core.flows.NotaryError @net.corda.core.serialization.CordaSerializable public static final class net.corda.core.flows.NotaryError$Conflict extends net.corda.core.flows.NotaryError
public <init>(net.corda.core.crypto.SecureHash, net.corda.core.crypto.SignedData)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SignedData component2() @org.jetbrains.annotations.NotNull public final Map component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$Conflict copy(net.corda.core.crypto.SecureHash, net.corda.core.crypto.SignedData) @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$Conflict copy(net.corda.core.crypto.SecureHash, Map)
public boolean equals(Object) public boolean equals(Object)
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SignedData getConflict()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTxId() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getTxId()
public int hashCode() public int hashCode()
@org.jetbrains.annotations.NotNull public String toString() @org.jetbrains.annotations.NotNull public String toString()
@ -1440,13 +1438,13 @@ public static final class net.corda.core.flows.NotaryError$TimeWindowInvalid$Com
public static final net.corda.core.flows.NotaryError$WrongNotary INSTANCE public static final net.corda.core.flows.NotaryError$WrongNotary INSTANCE
## ##
@net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotaryException extends net.corda.core.flows.FlowException @net.corda.core.serialization.CordaSerializable public final class net.corda.core.flows.NotaryException extends net.corda.core.flows.FlowException
public <init>(net.corda.core.flows.NotaryError) public <init>(net.corda.core.flows.NotaryError, net.corda.core.crypto.SecureHash)
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError getError() @org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError getError()
## ##
public final class net.corda.core.flows.NotaryFlow extends java.lang.Object public final class net.corda.core.flows.NotaryFlow extends java.lang.Object
public <init>() public <init>()
## ##
@net.corda.core.flows.InitiatingFlow public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.core.flows.FlowLogic @net.corda.core.DoNotImplement @net.corda.core.flows.InitiatingFlow public static class net.corda.core.flows.NotaryFlow$Client extends net.corda.core.flows.FlowLogic
public <init>(net.corda.core.transactions.SignedTransaction) public <init>(net.corda.core.transactions.SignedTransaction)
public <init>(net.corda.core.transactions.SignedTransaction, net.corda.core.utilities.ProgressTracker) public <init>(net.corda.core.transactions.SignedTransaction, net.corda.core.utilities.ProgressTracker)
@co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call() @co.paralleluniverse.fibers.Suspendable @org.jetbrains.annotations.NotNull public List call()
@ -3015,17 +3013,15 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
@org.jetbrains.annotations.NotNull public final String getReason() @org.jetbrains.annotations.NotNull public final String getReason()
## ##
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ContractUpgradeFilteredTransaction extends net.corda.core.transactions.CoreTransaction @net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ContractUpgradeFilteredTransaction extends net.corda.core.transactions.CoreTransaction
public <init>(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash) public <init>(Map, Map)
@org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final Map component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2() @org.jetbrains.annotations.NotNull public final Map component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component3() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction copy(Map, Map)
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction copy(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash)
public boolean equals(Object) public boolean equals(Object)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
@org.jetbrains.annotations.NotNull public List getInputs() @org.jetbrains.annotations.NotNull public List getInputs()
@org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary() @org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary()
@org.jetbrains.annotations.NotNull public List getOutputs() @org.jetbrains.annotations.NotNull public List getOutputs()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getRest()
public int hashCode() public int hashCode()
public String toString() public String toString()
## ##
@ -3052,8 +3048,8 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt()
@org.jetbrains.annotations.NotNull public Set getRequiredSigningKeys() @org.jetbrains.annotations.NotNull public Set getRequiredSigningKeys()
@org.jetbrains.annotations.NotNull public List getSigs() @org.jetbrains.annotations.NotNull public List getSigs()
@org.jetbrains.annotations.NotNull public final String getUpgradeContractClassName()
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Attachment getUpgradedContractAttachment() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.Attachment getUpgradedContractAttachment()
@org.jetbrains.annotations.NotNull public final String getUpgradedContractClassName()
public int hashCode() public int hashCode()
public String toString() public String toString()
public void verifyRequiredSignatures() public void verifyRequiredSignatures()
@ -3061,15 +3057,11 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
public void verifySignaturesExcept(java.security.PublicKey...) public void verifySignaturesExcept(java.security.PublicKey...)
## ##
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ContractUpgradeWireTransaction extends net.corda.core.transactions.CoreTransaction @net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.ContractUpgradeWireTransaction extends net.corda.core.transactions.CoreTransaction
public <init>(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, String, net.corda.core.crypto.SecureHash, net.corda.core.contracts.PrivacySalt) public <init>(List, net.corda.core.contracts.PrivacySalt)
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction buildFilteredTransaction() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction buildFilteredTransaction()
@org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component3() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeWireTransaction copy(List, net.corda.core.contracts.PrivacySalt)
@org.jetbrains.annotations.NotNull public final String component4()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component5()
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt component6()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeWireTransaction copy(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash, String, net.corda.core.crypto.SecureHash, net.corda.core.contracts.PrivacySalt)
public boolean equals(Object) public boolean equals(Object)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
@org.jetbrains.annotations.NotNull public List getInputs() @org.jetbrains.annotations.NotNull public List getInputs()
@ -3077,8 +3069,8 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
@org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary() @org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary()
@org.jetbrains.annotations.NotNull public List getOutputs() @org.jetbrains.annotations.NotNull public List getOutputs()
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt() @org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt getPrivacySalt()
@org.jetbrains.annotations.NotNull public final String getUpgradeContractClassName()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getUpgradedContractAttachmentId() @org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getUpgradedContractAttachmentId()
@org.jetbrains.annotations.NotNull public final String getUpgradedContractClassName()
public int hashCode() public int hashCode()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeLedgerTransaction resolve(net.corda.core.node.ServicesForResolution, List) @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeLedgerTransaction resolve(net.corda.core.node.ServicesForResolution, List)
public String toString() public String toString()
@ -3213,11 +3205,9 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro
public void verifySignaturesExcept(java.security.PublicKey...) public void verifySignaturesExcept(java.security.PublicKey...)
## ##
@net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction @net.corda.core.DoNotImplement @net.corda.core.serialization.CordaSerializable public final class net.corda.core.transactions.NotaryChangeWireTransaction extends net.corda.core.transactions.CoreTransaction
public <init>(List, net.corda.core.identity.Party, net.corda.core.identity.Party) public <init>(List)
@org.jetbrains.annotations.NotNull public final List component1() @org.jetbrains.annotations.NotNull public final List component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2() @org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeWireTransaction copy(List)
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component3()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeWireTransaction copy(List, net.corda.core.identity.Party, net.corda.core.identity.Party)
public boolean equals(Object) public boolean equals(Object)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId() @org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
@org.jetbrains.annotations.NotNull public List getInputs() @org.jetbrains.annotations.NotNull public List getInputs()

View File

@ -86,7 +86,8 @@ class SwapIdentitiesFlow(private val otherParty: Party,
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled) val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
val serializedIdentity = SerializedBytes<PartyAndCertificate>(legalIdentityAnonymous.serialize().bytes) val serializedIdentity = SerializedBytes<PartyAndCertificate>(legalIdentityAnonymous.serialize().bytes)
// Special case that if we're both parties, a single identity is generated // Special case that if we're both parties, a single identity is generated.
// TODO: for increased privacy, we should create one anonymous key per output state.
val identities = LinkedHashMap<Party, AnonymousParty>() val identities = LinkedHashMap<Party, AnonymousParty>()
if (serviceHub.myInfo.isLegalIdentity(otherParty)) { if (serviceHub.myInfo.isLegalIdentity(otherParty)) {
identities.put(otherParty, legalIdentityAnonymous.party.anonymise()) identities.put(otherParty, legalIdentityAnonymous.party.anonymise())

View File

@ -179,7 +179,7 @@ fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Cr
* which should never happen and suggests an unusual JVM or non-standard Java library. * which should never happen and suggests an unusual JVM or non-standard Java library.
*/ */
@Throws(NoSuchAlgorithmException::class) @Throws(NoSuchAlgorithmException::class)
fun secureRandomBytes(numOfBytes: Int): ByteArray = newSecureRandom().generateSeed(numOfBytes) fun secureRandomBytes(numOfBytes: Int): ByteArray = ByteArray(numOfBytes).apply { newSecureRandom().nextBytes(this) }
/** /**
* This is a hack added because during deserialisation when no-param constructors are called sometimes default values * This is a hack added because during deserialisation when no-param constructors are called sometimes default values
@ -257,7 +257,11 @@ fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentG
/** Return the SHA256(SHA256(nonce || serializedComponent)). */ /** Return the SHA256(SHA256(nonce || serializedComponent)). */
fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash = SecureHash.sha256Twice(nonce.bytes + opaqueBytes.bytes) fun componentHash(nonce: SecureHash, opaqueBytes: OpaqueBytes): SecureHash = SecureHash.sha256Twice(nonce.bytes + opaqueBytes.bytes)
/** Serialise the object and return the hash of the serialized bytes. */ /**
* Serialise the object and return the hash of the serialized bytes. Note that the resulting hash may not be deterministic
* across platform versions: serialization can produce different values if any of the types being serialized have changed,
* or if the version of serialization specified by the context changes.
*/
fun <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes.sha256() fun <T : Any> serializedHash(x: T): SecureHash = x.serialize(context = SerializationDefaults.P2P_CONTEXT.withoutReferences()).bytes.sha256()
/** /**

View File

@ -88,7 +88,7 @@ sealed class SecureHash(bytes: ByteArray) : OpaqueBytes(bytes) {
* Generates a random SHA-256 value. * Generates a random SHA-256 value.
*/ */
@JvmStatic @JvmStatic
fun randomSHA256() = sha256(newSecureRandom().generateSeed(32)) fun randomSHA256() = sha256(secureRandomBytes(32))
/** /**
* A SHA-256 hash value consisting of 32 0x00 bytes. * A SHA-256 hash value consisting of 32 0x00 bytes.

View File

@ -11,8 +11,7 @@
package net.corda.core.flows package net.corda.core.flows
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
@ -53,7 +52,7 @@ class NotarisationRequest(statesToConsume: List<StateRef>, val transactionId: Se
val signature = requestSignature.digitalSignature val signature = requestSignature.digitalSignature
if (intendedSigner.owningKey != signature.by) { if (intendedSigner.owningKey != signature.by) {
val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}" val errorMessage = "Expected a signature by ${intendedSigner.owningKey}, but received by ${signature.by}}"
throw NotaryException(NotaryError.RequestSignatureInvalid(IllegalArgumentException(errorMessage))) throw NotaryInternalException(NotaryError.RequestSignatureInvalid(IllegalArgumentException(errorMessage)))
} }
// TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to // TODO: if requestSignature was generated over an old version of NotarisationRequest, we need to be able to
// reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's // reserialize it in that version to get the exact same bytes. Modify the serialization logic once that's
@ -69,7 +68,7 @@ class NotarisationRequest(statesToConsume: List<StateRef>, val transactionId: Se
when (e) { when (e) {
is InvalidKeyException, is SignatureException -> { is InvalidKeyException, is SignatureException -> {
val error = NotaryError.RequestSignatureInvalid(e) val error = NotaryError.RequestSignatureInvalid(e)
throw NotaryException(error) throw NotaryInternalException(error)
} }
else -> throw e else -> throw e
} }
@ -108,4 +107,8 @@ data class NotarisationPayload(val transaction: Any, val requestSignature: Notar
* Should only be used by non-validating notaries. * Should only be used by non-validating notaries.
*/ */
val coreTransaction get() = transaction as CoreTransaction val coreTransaction get() = transaction as CoreTransaction
} }
/** Payload returned by the notary service flow to the client. */
@CordaSerializable
data class NotarisationResponse(val signatures: List<TransactionSignature>)

View File

@ -17,7 +17,7 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata import net.corda.core.crypto.SignatureMetadata
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.internal.NotaryChangeTransactionBuilder
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
@ -40,11 +40,11 @@ class NotaryChangeFlow<out T : ContractState>(
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx { override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
val inputs = resolveEncumbrances(originalState) val inputs = resolveEncumbrances(originalState)
val tx = NotaryChangeWireTransaction( val tx = NotaryChangeTransactionBuilder(
inputs.map { it.ref }, inputs.map { it.ref },
originalState.state.notary, originalState.state.notary,
modification modification
) ).build()
val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet() val participantKeys = inputs.flatMap { it.state.data.participants }.map { it.owningKey }.toSet()
// TODO: We need a much faster way of finding our key in the transaction // TODO: We need a much faster way of finding our key in the transaction

View File

@ -11,27 +11,24 @@
package net.corda.core.flows package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.DoNotImplement
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.keys
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.FetchDataFlow import net.corda.core.internal.FetchDataFlow
import net.corda.core.internal.generateSignature import net.corda.core.internal.generateSignature
import net.corda.core.internal.validateSignatures
import net.corda.core.node.services.NotaryService import net.corda.core.node.services.NotaryService
import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TrustedAuthorityNotaryService
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.ContractUpgradeWireTransaction import net.corda.core.transactions.ContractUpgradeWireTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap import net.corda.core.utilities.unwrap
import java.security.SignatureException
import java.time.Instant import java.time.Instant
import java.util.function.Predicate import java.util.function.Predicate
@ -46,6 +43,7 @@ class NotaryFlow {
* @throws NotaryException in case the any of the inputs to the transaction have been consumed * @throws NotaryException in case the any of the inputs to the transaction have been consumed
* by another transaction or the time-window is invalid. * by another transaction or the time-window is invalid.
*/ */
@DoNotImplement
@InitiatingFlow @InitiatingFlow
open class Client(private val stx: SignedTransaction, open class Client(private val stx: SignedTransaction,
override val progressTracker: ProgressTracker) : FlowLogic<List<TransactionSignature>>() { override val progressTracker: ProgressTracker) : FlowLogic<List<TransactionSignature>>() {
@ -78,44 +76,32 @@ class NotaryFlow {
check(serviceHub.loadStates(stx.inputs.toSet()).all { it.state.notary == notaryParty }) { check(serviceHub.loadStates(stx.inputs.toSet()).all { it.state.notary == notaryParty }) {
"Input states must have the same Notary" "Input states must have the same Notary"
} }
stx.resolveTransactionWithSignatures(serviceHub).verifySignaturesExcept(notaryParty.owningKey)
try {
stx.resolveTransactionWithSignatures(serviceHub).verifySignaturesExcept(notaryParty.owningKey)
} catch (ex: SignatureException) {
throw NotaryException(NotaryError.TransactionInvalid(ex))
}
return notaryParty return notaryParty
} }
/** Notarises the transaction with the [notaryParty], obtains the notary's signature(s). */ /** Notarises the transaction with the [notaryParty], obtains the notary's signature(s). */
@Throws(NotaryException::class) @Throws(NotaryException::class)
@Suspendable @Suspendable
protected fun notarise(notaryParty: Party): UntrustworthyData<List<TransactionSignature>> { protected fun notarise(notaryParty: Party): UntrustworthyData<NotarisationResponse> {
return try { val session = initiateFlow(notaryParty)
val session = initiateFlow(notaryParty) val requestSignature = NotarisationRequest(stx.inputs, stx.id).generateSignature(serviceHub)
val requestSignature = NotarisationRequest(stx.inputs, stx.id).generateSignature(serviceHub) return if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) { sendAndReceiveValidating(session, requestSignature)
sendAndReceiveValidating(session, requestSignature) } else {
} else { sendAndReceiveNonValidating(notaryParty, session, requestSignature)
sendAndReceiveNonValidating(notaryParty, session, requestSignature)
}
} catch (e: NotaryException) {
if (e.error is NotaryError.Conflict) {
e.error.conflict.verified()
}
throw e
} }
} }
@Suspendable @Suspendable
private fun sendAndReceiveValidating(session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> { private fun sendAndReceiveValidating(session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<NotarisationResponse> {
val payload = NotarisationPayload(stx, signature) val payload = NotarisationPayload(stx, signature)
subFlow(NotarySendTransactionFlow(session, payload)) subFlow(NotarySendTransactionFlow(session, payload))
return session.receive() return session.receive()
} }
@Suspendable @Suspendable
private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<List<TransactionSignature>> { private fun sendAndReceiveNonValidating(notaryParty: Party, session: FlowSession, signature: NotarisationRequestSignature): UntrustworthyData<NotarisationResponse> {
val ctx = stx.coreTransaction val ctx = stx.coreTransaction
val tx = when (ctx) { val tx = when (ctx) {
is ContractUpgradeWireTransaction -> ctx.buildFilteredTransaction() is ContractUpgradeWireTransaction -> ctx.buildFilteredTransaction()
@ -126,18 +112,13 @@ class NotaryFlow {
} }
/** Checks that the notary's signature(s) is/are valid. */ /** Checks that the notary's signature(s) is/are valid. */
protected fun validateResponse(response: UntrustworthyData<List<TransactionSignature>>, notaryParty: Party): List<TransactionSignature> { protected fun validateResponse(response: UntrustworthyData<NotarisationResponse>, notaryParty: Party): List<TransactionSignature> {
return response.unwrap { signatures -> return response.unwrap {
signatures.forEach { validateSignature(it, stx.id, notaryParty) } it.validateSignatures(stx.id, notaryParty)
signatures it.signatures
} }
} }
private fun validateSignature(sig: TransactionSignature, txId: SecureHash, notaryParty: Party) {
check(sig.by in notaryParty.owningKey.keys) { "Invalid signer for the notary result" }
sig.verify(txId)
}
/** /**
* The [NotarySendTransactionFlow] flow is similar to [SendTransactionFlow], but uses [NotarisationPayload] as the * The [NotarySendTransactionFlow] flow is similar to [SendTransactionFlow], but uses [NotarisationPayload] as the
* initial message, and retries message delivery. * initial message, and retries message delivery.
@ -166,11 +147,17 @@ class NotaryFlow {
check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) { check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) {
"We are not a notary on the network" "We are not a notary on the network"
} }
val (id, inputs, timeWindow, notary) = receiveAndVerifyTx() var txId: SecureHash? = null
checkNotary(notary) try {
service.validateTimeWindow(timeWindow) val parts = receiveAndVerifyTx()
service.commitInputStates(inputs, id, otherSideSession.counterparty) txId = parts.id
signAndSendResponse(id) checkNotary(parts.notary)
service.validateTimeWindow(parts.timestamp)
service.commitInputStates(parts.inputs, txId, otherSideSession.counterparty)
signTransactionAndSendResponse(txId)
} catch (e: NotaryInternalException) {
throw NotaryException(e.error, txId)
}
return null return null
} }
@ -185,14 +172,14 @@ class NotaryFlow {
@Suspendable @Suspendable
protected fun checkNotary(notary: Party?) { protected fun checkNotary(notary: Party?) {
if (notary?.owningKey != service.notaryIdentityKey) { if (notary?.owningKey != service.notaryIdentityKey) {
throw NotaryException(NotaryError.WrongNotary) throw NotaryInternalException(NotaryError.WrongNotary)
} }
} }
@Suspendable @Suspendable
private fun signAndSendResponse(txId: SecureHash) { private fun signTransactionAndSendResponse(txId: SecureHash) {
val signature = service.sign(txId) val signature = service.sign(txId)
otherSideSession.send(listOf(signature)) otherSideSession.send(NotarisationResponse(listOf(signature)))
} }
} }
} }
@ -207,14 +194,27 @@ data class TransactionParts(val id: SecureHash, val inputs: List<StateRef>, val
* Exception thrown by the notary service if any issues are encountered while trying to commit a transaction. The * Exception thrown by the notary service if any issues are encountered while trying to commit a transaction. The
* underlying [error] specifies the cause of failure. * underlying [error] specifies the cause of failure.
*/ */
class NotaryException(val error: NotaryError) : FlowException("Unable to notarise: $error") class NotaryException(
/** Cause of notarisation failure. */
val error: NotaryError,
/** Id of the transaction to be notarised. Can be _null_ if an error occurred before the id could be resolved. */
val txId: SecureHash? = null
) : FlowException("Unable to notarise transaction${txId ?: " "}: $error")
/** Exception internal to the notary service. Does not get exposed to CorDapps and flows calling [NotaryFlow.Client]. */
class NotaryInternalException(val error: NotaryError) : FlowException("Unable to notarise: $error")
/** Specifies the cause for notarisation request failure. */ /** Specifies the cause for notarisation request failure. */
@CordaSerializable @CordaSerializable
sealed class NotaryError { sealed class NotaryError {
/** Occurs when one or more input states of transaction with [txId] have already been consumed by another transaction. */ /** Occurs when one or more input states have already been consumed by another transaction. */
data class Conflict(val txId: SecureHash, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() { data class Conflict(
override fun toString() = "One or more input states for transaction $txId have been used in another transaction" /** Id of the transaction that was attempted to be notarised. */
val txId: SecureHash,
/** Specifies which states have already been consumed in another transaction. */
val consumedStates: Map<StateRef, StateConsumptionDetails>
) : NotaryError() {
override fun toString() = "One or more input states have been used in another transaction"
} }
/** Occurs when time specified in the [TimeWindow] command is outside the allowed tolerance. */ /** Occurs when time specified in the [TimeWindow] command is outside the allowed tolerance. */
@ -246,3 +246,15 @@ sealed class NotaryError {
override fun toString() = cause.toString() override fun toString() = cause.toString()
} }
} }
/** Contains information about the consuming transaction for a particular state. */
// TODO: include notary timestamp?
@CordaSerializable
data class StateConsumptionDetails(
/**
* Hash of the consuming transaction id.
*
* Note that this is NOT the transaction id itself revealing it could lead to privacy leaks.
*/
val hashOfTransactionId: SecureHash
)

View File

@ -34,9 +34,7 @@ import java.security.cert.X509Certificate
// also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications. // also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications.
// TODO: Link to the specification once it has a permanent URL // TODO: Link to the specification once it has a permanent URL
enum class CertRole(val validParents: NonEmptySet<CertRole?>, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable { enum class CertRole(val validParents: NonEmptySet<CertRole?>, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable {
/** /** Intermediate CA (Doorman service). */
* Intermediate CA (Doorman service).
*/
INTERMEDIATE_CA(NonEmptySet.of(null), false, false), INTERMEDIATE_CA(NonEmptySet.of(null), false, false),
/** Signing certificate for the network map. */ /** Signing certificate for the network map. */
NETWORK_MAP(NonEmptySet.of(null), false, false), NETWORK_MAP(NonEmptySet.of(null), false, false),
@ -47,6 +45,10 @@ enum class CertRole(val validParents: NonEmptySet<CertRole?>, val isIdentity: Bo
/** Transport layer security certificate for a node. */ /** Transport layer security certificate for a node. */
TLS(NonEmptySet.of(NODE_CA), false, false), TLS(NonEmptySet.of(NODE_CA), false, false),
/** Well known (publicly visible) identity of a legal entity. */ /** Well known (publicly visible) identity of a legal entity. */
// TODO: at the moment, Legal Identity certs are issued by Node CA only. However, [INTERMEDIATE_CA] is also added
// as a valid parent of [LEGAL_IDENTITY] for backwards compatibility purposes (eg. if we decide TLS has its
// own Root CA and Intermediate CA directly issues Legal Identities; thus, there won't be a requirement for
// Node CA). Consider removing [INTERMEDIATE_CA] from [validParents] when the model is finalised.
LEGAL_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA, NODE_CA), true, true), LEGAL_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA, NODE_CA), true, true),
/** Confidential (limited visibility) identity of a legal entity. */ /** Confidential (limited visibility) identity of a legal entity. */
CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false); CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false);

View File

@ -31,18 +31,18 @@ object ContractUpgradeUtils {
val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services) val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services)
val inputs = listOf(stateAndRef.ref) val inputs = listOf(stateAndRef.ref)
return ContractUpgradeWireTransaction( return ContractUpgradeTransactionBuilder(
inputs, inputs,
stateAndRef.state.notary, stateAndRef.state.notary,
legacyContractAttachmentId, legacyContractAttachmentId,
upgradedContractClass.name, upgradedContractClass.name,
upgradedContractAttachmentId, upgradedContractAttachmentId,
privacySalt privacySalt
) ).build()
} }
private fun getContractAttachmentId(name: ContractClassName, services: ServicesForResolution): AttachmentId { private fun getContractAttachmentId(name: ContractClassName, services: ServicesForResolution): AttachmentId {
return services.cordappProvider.getContractAttachmentID(name) return services.cordappProvider.getContractAttachmentID(name)
?: throw IllegalStateException("Attachment not found for contract: $name") ?: throw IllegalStateException("Attachment not found for contract: $name")
} }
} }

View File

@ -0,0 +1,16 @@
package net.corda.core.internal
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.isFulfilledBy
import net.corda.core.flows.NotarisationResponse
import net.corda.core.identity.Party
/**
* Checks that there are sufficient signatures to satisfy the notary signing requirement and validates the signatures
* against the given transaction id.
*/
fun NotarisationResponse.validateSignatures(txId: SecureHash, notary: Party) {
val signingKeys = signatures.map { it.by }
require(notary.owningKey.isFulfilledBy(signingKeys)) { "Insufficient signatures to fulfill the notary signing requirement for $notary" }
signatures.forEach { it.verify(txId) }
}

View File

@ -0,0 +1,45 @@
package net.corda.core.internal
import net.corda.core.contracts.ContractClassName
import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.identity.Party
import net.corda.core.serialization.serialize
import net.corda.core.transactions.ContractUpgradeWireTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import java.io.ByteArrayOutputStream
/** Constructs a [NotaryChangeWireTransaction]. */
class NotaryChangeTransactionBuilder(val inputs: List<StateRef>,
val notary: Party,
val newNotary: Party) {
fun build(): NotaryChangeWireTransaction {
val components = listOf(inputs, notary, newNotary).map { it.serialize() }
return NotaryChangeWireTransaction(components)
}
}
/** Constructs a [ContractUpgradeWireTransaction]. */
class ContractUpgradeTransactionBuilder(
val inputs: List<StateRef>,
val notary: Party,
val legacyContractAttachmentId: SecureHash,
val upgradedContractClassName: ContractClassName,
val upgradedContractAttachmentId: SecureHash,
val privacySalt: PrivacySalt = PrivacySalt()) {
fun build(): ContractUpgradeWireTransaction {
val components = listOf(inputs, notary, legacyContractAttachmentId, upgradedContractClassName, upgradedContractAttachmentId).map { it.serialize() }
return ContractUpgradeWireTransaction(components, privacySalt)
}
}
/** Concatenates the hash components into a single [ByteArray] and returns its hash. */
fun combinedHash(components: Iterable<SecureHash>): SecureHash {
val stream = ByteArrayOutputStream()
components.forEach {
stream.write(it.bytes)
}
return stream.toByteArray().sha256()
}

View File

@ -18,7 +18,6 @@ import net.corda.core.flows.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
import org.slf4j.Logger import org.slf4j.Logger
import java.security.PublicKey import java.security.PublicKey
@ -40,18 +39,19 @@ abstract class NotaryService : SingletonSerializeAsToken() {
} }
/** /**
* Checks if the current instant provided by the clock falls within the specified time window. * Checks if the current instant provided by the clock falls within the specified time window. Should only be
* used by a notary service flow.
* *
* @throws NotaryException if current time is outside the specified time window. The exception contains * @throws NotaryInternalException if current time is outside the specified time window. The exception contains
* the [NotaryError.TimeWindowInvalid] error. * the [NotaryError.TimeWindowInvalid] error.
*/ */
@JvmStatic @JvmStatic
@Throws(NotaryException::class) @Throws(NotaryInternalException::class)
fun validateTimeWindow(clock: Clock, timeWindow: TimeWindow?) { fun validateTimeWindow(clock: Clock, timeWindow: TimeWindow?) {
if (timeWindow == null) return if (timeWindow == null) return
val currentTime = clock.instant() val currentTime = clock.instant()
if (currentTime !in timeWindow) { if (currentTime !in timeWindow) {
throw NotaryException( throw NotaryInternalException(
NotaryError.TimeWindowInvalid(currentTime, timeWindow) NotaryError.TimeWindowInvalid(currentTime, timeWindow)
) )
} }
@ -92,28 +92,24 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
fun commitInputStates(inputs: List<StateRef>, txId: SecureHash, caller: Party) { fun commitInputStates(inputs: List<StateRef>, txId: SecureHash, caller: Party) {
try { try {
uniquenessProvider.commit(inputs, txId, caller) uniquenessProvider.commit(inputs, txId, caller)
} catch (e: UniquenessException) { } catch (e: NotaryInternalException) {
val conflicts = inputs.filterIndexed { i, stateRef -> if (e.error is NotaryError.Conflict) {
val consumingTx = e.error.stateHistory[stateRef] val conflicts = inputs.filterIndexed { _, stateRef ->
consumingTx != null && consumingTx != UniquenessProvider.ConsumingTx(txId, i, caller) val cause = e.error.consumedStates[stateRef]
} cause != null && cause.hashOfTransactionId != txId.sha256()
if (conflicts.isNotEmpty()) { }
// TODO: Create a new UniquenessException that only contains the conflicts filtered above. if (conflicts.isNotEmpty()) {
log.warn("Notary conflicts for $txId: $conflicts") // TODO: Create a new UniquenessException that only contains the conflicts filtered above.
throw notaryException(txId, e) log.info("Notary conflicts for $txId: $conflicts")
} throw e
}
} else throw e
} catch (e: Exception) { } catch (e: Exception) {
log.error("Internal error", e) log.error("Internal error", e)
throw NotaryException(NotaryError.General(Exception("Service unavailable, please try again later"))) throw NotaryInternalException(NotaryError.General(Exception("Service unavailable, please try again later")))
} }
} }
private fun notaryException(txId: SecureHash, e: UniquenessException): NotaryException {
val conflictData = e.error.serialize()
val signedConflict = SignedData(conflictData, sign(conflictData.bytes))
return NotaryException(NotaryError.Conflict(txId, signedConflict))
}
/** Sign a [ByteArray] input. */ /** Sign a [ByteArray] input. */
fun sign(bits: ByteArray): DigitalSignature.WithKey { fun sign(bits: ByteArray): DigitalSignature.WithKey {
return services.keyManagementService.sign(bits, notaryIdentityKey) return services.keyManagementService.sign(bits, notaryIdentityKey)
@ -127,6 +123,8 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
// TODO: Sign multiple transactions at once by building their Merkle tree and then signing over its root. // TODO: Sign multiple transactions at once by building their Merkle tree and then signing over its root.
@Deprecated("This property is no longer used") @Suppress("DEPRECATION") @Deprecated("This property is no longer used")
protected open val timeWindowChecker: TimeWindowChecker get() = throw UnsupportedOperationException("No default implementation, need to override") @Suppress("DEPRECATION")
protected open val timeWindowChecker: TimeWindowChecker
get() = throw UnsupportedOperationException("No default implementation, need to override")
} }

View File

@ -23,24 +23,22 @@ import net.corda.core.serialization.CordaSerializable
* A uniqueness provider is expected to be used from within the context of a flow. * A uniqueness provider is expected to be used from within the context of a flow.
*/ */
interface UniquenessProvider { interface UniquenessProvider {
/** Commits all input states of the given transaction */ /** Commits all input states of the given transaction. */
fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party) fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party)
/** Specifies the consuming transaction for every conflicting state */ /** Specifies the consuming transaction for every conflicting state. */
@CordaSerializable @CordaSerializable
@Deprecated("No longer used due to potential privacy leak")
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>) data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)
/** /**
* Specifies the transaction id, the position of the consumed state in the inputs, and * Specifies the transaction id, the position of the consumed state in the inputs, and
* the caller identity requesting the commit. * the caller identity requesting the commit.
*
* TODO: need to do more design work to prevent privacy problems: knowing the id of a
* transaction, by the rules of our system the party can obtain it and see its contents.
* This allows a party to just submit invalid transactions with outputs it was aware of and
* find out where exactly they were spent.
*/ */
@CordaSerializable @CordaSerializable
data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party) data class ConsumingTx(val id: SecureHash, val inputIndex: Int, val requestingParty: Party)
} }
@Deprecated("No longer used due to potential privacy leak")
@Suppress("DEPRECATION")
class UniquenessException(val error: UniquenessProvider.Conflict) : CordaException(UniquenessException::class.java.name) class UniquenessException(val error: UniquenessProvider.Conflict) : CordaException(UniquenessException::class.java.name)

View File

@ -16,13 +16,14 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.Vault.StateStatus
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.NonEmptySet
import rx.Observable import rx.Observable
import java.time.Instant import java.time.Instant
@ -120,15 +121,15 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
/** /**
* Returned in queries [VaultService.queryBy] and [VaultService.trackBy]. * Returned in queries [VaultService.queryBy] and [VaultService.trackBy].
* A Page contains: * A Page contains:
* 1) a [List] of actual [StateAndRef] requested by the specified [QueryCriteria] to a maximum of [MAX_PAGE_SIZE] * 1) a [List] of actual [StateAndRef] requested by the specified [QueryCriteria] to a maximum of [MAX_PAGE_SIZE].
* 2) a [List] of associated [Vault.StateMetadata], one per [StateAndRef] result * 2) a [List] of associated [Vault.StateMetadata], one per [StateAndRef] result.
* 3) a total number of states that met the given [QueryCriteria] if a [PageSpecification] was provided * 3) a total number of states that met the given [QueryCriteria] if a [PageSpecification] was provided,
* (otherwise defaults to -1) * otherwise it defaults to -1.
* 4) Status types used in this query: UNCONSUMED, CONSUMED, ALL * 4) Status types used in this query: [StateStatus.UNCONSUMED], [StateStatus.CONSUMED], [StateStatus.ALL].
* 5) Other results as a [List] of any type (eg. aggregate function results with/without group by) * 5) Other results as a [List] of any type (eg. aggregate function results with/without group by).
* *
* Note: currently otherResults are used only for Aggregate Functions (in which case, the states and statesMetadata * Note: currently otherResults are used only for Aggregate Functions (in which case, the states and statesMetadata
* results will be empty) * results will be empty).
*/ */
@CordaSerializable @CordaSerializable
data class Page<out T : ContractState>(val states: List<StateAndRef<T>>, data class Page<out T : ContractState>(val states: List<StateAndRef<T>>,
@ -168,17 +169,18 @@ interface VaultService {
/** /**
* Prefer the use of [updates] unless you know why you want to use this instead. * Prefer the use of [updates] unless you know why you want to use this instead.
* *
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the Vault will already incorporate * Get a synchronous [Observable] of updates. When observations are pushed to the Observer, the [Vault] will already
* the update, and the database transaction associated with the update will still be open and current. If for some * incorporate the update, and the database transaction associated with the update will still be open and current.
* reason the processing crosses outside of the database transaction (for example, the update is pushed outside the current * If for some reason the processing crosses outside of the database transaction (for example, the update is pushed
* JVM or across to another [Thread] which is executing in a different database transaction) then the Vault may * outside the current JVM or across to another [Thread], which is executing in a different database transaction),
* not incorporate the update due to racing with committing the current database transaction. * then the [Vault] may not incorporate the update due to racing with committing the current database transaction.
*/ */
val rawUpdates: Observable<Vault.Update<ContractState>> val rawUpdates: Observable<Vault.Update<ContractState>>
/** /**
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the Vault will already incorporate * Get a synchronous [Observable] of updates. When observations are pushed to the Observer, the [Vault] will
* the update, and the database transaction associated with the update will have been committed and closed. * already incorporate the update and the database transaction associated with the update will have been committed
* and closed.
*/ */
val updates: Observable<Vault.Update<ContractState>> val updates: Observable<Vault.Update<ContractState>>
@ -190,10 +192,10 @@ interface VaultService {
} }
/** /**
* Add a note to an existing [LedgerTransaction] given by its unique [SecureHash] id * Add a note to an existing [LedgerTransaction] given by its unique [SecureHash] id.
* Multiple notes may be attached to the same [LedgerTransaction]. * Multiple notes may be attached to the same [LedgerTransaction].
* These are additively and immutably persisted within the node local vault database in a single textual field * These are additively and immutably persisted within the node local vault database in a single textual field.
* using a semi-colon separator * using a semi-colon separator.
*/ */
fun addNoteToTransaction(txnId: SecureHash, noteText: String) fun addNoteToTransaction(txnId: SecureHash, noteText: String)
@ -202,7 +204,7 @@ interface VaultService {
// DOCEND VaultStatesQuery // DOCEND VaultStatesQuery
/** /**
* Soft locking is used to prevent multiple transactions trying to use the same output simultaneously. * Soft locking is used to prevent multiple transactions trying to use the same states simultaneously.
* Violation of a soft lock would result in a double spend being created and rejected by the notary. * Violation of a soft lock would result in a double spend being created and rejected by the notary.
*/ */
@ -210,35 +212,35 @@ interface VaultService {
/** /**
* Reserve a set of [StateRef] for a given [UUID] unique identifier. * Reserve a set of [StateRef] for a given [UUID] unique identifier.
* Typically, the unique identifier will refer to a [FlowLogic.runId.uuid] associated with an in-flight flow. * Typically, the unique identifier will refer to a [FlowLogic.runId]'s [UUID] associated with an in-flight flow.
* In this case if the flow terminates the locks will automatically be freed, even if there is an error. * In this case if the flow terminates the locks will automatically be freed, even if there is an error.
* However, the user can specify their own [UUID] and manage this manually, possibly across the lifetime of multiple flows, * However, the user can specify their own [UUID] and manage this manually, possibly across the lifetime of multiple
* or from other thread contexts e.g. [CordaService] instances. * flows, or from other thread contexts e.g. [CordaService] instances.
* In the case of coin selection, soft locks are automatically taken upon gathering relevant unconsumed input refs. * In the case of coin selection, soft locks are automatically taken upon gathering relevant unconsumed input refs.
* *
* @throws [StatesNotAvailableException] when not possible to softLock all of requested [StateRef] * @throws [StatesNotAvailableException] when not possible to soft-lock all of requested [StateRef].
*/ */
@Throws(StatesNotAvailableException::class) @Throws(StatesNotAvailableException::class)
fun softLockReserve(lockId: UUID, stateRefs: NonEmptySet<StateRef>) fun softLockReserve(lockId: UUID, stateRefs: NonEmptySet<StateRef>)
/** /**
* Release all or an explicitly specified set of [StateRef] for a given [UUID] unique identifier. * Release all or an explicitly specified set of [StateRef] for a given [UUID] unique identifier.
* A vault soft lock manager is automatically notified of a Flows that are terminated, such that any soft locked states * A [Vault] soft-lock manager is automatically notified from flows that are terminated, such that any soft locked
* may be released. * states may be released.
* In the case of coin selection, softLock are automatically released once previously gathered unconsumed input refs * In the case of coin selection, soft-locks are automatically released once previously gathered unconsumed
* are consumed as part of cash spending. * input refs are consumed as part of cash spending.
*/ */
fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>? = null) fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>? = null)
// DOCEND SoftLockAPI // DOCEND SoftLockAPI
/** /**
* Helper function to determine spendable states and soft locking them. * Helper function to determine spendable states and soft locking them.
* Currently performance will be worse than for the hand optimised version in `Cash.unconsumedCashStatesForSpending` * Currently performance will be worse than for the hand optimised version in `Cash.unconsumedCashStatesForSpending`.
* However, this is fully generic and can operate with custom [FungibleAsset] states. * However, this is fully generic and can operate with custom [FungibleAsset] states.
* @param lockId The [FlowLogic.runId.uuid] of the current flow used to soft lock the states. * @param lockId The [FlowLogic.runId]'s [UUID] of the current flow used to soft lock the states.
* @param eligibleStatesQuery A custom query object that selects down to the appropriate subset of all states of the * @param eligibleStatesQuery A custom query object that selects down to the appropriate subset of all states of the
* [contractStateType]. e.g. by selecting on account, issuer, etc. The query is internally augmented with the UNCONSUMED, * [contractStateType]. e.g. by selecting on account, issuer, etc. The query is internally augmented with the
* soft lock and contract type requirements. * [StateStatus.UNCONSUMED], soft lock and contract type requirements.
* @param amount The required amount of the asset, but with the issuer stripped off. * @param amount The required amount of the asset, but with the issuer stripped off.
* It is assumed that compatible issuer states will be filtered out by the [eligibleStatesQuery]. * It is assumed that compatible issuer states will be filtered out by the [eligibleStatesQuery].
* @param contractStateType class type of the result set. * @param contractStateType class type of the result set.
@ -259,12 +261,12 @@ interface VaultService {
* and returns a [Vault.Page] object containing the following: * and returns a [Vault.Page] object containing the following:
* 1. states as a List of <StateAndRef> (page number and size defined by [PageSpecification]) * 1. states as a List of <StateAndRef> (page number and size defined by [PageSpecification])
* 2. states metadata as a List of [Vault.StateMetadata] held in the Vault States table. * 2. states metadata as a List of [Vault.StateMetadata] held in the Vault States table.
* 3. total number of results available if [PageSpecification] supplied (otherwise returns -1) * 3. total number of results available if [PageSpecification] supplied (otherwise returns -1).
* 4. status types used in this query: UNCONSUMED, CONSUMED, ALL * 4. status types used in this query: [StateStatus.UNCONSUMED], [StateStatus.CONSUMED], [StateStatus.ALL].
* 5. other results (aggregate functions with/without using value groups) * 5. other results (aggregate functions with/without using value groups).
* *
* @throws VaultQueryException if the query cannot be executed for any reason * @throws VaultQueryException if the query cannot be executed for any reason
* (missing criteria or parsing error, paging errors, unsupported query, underlying database error) * (missing criteria or parsing error, paging errors, unsupported query, underlying database error).
* *
* Notes * Notes
* If no [PageSpecification] is provided, a maximum of [DEFAULT_PAGE_SIZE] results will be returned. * If no [PageSpecification] is provided, a maximum of [DEFAULT_PAGE_SIZE] results will be returned.
@ -281,11 +283,11 @@ interface VaultService {
/** /**
* Generic vault query function which takes a [QueryCriteria] object to define filters, * Generic vault query function which takes a [QueryCriteria] object to define filters,
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted), * optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
* and returns a [Vault.PageAndUpdates] object containing * and returns a [DataFeed] object containing:
* 1) a snapshot as a [Vault.Page] (described previously in [queryBy]) * 1) a snapshot as a [Vault.Page] (described previously in [queryBy]).
* 2) an [Observable] of [Vault.Update] * 2) an [Observable] of [Vault.Update].
* *
* @throws VaultQueryException if the query cannot be executed for any reason * @throws VaultQueryException if the query cannot be executed for any reason.
* *
* Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function. * Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function.
* the [QueryCriteria] applies to both snapshot and deltas (streaming updates). * the [QueryCriteria] applies to both snapshot and deltas (streaming updates).
@ -297,8 +299,8 @@ interface VaultService {
contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>> contractStateType: Class<out T>): DataFeed<Vault.Page<T>, Vault.Update<T>>
// DOCEND VaultQueryAPI // DOCEND VaultQueryAPI
// Note: cannot apply @JvmOverloads to interfaces nor interface implementations // Note: cannot apply @JvmOverloads to interfaces nor interface implementations.
// Java Helpers // Java Helpers.
fun <T : ContractState> queryBy(contractStateType: Class<out T>): Vault.Page<T> { fun <T : ContractState> queryBy(contractStateType: Class<out T>): Vault.Page<T> {
return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType) return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
} }

View File

@ -13,12 +13,18 @@ package net.corda.core.transactions
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.serializedHash import net.corda.core.crypto.componentHash
import net.corda.core.crypto.computeNonce
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.AttachmentWithContext import net.corda.core.internal.AttachmentWithContext
import net.corda.core.internal.combinedHash
import net.corda.core.node.NetworkParameters import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.deserialize
import net.corda.core.transactions.ContractUpgradeFilteredTransaction.FilteredComponent
import net.corda.core.transactions.ContractUpgradeWireTransaction.Component.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toBase58String import net.corda.core.utilities.toBase58String
import java.security.PublicKey import java.security.PublicKey
@ -28,13 +34,20 @@ import java.security.PublicKey
/** A special transaction for upgrading the contract of a state. */ /** A special transaction for upgrading the contract of a state. */
@CordaSerializable @CordaSerializable
data class ContractUpgradeWireTransaction( data class ContractUpgradeWireTransaction(
override val inputs: List<StateRef>, /**
override val notary: Party, * Contains all of the transaction components in serialized form.
val legacyContractAttachmentId: SecureHash, * This is used for calculating the transaction id in a deterministic fashion, since re-serializing properties
val upgradeContractClassName: ContractClassName, * may result in a different byte sequence depending on the serialization context.
val upgradedContractAttachmentId: SecureHash, */
val serializedComponents: List<OpaqueBytes>,
/** Required for hiding components in [ContractUpgradeFilteredTransaction]. */
val privacySalt: PrivacySalt = PrivacySalt() val privacySalt: PrivacySalt = PrivacySalt()
) : CoreTransaction() { ) : CoreTransaction() {
override val inputs: List<StateRef> = serializedComponents[INPUTS.ordinal].deserialize()
override val notary: Party by lazy { serializedComponents[NOTARY.ordinal].deserialize<Party>() }
val legacyContractAttachmentId: SecureHash by lazy { serializedComponents[LEGACY_ATTACHMENT.ordinal].deserialize<SecureHash>() }
val upgradedContractClassName: ContractClassName by lazy { serializedComponents[UPGRADED_CONTRACT.ordinal].deserialize<ContractClassName>() }
val upgradedContractAttachmentId: SecureHash by lazy { serializedComponents[UPGRADED_ATTACHMENT.ordinal].deserialize<SecureHash>() }
init { init {
check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" } check(inputs.isNotEmpty()) { "A contract upgrade transaction must have inputs" }
@ -49,11 +62,17 @@ data class ContractUpgradeWireTransaction(
get() = throw UnsupportedOperationException("ContractUpgradeWireTransaction does not contain output states, " + get() = throw UnsupportedOperationException("ContractUpgradeWireTransaction does not contain output states, " +
"outputs can only be obtained from a resolved ContractUpgradeLedgerTransaction") "outputs can only be obtained from a resolved ContractUpgradeLedgerTransaction")
/** Hash of the list of components that are hidden in the [ContractUpgradeFilteredTransaction]. */ override val id: SecureHash by lazy {
private val hiddenComponentHash: SecureHash val componentHashes =serializedComponents.mapIndexed { index, component ->
get() = serializedHash(listOf(legacyContractAttachmentId, upgradeContractClassName, privacySalt)) componentHash(nonces[index], component)
}
combinedHash(componentHashes)
}
override val id: SecureHash by lazy { serializedHash(inputs + notary).hashConcat(hiddenComponentHash) } /** Required for filtering transaction components. */
private val nonces = (0 until serializedComponents.size).map {
computeNonce(privacySalt, it, 0)
}
/** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */ /** Resolves input states and contract attachments, and builds a ContractUpgradeLedgerTransaction. */
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction { fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction {
@ -66,7 +85,7 @@ data class ContractUpgradeWireTransaction(
resolvedInputs, resolvedInputs,
notary, notary,
legacyContractAttachment, legacyContractAttachment,
upgradeContractClassName, upgradedContractClassName,
upgradedContractAttachment, upgradedContractAttachment,
id, id,
privacySalt, privacySalt,
@ -75,8 +94,23 @@ data class ContractUpgradeWireTransaction(
) )
} }
/** Constructs a filtered transaction: the inputs and the notary party are always visible, while the rest are hidden. */
fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction { fun buildFilteredTransaction(): ContractUpgradeFilteredTransaction {
return ContractUpgradeFilteredTransaction(inputs, notary, hiddenComponentHash) val totalComponents = (0 until serializedComponents.size).toSet()
val visibleComponents = mapOf(
INPUTS.ordinal to FilteredComponent(serializedComponents[INPUTS.ordinal], nonces[INPUTS.ordinal]),
NOTARY.ordinal to FilteredComponent(serializedComponents[NOTARY.ordinal], nonces[NOTARY.ordinal])
)
val hiddenComponents = (totalComponents - visibleComponents.keys).map { index ->
val hash = componentHash(nonces[index], serializedComponents[index])
index to hash
}.toMap()
return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents)
}
enum class Component {
INPUTS, NOTARY, LEGACY_ATTACHMENT, UPGRADED_CONTRACT, UPGRADED_ATTACHMENT
} }
} }
@ -84,19 +118,43 @@ data class ContractUpgradeWireTransaction(
* A filtered version of the [ContractUpgradeWireTransaction]. In comparison with a regular [FilteredTransaction], there * A filtered version of the [ContractUpgradeWireTransaction]. In comparison with a regular [FilteredTransaction], there
* is no flexibility on what parts of the transaction to reveal the inputs and notary field are always visible and the * is no flexibility on what parts of the transaction to reveal the inputs and notary field are always visible and the
* rest of the transaction is always hidden. Its only purpose is to hide transaction data when using a non-validating notary. * rest of the transaction is always hidden. Its only purpose is to hide transaction data when using a non-validating notary.
*
* @property inputs The inputs of this transaction.
* @property notary The notary for this transaction.
* @property rest Hash of the hidden components of the [ContractUpgradeWireTransaction].
*/ */
@CordaSerializable @CordaSerializable
data class ContractUpgradeFilteredTransaction( data class ContractUpgradeFilteredTransaction(
override val inputs: List<StateRef>, /** Transaction components that are exposed. */
override val notary: Party, val visibleComponents: Map<Int, FilteredComponent>,
val rest: SecureHash /**
* Hashes of the transaction components that are not revealed in this transaction.
* Required for computing the transaction id.
*/
val hiddenComponents: Map<Int, SecureHash>
) : CoreTransaction() { ) : CoreTransaction() {
override val id: SecureHash get() = serializedHash(inputs + notary).hashConcat(rest) override val inputs: List<StateRef> by lazy {
visibleComponents[INPUTS.ordinal]?.component?.deserialize<List<StateRef>>()
?: throw IllegalArgumentException("Inputs not specified")
}
override val notary: Party by lazy {
visibleComponents[NOTARY.ordinal]?.component?.deserialize<Party>()
?: throw IllegalArgumentException("Notary not specified")
}
override val id: SecureHash by lazy {
val totalComponents = visibleComponents.size + hiddenComponents.size
val hashList = (0 until totalComponents).map { i ->
when {
visibleComponents.containsKey(i) -> {
componentHash(visibleComponents[i]!!.nonce, visibleComponents[i]!!.component)
}
hiddenComponents.containsKey(i) -> hiddenComponents[i]!!
else -> throw IllegalStateException("Missing component hashes")
}
}
combinedHash(hashList)
}
override val outputs: List<TransactionState<ContractState>> get() = emptyList() override val outputs: List<TransactionState<ContractState>> get() = emptyList()
/** Contains the serialized component and the associated nonce for computing the transaction id. */
@CordaSerializable
class FilteredComponent(val component: OpaqueBytes, val nonce: SecureHash)
} }
/** /**
@ -113,7 +171,7 @@ data class ContractUpgradeLedgerTransaction(
override val inputs: List<StateAndRef<ContractState>>, override val inputs: List<StateAndRef<ContractState>>,
override val notary: Party, override val notary: Party,
val legacyContractAttachment: Attachment, val legacyContractAttachment: Attachment,
val upgradeContractClassName: ContractClassName, val upgradedContractClassName: ContractClassName,
val upgradedContractAttachment: Attachment, val upgradedContractAttachment: Attachment,
override val id: SecureHash, override val id: SecureHash,
val privacySalt: PrivacySalt, val privacySalt: PrivacySalt,
@ -175,7 +233,7 @@ data class ContractUpgradeLedgerTransaction(
// TODO: re-map encumbrance pointers // TODO: re-map encumbrance pointers
input.state.copy( input.state.copy(
data = upgradedState, data = upgradedState,
contract = upgradeContractClassName, contract = upgradedContractClassName,
constraint = outputConstraint constraint = outputConstraint
) )
} }
@ -192,7 +250,7 @@ data class ContractUpgradeLedgerTransaction(
private fun loadUpgradedContract(): UpgradedContract<ContractState, *> { private fun loadUpgradedContract(): UpgradedContract<ContractState, *> {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
return this::class.java.classLoader return this::class.java.classLoader
.loadClass(upgradeContractClassName) .loadClass(upgradedContractClassName)
.asSubclass(Contract::class.java) .asSubclass(Contract::class.java)
.getConstructor() .getConstructor()
.newInstance() as UpgradedContract<ContractState, *> .newInstance() as UpgradedContract<ContractState, *>

View File

@ -410,6 +410,6 @@ data class LedgerTransaction @JvmOverloads constructor(
notary: Party?, notary: Party?,
timeWindow: TimeWindow?, timeWindow: TimeWindow?,
privacySalt: PrivacySalt privacySalt: PrivacySalt
) = copy(inputs, outputs, commands, attachments, id, notary, timeWindow, privacySalt, null) ) = copy(inputs = inputs, outputs = outputs, commands = commands, attachments = attachments, id = id, notary = notary, timeWindow = timeWindow, privacySalt = privacySalt, networkParameters = null)
} }

View File

@ -95,18 +95,18 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes<List<PublicKey>>(it).deserialize() }) val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes<List<PublicKey>>(it).deserialize() })
val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes<CommandData>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) }) val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes<CommandData>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal } val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal }
if (group is FilteredComponentGroup) { return if (group is FilteredComponentGroup) {
check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" } check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" }
val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) } val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }
val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) } val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) }
if (leafIndices.isNotEmpty()) if (leafIndices.isNotEmpty())
check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" } check(leafIndices.max()!! < signersList.size) { "Invalid Transaction. A command with no corresponding signer detected" }
return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[leafIndices[index]]) } commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[leafIndices[index]]) }
} else { } else {
// It is a WireTransaction // It is a WireTransaction
// or a FilteredTransaction with no Commands (in which case group is null). // or a FilteredTransaction with no Commands (in which case group is null).
check(commandDataList.size == signersList.size) { "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" } check(commandDataList.size == signersList.size) { "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" }
return commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) } commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) }
} }
} }
} }
@ -158,9 +158,9 @@ class FilteredTransaction internal constructor(
// As all of the helper Map structures, like availableComponentNonces, availableComponentHashes // As all of the helper Map structures, like availableComponentNonces, availableComponentHashes
// and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be // and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be
// a match on Map.get ensuring it will never return null. // a match on Map.get ensuring it will never return null.
filteredSerialisedComponents.put(componentGroupIndex, mutableListOf(serialisedComponent)) filteredSerialisedComponents[componentGroupIndex] = mutableListOf(serialisedComponent)
filteredComponentNonces.put(componentGroupIndex, mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])) filteredComponentNonces[componentGroupIndex] = mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
filteredComponentHashes.put(componentGroupIndex, mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])) filteredComponentHashes[componentGroupIndex] = mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
} else { } else {
group.add(serialisedComponent) group.add(serialisedComponent)
// If the group[componentGroupIndex] existed, then we guarantee that // If the group[componentGroupIndex] existed, then we guarantee that
@ -175,9 +175,9 @@ class FilteredTransaction internal constructor(
val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal
// There exist commands, thus the signers group is not empty. // There exist commands, thus the signers group is not empty.
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex } val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
filteredSerialisedComponents.put(signersGroupIndex, signersGroupComponents.components.toMutableList()) filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList()
filteredComponentNonces.put(signersGroupIndex, wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()) filteredComponentNonces[signersGroupIndex] = wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()
filteredComponentHashes.put(signersGroupIndex, wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()) filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()
} }
} }
} }
@ -322,14 +322,14 @@ class FilteredTransaction internal constructor(
.filter { signers -> publicKey in signers }.size .filter { signers -> publicKey in signers }.size
} }
inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any) { private inline fun verificationCheck(value: Boolean, lazyMessage: () -> Any) {
if (!value) { if (!value) {
val message = lazyMessage() val message = lazyMessage()
throw FilteredTransactionVerificationException(id, message.toString()) throw FilteredTransactionVerificationException(id, message.toString())
} }
} }
inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) { private inline fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) {
if (!value) { if (!value) {
val message = lazyMessage() val message = lazyMessage()
throw ComponentVisibilityException(id, message.toString()) throw ComponentVisibilityException(id, message.toString())

View File

@ -13,11 +13,15 @@ package net.corda.core.transactions
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.serializedHash import net.corda.core.crypto.sha256
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.ServicesForResolution import net.corda.core.node.ServicesForResolution
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.NotaryChangeWireTransaction.Component.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toBase58String import net.corda.core.utilities.toBase58String
import java.security.PublicKey import java.security.PublicKey
@ -28,10 +32,18 @@ import java.security.PublicKey
*/ */
@CordaSerializable @CordaSerializable
data class NotaryChangeWireTransaction( data class NotaryChangeWireTransaction(
override val inputs: List<StateRef>, /**
override val notary: Party, * Contains all of the transaction components in serialized form.
val newNotary: Party * This is used for calculating the transaction id in a deterministic fashion, since re-serializing properties
* may result in a different byte sequence depending on the serialization context.
*/
val serializedComponents: List<OpaqueBytes>
) : CoreTransaction() { ) : CoreTransaction() {
override val inputs: List<StateRef> = serializedComponents[INPUTS.ordinal].deserialize()
override val notary: Party = serializedComponents[NOTARY.ordinal].deserialize()
/** Identity of the notary service to reassign the states to.*/
val newNotary: Party = serializedComponents[NEW_NOTARY.ordinal].deserialize()
/** /**
* This transaction does not contain any output states, outputs can be obtained by resolving a * This transaction does not contain any output states, outputs can be obtained by resolving a
* [NotaryChangeLedgerTransaction] and applying the notary modification to inputs. * [NotaryChangeLedgerTransaction] and applying the notary modification to inputs.
@ -49,16 +61,29 @@ data class NotaryChangeWireTransaction(
* A privacy salt is not really required in this case, because we already used nonces in normal transactions and * A privacy salt is not really required in this case, because we already used nonces in normal transactions and
* thus input state refs will always be unique. Also, filtering doesn't apply on this type of transactions. * thus input state refs will always be unique. Also, filtering doesn't apply on this type of transactions.
*/ */
override val id: SecureHash by lazy { serializedHash(inputs + notary + newNotary) } override val id: SecureHash by lazy {
serializedComponents.map { component ->
component.bytes.sha256()
}.reduce { combinedHash, componentHash ->
combinedHash.hashConcat(componentHash)
}
}
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */ /** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>) : NotaryChangeLedgerTransaction { fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
val resolvedInputs = services.loadStates(inputs.toSet()).toList() val resolvedInputs = services.loadStates(inputs.toSet()).toList()
return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs) return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs)
} }
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */ /** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>) = resolve(services as ServicesForResolution, sigs) fun resolve(services: ServiceHub, sigs: List<TransactionSignature>) = resolve(services as ServicesForResolution, sigs)
enum class Component {
INPUTS, NOTARY, NEW_NOTARY
}
@Deprecated("Required only for backwards compatibility purposes. This type of transaction should not be constructed outside Corda code.", ReplaceWith("NotaryChangeTransactionBuilder"), DeprecationLevel.WARNING)
constructor(inputs: List<StateRef>, notary: Party, newNotary: Party) : this(listOf(inputs, notary, newNotary).map { it.serialize() })
} }
/** /**

View File

@ -112,7 +112,7 @@ open class TransactionBuilder(
// with an explicit [AttachmentConstraint] // with an explicit [AttachmentConstraint]
val resolvedOutputs = outputs.map { state -> val resolvedOutputs = outputs.map { state ->
when { when {
state.constraint !is AutomaticHashConstraint -> state state.constraint !== AutomaticHashConstraint -> state
useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint) useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint)
else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let { else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let {
state.copy(constraint = HashAttachmentConstraint(it)) state.copy(constraint = HashAttachmentConstraint(it))

View File

@ -13,7 +13,7 @@ Running the UI
**Other**:: **Other**::
./gradlew tools:explorer:run ./gradlew tools:explorer:run
Running demo nodes Running demo nodes
------------------ ------------------
@ -79,11 +79,11 @@ The Demo Nodes can be started in one of three modes:
.. note:: 5 Corda nodes will be created on the following port on localhost by default. .. note:: 5 Corda nodes will be created on the following port on localhost by default.
* Notary -> 20001 (Does not accept logins) * Notary -> 20005 (Does not accept logins)
* Alice -> 20004 * UK Bank Plc -> 20011 (*Issuer node*)
* Bob -> 20007 * USA Bank Corp -> 20008 (*Issuer node*)
* UK Bank Plc -> 20010 (*Issuer node*) * Alice -> 20017
* USA Bank Corp -> 20013 (*Issuer node*) * Bob -> 20014
Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``. Explorer login credentials to the Issuer nodes are defaulted to ``manager`` and ``test``.
Explorer login credentials to the Participants nodes are defaulted to ``user1`` and ``test``. Explorer login credentials to the Participants nodes are defaulted to ``user1`` and ``test``.
@ -102,21 +102,21 @@ Please note you are not allowed to login to the notary.
Interface Interface
--------- ---------
Login Login
User can login to any Corda node using the explorer. Alternatively, ``gradlew explorer:runDemoNodes`` can be used to start up demo nodes for testing. User can login to any Corda node using the explorer. Alternatively, ``gradlew explorer:runDemoNodes`` can be used to start up demo nodes for testing.
Corda node address, username and password are required for login, the address is defaulted to localhost:0 if leave blank. Corda node address, username and password are required for login, the address is defaulted to localhost:0 if leave blank.
Username and password can be configured via the ``rpcUsers`` field in node's configuration file. Username and password can be configured via the ``rpcUsers`` field in node's configuration file.
.. image:: resources/explorer/login.png .. image:: resources/explorer/login.png
:scale: 50 % :scale: 50 %
:align: center :align: center
Dashboard Dashboard
The dashboard shows the top level state of node and vault. The dashboard shows the top level state of node and vault.
Currently, it shows your cash balance and the numbers of transaction executed. Currently, it shows your cash balance and the numbers of transaction executed.
The dashboard is intended to house widgets from different CordApps and provide useful information to system admin at a glance. The dashboard is intended to house widgets from different CordApps and provide useful information to system admin at a glance.
.. image:: resources/explorer/dashboard.png .. image:: resources/explorer/dashboard.png
Cash Cash
The cash view shows all currencies you currently own in a tree table format, it is grouped by issuer -> currency. The cash view shows all currencies you currently own in a tree table format, it is grouped by issuer -> currency.
Individual cash transactions can be viewed by clicking on the table row. The user can also use the search field to narrow down the scope. Individual cash transactions can be viewed by clicking on the table row. The user can also use the search field to narrow down the scope.
@ -138,16 +138,16 @@ Issuer Nodes
.. image:: resources/explorer/newTransactionIssuer.png .. image:: resources/explorer/newTransactionIssuer.png
Transactions Transactions
The transaction view contains all transactions handled by the node in a table view. It shows basic information on the table e.g. Transaction ID, The transaction view contains all transactions handled by the node in a table view. It shows basic information on the table e.g. Transaction ID,
command type, USD equivalence value etc. User can expand the row by double clicking to view the inputs, command type, USD equivalence value etc. User can expand the row by double clicking to view the inputs,
outputs and the signatures details for that transaction. outputs and the signatures details for that transaction.
.. image:: resources/explorer/transactionView.png .. image:: resources/explorer/transactionView.png
Network Network
The network view shows the network information on the world map. Currently only the user's node is rendered on the map. The network view shows the network information on the world map. Currently only the user's node is rendered on the map.
This will be extended to other peers in a future release. This will be extended to other peers in a future release.
The map provides an intuitive way of visualizing the Corda network and the participants. The map provides an intuitive way of visualizing the Corda network and the participants.
.. image:: resources/explorer/network.png .. image:: resources/explorer/network.png

View File

@ -3,9 +3,10 @@
Writing a custom notary service (experimental) Writing a custom notary service (experimental)
============================================== ==============================================
.. warning:: Customising a notary service is still an experimental feature and not recommended for most use-cases. Currently, .. warning:: Customising a notary service is still an experimental feature and not recommended for most use-cases. The APIs
customising Raft or BFT notaries is not yet fully supported. If you want to write your own Raft notary you will have to for writing a custom notary may change in the future. Additionally, customising Raft or BFT notaries is not yet
implement a custom database connector (or use a separate database for the notary), and use a custom configuration file. fully supported. If you want to write your own Raft notary you will have to implement a custom database connector
(or use a separate database for the notary), and use a custom configuration file.
Similarly to writing an oracle service, the first step is to create a service class in your CorDapp and annotate it Similarly to writing an oracle service, the first step is to create a service class in your CorDapp and annotate it
with ``@CordaService``. The Corda node scans for any class with this annotation and initialises them. The custom notary with ``@CordaService``. The Corda node scans for any class with this annotation and initialises them. The custom notary

View File

@ -27,26 +27,37 @@ sealed class ConnectionDirection {
) : ConnectionDirection() ) : ConnectionDirection()
} }
/** Class to set Artemis TCP configuration options. */
class ArtemisTcpTransport { class ArtemisTcpTransport {
companion object { companion object {
const val VERIFY_PEER_LEGAL_NAME = "corda.verifyPeerCommonName" const val VERIFY_PEER_LEGAL_NAME = "corda.verifyPeerCommonName"
// Restrict enabled TLS cipher suites to: /**
// AES128 using Galois/Counter Mode (GCM) for the block cipher being used to encrypt the message stream. * Corda supported TLS schemes.
// SHA256 as message authentication algorithm. * <p><ul>
// ECDHE as key exchange algorithm. DHE is also supported if one wants to completely avoid the use of ECC for TLS. * <li>TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
// ECDSA and RSA for digital signatures. Our self-generated certificates all use ECDSA for handshakes, * <li>TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
// but we allow classical RSA certificates to work in case: * <li>TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
// a) we need to use keytool certificates in some demos, * </ul></p>
// b) we use cloud providers or HSMs that do not support ECC. * As shown above, current version restricts enabled TLS cipher suites to:
* AES128 using Galois/Counter Mode (GCM) for the block cipher being used to encrypt the message stream.
* SHA256 as message authentication algorithm.
* Ephemeral Diffie Hellman key exchange for advanced forward secrecy. ECDHE is preferred, but DHE is also
* supported in case one wants to completely avoid the use of ECC for TLS.
* ECDSA and RSA for digital signatures. Our self-generated certificates all use ECDSA for handshakes,
* but we allow classical RSA certificates to work in case one uses external tools or cloud providers or HSMs
* that do not support ECC certificates.
*/
val CIPHER_SUITES = listOf( val CIPHER_SUITES = listOf(
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_DHE_RSA_WITH_AES_128_GCM_SHA256" "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256"
) )
/** Supported TLS versions, currently TLSv1.2 only. */
val TLS_VERSIONS = listOf("TLSv1.2") val TLS_VERSIONS = listOf("TLSv1.2")
/** Specify [TransportConfiguration] for TCP communication. */
fun tcpTransport( fun tcpTransport(
direction: ConnectionDirection, direction: ConnectionDirection,
hostAndPort: NetworkHostAndPort, hostAndPort: NetworkHostAndPort,

View File

@ -41,13 +41,6 @@ class ArtemisMessagingComponent {
const val BRIDGE_NOTIFY = "${INTERNAL_PREFIX}bridge.notify" const val BRIDGE_NOTIFY = "${INTERNAL_PREFIX}bridge.notify"
const val NOTIFICATIONS_ADDRESS = "${INTERNAL_PREFIX}activemq.notifications" const val NOTIFICATIONS_ADDRESS = "${INTERNAL_PREFIX}activemq.notifications"
/**
* In the operation mode where we have an out of process bridge we cannot correctly populate the Artemis validated user header
* as the TLS does not terminate directly onto Artemis. We therefore use this internal only header to forward
* the equivalent information from the Float.
*/
val bridgedCertificateSubject = SimpleString("sender-subject-name")
object P2PMessagingHeaders { object P2PMessagingHeaders {
// This is a "property" attached to an Artemis MQ message object, which contains our own notion of "topic". // This is a "property" attached to an Artemis MQ message object, which contains our own notion of "topic".
// We should probably try to unify our notion of "topic" (really, just a string that identifies an endpoint // We should probably try to unify our notion of "topic" (really, just a string that identifies an endpoint

View File

@ -54,6 +54,10 @@ data class ParametersUpdate(
val updateDeadline: Instant val updateDeadline: Instant
) )
/** Verify that a Network Map certificate is issued by Root CA and its [CertRole] is correct. */
// TODO: Current implementation works under the assumption that there are no intermediate CAs between Root and
// Network Map. Consider a more flexible implementation without the above assumption.
fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCert: X509Certificate): T { fun <T : Any> SignedDataWithCert<T>.verifiedNetworkMapCert(rootCert: X509Certificate): T {
require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" } require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" }
X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert) X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert)

View File

@ -44,7 +44,12 @@ class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() {
// is: https://youtrack.jetbrains.com/issue/KT-13077 // is: https://youtrack.jetbrains.com/issue/KT-13077
// TODO: Revisit this when Kotlin issue is fixed. // TODO: Revisit this when Kotlin issue is fixed.
loggerFor<PropertySerializer>().error("Unexpected internal Kotlin error", e) // So this used to report as an error, but given we serialise exceptions all the time it
// provides for very scary log files so move this to trace level
loggerFor<PropertySerializer>().let { logger ->
logger.trace("Using kotlin introspection on internal type ${this.declaringClass}")
logger.trace("Unexpected internal Kotlin error", e)
}
return true return true
} }
} }
@ -80,7 +85,13 @@ class PrivatePropertyReader(val field: Field, parentType: Type) : PropertyReader
// This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue // This might happen for some types, e.g. kotlin.Throwable? - the root cause of the issue
// is: https://youtrack.jetbrains.com/issue/KT-13077 // is: https://youtrack.jetbrains.com/issue/KT-13077
// TODO: Revisit this when Kotlin issue is fixed. // TODO: Revisit this when Kotlin issue is fixed.
loggerFor<PropertySerializer>().error("Unexpected internal Kotlin error", e)
// So this used to report as an error, but given we serialise exceptions all the time it
// provides for very scary log files so move this to trace level
loggerFor<PropertySerializer>().let { logger ->
logger.trace("Using kotlin introspection on internal type ${field}")
logger.trace("Unexpected internal Kotlin error", e)
}
true true
} }
} }

View File

@ -32,10 +32,7 @@ import net.corda.core.serialization.MissingAttachmentsException
import net.corda.core.serialization.SerializationWhitelist import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.transactions.ContractUpgradeWireTransaction import net.corda.core.transactions.*
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.toNonEmptySet
import net.corda.nodeapi.internal.serialization.CordaClassResolver import net.corda.nodeapi.internal.serialization.CordaClassResolver
@ -139,6 +136,7 @@ object DefaultKryoCustomizer {
register(java.lang.invoke.SerializedLambda::class.java) register(java.lang.invoke.SerializedLambda::class.java)
register(ClosureSerializer.Closure::class.java, CordaClosureBlacklistSerializer) register(ClosureSerializer.Closure::class.java, CordaClosureBlacklistSerializer)
register(ContractUpgradeWireTransaction::class.java, ContractUpgradeWireTransactionSerializer) register(ContractUpgradeWireTransaction::class.java, ContractUpgradeWireTransactionSerializer)
register(ContractUpgradeFilteredTransaction::class.java, ContractUpgradeFilteredTransactionSerializer)
for (whitelistProvider in serializationWhitelists) { for (whitelistProvider in serializationWhitelists) {
val types = whitelistProvider.whitelist val types = whitelistProvider.whitelist

View File

@ -18,14 +18,10 @@ import com.esotericsoftware.kryo.serializers.CompatibleFieldSerializer
import com.esotericsoftware.kryo.serializers.FieldSerializer import com.esotericsoftware.kryo.serializers.FieldSerializer
import com.esotericsoftware.kryo.util.MapReferenceResolver import com.esotericsoftware.kryo.util.MapReferenceResolver
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PrivacySalt import net.corda.core.contracts.PrivacySalt
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.identity.Party
import net.corda.core.internal.uncheckedCast import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.SerializationContext import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint import net.corda.core.serialization.SerializationContext.UseCase.Checkpoint
@ -35,6 +31,7 @@ import net.corda.core.serialization.SerializedBytes
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.toObservable import net.corda.core.toObservable
import net.corda.core.transactions.* import net.corda.core.transactions.*
import net.corda.core.utilities.OpaqueBytes
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.core.utilities.SgxSupport import net.corda.core.utilities.SgxSupport
import net.corda.nodeapi.internal.serialization.CordaClassResolver import net.corda.nodeapi.internal.serialization.CordaClassResolver
@ -268,40 +265,41 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
@ThreadSafe @ThreadSafe
object NotaryChangeWireTransactionSerializer : Serializer<NotaryChangeWireTransaction>() { object NotaryChangeWireTransactionSerializer : Serializer<NotaryChangeWireTransaction>() {
override fun write(kryo: Kryo, output: Output, obj: NotaryChangeWireTransaction) { override fun write(kryo: Kryo, output: Output, obj: NotaryChangeWireTransaction) {
kryo.writeClassAndObject(output, obj.inputs) kryo.writeClassAndObject(output, obj.serializedComponents)
kryo.writeClassAndObject(output, obj.notary)
kryo.writeClassAndObject(output, obj.newNotary)
} }
override fun read(kryo: Kryo, input: Input, type: Class<NotaryChangeWireTransaction>): NotaryChangeWireTransaction { override fun read(kryo: Kryo, input: Input, type: Class<NotaryChangeWireTransaction>): NotaryChangeWireTransaction {
val inputs: List<StateRef> = uncheckedCast(kryo.readClassAndObject(input)) val components : List<OpaqueBytes> = uncheckedCast(kryo.readClassAndObject(input))
val notary = kryo.readClassAndObject(input) as Party return NotaryChangeWireTransaction(components)
val newNotary = kryo.readClassAndObject(input) as Party
return NotaryChangeWireTransaction(inputs, notary, newNotary)
} }
} }
@ThreadSafe @ThreadSafe
object ContractUpgradeWireTransactionSerializer : Serializer<ContractUpgradeWireTransaction>() { object ContractUpgradeWireTransactionSerializer : Serializer<ContractUpgradeWireTransaction>() {
override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeWireTransaction) { override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeWireTransaction) {
kryo.writeClassAndObject(output, obj.inputs) kryo.writeClassAndObject(output, obj.serializedComponents)
kryo.writeClassAndObject(output, obj.notary)
kryo.writeClassAndObject(output, obj.legacyContractAttachmentId)
kryo.writeClassAndObject(output, obj.upgradeContractClassName)
kryo.writeClassAndObject(output, obj.upgradedContractAttachmentId)
kryo.writeClassAndObject(output, obj.privacySalt) kryo.writeClassAndObject(output, obj.privacySalt)
} }
override fun read(kryo: Kryo, input: Input, type: Class<ContractUpgradeWireTransaction>): ContractUpgradeWireTransaction { override fun read(kryo: Kryo, input: Input, type: Class<ContractUpgradeWireTransaction>): ContractUpgradeWireTransaction {
val inputs: List<StateRef> = uncheckedCast(kryo.readClassAndObject(input)) val components: List<OpaqueBytes> = uncheckedCast(kryo.readClassAndObject(input))
val notary = kryo.readClassAndObject(input) as Party
val legacyContractAttachment = kryo.readClassAndObject(input) as SecureHash
val upgradeContractClassName = kryo.readClassAndObject(input) as String
val upgradedContractAttachment = kryo.readClassAndObject(input) as SecureHash
val privacySalt = kryo.readClassAndObject(input) as PrivacySalt val privacySalt = kryo.readClassAndObject(input) as PrivacySalt
return ContractUpgradeWireTransaction(inputs, notary, legacyContractAttachment, upgradeContractClassName, upgradedContractAttachment, privacySalt) return ContractUpgradeWireTransaction(components, privacySalt)
}
}
@ThreadSafe
object ContractUpgradeFilteredTransactionSerializer : Serializer<ContractUpgradeFilteredTransaction>() {
override fun write(kryo: Kryo, output: Output, obj: ContractUpgradeFilteredTransaction) {
kryo.writeClassAndObject(output, obj.visibleComponents)
kryo.writeClassAndObject(output, obj.hiddenComponents)
}
override fun read(kryo: Kryo, input: Input, type: Class<ContractUpgradeFilteredTransaction>): ContractUpgradeFilteredTransaction {
val visibleComponents: Map<Int, ContractUpgradeFilteredTransaction.FilteredComponent> = uncheckedCast(kryo.readClassAndObject(input))
val hiddenComponents: Map<Int, SecureHash> = uncheckedCast(kryo.readClassAndObject(input))
return ContractUpgradeFilteredTransaction(visibleComponents, hiddenComponents)
} }
} }

View File

@ -15,6 +15,7 @@ package net.corda.nodeapi.internal.serialization.amqp
import com.nhaarman.mockito_kotlin.doReturn import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.client.rpc.RPCException import net.corda.client.rpc.RPCException
import net.corda.core.CordaException
import net.corda.core.CordaRuntimeException import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -1187,5 +1188,13 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
PrivateAckWrapper.serialize() PrivateAckWrapper.serialize()
} }
@Test
fun throwable() {
class TestException(message: String?, cause: Throwable?) : CordaException(message, cause)
val testExcp = TestException("hello", Throwable().apply { stackTrace = Thread.currentThread().stackTrace } )
val factory = testDefaultFactoryNoEvolution()
SerializationOutput(factory).serialize(testExcp)
}
} }

View File

@ -16,6 +16,7 @@ import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint
import net.corda.core.contracts.ContractState import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryError import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow import net.corda.core.flows.NotaryFlow
@ -162,13 +163,12 @@ class BFTNotaryServiceTests : IntegrationTest() {
}.single() }.single()
spendTxs.zip(results).forEach { (tx, result) -> spendTxs.zip(results).forEach { (tx, result) ->
if (result is Try.Failure) { if (result is Try.Failure) {
val error = (result.exception as NotaryException).error as NotaryError.Conflict val exception = result.exception as NotaryException
val error = exception.error as NotaryError.Conflict
assertEquals(tx.id, error.txId) assertEquals(tx.id, error.txId)
val (stateRef, consumingTx) = error.conflict.verified().stateHistory.entries.single() val (stateRef, cause) = error.consumedStates.entries.single()
assertEquals(StateRef(issueTx.id, 0), stateRef) assertEquals(StateRef(issueTx.id, 0), stateRef)
assertEquals(spendTxs[successfulIndex].id, consumingTx.id) assertEquals(spendTxs[successfulIndex].id.sha256(), cause.hashOfTransactionId)
assertEquals(0, consumingTx.inputIndex)
assertEquals(info.singleIdentity(), consumingTx.requestingParty)
} }
} }
} }

View File

@ -14,16 +14,11 @@ import co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.SettableFuture
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic import net.corda.core.crypto.SignedData
import net.corda.core.flows.FlowSession import net.corda.core.flows.*
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotarisationRequest
import net.corda.core.node.services.NotaryService import net.corda.core.node.services.NotaryService
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
@ -91,18 +86,22 @@ class BFTNonValidatingNotaryService(
@Suspendable @Suspendable
override fun call(): Void? { override fun call(): Void? {
val payload = otherSideSession.receive<NotarisationPayload>().unwrap { it } val payload = otherSideSession.receive<NotarisationPayload>().unwrap { it }
val signatures = commit(payload) val response = commit(payload)
otherSideSession.send(signatures) otherSideSession.send(response)
return null return null
} }
private fun commit(payload: NotarisationPayload): List<DigitalSignature> { private fun commit(payload: NotarisationPayload): NotarisationResponse {
val response = service.commitTransaction(payload, otherSideSession.counterparty) val response = service.commitTransaction(payload, otherSideSession.counterparty)
when (response) { when (response) {
is BFTSMaRt.ClusterResponse.Error -> throw NotaryException(response.error) is BFTSMaRt.ClusterResponse.Error -> {
// TODO: here we assume that all error will be the same, but there might be invalid onces from mailicious nodes
val responseError = response.errors.first().verified()
throw NotaryException(responseError, payload.coreTransaction.id)
}
is BFTSMaRt.ClusterResponse.Signatures -> { is BFTSMaRt.ClusterResponse.Signatures -> {
log.debug("All input states of transaction ${payload.coreTransaction.id} have been committed") log.debug("All input states of transaction ${payload.coreTransaction.id} have been committed")
return response.txSignatures return NotarisationResponse(response.txSignatures)
} }
} }
} }
@ -160,13 +159,16 @@ class BFTNonValidatingNotaryService(
val inputs = transaction.inputs val inputs = transaction.inputs
val notary = transaction.notary val notary = transaction.notary
if (transaction is FilteredTransaction) NotaryService.validateTimeWindow(services.clock, transaction.timeWindow) if (transaction is FilteredTransaction) NotaryService.validateTimeWindow(services.clock, transaction.timeWindow)
if (notary !in services.myInfo.legalIdentities) throw NotaryException(NotaryError.WrongNotary) if (notary !in services.myInfo.legalIdentities) throw NotaryInternalException(NotaryError.WrongNotary)
commitInputStates(inputs, id, callerIdentity) commitInputStates(inputs, id, callerIdentity)
log.debug { "Inputs committed successfully, signing $id" } log.debug { "Inputs committed successfully, signing $id" }
BFTSMaRt.ReplicaResponse.Signature(sign(id)) BFTSMaRt.ReplicaResponse.Signature(sign(id))
} catch (e: NotaryException) { } catch (e: NotaryInternalException) {
log.debug { "Error processing transaction: ${e.error}" } log.debug { "Error processing transaction: ${e.error}" }
BFTSMaRt.ReplicaResponse.Error(e.error) val serializedError = e.error.serialize()
val errorSignature = sign(serializedError.bytes)
val signedError = SignedData(serializedError, errorSignature)
BFTSMaRt.ReplicaResponse.Error(signedError)
} }
} }

View File

@ -24,10 +24,8 @@ import bftsmart.tom.server.defaultservices.DefaultReplier
import bftsmart.tom.util.Extractor import bftsmart.tom.util.Extractor
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.* import net.corda.core.crypto.*
import net.corda.core.flows.NotaryError import net.corda.core.flows.*
import net.corda.core.flows.NotaryException
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.flows.NotarisationPayload
import net.corda.core.internal.declaredField import net.corda.core.internal.declaredField
import net.corda.core.internal.toTypedArray import net.corda.core.internal.toTypedArray
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
@ -66,15 +64,15 @@ object BFTSMaRt {
/** Sent from [Replica] to [Client]. */ /** Sent from [Replica] to [Client]. */
@CordaSerializable @CordaSerializable
sealed class ReplicaResponse { sealed class ReplicaResponse {
data class Error(val error: NotaryError) : ReplicaResponse() data class Error(val error: SignedData<NotaryError>) : ReplicaResponse()
data class Signature(val txSignature: DigitalSignature) : ReplicaResponse() data class Signature(val txSignature: TransactionSignature) : ReplicaResponse()
} }
/** An aggregate response from all replica ([Replica]) replies sent from [Client] back to the calling application. */ /** An aggregate response from all replica ([Replica]) replies sent from [Client] back to the calling application. */
@CordaSerializable @CordaSerializable
sealed class ClusterResponse { sealed class ClusterResponse {
data class Error(val error: NotaryError) : ClusterResponse() data class Error(val errors: List<SignedData<NotaryError>>) : ClusterResponse()
data class Signatures(val txSignatures: List<DigitalSignature>) : ClusterResponse() data class Signatures(val txSignatures: List<TransactionSignature>) : ClusterResponse()
} }
interface Cluster { interface Cluster {
@ -146,7 +144,7 @@ object BFTSMaRt {
ClusterResponse.Signatures(accepted.map { it.txSignature }) ClusterResponse.Signatures(accepted.map { it.txSignature })
} else { } else {
log.debug { "Cluster response - error: ${rejected.first().error}" } log.debug { "Cluster response - error: ${rejected.first().error}" }
ClusterResponse.Error(rejected.first().error) ClusterResponse.Error(rejected.map { it.error })
} }
val messageContent = aggregateResponse.serialize().bytes val messageContent = aggregateResponse.serialize().bytes
@ -242,10 +240,9 @@ object BFTSMaRt {
} }
} else { } else {
log.debug { "Conflict detected the following inputs have already been committed: ${conflicts.keys.joinToString()}" } log.debug { "Conflict detected the following inputs have already been committed: ${conflicts.keys.joinToString()}" }
val conflict = UniquenessProvider.Conflict(conflicts) val conflict = conflicts.mapValues { StateConsumptionDetails(it.value.id.sha256()) }
val conflictData = conflict.serialize() val error = NotaryError.Conflict(txId, conflict)
val signedConflict = SignedData(conflictData, sign(conflictData.bytes)) throw NotaryInternalException(error)
throw NotaryException(NotaryError.Conflict(txId, signedConflict))
} }
} }
} }

View File

@ -17,20 +17,19 @@ import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource import com.zaxxer.hikari.HikariDataSource
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryInternalException
import net.corda.core.flows.StateConsumptionDetails
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.UniquenessException
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.services.config.MySQLConfiguration import net.corda.node.services.config.MySQLConfiguration
import java.security.PublicKey
import java.sql.BatchUpdateException import java.sql.BatchUpdateException
import java.sql.Connection import java.sql.Connection
import java.sql.SQLTransientConnectionException import java.sql.SQLTransientConnectionException
import java.util.*
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
/** /**
@ -117,7 +116,7 @@ class MySQLUniquenessProvider(
} catch (e: BatchUpdateException) { } catch (e: BatchUpdateException) {
log.info("Unable to commit input states, finding conflicts, txId: $txId", e) log.info("Unable to commit input states, finding conflicts, txId: $txId", e)
conflictCounter.inc() conflictCounter.inc()
retryTransaction(FindConflicts(states)) retryTransaction(FindConflicts(txId, states))
} finally { } finally {
val dt = s.stop().elapsed(TimeUnit.MILLISECONDS) val dt = s.stop().elapsed(TimeUnit.MILLISECONDS)
commitTimer.update(dt, TimeUnit.MILLISECONDS) commitTimer.update(dt, TimeUnit.MILLISECONDS)
@ -173,26 +172,22 @@ class MySQLUniquenessProvider(
} }
} }
private class FindConflicts(val states: List<StateRef>) : RetryableTransaction { private class FindConflicts(val txId: SecureHash, val states: List<StateRef>) : RetryableTransaction {
override fun run(conn: Connection) { override fun run(conn: Connection) {
val conflicts = mutableMapOf<StateRef, UniquenessProvider.ConsumingTx>() val conflicts = mutableMapOf<StateRef, StateConsumptionDetails>()
states.forEach { states.forEach {
val st = conn.prepareStatement(findStatement).apply { val st = conn.prepareStatement(findStatement).apply {
setBytes(1, it.txhash.bytes) setBytes(1, it.txhash.bytes)
setInt(2, it.index) setInt(2, it.index)
} }
val result = st.executeQuery() val result = st.executeQuery()
if (result.next()) { if (result.next()) {
val consumingTxId = SecureHash.SHA256(result.getBytes(1)) val consumingTxId = SecureHash.SHA256(result.getBytes(1))
val inputIndex = result.getInt(2) conflicts[it] = StateConsumptionDetails(consumingTxId.sha256())
val partyName = CordaX500Name.parse(result.getString(3))
val partyKey: PublicKey = result.getBytes(4).deserialize()
conflicts[it] = UniquenessProvider.ConsumingTx(consumingTxId, inputIndex, Party(partyName, partyKey))
} }
} }
conn.commit() conn.commit()
if (conflicts.isNotEmpty()) throw UniquenessException(UniquenessProvider.Conflict(conflicts)) if (conflicts.isNotEmpty()) throw NotaryInternalException(NotaryError.Conflict(txId, conflicts))
} }
} }
} }

View File

@ -13,10 +13,13 @@ package net.corda.node.services.transactions
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryInternalException
import net.corda.core.flows.StateConsumptionDetails
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.node.services.UniquenessException
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
@ -78,8 +81,9 @@ class PersistentUniquenessProvider : UniquenessProvider, SingletonSerializeAsTok
toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) }, toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) },
fromPersistentEntity = { fromPersistentEntity = {
//TODO null check will become obsolete after making DB/JPA columns not nullable //TODO null check will become obsolete after making DB/JPA columns not nullable
var txId = it.id.txId ?: throw IllegalStateException("DB returned null SecureHash transactionId") val txId = it.id.txId
var index = it.id.index ?: throw IllegalStateException("DB returned null SecureHash index") ?: throw IllegalStateException("DB returned null SecureHash transactionId")
val index = it.id.index ?: throw IllegalStateException("DB returned null SecureHash index")
Pair(StateRef(txhash = SecureHash.parse(txId), index = index), Pair(StateRef(txhash = SecureHash.parse(txId), index = index),
UniquenessProvider.ConsumingTx( UniquenessProvider.ConsumingTx(
id = SecureHash.parse(it.consumingTxHash), id = SecureHash.parse(it.consumingTxHash),
@ -101,7 +105,6 @@ class PersistentUniquenessProvider : UniquenessProvider, SingletonSerializeAsTok
} }
override fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party) { override fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party) {
val conflict = mutex.locked { val conflict = mutex.locked {
val conflictingStates = LinkedHashMap<StateRef, UniquenessProvider.ConsumingTx>() val conflictingStates = LinkedHashMap<StateRef, UniquenessProvider.ConsumingTx>()
for (inputState in states) { for (inputState in states) {
@ -110,7 +113,8 @@ class PersistentUniquenessProvider : UniquenessProvider, SingletonSerializeAsTok
} }
if (conflictingStates.isNotEmpty()) { if (conflictingStates.isNotEmpty()) {
log.debug("Failure, input states already committed: ${conflictingStates.keys}") log.debug("Failure, input states already committed: ${conflictingStates.keys}")
UniquenessProvider.Conflict(conflictingStates) val conflict = conflictingStates.mapValues { StateConsumptionDetails(it.value.id.sha256()) }
conflict
} else { } else {
states.forEachIndexed { i, stateRef -> states.forEachIndexed { i, stateRef ->
committedStates[stateRef] = UniquenessProvider.ConsumingTx(txId, i, callerIdentity) committedStates[stateRef] = UniquenessProvider.ConsumingTx(txId, i, callerIdentity)
@ -120,6 +124,6 @@ class PersistentUniquenessProvider : UniquenessProvider, SingletonSerializeAsTok
} }
} }
if (conflict != null) throw UniquenessException(conflict) if (conflict != null) throw NotaryInternalException(NotaryError.Conflict(txId, conflict))
} }
} }

View File

@ -29,8 +29,11 @@ import io.atomix.copycat.server.storage.Storage
import io.atomix.copycat.server.storage.StorageLevel import io.atomix.copycat.server.storage.StorageLevel
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryInternalException
import net.corda.core.flows.StateConsumptionDetails
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.UniquenessException
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
@ -214,7 +217,11 @@ class RaftUniquenessProvider(private val transportConfiguration: NodeSSLConfigur
val commitCommand = DistributedImmutableMap.Commands.PutAll(encode(entries)) val commitCommand = DistributedImmutableMap.Commands.PutAll(encode(entries))
val conflicts = client.submit(commitCommand).get() val conflicts = client.submit(commitCommand).get()
if (conflicts.isNotEmpty()) throw UniquenessException(UniquenessProvider.Conflict(decode(conflicts))) if (conflicts.isNotEmpty()) {
val conflictingStates = decode(conflicts).mapValues { StateConsumptionDetails(it.value.id.sha256()) }
val error = NotaryError.Conflict(txId, conflictingStates)
throw NotaryInternalException(error)
}
log.debug("All input states of transaction $txId have been committed") log.debug("All input states of transaction $txId have been committed")
} }

View File

@ -47,7 +47,7 @@ class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthor
} catch (e: Exception) { } catch (e: Exception) {
throw when (e) { throw when (e) {
is TransactionVerificationException, is TransactionVerificationException,
is SignatureException -> NotaryException(NotaryError.TransactionInvalid(e)) is SignatureException -> NotaryInternalException(NotaryError.TransactionInvalid(e))
else -> e else -> e
} }
} }
@ -77,7 +77,7 @@ class ValidatingNotaryFlow(otherSideSession: FlowSession, service: TrustedAuthor
try { try {
tx.verifySignaturesExcept(service.notaryIdentityKey) tx.verifySignaturesExcept(service.notaryIdentityKey)
} catch (e: SignatureException) { } catch (e: SignatureException) {
throw NotaryException(NotaryError.TransactionInvalid(e)) throw NotaryInternalException(NotaryError.TransactionInvalid(e))
} }
} }
} }

View File

@ -13,10 +13,7 @@ package net.corda.node.services.transactions
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.crypto.Crypto import net.corda.core.crypto.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.sign
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.generateSignature import net.corda.core.internal.generateSignature
@ -147,32 +144,45 @@ class NotaryServiceTests {
@Test @Test
fun `should report conflict when inputs are reused across transactions`() { fun `should report conflict when inputs are reused across transactions`() {
val inputState = issueState(aliceNode.services, alice) val firstState = issueState(aliceNode.services, alice)
val stx = run { val secondState = issueState(aliceNode.services, alice)
val tx = TransactionBuilder(notary)
.addInputState(inputState) fun spendState(state: StateAndRef<*>): SignedTransaction {
.addCommand(dummyCommand(alice.owningKey)) val stx = run {
aliceNode.services.signInitialTransaction(tx) val tx = TransactionBuilder(notary)
.addInputState(state)
.addCommand(dummyCommand(alice.owningKey))
aliceNode.services.signInitialTransaction(tx)
}
aliceNode.services.startFlow(NotaryFlow.Client(stx))
mockNet.runNetwork()
return stx
} }
val stx2 = run {
val firstSpendTx = spendState(firstState)
val secondSpendTx = spendState(secondState)
val doubleSpendTx = run {
val tx = TransactionBuilder(notary) val tx = TransactionBuilder(notary)
.addInputState(inputState)
.addInputState(issueState(aliceNode.services, alice)) .addInputState(issueState(aliceNode.services, alice))
.addInputState(firstState)
.addInputState(secondState)
.addCommand(dummyCommand(alice.owningKey)) .addCommand(dummyCommand(alice.owningKey))
aliceNode.services.signInitialTransaction(tx) aliceNode.services.signInitialTransaction(tx)
} }
val firstSpend = NotaryFlow.Client(stx) val doubleSpend = NotaryFlow.Client(doubleSpendTx) // Double spend the inputState in a second transaction.
val secondSpend = NotaryFlow.Client(stx2) // Double spend the inputState in a second transaction. val future = aliceNode.services.startFlow(doubleSpend)
aliceNode.services.startFlow(firstSpend)
val future = aliceNode.services.startFlow(secondSpend)
mockNet.runNetwork() mockNet.runNetwork()
val ex = assertFailsWith(NotaryException::class) { future.resultFuture.getOrThrow() } val ex = assertFailsWith(NotaryException::class) { future.resultFuture.getOrThrow() }
val notaryError = ex.error as NotaryError.Conflict val notaryError = ex.error as NotaryError.Conflict
assertEquals(notaryError.txId, stx2.id) assertEquals(notaryError.txId, doubleSpendTx.id)
notaryError.conflict.verified() with(notaryError) {
assertEquals(consumedStates.size, 2)
assertEquals(consumedStates[firstState.ref]!!.hashOfTransactionId, firstSpendTx.id.sha256())
assertEquals(consumedStates[secondState.ref]!!.hashOfTransactionId, secondSpendTx.id.sha256())
}
} }
@Test @Test

View File

@ -11,8 +11,10 @@
package net.corda.node.services.transactions package net.corda.node.services.transactions
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryInternalException
import net.corda.core.flows.NotaryError
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.node.services.UniquenessException
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -70,12 +72,11 @@ class PersistentUniquenessProviderTests {
val inputs = listOf(inputState) val inputs = listOf(inputState)
provider.commit(inputs, txID, identity) provider.commit(inputs, txID, identity)
val ex = assertFailsWith<UniquenessException> { provider.commit(inputs, txID, identity) } val ex = assertFailsWith<NotaryInternalException> { provider.commit(inputs, txID, identity) }
val error = ex.error as NotaryError.Conflict
val consumingTx = ex.error.stateHistory[inputState]!! val conflictCause = error.consumedStates[inputState]!!
assertEquals(consumingTx.id, txID) assertEquals(conflictCause.hashOfTransactionId, txID.sha256())
assertEquals(consumingTx.inputIndex, inputs.indexOf(inputState))
assertEquals(consumingTx.requestingParty, identity)
} }
} }
} }

View File

@ -24,7 +24,6 @@ import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.issueInvalidState import net.corda.node.services.issueInvalidState
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME import net.corda.testing.core.ALICE_NAME
@ -90,14 +89,13 @@ class ValidatingNotaryServiceTests {
aliceNode.services.signInitialTransaction(tx) aliceNode.services.signInitialTransaction(tx)
} }
val ex = assertFailsWith(NotaryException::class) { // Expecting SignaturesMissingException instead of NotaryException, since the exception should originate from
// the client flow.
val ex = assertFailsWith<SignedTransaction.SignaturesMissingException> {
val future = runClient(stx) val future = runClient(stx)
future.getOrThrow() future.getOrThrow()
} }
val notaryError = ex.error as NotaryError.TransactionInvalid val missingKeys = ex.missing
assertThat(notaryError.cause).isInstanceOf(SignedTransaction.SignaturesMissingException::class.java)
val missingKeys = (notaryError.cause as SignedTransaction.SignaturesMissingException).missing
assertEquals(setOf(expectedMissingKey), missingKeys) assertEquals(setOf(expectedMissingKey), missingKeys)
} }

View File

@ -14,10 +14,14 @@ import co.paralleluniverse.fibers.Suspendable
import com.nhaarman.mockito_kotlin.argThat import com.nhaarman.mockito_kotlin.argThat
import com.nhaarman.mockito_kotlin.doNothing import com.nhaarman.mockito_kotlin.doNothing
import com.nhaarman.mockito_kotlin.whenever import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.* import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.NullKeys import net.corda.core.crypto.NullKeys
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.* import net.corda.core.identity.*
import net.corda.core.internal.NotaryChangeTransactionBuilder
import net.corda.core.internal.packageName import net.corda.core.internal.packageName
import net.corda.core.node.StatesToRecord import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.StatesNotAvailableException import net.corda.core.node.services.StatesNotAvailableException
@ -27,7 +31,6 @@ import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.QueryCriteria.* import net.corda.core.node.services.vault.QueryCriteria.*
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.NonEmptySet
@ -617,7 +620,7 @@ class NodeVaultServiceTest {
// Change notary // Change notary
services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY) services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY)
val newNotary = DUMMY_NOTARY val newNotary = DUMMY_NOTARY
val changeNotaryTx = NotaryChangeWireTransaction(listOf(initialCashState.ref), issueStx.notary!!, newNotary) val changeNotaryTx = NotaryChangeTransactionBuilder(listOf(initialCashState.ref), issueStx.notary!!, newNotary).build()
val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0)) val cashStateWithNewNotary = StateAndRef(initialCashState.state.copy(notary = newNotary), StateRef(changeNotaryTx.id, 0))
database.transaction { database.transaction {

View File

@ -14,14 +14,11 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.TimeWindow import net.corda.core.contracts.TimeWindow
import net.corda.core.contracts.TransactionVerificationException import net.corda.core.contracts.TransactionVerificationException
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotarisationRequest
import net.corda.core.internal.ResolveTransactionsFlow import net.corda.core.internal.ResolveTransactionsFlow
import net.corda.core.internal.validateRequest import net.corda.core.internal.validateRequest
import net.corda.core.node.AppServiceHub import net.corda.core.node.AppServiceHub
import net.corda.core.node.services.CordaService import net.corda.core.node.services.CordaService
import net.corda.core.node.services.TrustedAuthorityNotaryService import net.corda.core.node.services.TrustedAuthorityNotaryService
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionWithSignatures import net.corda.core.transactions.TransactionWithSignatures
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
@ -68,7 +65,7 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
} catch (e: Exception) { } catch (e: Exception) {
throw when (e) { throw when (e) {
is TransactionVerificationException, is TransactionVerificationException,
is SignatureException -> NotaryException(NotaryError.TransactionInvalid(e)) is SignatureException -> NotaryInternalException(NotaryError.TransactionInvalid(e))
else -> e else -> e
} }
} }
@ -99,7 +96,7 @@ class MyValidatingNotaryFlow(otherSide: FlowSession, service: MyCustomValidating
try { try {
tx.verifySignaturesExcept(service.notaryIdentityKey) tx.verifySignaturesExcept(service.notaryIdentityKey)
} catch (e: SignatureException) { } catch (e: SignatureException) {
throw NotaryException(NotaryError.TransactionInvalid(e)) throw NotaryInternalException(NotaryError.TransactionInvalid(e))
} }
} }

View File

@ -21,6 +21,7 @@ import javafx.collections.ObservableList
import javafx.geometry.Insets import javafx.geometry.Insets
import javafx.scene.Parent import javafx.scene.Parent
import javafx.scene.chart.NumberAxis import javafx.scene.chart.NumberAxis
import javafx.scene.chart.XYChart
import javafx.scene.control.* import javafx.scene.control.*
import javafx.scene.input.MouseButton import javafx.scene.input.MouseButton
import javafx.scene.layout.BorderPane import javafx.scene.layout.BorderPane
@ -323,12 +324,26 @@ class CashViewer : CordaView("Cash") {
linechart(null, xAxis, yAxis) { linechart(null, xAxis, yAxis) {
series("USD") { series("USD") {
sumAmount.addListener { _, _, _ -> sumAmount.addListener { _, _, _ ->
val lastAmount = data.last().value?.yValue
val currAmount = sumAmount.value.toDecimal()
val lastTimeStamp = data.last().value?.xValue val lastTimeStamp = data.last().value?.xValue
if (lastTimeStamp == null || System.currentTimeMillis() - lastTimeStamp.toLong() > 1.seconds.toMillis()) { val currentTimeStamp = System.currentTimeMillis()
data(System.currentTimeMillis(), sumAmount.value.quantity)
runInFxApplicationThread { // If amount is not the same - always add a data point.
// Modify data in UI thread. if (lastAmount == null || lastAmount != currAmount) {
if (data.size > 300) data.remove(0, 1) // If update arrived in very close succession to the previous one - kill the last point received to eliminate un-necessary noise on the graph.
if(lastTimeStamp != null && currentTimeStamp - lastTimeStamp.toLong() < 1.seconds.toMillis()) {
data.safelyTransition {
remove(size - 1, size)
}
}
// Add a new data point.
data(currentTimeStamp, currAmount)
// Limit population of data points to make graph painting faster.
data.safelyTransition {
if (size > 300) remove(0, 1)
} }
} }
} }
@ -337,5 +352,12 @@ class CashViewer : CordaView("Cash") {
animated = false animated = false
} }
} }
private fun <X, Y> ObservableList<XYChart.Data<X, Y>>.safelyTransition(block: ObservableList<XYChart.Data<X, Y>>.() -> Unit) {
runInFxApplicationThread {
// Modify data in UI thread to properly propagate to GUI.
this.block()
}
}
} }
} }