mirror of
https://github.com/corda/corda.git
synced 2024-12-28 00:38:55 +00:00
commit
ac328aeb67
@ -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()
|
||||||
|
@ -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())
|
||||||
|
@ -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()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.
|
||||||
|
@ -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>)
|
@ -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
|
||||||
|
@ -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
|
||||||
|
)
|
@ -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);
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
16
core/src/main/kotlin/net/corda/core/internal/NotaryUtils.kt
Normal file
16
core/src/main/kotlin/net/corda/core/internal/NotaryUtils.kt
Normal 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) }
|
||||||
|
}
|
@ -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()
|
||||||
|
}
|
@ -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")
|
||||||
}
|
}
|
@ -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)
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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, *>
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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())
|
||||||
|
@ -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() })
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user