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

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

View File

@ -1384,12 +1384,10 @@ public static final class net.corda.core.flows.NotarisationRequest$Companion ext
@net.corda.core.serialization.CordaSerializable public abstract class net.corda.core.flows.NotaryError extends java.lang.Object
##
@net.corda.core.serialization.CordaSerializable public 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.SignedData 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 Map component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.flows.NotaryError$Conflict copy(net.corda.core.crypto.SecureHash, Map)
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()
public int hashCode()
@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
##
@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()
##
public final class net.corda.core.flows.NotaryFlow extends java.lang.Object
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, net.corda.core.utilities.ProgressTracker)
@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()
##
@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)
@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.crypto.SecureHash component3()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction copy(List, net.corda.core.identity.Party, net.corda.core.crypto.SecureHash)
public <init>(Map, Map)
@org.jetbrains.annotations.NotNull public final Map component1()
@org.jetbrains.annotations.NotNull public final Map component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeFilteredTransaction copy(Map, Map)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
@org.jetbrains.annotations.NotNull public List getInputs()
@org.jetbrains.annotations.NotNull public net.corda.core.identity.Party getNotary()
@org.jetbrains.annotations.NotNull public List getOutputs()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash getRest()
public int hashCode()
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 Set getRequiredSigningKeys()
@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 String getUpgradedContractClassName()
public int hashCode()
public String toString()
public void verifyRequiredSignatures()
@ -3061,15 +3057,11 @@ public static final class net.corda.core.serialization.SingletonSerializationTok
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
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 List component1()
@org.jetbrains.annotations.NotNull public final net.corda.core.identity.Party component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.crypto.SecureHash component3()
@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)
@org.jetbrains.annotations.NotNull public final net.corda.core.contracts.PrivacySalt component2()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeWireTransaction copy(List, net.corda.core.contracts.PrivacySalt)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
@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 List getOutputs()
@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 String getUpgradedContractClassName()
public int hashCode()
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.ContractUpgradeLedgerTransaction resolve(net.corda.core.node.ServicesForResolution, List)
public String toString()
@ -3213,11 +3205,9 @@ public static final class net.corda.core.transactions.LedgerTransaction$InOutGro
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
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 net.corda.core.identity.Party component2()
@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)
@org.jetbrains.annotations.NotNull public final net.corda.core.transactions.NotaryChangeWireTransaction copy(List)
public boolean equals(Object)
@org.jetbrains.annotations.NotNull public net.corda.core.crypto.SecureHash getId()
@org.jetbrains.annotations.NotNull public List getInputs()

View File

@ -86,7 +86,8 @@ class SwapIdentitiesFlow(private val otherParty: Party,
val legalIdentityAnonymous = serviceHub.keyManagementService.freshKeyAndCert(ourIdentityAndCert, revocationEnabled)
val 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>()
if (serviceHub.myInfo.isLegalIdentity(otherParty)) {
identities.put(otherParty, legalIdentityAnonymous.party.anonymise())

View File

@ -179,7 +179,7 @@ fun KeyPair.verify(signatureData: ByteArray, clearData: ByteArray): Boolean = Cr
* which should never happen and suggests an unusual JVM or non-standard Java library.
*/
@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
@ -257,7 +257,11 @@ fun componentHash(opaqueBytes: OpaqueBytes, privacySalt: PrivacySalt, componentG
/** Return the SHA256(SHA256(nonce || serializedComponent)). */
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()
/**

View File

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

View File

@ -11,8 +11,7 @@
package net.corda.core.flows
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.*
import net.corda.core.identity.Party
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.serialize
@ -53,7 +52,7 @@ class NotarisationRequest(statesToConsume: List<StateRef>, val transactionId: Se
val signature = requestSignature.digitalSignature
if (intendedSigner.owningKey != 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
// 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) {
is InvalidKeyException, is SignatureException -> {
val error = NotaryError.RequestSignatureInvalid(e)
throw NotaryException(error)
throw NotaryInternalException(error)
}
else -> throw e
}
@ -109,3 +108,7 @@ data class NotarisationPayload(val transaction: Any, val requestSignature: Notar
*/
val coreTransaction get() = transaction as CoreTransaction
}
/** Payload returned by the notary service flow to the client. */
@CordaSerializable
data class NotarisationResponse(val signatures: List<TransactionSignature>)

View File

@ -17,7 +17,7 @@ import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignableData
import net.corda.core.crypto.SignatureMetadata
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.utilities.ProgressTracker
@ -40,11 +40,11 @@ class NotaryChangeFlow<out T : ContractState>(
override fun assembleTx(): AbstractStateReplacementFlow.UpgradeTx {
val inputs = resolveEncumbrances(originalState)
val tx = NotaryChangeWireTransaction(
val tx = NotaryChangeTransactionBuilder(
inputs.map { it.ref },
originalState.state.notary,
modification
)
).build()
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

View File

@ -11,27 +11,24 @@
package net.corda.core.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.DoNotImplement
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.keys
import net.corda.core.identity.Party
import net.corda.core.internal.FetchDataFlow
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.TrustedAuthorityNotaryService
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.ContractUpgradeWireTransaction
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.UntrustworthyData
import net.corda.core.utilities.unwrap
import java.security.SignatureException
import java.time.Instant
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
* by another transaction or the time-window is invalid.
*/
@DoNotImplement
@InitiatingFlow
open class Client(private val stx: SignedTransaction,
override val progressTracker: ProgressTracker) : FlowLogic<List<TransactionSignature>>() {
@ -78,44 +76,32 @@ class NotaryFlow {
check(serviceHub.loadStates(stx.inputs.toSet()).all { it.state.notary == notaryParty }) {
"Input states must have the same Notary"
}
try {
stx.resolveTransactionWithSignatures(serviceHub).verifySignaturesExcept(notaryParty.owningKey)
} catch (ex: SignatureException) {
throw NotaryException(NotaryError.TransactionInvalid(ex))
}
return notaryParty
}
/** Notarises the transaction with the [notaryParty], obtains the notary's signature(s). */
@Throws(NotaryException::class)
@Suspendable
protected fun notarise(notaryParty: Party): UntrustworthyData<List<TransactionSignature>> {
return try {
protected fun notarise(notaryParty: Party): UntrustworthyData<NotarisationResponse> {
val session = initiateFlow(notaryParty)
val requestSignature = NotarisationRequest(stx.inputs, stx.id).generateSignature(serviceHub)
if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
return if (serviceHub.networkMapCache.isValidatingNotary(notaryParty)) {
sendAndReceiveValidating(session, requestSignature)
} else {
sendAndReceiveNonValidating(notaryParty, session, requestSignature)
}
} catch (e: NotaryException) {
if (e.error is NotaryError.Conflict) {
e.error.conflict.verified()
}
throw e
}
}
@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)
subFlow(NotarySendTransactionFlow(session, payload))
return session.receive()
}
@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 tx = when (ctx) {
is ContractUpgradeWireTransaction -> ctx.buildFilteredTransaction()
@ -126,18 +112,13 @@ class NotaryFlow {
}
/** Checks that the notary's signature(s) is/are valid. */
protected fun validateResponse(response: UntrustworthyData<List<TransactionSignature>>, notaryParty: Party): List<TransactionSignature> {
return response.unwrap { signatures ->
signatures.forEach { validateSignature(it, stx.id, notaryParty) }
signatures
protected fun validateResponse(response: UntrustworthyData<NotarisationResponse>, notaryParty: Party): List<TransactionSignature> {
return response.unwrap {
it.validateSignatures(stx.id, notaryParty)
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
* initial message, and retries message delivery.
@ -166,11 +147,17 @@ class NotaryFlow {
check(serviceHub.myInfo.legalIdentities.any { serviceHub.networkMapCache.isNotary(it) }) {
"We are not a notary on the network"
}
val (id, inputs, timeWindow, notary) = receiveAndVerifyTx()
checkNotary(notary)
service.validateTimeWindow(timeWindow)
service.commitInputStates(inputs, id, otherSideSession.counterparty)
signAndSendResponse(id)
var txId: SecureHash? = null
try {
val parts = receiveAndVerifyTx()
txId = parts.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
}
@ -185,14 +172,14 @@ class NotaryFlow {
@Suspendable
protected fun checkNotary(notary: Party?) {
if (notary?.owningKey != service.notaryIdentityKey) {
throw NotaryException(NotaryError.WrongNotary)
throw NotaryInternalException(NotaryError.WrongNotary)
}
}
@Suspendable
private fun signAndSendResponse(txId: SecureHash) {
private fun signTransactionAndSendResponse(txId: SecureHash) {
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
* 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. */
@CordaSerializable
sealed class NotaryError {
/** Occurs when one or more input states of transaction with [txId] have already been consumed by another transaction. */
data class Conflict(val txId: SecureHash, val conflict: SignedData<UniquenessProvider.Conflict>) : NotaryError() {
override fun toString() = "One or more input states for transaction $txId have been used in another transaction"
/** Occurs when one or more input states have already been consumed by another transaction. */
data class Conflict(
/** 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. */
@ -246,3 +246,15 @@ sealed class NotaryError {
override fun toString() = cause.toString()
}
}
/** Contains information about the consuming transaction for a particular state. */
// TODO: include notary timestamp?
@CordaSerializable
data class StateConsumptionDetails(
/**
* Hash of the consuming transaction id.
*
* Note that this is NOT the transaction id itself revealing it could lead to privacy leaks.
*/
val hashOfTransactionId: SecureHash
)

View File

@ -34,9 +34,7 @@ import java.security.cert.X509Certificate
// also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications.
// 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 {
/**
* Intermediate CA (Doorman service).
*/
/** Intermediate CA (Doorman service). */
INTERMEDIATE_CA(NonEmptySet.of(null), false, false),
/** Signing certificate for the network map. */
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. */
TLS(NonEmptySet.of(NODE_CA), false, false),
/** 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),
/** Confidential (limited visibility) identity of a legal entity. */
CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false);

View File

@ -31,14 +31,14 @@ object ContractUpgradeUtils {
val upgradedContractAttachmentId = getContractAttachmentId(upgradedContractClass.name, services)
val inputs = listOf(stateAndRef.ref)
return ContractUpgradeWireTransaction(
return ContractUpgradeTransactionBuilder(
inputs,
stateAndRef.state.notary,
legacyContractAttachmentId,
upgradedContractClass.name,
upgradedContractAttachmentId,
privacySalt
)
).build()
}
private fun getContractAttachmentId(name: ContractClassName, services: ServicesForResolution): AttachmentId {

View File

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

View File

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

View File

@ -18,7 +18,6 @@ import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import org.slf4j.Logger
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.
*/
@JvmStatic
@Throws(NotaryException::class)
@Throws(NotaryInternalException::class)
fun validateTimeWindow(clock: Clock, timeWindow: TimeWindow?) {
if (timeWindow == null) return
val currentTime = clock.instant()
if (currentTime !in timeWindow) {
throw NotaryException(
throw NotaryInternalException(
NotaryError.TimeWindowInvalid(currentTime, timeWindow)
)
}
@ -92,28 +92,24 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
fun commitInputStates(inputs: List<StateRef>, txId: SecureHash, caller: Party) {
try {
uniquenessProvider.commit(inputs, txId, caller)
} catch (e: UniquenessException) {
val conflicts = inputs.filterIndexed { i, stateRef ->
val consumingTx = e.error.stateHistory[stateRef]
consumingTx != null && consumingTx != UniquenessProvider.ConsumingTx(txId, i, caller)
} catch (e: NotaryInternalException) {
if (e.error is NotaryError.Conflict) {
val conflicts = inputs.filterIndexed { _, stateRef ->
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.
log.warn("Notary conflicts for $txId: $conflicts")
throw notaryException(txId, e)
log.info("Notary conflicts for $txId: $conflicts")
throw e
}
} else throw e
} catch (e: Exception) {
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. */
fun sign(bits: ByteArray): DigitalSignature.WithKey {
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.
@Deprecated("This property is no longer used") @Suppress("DEPRECATION")
protected open val timeWindowChecker: TimeWindowChecker get() = throw UnsupportedOperationException("No default implementation, need to override")
@Deprecated("This property is no longer used")
@Suppress("DEPRECATION")
protected open val timeWindowChecker: TimeWindowChecker
get() = throw UnsupportedOperationException("No default implementation, need to override")
}

View File

@ -23,24 +23,22 @@ import net.corda.core.serialization.CordaSerializable
* A uniqueness provider is expected to be used from within the context of a flow.
*/
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)
/** Specifies the consuming transaction for every conflicting state */
/** Specifies the consuming transaction for every conflicting state. */
@CordaSerializable
@Deprecated("No longer used due to potential privacy leak")
data class Conflict(val stateHistory: Map<StateRef, ConsumingTx>)
/**
* Specifies the transaction id, the position of the consumed state in the inputs, and
* 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
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)

View File

@ -16,13 +16,14 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.AbstractParty
import net.corda.core.messaging.DataFeed
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.node.services.Vault.StateStatus
import net.corda.core.node.services.vault.*
import net.corda.core.serialization.CordaSerializable
import net.corda.core.toFuture
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.NonEmptySet
import rx.Observable
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].
* A Page contains:
* 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
* 3) a total number of states that met the given [QueryCriteria] if a [PageSpecification] was provided
* (otherwise defaults to -1)
* 4) Status types used in this query: UNCONSUMED, CONSUMED, ALL
* 5) Other results as a [List] of any type (eg. aggregate function results with/without group by)
* 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.
* 3) a total number of states that met the given [QueryCriteria] if a [PageSpecification] was provided,
* otherwise it defaults to -1.
* 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).
*
* 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
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.
*
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the Vault will already incorporate
* the update, and the database transaction associated with the update will still be open and current. If for some
* reason the processing crosses outside of the database transaction (for example, the update is pushed outside the current
* JVM or across to another [Thread] which is executing in a different database transaction) then the Vault may
* not incorporate the update due to racing with committing the current database transaction.
* Get a synchronous [Observable] of updates. When observations are pushed to the Observer, the [Vault] will already
* incorporate the update, and the database transaction associated with the update will still be open and current.
* If for some reason the processing crosses outside of the database transaction (for example, the update is pushed
* outside the current JVM or across to another [Thread], which is executing in a different 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>>
/**
* Get a synchronous Observable of updates. When observations are pushed to the Observer, the Vault will already incorporate
* the update, and the database transaction associated with the update will have been committed and closed.
* Get a synchronous [Observable] of updates. When observations are pushed to the Observer, the [Vault] will
* already incorporate the update and the database transaction associated with the update will have been committed
* and closed.
*/
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].
* These are additively and immutably persisted within the node local vault database in a single textual field
* using a semi-colon separator
* These are additively and immutably persisted within the node local vault database in a single textual field.
* using a semi-colon separator.
*/
fun addNoteToTransaction(txnId: SecureHash, noteText: String)
@ -202,7 +204,7 @@ interface VaultService {
// 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.
*/
@ -210,35 +212,35 @@ interface VaultService {
/**
* 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.
* However, the user can specify their own [UUID] and manage this manually, possibly across the lifetime of multiple flows,
* or from other thread contexts e.g. [CordaService] instances.
* However, the user can specify their own [UUID] and manage this manually, possibly across the lifetime of multiple
* 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.
*
* @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)
fun softLockReserve(lockId: UUID, stateRefs: NonEmptySet<StateRef>)
/**
* 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
* may be released.
* In the case of coin selection, softLock are automatically released once previously gathered unconsumed input refs
* are consumed as part of cash spending.
* A [Vault] soft-lock manager is automatically notified from flows that are terminated, such that any soft locked
* states may be released.
* In the case of coin selection, soft-locks are automatically released once previously gathered unconsumed
* input refs are consumed as part of cash spending.
*/
fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>? = null)
// DOCEND SoftLockAPI
/**
* 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.
* @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
* [contractStateType]. e.g. by selecting on account, issuer, etc. The query is internally augmented with the UNCONSUMED,
* soft lock and contract type requirements.
* [contractStateType]. e.g. by selecting on account, issuer, etc. The query is internally augmented with the
* [StateStatus.UNCONSUMED], soft lock and contract type requirements.
* @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].
* @param contractStateType class type of the result set.
@ -259,12 +261,12 @@ interface VaultService {
* and returns a [Vault.Page] object containing the following:
* 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.
* 3. total number of results available if [PageSpecification] supplied (otherwise returns -1)
* 4. status types used in this query: UNCONSUMED, CONSUMED, ALL
* 5. other results (aggregate functions with/without using value groups)
* 3. total number of results available if [PageSpecification] supplied (otherwise returns -1).
* 4. status types used in this query: [StateStatus.UNCONSUMED], [StateStatus.CONSUMED], [StateStatus.ALL].
* 5. other results (aggregate functions with/without using value groups).
*
* @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
* 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,
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
* and returns a [Vault.PageAndUpdates] object containing
* 1) a snapshot as a [Vault.Page] (described previously in [queryBy])
* 2) an [Observable] of [Vault.Update]
* and returns a [DataFeed] object containing:
* 1) a snapshot as a [Vault.Page] (described previously in [queryBy]).
* 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.
* 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>>
// DOCEND VaultQueryAPI
// Note: cannot apply @JvmOverloads to interfaces nor interface implementations
// Java Helpers
// Note: cannot apply @JvmOverloads to interfaces nor interface implementations.
// Java Helpers.
fun <T : ContractState> queryBy(contractStateType: Class<out T>): Vault.Page<T> {
return _queryBy(QueryCriteria.VaultQueryCriteria(), PageSpecification(), Sort(emptySet()), contractStateType)
}

View File

@ -13,12 +13,18 @@ package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
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.internal.AttachmentWithContext
import net.corda.core.internal.combinedHash
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
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 java.security.PublicKey
@ -28,13 +34,20 @@ import java.security.PublicKey
/** A special transaction for upgrading the contract of a state. */
@CordaSerializable
data class ContractUpgradeWireTransaction(
override val inputs: List<StateRef>,
override val notary: Party,
val legacyContractAttachmentId: SecureHash,
val upgradeContractClassName: ContractClassName,
val upgradedContractAttachmentId: SecureHash,
/**
* Contains all of the transaction components in serialized form.
* 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>,
/** Required for hiding components in [ContractUpgradeFilteredTransaction]. */
val privacySalt: PrivacySalt = PrivacySalt()
) : 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 {
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, " +
"outputs can only be obtained from a resolved ContractUpgradeLedgerTransaction")
/** Hash of the list of components that are hidden in the [ContractUpgradeFilteredTransaction]. */
private val hiddenComponentHash: SecureHash
get() = serializedHash(listOf(legacyContractAttachmentId, upgradeContractClassName, privacySalt))
override val id: SecureHash by lazy {
val componentHashes =serializedComponents.mapIndexed { index, component ->
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. */
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): ContractUpgradeLedgerTransaction {
@ -66,7 +85,7 @@ data class ContractUpgradeWireTransaction(
resolvedInputs,
notary,
legacyContractAttachment,
upgradeContractClassName,
upgradedContractClassName,
upgradedContractAttachment,
id,
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 {
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
* 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.
*
* @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
data class ContractUpgradeFilteredTransaction(
override val inputs: List<StateRef>,
override val notary: Party,
val rest: SecureHash
/** Transaction components that are exposed. */
val visibleComponents: Map<Int, FilteredComponent>,
/**
* Hashes of the transaction components that are not revealed in this transaction.
* Required for computing the transaction id.
*/
val hiddenComponents: Map<Int, SecureHash>
) : 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()
/** 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 notary: Party,
val legacyContractAttachment: Attachment,
val upgradeContractClassName: ContractClassName,
val upgradedContractClassName: ContractClassName,
val upgradedContractAttachment: Attachment,
override val id: SecureHash,
val privacySalt: PrivacySalt,
@ -175,7 +233,7 @@ data class ContractUpgradeLedgerTransaction(
// TODO: re-map encumbrance pointers
input.state.copy(
data = upgradedState,
contract = upgradeContractClassName,
contract = upgradedContractClassName,
constraint = outputConstraint
)
}
@ -192,7 +250,7 @@ data class ContractUpgradeLedgerTransaction(
private fun loadUpgradedContract(): UpgradedContract<ContractState, *> {
@Suppress("UNCHECKED_CAST")
return this::class.java.classLoader
.loadClass(upgradeContractClassName)
.loadClass(upgradedContractClassName)
.asSubclass(Contract::class.java)
.getConstructor()
.newInstance() as UpgradedContract<ContractState, *>

View File

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

View File

@ -95,18 +95,18 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes<List<PublicKey>>(it).deserialize() })
val 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 }
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" }
val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }
val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) }
if (leafIndices.isNotEmpty())
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 {
// It is a WireTransaction
// 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" }
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
// and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be
// a match on Map.get ensuring it will never return null.
filteredSerialisedComponents.put(componentGroupIndex, mutableListOf(serialisedComponent))
filteredComponentNonces.put(componentGroupIndex, mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex]))
filteredComponentHashes.put(componentGroupIndex, mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex]))
filteredSerialisedComponents[componentGroupIndex] = mutableListOf(serialisedComponent)
filteredComponentNonces[componentGroupIndex] = mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
filteredComponentHashes[componentGroupIndex] = mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
} else {
group.add(serialisedComponent)
// If the group[componentGroupIndex] existed, then we guarantee that
@ -175,9 +175,9 @@ class FilteredTransaction internal constructor(
val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal
// There exist commands, thus the signers group is not empty.
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
filteredSerialisedComponents.put(signersGroupIndex, signersGroupComponents.components.toMutableList())
filteredComponentNonces.put(signersGroupIndex, wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList())
filteredComponentHashes.put(signersGroupIndex, wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList())
filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList()
filteredComponentNonces[signersGroupIndex] = wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()
filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()
}
}
}
@ -322,14 +322,14 @@ class FilteredTransaction internal constructor(
.filter { signers -> publicKey in signers }.size
}
inline private fun verificationCheck(value: Boolean, lazyMessage: () -> Any) {
private inline fun verificationCheck(value: Boolean, lazyMessage: () -> Any) {
if (!value) {
val message = lazyMessage()
throw FilteredTransactionVerificationException(id, message.toString())
}
}
inline private fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) {
private inline fun visibilityCheck(value: Boolean, lazyMessage: () -> Any) {
if (!value) {
val message = lazyMessage()
throw ComponentVisibilityException(id, message.toString())

View File

@ -13,11 +13,15 @@ package net.corda.core.transactions
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
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.node.ServiceHub
import net.corda.core.node.ServicesForResolution
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 java.security.PublicKey
@ -28,10 +32,18 @@ import java.security.PublicKey
*/
@CordaSerializable
data class NotaryChangeWireTransaction(
override val inputs: List<StateRef>,
override val notary: Party,
val newNotary: Party
/**
* Contains all of the transaction components in serialized form.
* 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() {
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
* [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
* 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]. */
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>) : NotaryChangeLedgerTransaction {
fun resolve(services: ServicesForResolution, sigs: List<TransactionSignature>): NotaryChangeLedgerTransaction {
val resolvedInputs = services.loadStates(inputs.toSet()).toList()
return NotaryChangeLedgerTransaction(resolvedInputs, notary, newNotary, id, sigs)
}
/** Resolves input states and builds a [NotaryChangeLedgerTransaction]. */
fun resolve(services: ServiceHub, sigs: List<TransactionSignature>) = resolve(services as ServicesForResolution, sigs)
enum class Component {
INPUTS, NOTARY, NEW_NOTARY
}
@Deprecated("Required only for backwards compatibility purposes. This type of transaction should not be constructed outside Corda code.", ReplaceWith("NotaryChangeTransactionBuilder"), DeprecationLevel.WARNING)
constructor(inputs: List<StateRef>, notary: Party, newNotary: Party) : this(listOf(inputs, notary, newNotary).map { it.serialize() })
}
/**

View File

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

View File

@ -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.
* Notary -> 20001 (Does not accept logins)
* Alice -> 20004
* Bob -> 20007
* UK Bank Plc -> 20010 (*Issuer node*)
* USA Bank Corp -> 20013 (*Issuer node*)
* Notary -> 20005 (Does not accept logins)
* UK Bank Plc -> 20011 (*Issuer node*)
* USA Bank Corp -> 20008 (*Issuer node*)
* Alice -> 20017
* Bob -> 20014
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``.

View File

@ -3,9 +3,10 @@
Writing a custom notary service (experimental)
==============================================
.. warning:: Customising a notary service is still an experimental feature and not recommended for most use-cases. Currently,
customising Raft or BFT notaries is not yet 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.
.. warning:: Customising a notary service is still an experimental feature and not recommended for most use-cases. The APIs
for writing a custom notary may change in the future. Additionally, customising Raft or BFT notaries is not yet
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
with ``@CordaService``. The Corda node scans for any class with this annotation and initialises them. The custom notary

View File

@ -27,26 +27,37 @@ sealed class ConnectionDirection {
) : ConnectionDirection()
}
/** Class to set Artemis TCP configuration options. */
class ArtemisTcpTransport {
companion object {
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.
// SHA256 as message authentication algorithm.
// ECDHE as key exchange algorithm. DHE is also supported if 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:
// a) we need to use keytool certificates in some demos,
// b) we use cloud providers or HSMs that do not support ECC.
/**
* Corda supported TLS schemes.
* <p><ul>
* <li>TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
* <li>TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* <li>TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
* </ul></p>
* 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(
"TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_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")
/** Specify [TransportConfiguration] for TCP communication. */
fun tcpTransport(
direction: ConnectionDirection,
hostAndPort: NetworkHostAndPort,

View File

@ -41,13 +41,6 @@ class ArtemisMessagingComponent {
const val BRIDGE_NOTIFY = "${INTERNAL_PREFIX}bridge.notify"
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 {
// 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

View File

@ -54,6 +54,10 @@ data class ParametersUpdate(
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 {
require(CertRole.extract(sig.by) == CertRole.NETWORK_MAP) { "Incorrect cert role: ${CertRole.extract(sig.by)}" }
X509Utilities.validateCertificateChain(rootCert, sig.by, rootCert)

View File

@ -44,7 +44,12 @@ class PublicPropertyReader(private val readMethod: Method?) : PropertyReader() {
// is: https://youtrack.jetbrains.com/issue/KT-13077
// 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
}
}
@ -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
// is: https://youtrack.jetbrains.com/issue/KT-13077
// 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
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -14,16 +14,11 @@ import co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.crypto.SignedData
import net.corda.core.flows.*
import net.corda.core.identity.CordaX500Name
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.UniquenessProvider
import net.corda.core.schemas.PersistentStateRef
@ -91,18 +86,22 @@ class BFTNonValidatingNotaryService(
@Suspendable
override fun call(): Void? {
val payload = otherSideSession.receive<NotarisationPayload>().unwrap { it }
val signatures = commit(payload)
otherSideSession.send(signatures)
val response = commit(payload)
otherSideSession.send(response)
return null
}
private fun commit(payload: NotarisationPayload): List<DigitalSignature> {
private fun commit(payload: NotarisationPayload): NotarisationResponse {
val response = service.commitTransaction(payload, otherSideSession.counterparty)
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 -> {
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 notary = transaction.notary
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)
log.debug { "Inputs committed successfully, signing $id" }
BFTSMaRt.ReplicaResponse.Signature(sign(id))
} catch (e: NotaryException) {
} catch (e: NotaryInternalException) {
log.debug { "Error processing transaction: ${e.error}" }
BFTSMaRt.ReplicaResponse.Error(e.error)
val serializedError = e.error.serialize()
val errorSignature = sign(serializedError.bytes)
val signedError = SignedData(serializedError, errorSignature)
BFTSMaRt.ReplicaResponse.Error(signedError)
}
}

View File

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

View File

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

View File

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

View File

@ -29,8 +29,11 @@ import io.atomix.copycat.server.storage.Storage
import io.atomix.copycat.server.storage.StorageLevel
import net.corda.core.contracts.StateRef
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.node.services.UniquenessException
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SingletonSerializeAsToken
@ -214,7 +217,11 @@ class RaftUniquenessProvider(private val transportConfiguration: NodeSSLConfigur
val commitCommand = DistributedImmutableMap.Commands.PutAll(encode(entries))
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")
}

View File

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

View File

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

View File

@ -11,8 +11,10 @@
package net.corda.node.services.transactions
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.node.services.UniquenessException
import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService
import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -70,12 +72,11 @@ class PersistentUniquenessProviderTests {
val inputs = listOf(inputState)
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]!!
assertEquals(consumingTx.id, txID)
assertEquals(consumingTx.inputIndex, inputs.indexOf(inputState))
assertEquals(consumingTx.requestingParty, identity)
val conflictCause = error.consumedStates[inputState]!!
assertEquals(conflictCause.hashOfTransactionId, txID.sha256())
}
}
}

View File

@ -24,7 +24,6 @@ import net.corda.core.node.ServiceHub
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.issueInvalidState
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.ALICE_NAME
@ -90,14 +89,13 @@ class ValidatingNotaryServiceTests {
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)
future.getOrThrow()
}
val notaryError = ex.error as NotaryError.TransactionInvalid
assertThat(notaryError.cause).isInstanceOf(SignedTransaction.SignaturesMissingException::class.java)
val missingKeys = (notaryError.cause as SignedTransaction.SignaturesMissingException).missing
val missingKeys = ex.missing
assertEquals(setOf(expectedMissingKey), missingKeys)
}

View File

@ -14,10 +14,14 @@ import co.paralleluniverse.fibers.Suspendable
import com.nhaarman.mockito_kotlin.argThat
import com.nhaarman.mockito_kotlin.doNothing
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.generateKeyPair
import net.corda.core.identity.*
import net.corda.core.internal.NotaryChangeTransactionBuilder
import net.corda.core.internal.packageName
import net.corda.core.node.StatesToRecord
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.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.TransactionBuilder
import net.corda.core.utilities.NonEmptySet
@ -617,7 +620,7 @@ class NodeVaultServiceTest {
// Change notary
services.identityService.verifyAndRegisterIdentity(DUMMY_NOTARY_IDENTITY)
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))
database.transaction {

View File

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

View File

@ -21,6 +21,7 @@ import javafx.collections.ObservableList
import javafx.geometry.Insets
import javafx.scene.Parent
import javafx.scene.chart.NumberAxis
import javafx.scene.chart.XYChart
import javafx.scene.control.*
import javafx.scene.input.MouseButton
import javafx.scene.layout.BorderPane
@ -323,12 +324,26 @@ class CashViewer : CordaView("Cash") {
linechart(null, xAxis, yAxis) {
series("USD") {
sumAmount.addListener { _, _, _ ->
val lastAmount = data.last().value?.yValue
val currAmount = sumAmount.value.toDecimal()
val lastTimeStamp = data.last().value?.xValue
if (lastTimeStamp == null || System.currentTimeMillis() - lastTimeStamp.toLong() > 1.seconds.toMillis()) {
data(System.currentTimeMillis(), sumAmount.value.quantity)
runInFxApplicationThread {
// Modify data in UI thread.
if (data.size > 300) data.remove(0, 1)
val currentTimeStamp = System.currentTimeMillis()
// If amount is not the same - always add a data point.
if (lastAmount == null || lastAmount != currAmount) {
// 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
}
}
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()
}
}
}
}