Merge branch 'master' into arri_update_collectSignaturesFlow_Docs

This commit is contained in:
Alberto Arri 2017-08-11 15:17:43 +01:00 committed by GitHub
commit 4deea43b98
97 changed files with 1542 additions and 1675 deletions

View File

@ -9,6 +9,7 @@ import net.corda.core.utilities.NetworkHostAndPort
import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport import net.corda.nodeapi.ArtemisTcpTransport.Companion.tcpTransport
import net.corda.nodeapi.ConnectionDirection import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.config.SSLConfiguration import net.corda.nodeapi.config.SSLConfiguration
import net.corda.nodeapi.internal.serialization.AMQPClientSerializationScheme
import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT import net.corda.nodeapi.internal.serialization.KRYO_P2P_CONTEXT
import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT import net.corda.nodeapi.internal.serialization.KRYO_RPC_CLIENT_CONTEXT
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
@ -71,6 +72,7 @@ class CordaRPCClient(
try { try {
SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply {
registerScheme(KryoClientSerializationScheme()) registerScheme(KryoClientSerializationScheme())
registerScheme(AMQPClientSerializationScheme())
} }
SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT
SerializationDefaults.RPC_CLIENT_CONTEXT = KRYO_RPC_CLIENT_CONTEXT SerializationDefaults.RPC_CLIENT_CONTEXT = KRYO_RPC_CLIENT_CONTEXT

View File

@ -2,7 +2,6 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.core.contracts.clauses.Clause
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.secureRandomBytes import net.corda.core.crypto.secureRandomBytes
import net.corda.core.flows.FlowLogicRef import net.corda.core.flows.FlowLogicRef
@ -211,26 +210,6 @@ interface LinearState : ContractState {
* True if this should be tracked by our vault(s). * True if this should be tracked by our vault(s).
*/ */
fun isRelevant(ourKeys: Set<PublicKey>): Boolean fun isRelevant(ourKeys: Set<PublicKey>): Boolean
/**
* Standard clause to verify the LinearState safety properties.
*/
@CordaSerializable
class ClauseVerifier<in S : LinearState, C : CommandData> : Clause<S, C, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<S>,
outputs: List<S>,
commands: List<AuthenticatedObject<C>>,
groupingKey: Unit?): Set<C> {
val inputIds = inputs.map { it.linearId }.distinct()
val outputIds = outputs.map { it.linearId }.distinct()
requireThat {
"LinearStates are not merged" using (inputIds.count() == inputs.count())
"LinearStates are not split" using (outputIds.count() == outputs.count())
}
return emptySet()
}
}
} }
// DOCEND 2 // DOCEND 2

View File

@ -1,10 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
/**
* Compose a number of clauses, such that all of the clauses must run for verification to pass.
*/
@Deprecated("Use AllOf")
class AllComposition<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : AllOf<S, C, K>(firstClause, *remainingClauses)

View File

@ -1,38 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.*
/**
* Compose a number of clauses, such that all of the clauses must run for verification to pass.
*/
open class AllOf<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses = ArrayList<Clause<S, C, K>>()
init {
clauses.add(firstClause)
clauses.addAll(remainingClauses)
}
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
clauses.forEach { clause ->
check(clause.matches(commands)) { "Failed to match clause $clause" }
}
return clauses
}
override fun verify(tx: LedgerTransaction,
inputs: List<S>,
outputs: List<S>,
commands: List<AuthenticatedObject<C>>,
groupingKey: K?): Set<C> {
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
clause.verify(tx, inputs, outputs, commands, groupingKey)
}
}
override fun toString() = "All: $clauses.toList()"
}

View File

@ -1,10 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
/**
* Compose a number of clauses, such that any number of the clauses can run.
*/
@Deprecated("Use AnyOf instead, although note that any of requires at least one matched clause")
class AnyComposition<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : AnyOf<S, C, K>(*rawClauses)

View File

@ -1,28 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.*
/**
* Compose a number of clauses, such that one or more of the clauses can run.
*/
open class AnyOf<in S : ContractState, C : CommandData, in K : Any>(vararg rawClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses: List<Clause<S, C, K>> = rawClauses.toList()
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> {
val matched = clauses.filter { it.matches(commands) }
require(matched.isNotEmpty()) { "At least one clause must match" }
return matched
}
override fun verify(tx: LedgerTransaction, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
return matchedClauses(commands).flatMapTo(HashSet<C>()) { clause ->
clause.verify(tx, inputs, outputs, commands, groupingKey)
}
}
override fun toString(): String = "Any: ${clauses.toList()}"
}

View File

@ -1,73 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.loggerFor
/**
* A clause of a contract, containing a chunk of verification logic. That logic may be delegated to other clauses, or
* provided directly by this clause.
*
* @param S the type of contract state this clause operates on.
* @param C a common supertype of commands this clause operates on.
* @param K the type of the grouping key for states this clause operates on. Use [Unit] if not applicable.
*
* @see CompositeClause
*/
abstract class Clause<in S : ContractState, C : CommandData, in K : Any> {
companion object {
val log = loggerFor<Clause<*, *, *>>()
}
/** Determine whether this clause runs or not */
open val requiredCommands: Set<Class<out CommandData>> = emptySet()
/**
* Determine the subclauses which will be verified as a result of verifying this clause.
*
* @throws IllegalStateException if the given commands do not result in a valid execution (for example no match
* with [FirstOf]).
*/
@Throws(IllegalStateException::class)
open fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= listOf(this)
/**
* Verify the transaction matches the conditions from this clause. For example, a "no zero amount output" clause
* would check each of the output states that it applies to, looking for a zero amount, and throw IllegalStateException
* if any matched.
*
* @param tx the full transaction being verified. This is provided for cases where clauses need to access
* states or commands outside of their normal scope.
* @param inputs input states which are relevant to this clause. By default this is the set passed into [verifyClause],
* but may be further reduced by clauses such as [GroupClauseVerifier].
* @param outputs output states which are relevant to this clause. By default this is the set passed into [verifyClause],
* but may be further reduced by clauses such as [GroupClauseVerifier].
* @param commands commands which are relevant to this clause. By default this is the set passed into [verifyClause],
* but may be further reduced by clauses such as [GroupClauseVerifier].
* @param groupingKey a grouping key applied to states and commands, where applicable. Taken from
* [LedgerTransaction.InOutGroup].
* @return the set of commands that are consumed IF this clause is matched, and cannot be used to match a
* later clause. This would normally be all commands matching "requiredCommands" for this clause, but some
* verify() functions may do further filtering on possible matches, and return a subset. This may also include
* commands that were not required (for example the Exit command for fungible assets is optional).
*/
@Throws(IllegalStateException::class)
abstract fun verify(tx: LedgerTransaction,
inputs: List<S>,
outputs: List<S>,
commands: List<AuthenticatedObject<C>>,
groupingKey: K?): Set<C>
}
/**
* Determine if the given list of commands matches the required commands for a clause to trigger.
*/
fun <C : CommandData> Clause<*, C, *>.matches(commands: List<AuthenticatedObject<C>>): Boolean {
return if (requiredCommands.isEmpty())
true
else
commands.map { it.value.javaClass }.toSet().containsAll(requiredCommands)
}

View File

@ -1,29 +0,0 @@
@file:JvmName("ClauseVerifier")
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
/**
* Verify a transaction against the given list of clauses.
*
* @param tx transaction to be verified.
* @param clauses the clauses to verify.
* @param commands commands extracted from the transaction, which are relevant to the
* clauses.
*/
fun <C : CommandData> verifyClause(tx: LedgerTransaction,
clause: Clause<ContractState, C, Unit>,
commands: List<AuthenticatedObject<C>>) {
if (Clause.log.isTraceEnabled) {
clause.getExecutionPath(commands).forEach {
Clause.log.trace("Tx ${tx.id} clause: $clause")
}
}
val matchedCommands = clause.verify(tx, tx.inputStates, tx.outputStates, commands, null)
check(matchedCommands.containsAll(commands.map { it.value })) { "The following commands were not matched at the end of execution: " + (commands - matchedCommands) }
}

View File

@ -1,25 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
/**
* Abstract supertype for clauses which compose other clauses together in some logical manner.
*/
abstract class CompositeClause<in S : ContractState, C : CommandData, in K : Any> : Clause<S, C, K>() {
/** List of clauses under this composite clause */
abstract val clauses: List<Clause<S, C, K>>
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= matchedClauses(commands).flatMap { it.getExecutionPath(commands) }
/**
* Determine which clauses are matched by the supplied commands.
*
* @throws IllegalStateException if the given commands do not result in a valid execution (for example no match
* with [FirstOf]).
*/
@Throws(IllegalStateException::class)
abstract fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>>
}

View File

@ -1,25 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
/**
* Filter the states that are passed through to the wrapped clause, to restrict them to a specific type.
*/
class FilterOn<S : ContractState, C : CommandData, in K : Any>(val clause: Clause<S, C, K>,
val filterStates: (List<ContractState>) -> List<S>) : Clause<ContractState, C, K>() {
override val requiredCommands: Set<Class<out CommandData>>
= clause.requiredCommands
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= clause.getExecutionPath(commands)
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<C>>,
groupingKey: K?): Set<C>
= clause.verify(tx, filterStates(inputs), filterStates(outputs), commands, groupingKey)
}

View File

@ -1,28 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.*
/**
* Compose a number of clauses, such that the first match is run, and it errors if none is run.
*/
@Deprecated("Use FirstOf instead")
class FirstComposition<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
override val clauses = ArrayList<Clause<S, C, K>>()
override fun matchedClauses(commands: List<AuthenticatedObject<C>>): List<Clause<S, C, K>> = listOf(clauses.first { it.matches(commands) })
init {
clauses.add(firstClause)
clauses.addAll(remainingClauses)
}
override fun verify(tx: LedgerTransaction, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
val clause = matchedClauses(commands).singleOrNull() ?: throw IllegalStateException("No delegate clause matched in first composition")
return clause.verify(tx, inputs, outputs, commands, groupingKey)
}
override fun toString() = "First: ${clauses.toList()}"
}

View File

@ -1,41 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.loggerFor
import java.util.*
/**
* Compose a number of clauses, such that the first match is run, and it errors if none is run.
*/
class FirstOf<S : ContractState, C : CommandData, K : Any>(firstClause: Clause<S, C, K>, vararg remainingClauses: Clause<S, C, K>) : CompositeClause<S, C, K>() {
companion object {
val logger = loggerFor<FirstOf<*, *, *>>()
}
override val clauses = ArrayList<Clause<S, C, K>>()
/**
* Get the single matched clause from the set this composes, based on the given commands. This is provided as
* helper method for internal use, rather than using the exposed [matchedClauses] function which unnecessarily
* wraps the clause in a list.
*/
private fun matchedClause(commands: List<AuthenticatedObject<C>>): Clause<S, C, K> {
return clauses.firstOrNull { it.matches(commands) } ?: throw IllegalStateException("No delegate clause matched in first composition")
}
override fun matchedClauses(commands: List<AuthenticatedObject<C>>) = listOf(matchedClause(commands))
init {
clauses.add(firstClause)
clauses.addAll(remainingClauses)
}
override fun verify(tx: LedgerTransaction, inputs: List<S>, outputs: List<S>, commands: List<AuthenticatedObject<C>>, groupingKey: K?): Set<C> {
return matchedClause(commands).verify(tx, inputs, outputs, commands, groupingKey)
}
override fun toString() = "First: ${clauses.toList()}"
}

View File

@ -1,29 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.*
abstract class GroupClauseVerifier<S : ContractState, C : CommandData, K : Any>(val clause: Clause<S, C, K>) : Clause<ContractState, C, Unit>() {
abstract fun groupStates(tx: LedgerTransaction): List<LedgerTransaction.InOutGroup<S, K>>
override fun getExecutionPath(commands: List<AuthenticatedObject<C>>): List<Clause<*, *, *>>
= clause.getExecutionPath(commands)
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<C>>,
groupingKey: Unit?): Set<C> {
val groups = groupStates(tx)
val matchedCommands = HashSet<C>()
for ((groupInputs, groupOutputs, groupToken) in groups) {
matchedCommands.addAll(clause.verify(tx, groupInputs, groupOutputs, commands, groupToken))
}
return matchedCommands
}
}

View File

@ -14,19 +14,19 @@ import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec
import org.bouncycastle.asn1.* import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.DLSequence
import org.bouncycastle.asn1.bc.BCObjectIdentifiers import org.bouncycastle.asn1.bc.BCObjectIdentifiers
import org.bouncycastle.asn1.nist.NISTObjectIdentifiers import org.bouncycastle.asn1.nist.NISTObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
import org.bouncycastle.asn1.sec.SECObjectIdentifiers import org.bouncycastle.asn1.sec.SECObjectIdentifiers
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.asn1.x509.* import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.asn1.x9.X9ObjectIdentifiers import org.bouncycastle.asn1.x9.X9ObjectIdentifiers
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.bc.BcX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey
import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateKey import org.bouncycastle.jcajce.provider.asymmetric.rsa.BCRSAPrivateKey
@ -40,10 +40,6 @@ import org.bouncycastle.jce.spec.ECPublicKeySpec
import org.bouncycastle.math.ec.ECConstants import org.bouncycastle.math.ec.ECConstants
import org.bouncycastle.math.ec.FixedPointCombMultiplier import org.bouncycastle.math.ec.FixedPointCombMultiplier
import org.bouncycastle.math.ec.WNafUtil import org.bouncycastle.math.ec.WNafUtil
import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PrivateKey
import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey import org.bouncycastle.pqc.jcajce.provider.sphincs.BCSphincs256PublicKey
@ -53,7 +49,6 @@ import java.security.*
import java.security.spec.InvalidKeySpecException import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.PKCS8EncodedKeySpec
import java.security.spec.X509EncodedKeySpec import java.security.spec.X509EncodedKeySpec
import java.util.*
import javax.crypto.Mac import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
@ -196,7 +191,7 @@ object Crypto {
// that could cause unexpected and suspicious behaviour. // that could cause unexpected and suspicious behaviour.
// i.e. if someone removes a Provider and then he/she adds a new one with the same name. // i.e. if someone removes a Provider and then he/she adds a new one with the same name.
// The val is private to avoid any harmful state changes. // The val is private to avoid any harmful state changes.
private val providerMap: Map<String, Provider> = mapOf( val providerMap: Map<String, Provider> = mapOf(
BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(), BouncyCastleProvider.PROVIDER_NAME to getBouncyCastleProvider(),
CordaSecurityProvider.PROVIDER_NAME to CordaSecurityProvider(), CordaSecurityProvider.PROVIDER_NAME to CordaSecurityProvider(),
"BCPQC" to BouncyCastlePQCProvider()) // unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it. "BCPQC" to BouncyCastlePQCProvider()) // unfortunately, provider's name is not final in BouncyCastlePQCProvider, so we explicitly set it.
@ -770,90 +765,6 @@ object Crypto {
return mac.doFinal(seed) return mac.doFinal(seed)
} }
/**
* Build a partial X.509 certificate ready for signing.
*
* @param issuer name of the issuing entity.
* @param subject name of the certificate subject.
* @param subjectPublicKey public key of the certificate subject.
* @param validityWindow the time period the certificate is valid for.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
*/
fun createCertificate(certificateType: CertificateType, issuer: X500Name,
subject: X500Name, subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509v3CertificateBuilder {
val serial = BigInteger.valueOf(random63BitValue())
val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } })
val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded))
val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey)
.addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo))
.addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA))
.addExtension(Extension.keyUsage, false, certificateType.keyUsage)
.addExtension(Extension.extendedKeyUsage, false, keyPurposes)
if (nameConstraints != null) {
builder.addExtension(Extension.nameConstraints, true, nameConstraints)
}
return builder
}
/**
* Build and sign an X.509 certificate with the given signer.
*
* @param issuer name of the issuing entity.
* @param issuerSigner content signer to sign the certificate with.
* @param subject name of the certificate subject.
* @param subjectPublicKey public key of the certificate subject.
* @param validityWindow the time period the certificate is valid for.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
*/
fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerSigner: ContentSigner,
subject: X500Name, subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509CertificateHolder {
val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
return builder.build(issuerSigner).apply {
require(isValidOn(Date()))
}
}
/**
* Build and sign an X.509 certificate with CA cert private key.
*
* @param issuer name of the issuing entity.
* @param issuerKeyPair the public & private key to sign the certificate with.
* @param subject name of the certificate subject.
* @param subjectPublicKey public key of the certificate subject.
* @param validityWindow the time period the certificate is valid for.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
*/
fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerKeyPair: KeyPair,
subject: X500Name, subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509CertificateHolder {
val signatureScheme = findSignatureScheme(issuerKeyPair.private)
val provider = providerMap[signatureScheme.providerName]
val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
return builder.build(signer).apply {
require(isValidOn(Date()))
require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public)))
}
}
/**
* Create certificate signing request using provided information.
*/
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme): PKCS10CertificationRequest {
val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, providerMap[signatureScheme.providerName])
return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public).build(signer)
}
private class KeyInfoConverter(val signatureScheme: SignatureScheme) : AsymmetricKeyInfoConverter { private class KeyInfoConverter(val signatureScheme: SignatureScheme) : AsymmetricKeyInfoConverter {
override fun generatePublic(keyInfo: SubjectPublicKeyInfo?): PublicKey? = keyInfo?.let { decodePublicKey(signatureScheme, it.encoded) } override fun generatePublic(keyInfo: SubjectPublicKeyInfo?): PublicKey? = keyInfo?.let { decodePublicKey(signatureScheme, it.encoded) }
override fun generatePrivate(keyInfo: PrivateKeyInfo?): PrivateKey? = keyInfo?.let { decodePrivateKey(signatureScheme, it.encoded) } override fun generatePrivate(keyInfo: PrivateKeyInfo?): PrivateKey? = keyInfo?.let { decodePrivateKey(signatureScheme, it.encoded) }

View File

@ -0,0 +1,77 @@
@file:JvmName("X500NameUtils")
package net.corda.core.crypto
import org.bouncycastle.asn1.ASN1Encodable
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder
import org.bouncycastle.asn1.x500.style.BCStyle
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import java.security.KeyPair
import java.security.cert.X509Certificate
/**
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present.
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
*/
fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
/**
* Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this
* adds one.
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
*/
fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
/**
* Rebuild the distinguished name, replacing the common name with a value generated from the provided function.
*
* @param mutator a function to generate the new value from the previous one.
* @throws IllegalArgumentException if the distinguished name does not contain a common name element.
*/
private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name {
val builder = X500NameBuilder(BCStyle.INSTANCE)
var matched = false
this.rdNs.forEach { rdn ->
rdn.typesAndValues.forEach { typeAndValue ->
when (typeAndValue.type) {
BCStyle.CN -> {
matched = true
builder.addRDN(typeAndValue.type, mutator(typeAndValue.value))
}
else -> {
builder.addRDN(typeAndValue)
}
}
}
}
require(matched) { "Input X.500 name must include a common name (CN) attribute: ${this}" }
return builder.build()
}
val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.first?.value?.toString()
val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
val X500Name.locationOrNull: String? get() = try {
location
} catch (e: Exception) {
null
}
val X509Certificate.subject: X500Name get() = X509CertificateHolder(encoded).subject
val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
/**
* Generate a distinguished name from the provided values.
*/
@JvmOverloads
fun getX509Name(myLegalName: String, nearestCity: String, email: String, country: String? = null): X500Name {
return X500NameBuilder(BCStyle.INSTANCE).let { builder ->
builder.addRDN(BCStyle.CN, myLegalName)
builder.addRDN(BCStyle.L, nearestCity)
country?.let { builder.addRDN(BCStyle.C, it) }
builder.addRDN(BCStyle.E, email)
builder.build()
}
}
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)

View File

@ -5,18 +5,13 @@ import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowException import net.corda.core.flows.FlowException
import net.corda.core.identity.AbstractParty import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.identity.Party
import net.corda.core.messaging.DataFeed
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NonEmptySet import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.OpaqueBytes
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.security.PublicKey
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
@ -224,29 +219,7 @@ interface VaultService {
fun getTransactionNotes(txnId: SecureHash): Iterable<String> fun getTransactionNotes(txnId: SecureHash): Iterable<String>
/** // DOCEND VaultStatesQuery
* Generate a transaction that moves an amount of currency to the given pubkey.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param amount How much currency to send.
* @param to a key of the recipient.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@Throws(InsufficientBalanceException::class)
@Suspendable
fun generateSpend(tx: TransactionBuilder,
amount: Amount<Currency>,
to: AbstractParty,
onlyFromParties: Set<AbstractParty>? = null): Pair<TransactionBuilder, List<PublicKey>>
/** /**
* 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 output simultaneously.
@ -257,7 +230,10 @@ interface VaultService {
/** /**
* Reserve a set of [StateRef] for a given [UUID] unique identifier. * Reserve a set of [StateRef] for a given [UUID] unique identifier.
* Typically, the unique identifier will refer to a Flow lockId associated with a [Transaction] in an in-flight flow. * Typically, the unique identifier will refer to a [FlowLogic.runId.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.
* In the case of coin selection, soft locks are automatically taken upon gathering relevant unconsumed input refs. * In the case of coin selection, soft locks are automatically taken upon gathering relevant unconsumed input refs.
* *
* @throws [StatesNotAvailableException] when not possible to softLock all of requested [StateRef] * @throws [StatesNotAvailableException] when not possible to softLock all of requested [StateRef]
@ -273,21 +249,32 @@ interface VaultService {
* are consumed as part of cash spending. * are consumed as part of cash spending.
*/ */
fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>? = null) fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>? = null)
// DOCEND SoftLockAPI // DOCEND SoftLockAPI
/** /**
* TODO: this function should be private to the vault, but currently Cash Exit functionality * Helper function to combine using [VaultQueryService] calls to determine spendable states and soft locking them.
* is implemented in a separate module (finance) and requires access to it. * 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 eligibleStatesQuery A custom query object that selects down to the appropriate subset of all states of the
* [contractType]. e.g. by selecting on account, issuer, etc. The query is internally augmented with the 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 contractType class type of the result set.
* @return Returns a locked subset of the [eligibleStatesQuery] sufficient to satisfy the requested amount,
* or else an empty list and no change in the stored lock states when their are insufficient resources available.
*/ */
@Suspendable @Suspendable
fun <T : ContractState> unconsumedStatesForSpending(amount: Amount<Currency>, @Throws(StatesNotAvailableException::class)
onlyFromIssuerParties: Set<AbstractParty>? = null, fun <T : FungibleAsset<U>, U : Any> tryLockFungibleStatesForSpending(lockId: UUID,
notary: Party? = null, eligibleStatesQuery: QueryCriteria,
lockId: UUID, amount: Amount<U>,
withIssuerRefs: Set<OpaqueBytes>? = null): List<StateAndRef<T>> contractType: Class<out T>): List<StateAndRef<T>>
} }
class StatesNotAvailableException(override val message: String?, override val cause: Throwable? = null) : FlowException(message, cause) { class StatesNotAvailableException(override val message: String?, override val cause: Throwable? = null) : FlowException(message, cause) {
override fun toString() = "Soft locking error: $message" override fun toString() = "Soft locking error: $message"
} }

View File

@ -0,0 +1,30 @@
package net.corda.core.schemas.converters
import net.corda.core.identity.AbstractParty
import net.corda.core.node.services.IdentityService
import org.bouncycastle.asn1.x500.X500Name
import javax.persistence.AttributeConverter
import javax.persistence.Converter
/**
* Converter to persist a party as its's well known identity (where resolvable)
* Completely anonymous parties are stored as null (to preserve privacy)
*/
@Converter(autoApply = true)
class AbstractPartyToX500NameAsStringConverter(val identitySvc: IdentityService) : AttributeConverter<AbstractParty, String> {
override fun convertToDatabaseColumn(party: AbstractParty?): String? {
party?.let {
return identitySvc.partyFromAnonymous(party)?.toString()
}
return null // non resolvable anonymous parties
}
override fun convertToEntityAttribute(dbData: String?): AbstractParty? {
dbData?.let {
val party = identitySvc.partyFromX500Name(X500Name(dbData))
return party as AbstractParty
}
return null // non resolvable anonymous parties are stored as nulls
}
}

View File

@ -5,6 +5,7 @@ import net.corda.core.crypto.TransactionSignature
import net.corda.core.crypto.isFulfilledBy import net.corda.core.crypto.isFulfilledBy
import net.corda.core.transactions.SignedTransaction.SignaturesMissingException import net.corda.core.transactions.SignedTransaction.SignaturesMissingException
import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.toNonEmptySet
import java.security.InvalidKeyException
import java.security.PublicKey import java.security.PublicKey
import java.security.SignatureException import java.security.SignatureException
@ -52,9 +53,10 @@ interface TransactionWithSignatures : NamedByHash {
* corrupt. If you use this function directly you'll need to do the other checks yourself. Probably you * corrupt. If you use this function directly you'll need to do the other checks yourself. Probably you
* want [verifySignatures] instead. * want [verifySignatures] instead.
* *
* @throws InvalidKeyException if the key on a signature is invalid.
* @throws SignatureException if a signature fails to verify. * @throws SignatureException if a signature fails to verify.
*/ */
@Throws(SignatureException::class) @Throws(InvalidKeyException::class, SignatureException::class)
fun checkSignaturesAreValid() { fun checkSignaturesAreValid() {
for (sig in sigs) { for (sig in sigs) {
sig.verify(id) sig.verify(id)

View File

@ -1,32 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class AllOfTests {
@Test
fun minimal() {
val counter = AtomicInteger(0)
val clause = AllOf(matchedClause(counter), matchedClause(counter))
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of two clauses
assertEquals(2, counter.get())
}
@Test
fun `not all match`() {
val clause = AllOf(matchedClause(), unmatchedClause())
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>()) }
}
}

View File

@ -1,45 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.PrivacySalt
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction
import org.junit.Test
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class AnyOfTests {
@Test
fun minimal() {
val counter = AtomicInteger(0)
val clause = AnyOf(matchedClause(counter), matchedClause(counter))
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of two clauses
assertEquals(2, counter.get())
}
@Test
fun `not all match`() {
val counter = AtomicInteger(0)
val clause = AnyOf(matchedClause(counter), unmatchedClause(counter))
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
// Check that we've run the verify() function of one clause
assertEquals(1, counter.get())
}
@Test
fun `none match`() {
val counter = AtomicInteger(0)
val clause = AnyOf(unmatchedClause(counter), unmatchedClause(counter))
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
assertFailsWith(IllegalArgumentException::class) {
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
}
}
}

View File

@ -1,30 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.AuthenticatedObject
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.ContractState
import net.corda.core.transactions.LedgerTransaction
import java.util.concurrent.atomic.AtomicInteger
internal fun matchedClause(counter: AtomicInteger? = null) = object : Clause<ContractState, CommandData, Unit>() {
override val requiredCommands: Set<Class<out CommandData>> = emptySet()
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> {
counter?.incrementAndGet()
return emptySet()
}
}
/** A clause that can never be matched */
internal fun unmatchedClause(counter: AtomicInteger? = null) = object : Clause<ContractState, CommandData, Unit>() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(object : CommandData {}.javaClass)
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> {
counter?.incrementAndGet()
return emptySet()
}
}

View File

@ -1,40 +0,0 @@
package net.corda.core.contracts.clauses
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction
import net.corda.testing.contracts.DummyContract
import org.junit.Test
import kotlin.test.assertFailsWith
/**
* Tests for the clause verifier.
*/
class VerifyClausesTests {
/** Very simple check that the function doesn't error when given any clause */
@Test
fun minimal() {
val clause = object : Clause<ContractState, CommandData, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> = emptySet()
}
val tx = LedgerTransaction(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
verifyClause(tx, clause, emptyList<AuthenticatedObject<CommandData>>())
}
@Test
fun errorSuperfluousCommands() {
val clause = object : Clause<ContractState, CommandData, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<CommandData>>, groupingKey: Unit?): Set<CommandData> = emptySet()
}
val command = AuthenticatedObject(emptyList(), emptyList(), DummyContract.Commands.Create())
val tx = LedgerTransaction(emptyList(), emptyList(), listOf(command), emptyList(), SecureHash.randomSHA256(), null, null, PrivacySalt())
// The clause is matched, but doesn't mark the command as consumed, so this should error
assertFailsWith<IllegalStateException> { verifyClause(tx, clause, listOf(command)) }
}
}

View File

@ -8,9 +8,7 @@ import net.corda.core.internal.declaredField
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.node.utilities.loadKeyStore import net.corda.node.utilities.*
import net.corda.node.utilities.loadOrCreateKeyStore
import net.corda.node.utilities.save
import net.corda.testing.TestDependencyInjectionBase import net.corda.testing.TestDependencyInjectionBase
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.junit.Rule import org.junit.Rule

View File

@ -1,9 +1,7 @@
package net.corda.core.crypto package net.corda.core.crypto
import net.corda.core.internal.toTypedArray import net.corda.core.internal.toTypedArray
import net.corda.node.utilities.KEYSTORE_TYPE import net.corda.node.utilities.*
import net.corda.node.utilities.addOrReplaceCertificate
import net.corda.node.utilities.addOrReplaceKey
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.GeneralName import org.bouncycastle.asn1.x509.GeneralName
import org.bouncycastle.asn1.x509.GeneralSubtree import org.bouncycastle.asn1.x509.GeneralSubtree
@ -20,13 +18,13 @@ class X509NameConstraintsTest {
private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> { private fun makeKeyStores(subjectName: X500Name, nameConstraints: NameConstraints): Pair<KeyStore, KeyStore> {
val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val rootKeys = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(X509Utilities.getX509Name("Corda Root CA","London","demo@r3.com",null), rootKeys) val rootCACert = X509Utilities.createSelfSignedCACertificate(getX509Name("Corda Root CA", "London", "demo@r3.com", null), rootKeys)
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, X509Utilities.getX509Name("Corda Intermediate CA","London","demo@r3.com",null), intermediateCAKeyPair.public) val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootKeys, getX509Name("Corda Intermediate CA", "London", "demo@r3.com", null), intermediateCAKeyPair.public)
val clientCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val clientCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, X509Utilities.getX509Name("Corda Client CA","London","demo@r3.com",null), clientCAKeyPair.public, nameConstraints = nameConstraints) val clientCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, intermediateCACert, intermediateCAKeyPair, getX509Name("Corda Client CA", "London", "demo@r3.com", null), clientCAKeyPair.public, nameConstraints = nameConstraints)
val keyPass = "password" val keyPass = "password"
val trustStore = KeyStore.getInstance(KEYSTORE_TYPE) val trustStore = KeyStore.getInstance(KEYSTORE_TYPE)

View File

@ -93,6 +93,13 @@ Several examples of entities and mappings are provided in the codebase, includin
.. literalinclude:: ../../finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt .. literalinclude:: ../../finance/src/main/kotlin/net/corda/schemas/CashSchemaV1.kt
:language: kotlin :language: kotlin
Identity mapping
----------------
Schema entity attributes defined by identity types (``AbstractParty``, ``Party``, ``AnonymousParty``) are automatically
processed to ensure only the ``X500Name`` of the identity is persisted where an identity is well known, otherwise a null
value is stored in the associated column. To preserve privacy, identity keys are never persisted. Developers should use
the ``IdentityService`` to resolve keys from well know X500 identity names.
JDBC session JDBC session
------------ ------------
Apps may also interact directly with the underlying Node's database by using a standard Apps may also interact directly with the underlying Node's database by using a standard

View File

@ -7,8 +7,36 @@ from the previous milestone release.
UNRELEASED UNRELEASED
---------- ----------
* The concept of ``TransactionType`` has been removed. Transactions no longer carry a `type` property. All usages of * Vault Query fix: filter by multiple issuer names in ``FungibleAssetQueryCriteria``
``TransactionType.General.Builder()`` have to be replaced with ``TransactionBuilder()``.
* Following deprecated methods have been removed:
* In ``DataFeed``
* ``first`` and ``current``, replaced by ``snapshot``
* ``second`` and ``future``, replaced by ``updates``
* In ``CordaRPCOps``
* ``stateMachinesAndUpdates``, replaced by ``stateMachinesFeed``
* ``verifiedTransactions``, replaced by ``verifiedTransactionsFeed``
* ``stateMachineRecordedTransactionMapping``, replaced by ``stateMachineRecordedTransactionMappingFeed``
* ``networkMapUpdates``, replaced by ``networkMapFeed``
* Due to security concerns and the need to remove the concept of state relevancy (which isn't needed in Corda),
``ResolveTransactionsFlow`` has been made internal. Instead merge the receipt of the ``SignedTransaction`` and the subsequent
sub-flow call to ``ResolveTransactionsFlow`` with a single call to ``ReceiveTransactionFlow``. The flow running on the counterparty
must use ``SendTransactionFlow`` at the correct place. There is also ``ReceiveStateAndRefFlow`` and ``SendStateAndRefFlow`` for
dealing with ``StateAndRef``s.
* Vault query soft locking enhancements and deprecations
* removed original ``VaultService`` ``softLockedStates` query mechanism.
* introduced improved ``SoftLockingCondition`` filterable attribute in ``VaultQueryCriteria`` to enable specification
of different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified
by set of lock ids)
* Trader demo now issues cash and commercial paper directly from the bank node, rather than the seller node self-issuing
commercial paper but labelling it as if issued by the bank.
Milestone 14
------------
* Changes in ``NodeInfo``: * Changes in ``NodeInfo``:
@ -37,7 +65,7 @@ UNRELEASED
* Moved the core flows previously found in ``net.corda.flows`` into ``net.corda.core.flows``. This is so that all packages * Moved the core flows previously found in ``net.corda.flows`` into ``net.corda.core.flows``. This is so that all packages
in the ``core`` module begin with ``net.corda.core``. in the ``core`` module begin with ``net.corda.core``.
* ``FinalityFlow`` now has can be subclassed, and the ``broadcastTransaction`` and ``lookupParties`` function can be * ``FinalityFlow`` can now be subclassed, and the ``broadcastTransaction`` and ``lookupParties`` function can be
overriden in order to handle cases where no single transaction participant is aware of all parties, and therefore overriden in order to handle cases where no single transaction participant is aware of all parties, and therefore
the transaction must be relayed between participants rather than sent from a single node. the transaction must be relayed between participants rather than sent from a single node.
@ -60,32 +88,45 @@ UNRELEASED
* ``Cordformation`` adds a ``corda`` and ``cordaRuntime`` configuration to projects which cordapp developers should * ``Cordformation`` adds a ``corda`` and ``cordaRuntime`` configuration to projects which cordapp developers should
use to exclude core Corda JARs from being built into Cordapp fat JARs. use to exclude core Corda JARs from being built into Cordapp fat JARs.
.. Milestone 15: * ``database`` field in ``AbstractNode`` class has changed the type from ``org.jetbrains.exposed.sql.Database`` to
net.corda.node.utilities.CordaPersistence - no change is needed for the typical use
(i.e. services.database.transaction { code block } ) however a change is required when Database was explicitly declared
* Vault Query fix: filter by multiple issuer names in ``FungibleAssetQueryCriteria`` * ``DigitalSignature.LegallyIdentifiable``, previously used to identify a signer (e.g. in Oracles), has been removed.
One can use the public key to derive the corresponding identity.
* Following deprecated methods have been removed: * Vault Query improvements and fixes:
* In ``DataFeed``
* ``first`` and ``current``, replaced by ``snapshot``
* ``second`` and ``future``, replaced by ``updates``
* In ``CordaRPCOps``
* ``stateMachinesAndUpdates``, replaced by ``stateMachinesFeed``
* ``verifiedTransactions``, replaced by ``verifiedTransactionsFeed``
* ``stateMachineRecordedTransactionMapping``, replaced by ``stateMachineRecordedTransactionMappingFeed``
* ``networkMapUpdates``, replaced by ``networkMapFeed``
* Due to security concerns and the need to remove the concept of state relevancy (which isn't needed in Corda), * FIX inconsistent behaviour: Vault Query defaults to UNCONSUMED in all QueryCriteria types
``ResolveTransactionsFlow`` has been made internal. Instead merge the receipt of the ``SignedTransaction`` and the subsequent
sub-flow call to ``ResolveTransactionsFlow`` with a single call to ``ReceiveTransactionFlow``. The flow running on the counterparty
must use ``SendTransactionFlow`` at the correct place. There is also ``ReceiveStateAndRefFlow`` and ``SendStateAndRefFlow`` for
dealing with ``StateAndRef``s.
* FIX serialization error: Vault Query over RPC when using custom attributes using VaultCustomQueryCriteria.
* Vault query soft locking enhancements and deprecations * Aggregate function support: extended VaultCustomQueryCriteria and associated DSL to enable specification of
* removed original ``VaultService`` ``softLockedStates` query mechanism. Aggregate Functions (sum, max, min, avg, count) with, optional, group by clauses and sorting (on calculated aggregate)
* introduced improved ``SoftLockingCondition`` filterable attribute in ``VaultQueryCriteria`` to enable specification
of different soft locking retrieval behaviours (exclusive of soft locked states, soft locked states only, specified * Pagination simplification
by set of lock ids) Pagination continues to be optional, but with following changes:
- If no PageSpecification provided then a maximum of MAX_PAGE_SIZE (200) results will be returned, otherwise we fail-fast with a ``VaultQueryException`` to alert the API user to the need to specify a PageSpecification.
Internally, we no longer need to calculate a results count (thus eliminating an expensive SQL query) unless a PageSpecification is supplied (note: that a value of -1 is returned for total_results in this scenario).
Internally, we now use the AggregateFunction capability to perform the count.
- Paging now starts from 1 (was previously 0).
* Additional Sort criteria: by StateRef (or constituents: txId, index)
* Confidential identities API improvements
* Registering anonymous identities now takes in AnonymousPartyAndPath
* AnonymousParty.toString() now uses toStringShort() to match other toString() functions
* Add verifyAnonymousIdentity() function to verify without storing an identity
* Replace pathForAnonymous() with anonymousFromKey() which matches actual use-cases better
* Add unit test for fetching the anonymous identity from a key
* Update verifyAnonymousIdentity() function signature to match registerAnonymousIdentity()
* Rename AnonymisedIdentity to AnonymousPartyAndPath
* Remove certificate from AnonymousPartyAndPath as it's not actually used.
* Rename registerAnonymousIdentity() to verifyAndRegisterAnonymousIdentity()
* Added JPA ``AbstractPartyConverter`` to ensure identity schema attributes are persisted securely according to type
(well known party, resolvable anonymous party, completely anonymous party).
Milestone 13 Milestone 13
------------ ------------

View File

@ -26,6 +26,7 @@ import net.corda.testing.contracts.DummyState;
import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500Name;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.security.GeneralSecurityException;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.SignatureException; import java.security.SignatureException;
import java.time.Duration; import java.time.Duration;
@ -506,7 +507,7 @@ public class FlowCookbookJava {
twiceSignedTx.checkSignaturesAreValid(); twiceSignedTx.checkSignaturesAreValid();
// DOCEND 37 // DOCEND 37
} catch (SignatureException e) { } catch (GeneralSecurityException e) {
// Handle this as required. // Handle this as required.
} }

View File

@ -5,6 +5,7 @@ import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued import net.corda.core.contracts.Issued
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.withoutIssuer
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature import net.corda.core.crypto.TransactionSignature
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
@ -13,7 +14,6 @@ import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.builder import net.corda.core.node.services.vault.builder
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -31,9 +31,10 @@ private data class FxRequest(val tradeId: String,
val notary: Party? = null) val notary: Party? = null)
// DOCSTART 1 // DOCSTART 1
// This is equivalent to the VaultService.generateSpend // This is equivalent to the Cash.generateSpend
// Which is brought here to make the filtering logic more visible in the example // Which is brought here to make the filtering logic more visible in the example
private fun gatherOurInputs(serviceHub: ServiceHub, private fun gatherOurInputs(serviceHub: ServiceHub,
lockId: UUID,
amountRequired: Amount<Issued<Currency>>, amountRequired: Amount<Issued<Currency>>,
notary: Party?): Pair<List<StateAndRef<Cash.State>>, Long> { notary: Party?): Pair<List<StateAndRef<Cash.State>>, Long> {
// extract our identity for convenience // extract our identity for convenience
@ -41,34 +42,25 @@ private fun gatherOurInputs(serviceHub: ServiceHub,
val ourParties = ourKeys.map { serviceHub.identityService.partyFromKey(it) ?: throw IllegalStateException("Unable to resolve party from key") } val ourParties = ourKeys.map { serviceHub.identityService.partyFromKey(it) ?: throw IllegalStateException("Unable to resolve party from key") }
val fungibleCriteria = QueryCriteria.FungibleAssetQueryCriteria(owner = ourParties) val fungibleCriteria = QueryCriteria.FungibleAssetQueryCriteria(owner = ourParties)
val notaryName = if (notary != null) notary.name else serviceHub.networkMapCache.getAnyNotary()!!.name
val vaultCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(notaryName = listOf(notaryName))
val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.equal(amountRequired.token.product.currencyCode) } val logicalExpression = builder { CashSchemaV1.PersistentCashState::currency.equal(amountRequired.token.product.currencyCode) }
val cashCriteria = QueryCriteria.VaultCustomQueryCriteria(logicalExpression) val cashCriteria = QueryCriteria.VaultCustomQueryCriteria(logicalExpression)
// Collect cash type inputs val fullCriteria = fungibleCriteria.and(vaultCriteria).and(cashCriteria)
val suitableCashStates = serviceHub.vaultQueryService.queryBy<Cash.State>(fungibleCriteria.and(cashCriteria)).states
require(!suitableCashStates.isEmpty()) { "Insufficient funds" }
var remaining = amountRequired.quantity val eligibleStates = serviceHub.vaultService.tryLockFungibleStatesForSpending<Cash.State, Currency>(lockId, fullCriteria, amountRequired.withoutIssuer(), Cash.State::class.java)
// We will need all of the inputs to be on the same notary.
// For simplicity we just filter on the first notary encountered
// A production quality flow would need to migrate notary if the
// the amounts were not sufficient in any one notary
val sourceNotary: Party = notary ?: suitableCashStates.first().state.notary
val inputsList = mutableListOf<StateAndRef<Cash.State>>() check(eligibleStates.isNotEmpty()) { "Insufficient funds" }
// Iterate over filtered cash states to gather enough to pay val amount = eligibleStates.fold(0L) { tot, x -> tot + x.state.data.amount.quantity }
for (cash in suitableCashStates.filter { it.state.notary == sourceNotary }) { val change = amount - amountRequired.quantity
inputsList += cash
if (remaining <= cash.state.data.amount.quantity) { return Pair(eligibleStates, change)
return Pair(inputsList, cash.state.data.amount.quantity - remaining)
}
remaining -= cash.state.data.amount.quantity
}
throw IllegalStateException("Insufficient funds")
} }
// DOCEND 1 // DOCEND 1
private fun prepareOurInputsAndOutputs(serviceHub: ServiceHub, request: FxRequest): Pair<List<StateAndRef<Cash.State>>, List<Cash.State>> { private fun prepareOurInputsAndOutputs(serviceHub: ServiceHub, lockId: UUID, request: FxRequest): Pair<List<StateAndRef<Cash.State>>, List<Cash.State>> {
// Create amount with correct issuer details // Create amount with correct issuer details
val sellAmount = request.amount val sellAmount = request.amount
@ -78,7 +70,7 @@ private fun prepareOurInputsAndOutputs(serviceHub: ServiceHub, request: FxReques
// we will use query manually in the helper function below. // we will use query manually in the helper function below.
// Putting this into a non-suspendable function also prevents issues when // Putting this into a non-suspendable function also prevents issues when
// the flow is suspended. // the flow is suspended.
val (inputs, residual) = gatherOurInputs(serviceHub, sellAmount, request.notary) val (inputs, residual) = gatherOurInputs(serviceHub, lockId, sellAmount, request.notary)
// Build and an output state for the counterparty // Build and an output state for the counterparty
val transferedFundsOutput = Cash.State(sellAmount, request.counterparty) val transferedFundsOutput = Cash.State(sellAmount, request.counterparty)
@ -119,7 +111,7 @@ class ForeignExchangeFlow(val tradeId: String,
} else throw IllegalArgumentException("Our identity must be one of the parties in the trade.") } else throw IllegalArgumentException("Our identity must be one of the parties in the trade.")
// Call the helper method to identify suitable inputs and make the outputs // Call the helper method to identify suitable inputs and make the outputs
val (outInputStates, ourOutputStates) = prepareOurInputsAndOutputs(serviceHub, localRequest) val (outInputStates, ourOutputStates) = prepareOurInputsAndOutputs(serviceHub, runId.uuid, localRequest)
// identify the notary for our states // identify the notary for our states
val notary = outInputStates.first().state.notary val notary = outInputStates.first().state.notary
@ -231,7 +223,7 @@ class ForeignExchangeRemoteFlow(val source: Party) : FlowLogic<Unit>() {
// we will use query manually in the helper function below. // we will use query manually in the helper function below.
// Putting this into a non-suspendable function also prevent issues when // Putting this into a non-suspendable function also prevent issues when
// the flow is suspended. // the flow is suspended.
val (ourInputState, ourOutputState) = prepareOurInputsAndOutputs(serviceHub, request) val (ourInputState, ourOutputState) = prepareOurInputsAndOutputs(serviceHub, runId.uuid, request)
// Send back our proposed states and await the full transaction to verify // Send back our proposed states and await the full transaction to verify
val ourKey = serviceHub.keyManagementService.filterMyKeys(ourInputState.flatMap { it.state.data.participants }.map { it.owningKey }).single() val ourKey = serviceHub.keyManagementService.filterMyKeys(ourInputState.flatMap { it.state.data.participants }.map { it.owningKey }).single()

View File

@ -279,7 +279,7 @@ This code is longer but no more complicated. Here are some things to pay attenti
1. We do some sanity checking on the proposed trade transaction received from the seller to ensure we're being offered 1. We do some sanity checking on the proposed trade transaction received from the seller to ensure we're being offered
what we expected to be offered. what we expected to be offered.
2. We create a cash spend using ``VaultService.generateSpend``. You can read the vault documentation to learn more about this. 2. We create a cash spend using ``Cash.generateSpend``. You can read the vault documentation to learn more about this.
3. We access the *service hub* as needed to access things that are transient and may change or be recreated 3. We access the *service hub* as needed to access things that are transient and may change or be recreated
whilst a flow is suspended, such as the wallet or the network map. whilst a flow is suspended, such as the wallet or the network map.
4. We call ``CollectSignaturesFlow`` as a subflow to send the unfinished, still-invalid transaction to the seller so 4. We call ``CollectSignaturesFlow`` as a subflow to send the unfinished, still-invalid transaction to the seller so

View File

@ -78,8 +78,8 @@ define an ``IOUState``:
class IOUState(val value: Int, class IOUState(val value: Int,
val lender: Party, val lender: Party,
val borrower: Party) : ContractState { val borrower: Party,
override val contract: IOUContract = IOUContract() override val contract: TemplateContract) : ContractState {
override val participants get() = listOf(lender, borrower) override val participants get() = listOf(lender, borrower)
} }
@ -153,4 +153,4 @@ We've defined an ``IOUState`` that can be used to represent IOUs as shared facts
Corda are simply JVM classes that implement the ``ContractState`` interface. They can have any additional properties and Corda are simply JVM classes that implement the ``ContractState`` interface. They can have any additional properties and
methods you like. methods you like.
Next, we'll be writing our ``IOUContract`` to control the evolution of these shared facts over time. Next, we'll be writing our ``IOUContract`` to control the evolution of these shared facts over time.

View File

@ -6,9 +6,45 @@ Here are release notes for each snapshot release from M9 onwards.
Unreleased Unreleased
---------- ----------
The transaction finalisation flow (``FinalityFlow``) has had hooks for alternative implementations, for example in Milestone 14
------------
This release continues with the goal to improve API stability and developer friendliness. There have also been more
bug fixes and other improvements across the board.
The CorDapp template repository has been replaced with a specific repository for
`Java <https://github.com/corda/cordapp-template-java>`_ and `Kotlin <https://github.com/corda/cordapp-template-kotlin>`_
to improve the experience of starting a new project and to simplify the build system.
It is now possible to specify multiple IP addresses and legal identities for a single node, allowing node operators
more flexibility in setting up nodes.
A format has been introduced for CorDapp JARs that standardises the contents of CorDapps across nodes. This new format
now requires CorDapps to contain their own external dependencies. This paves the way for significantly improved
dependency management for CorDapps with the release of `Jigsaw (Java Modules) <http://openjdk.java.net/projects/jigsaw/>`_. For those using non-gradle build systems it is important
to read :doc:`cordapp-build-systems` to learn more. Those using our ``cordformation`` plugin simply need to update
to the latest version (``0.14.0``) to get the fixes.
We've now begun the process of demarcating which classes are part of our public API and which ones are internal.
Everything found in ``net.corda.core.internal`` and other packages in the ``net.corda`` namespace which has ``.internal`` in it are
considered internal and not for public use. In a future release any CorDapp using these packages will fail to load, and
when we migrate to Jigsaw these will not be exported.
The transaction finalisation flow (``FinalityFlow``) has had hooks added for alternative implementations, for example in
scenarios where no single participant in a transaction is aware of the well known identities of all parties. scenarios where no single participant in a transaction is aware of the well known identities of all parties.
DemoBench has a fix for a rare but inconvenient crash that can occur when sharing your display across multiple devices,
e.g. a projector while performing demonstrations in front of an audience.
Guava types are being removed because Guava does not have backwards compatibility across versions, which has serious
issues when multiple libraries depend on different versions of the library.
The identity service API has been tweaked, primarily so anonymous identity registration now takes in
AnonymousPartyAndPath rather than the individual components of the identity, as typically the caller will have
an AnonymousPartyAndPath instance. See change log for further detail.
Upgrading to this release is strongly recommended in order to keep up with the API changes, removal and additions.
Milestone 13 Milestone 13
------------ ------------

View File

@ -32,7 +32,7 @@ To run from the command line in Unix:
1. Run ``./gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under ``samples/trader-demo/build/nodes`` 1. Run ``./gradlew samples:trader-demo:deployNodes`` to create a set of configs and installs under ``samples/trader-demo/build/nodes``
2. Run ``./samples/trader-demo/build/nodes/runnodes`` to open up four new terminals with the four nodes 2. Run ``./samples/trader-demo/build/nodes/runnodes`` to open up four new terminals with the four nodes
3. Run ``./gradlew samples:trader-demo:runBuyer`` to instruct the buyer node to request issuance of some cash from the Bank of Corda node 3. Run ``./gradlew samples:trader-demo:runBank`` to instruct the bank node to issue cash and commercial paper to the buyer and seller nodes respectively.
4. Run ``./gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch`` 4. Run ``./gradlew samples:trader-demo:runSeller`` to trigger the transaction. If you entered ``flow watch``
you can see flows running on both sides of transaction. Additionally you should see final trade information displayed you can see flows running on both sides of transaction. Additionally you should see final trade information displayed
to your terminal. to your terminal.

View File

@ -116,11 +116,12 @@ standard ``CashState`` in the ``:financial`` Gradle module. The Cash
contract uses ``FungibleAsset`` states to model holdings of contract uses ``FungibleAsset`` states to model holdings of
interchangeable assets and allow the split/merge and summing of interchangeable assets and allow the split/merge and summing of
states to meet a contractual obligation. We would normally use the states to meet a contractual obligation. We would normally use the
``generateSpend`` method on the ``VaultService`` to gather the required ``Cash.generateSpend`` method to gather the required
amount of cash into a ``TransactionBuilder``, set the outputs and move amount of cash into a ``TransactionBuilder``, set the outputs and move
command. However, to elucidate more clearly example flow code is shown command. However, to elucidate more clearly example flow code is shown
here that will manually carry out the inputs queries by specifying relevant here that will manually carry out the inputs queries by specifying relevant
query criteria filters to the ``queryBy`` method of the ``VaultQueryService``. query criteria filters to the ``tryLockFungibleStatesForSpending`` method
of the ``VaultService``.
.. literalinclude:: example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt .. literalinclude:: example-code/src/main/kotlin/net/corda/docs/FxTransactionBuildTutorial.kt
:language: kotlin :language: kotlin

View File

@ -680,9 +680,9 @@ Finally, we can do redemption.
.. sourcecode:: kotlin .. sourcecode:: kotlin
@Throws(InsufficientBalanceException::class) @Throws(InsufficientBalanceException::class)
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, vault: VaultService) { fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, services: ServiceHub) {
// Add the cash movement using the states in our vault. // Add the cash movement using the states in our vault.
vault.generateSpend(tx, paper.state.data.faceValue.withoutIssuer(), paper.state.data.owner) Cash.generateSpend(services, tx, paper.state.data.faceValue.withoutIssuer(), paper.state.data.owner)
tx.addInputState(paper) tx.addInputState(paper)
tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner.owningKey)) tx.addCommand(Command(Commands.Redeem(), paper.state.data.owner.owningKey))
} }
@ -698,7 +698,7 @@ from the issuer of the commercial paper to the current owner. If we don't have e
an exception is thrown. Then we add the paper itself as an input, but, not an output (as we wish to remove it an exception is thrown. Then we add the paper itself as an input, but, not an output (as we wish to remove it
from the ledger). Finally, we add a Redeem command that should be signed by the owner of the commercial paper. from the ledger). Finally, we add a Redeem command that should be signed by the owner of the commercial paper.
.. warning:: The amount we pass to the ``generateSpend`` function has to be treated first with ``withoutIssuer``. .. warning:: The amount we pass to the ``Cash.generateSpend`` function has to be treated first with ``withoutIssuer``.
This reflects the fact that the way we handle issuer constraints is still evolving; the commercial paper This reflects the fact that the way we handle issuer constraints is still evolving; the commercial paper
contract requires payment in the form of a currency issued by a specific party (e.g. the central bank, contract requires payment in the form of a currency issued by a specific party (e.g. the central bank,
or the issuers own bank perhaps). But the vault wants to assemble spend transactions using cash states from or the issuers own bank perhaps). But the vault wants to assemble spend transactions using cash states from

View File

@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import kotlin.Pair; import kotlin.Pair;
import kotlin.Unit; import kotlin.Unit;
import net.corda.contracts.asset.Cash;
import net.corda.contracts.asset.CashKt; import net.corda.contracts.asset.CashKt;
import net.corda.core.contracts.*; import net.corda.core.contracts.*;
import net.corda.core.crypto.SecureHash; import net.corda.core.crypto.SecureHash;
@ -12,15 +13,14 @@ import net.corda.core.crypto.testing.NullPublicKey;
import net.corda.core.identity.AbstractParty; import net.corda.core.identity.AbstractParty;
import net.corda.core.identity.AnonymousParty; import net.corda.core.identity.AnonymousParty;
import net.corda.core.identity.Party; import net.corda.core.identity.Party;
import net.corda.core.node.services.VaultService; import net.corda.core.node.ServiceHub;
import net.corda.core.transactions.LedgerTransaction; import net.corda.core.transactions.LedgerTransaction;
import net.corda.core.transactions.TransactionBuilder; import net.corda.core.transactions.TransactionBuilder;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.time.Instant; import java.time.Instant;
import java.util.Currency; import java.util.*;
import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static net.corda.core.contracts.ContractsDSL.requireSingleCommand; import static net.corda.core.contracts.ContractsDSL.requireSingleCommand;
@ -255,8 +255,8 @@ public class JavaCommercialPaper implements Contract {
} }
@Suspendable @Suspendable
public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, VaultService vault) throws InsufficientBalanceException { public void generateRedeem(TransactionBuilder tx, StateAndRef<State> paper, ServiceHub services) throws InsufficientBalanceException {
vault.generateSpend(tx, Structures.withoutIssuer(paper.getState().getData().getFaceValue()), paper.getState().getData().getOwner(), null); Cash.generateSpend(services, tx, Structures.withoutIssuer(paper.getState().getData().getFaceValue()), paper.getState().getData().getOwner(), Collections.EMPTY_SET);
tx.addInputState(paper); tx.addInputState(paper);
tx.addCommand(new Command<>(new Commands.Redeem(), paper.getState().getData().getOwner().getOwningKey())); tx.addCommand(new Command<>(new Commands.Redeem(), paper.getState().getData().getOwner().getOwningKey()));
} }

View File

@ -1,6 +1,7 @@
package net.corda.contracts package net.corda.contracts
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.sumCashBy import net.corda.contracts.asset.sumCashBy
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
@ -9,7 +10,7 @@ import net.corda.core.crypto.toBase58String
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.Emoji import net.corda.core.internal.Emoji
import net.corda.core.node.services.VaultService import net.corda.core.node.ServiceHub
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState import net.corda.core.schemas.QueryableState
@ -144,7 +145,6 @@ class CommercialPaper : Contract {
"output values sum to more than the inputs" using (output.faceValue.quantity > 0) "output values sum to more than the inputs" using (output.faceValue.quantity > 0)
"the maturity date is not in the past" using (time < output.maturityDate) "the maturity date is not in the past" using (time < output.maturityDate)
// Don't allow an existing CP state to be replaced by this issuance. // Don't allow an existing CP state to be replaced by this issuance.
// TODO: this has a weird/incorrect assertion string because it doesn't quite match the logic in the clause version.
// TODO: Consider how to handle the case of mistaken issuances, or other need to patch. // TODO: Consider how to handle the case of mistaken issuances, or other need to patch.
"output values sum to more than the inputs" using inputs.isEmpty() "output values sum to more than the inputs" using inputs.isEmpty()
} }
@ -185,9 +185,9 @@ class CommercialPaper : Contract {
*/ */
@Throws(InsufficientBalanceException::class) @Throws(InsufficientBalanceException::class)
@Suspendable @Suspendable
fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, vault: VaultService) { fun generateRedeem(tx: TransactionBuilder, paper: StateAndRef<State>, services: ServiceHub) {
// Add the cash movement using the states in our vault. // Add the cash movement using the states in our vault.
vault.generateSpend(tx, paper.state.data.faceValue.withoutIssuer(), paper.state.data.owner) Cash.generateSpend(services, tx, paper.state.data.faceValue.withoutIssuer(), paper.state.data.owner)
tx.addInputState(paper) tx.addInputState(paper)
tx.addCommand(Commands.Redeem(), paper.state.data.owner.owningKey) tx.addCommand(Commands.Redeem(), paper.state.data.owner.owningKey)
} }

View File

@ -1,5 +1,7 @@
package net.corda.contracts.asset package net.corda.contracts.asset
import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.entropyToKeyPair
@ -9,16 +11,27 @@ import net.corda.core.crypto.toBase58String
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.Emoji import net.corda.core.internal.Emoji
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.QueryableState import net.corda.core.schemas.QueryableState
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.deserialize
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toHexString
import net.corda.core.utilities.toNonEmptySet
import net.corda.core.utilities.trace
import net.corda.schemas.CashSchemaV1 import net.corda.schemas.CashSchemaV1
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import java.math.BigInteger import java.math.BigInteger
import java.security.PublicKey import java.security.PublicKey
import java.sql.SQLException
import java.util.* import java.util.*
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// //
@ -212,6 +225,163 @@ class Cash : OnLedgerAsset<Currency, Cash.Commands, Cash.State>() {
"there is only a single issue command" using (cashCommands.count() == 1) "there is only a single issue command" using (cashCommands.count() == 1)
} }
} }
companion object {
// coin selection retry loop counter, sleep (msecs) and lock for selecting states
private val MAX_RETRIES = 5
private val RETRY_SLEEP = 100
private val spendLock: ReentrantLock = ReentrantLock()
/**
* Generate a transaction that moves an amount of currency to the given pubkey.
*
* Note: an [Amount] of [Currency] is only fungible for a given Issuer Party within a [FungibleAsset]
*
* @param services The [ServiceHub] to provide access to the database session.
* @param tx A builder, which may contain inputs, outputs and commands already. The relevant components needed
* to move the cash will be added on top.
* @param amount How much currency to send.
* @param to a key of the recipient.
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
* @return A [Pair] of the same transaction builder passed in as [tx], and the list of keys that need to sign
* the resulting transaction for it to be valid.
* @throws InsufficientBalanceException when a cash spending transaction fails because
* there is insufficient quantity for a given currency (and optionally set of Issuer Parties).
*/
@JvmStatic
@Throws(InsufficientBalanceException::class)
@Suspendable
fun generateSpend(services: ServiceHub,
tx: TransactionBuilder,
amount: Amount<Currency>,
to: AbstractParty,
onlyFromParties: Set<AbstractParty> = emptySet()): Pair<TransactionBuilder, List<PublicKey>> {
fun deriveState(txState: TransactionState<Cash.State>, amt: Amount<Issued<Currency>>, owner: AbstractParty)
= txState.copy(data = txState.data.copy(amount = amt, owner = owner))
// Retrieve unspent and unlocked cash states that meet our spending criteria.
val acceptableCoins = Cash.unconsumedCashStatesForSpending(services, amount, onlyFromParties, tx.notary, tx.lockId)
return OnLedgerAsset.generateSpend(tx, amount, to, acceptableCoins,
{ state, quantity, owner -> deriveState(state, quantity, owner) },
{ Cash().generateMoveCommand() })
}
/**
* An optimised query to gather Cash states that are available and retry if they are temporarily unavailable.
* @param services The service hub to allow access to the database session
* @param amount The amount of currency desired (ignoring issues, but specifying the currency)
* @param onlyFromIssuerParties If empty the operation ignores the specifics of the issuer,
* otherwise the set of eligible states wil be filtered to only include those from these issuers.
* @param notary If null the notary source is ignored, if specified then only states marked
* with this notary are included.
* @param lockId The [FlowLogic.runId.uuid] of the flow, which is used to soft reserve the states.
* Also, previous outputs of the flow will be eligible as they are implicitly locked with this id until the flow completes.
* @param withIssuerRefs If not empty the specific set of issuer references to match against.
* @return The matching states that were found. If sufficient funds were found these will be locked,
* otherwise what is available is returned unlocked for informational purposes.
*/
@JvmStatic
@Suspendable
fun unconsumedCashStatesForSpending(services: ServiceHub,
amount: Amount<Currency>,
onlyFromIssuerParties: Set<AbstractParty> = emptySet(),
notary: Party? = null,
lockId: UUID,
withIssuerRefs: Set<OpaqueBytes> = emptySet()): List<StateAndRef<Cash.State>> {
val issuerKeysStr = onlyFromIssuerParties.fold("") { left, right -> left + "('${right.owningKey.toBase58String()}')," }.dropLast(1)
val issuerRefsStr = withIssuerRefs.fold("") { left, right -> left + "('${right.bytes.toHexString()}')," }.dropLast(1)
val stateAndRefs = mutableListOf<StateAndRef<Cash.State>>()
// TODO: Need to provide a database provider independent means of performing this function.
// We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins:
// 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the
// running total of such an accumulator
// 2) H2 uses session variables to perform this accumulator function:
// http://www.h2database.com/html/functions.html#set
// 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries)
for (retryCount in 1..MAX_RETRIES) {
spendLock.withLock {
val statement = services.jdbcSession().createStatement()
try {
statement.execute("CALL SET(@t, 0);")
// we select spendable states irrespective of lock but prioritised by unlocked ones (Eg. null)
// the softLockReserve update will detect whether we try to lock states locked by others
val selectJoin = """
SELECT vs.transaction_id, vs.output_index, vs.contract_state, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id
FROM vault_states AS vs, contract_cash_states AS ccs
WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index
AND vs.state_status = 0
AND ccs.ccy_code = '${amount.token}' and @t < ${amount.quantity}
AND (vs.lock_id = '$lockId' OR vs.lock_id is null)
""" +
(if (notary != null)
" AND vs.notary_key = '${notary.owningKey.toBase58String()}'" else "") +
(if (onlyFromIssuerParties.isNotEmpty())
" AND ccs.issuer_key IN ($issuerKeysStr)" else "") +
(if (withIssuerRefs.isNotEmpty())
" AND ccs.issuer_ref IN ($issuerRefsStr)" else "")
// Retrieve spendable state refs
val rs = statement.executeQuery(selectJoin)
stateAndRefs.clear()
log.debug(selectJoin)
var totalPennies = 0L
while (rs.next()) {
val txHash = SecureHash.parse(rs.getString(1))
val index = rs.getInt(2)
val stateRef = StateRef(txHash, index)
val state = rs.getBytes(3).deserialize<TransactionState<Cash.State>>(context = SerializationDefaults.STORAGE_CONTEXT)
val pennies = rs.getLong(4)
totalPennies = rs.getLong(5)
val rowLockId = rs.getString(6)
stateAndRefs.add(StateAndRef(state, stateRef))
log.trace { "ROW: $rowLockId ($lockId): $stateRef : $pennies ($totalPennies)" }
}
if (stateAndRefs.isNotEmpty() && totalPennies >= amount.quantity) {
// we should have a minimum number of states to satisfy our selection `amount` criteria
log.trace("Coin selection for $amount retrieved ${stateAndRefs.count()} states totalling $totalPennies pennies: $stateAndRefs")
// With the current single threaded state machine available states are guaranteed to lock.
// TODO However, we will have to revisit these methods in the future multi-threaded.
services.vaultService.softLockReserve(lockId, (stateAndRefs.map { it.ref }).toNonEmptySet())
return stateAndRefs
}
log.trace("Coin selection requested $amount but retrieved $totalPennies pennies with state refs: ${stateAndRefs.map { it.ref }}")
// retry as more states may become available
} catch (e: SQLException) {
log.error("""Failed retrieving unconsumed states for: amount [$amount], onlyFromIssuerParties [$onlyFromIssuerParties], notary [$notary], lockId [$lockId]
$e.
""")
} catch (e: StatesNotAvailableException) { // Should never happen with single threaded state machine
stateAndRefs.clear()
log.warn(e.message)
// retry only if there are locked states that may become available again (or consumed with change)
} finally {
statement.close()
}
}
log.warn("Coin selection failed on attempt $retryCount")
// TODO: revisit the back off strategy for contended spending.
if (retryCount != MAX_RETRIES) {
Strand.sleep(RETRY_SLEEP * retryCount.toLong())
}
}
log.warn("Insufficient spendable states identified for $amount")
return stateAndRefs
}
}
} }
// Small DSL extensions. // Small DSL extensions.

View File

@ -226,8 +226,6 @@ abstract class OnLedgerAsset<T : Any, C : CommandData, S : FungibleAsset<T>> : C
* *
* @param tx transaction builder to add states and commands to. * @param tx transaction builder to add states and commands to.
* @param amountIssued the amount to be exited, represented as a quantity of issued currency. * @param amountIssued the amount to be exited, represented as a quantity of issued currency.
* @param changeKey the key to send any change to. This needs to be explicitly stated as the input states are not
* necessarily owned by us.
* @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is * @param assetStates the asset states to take funds from. No checks are done about ownership of these states, it is
* the responsibility of the caller to check that they do not exit funds held by others. * the responsibility of the caller to check that they do not exit funds held by others.
* @return the public keys which must sign the transaction for it to be valid. * @return the public keys which must sign the transaction for it to be valid.

View File

@ -11,8 +11,8 @@ import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.DEFAULT_PAGE_NUM import net.corda.core.node.services.vault.DEFAULT_PAGE_NUM
import net.corda.core.node.services.vault.PageSpecification import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import java.util.* import java.util.*
@ -41,7 +41,7 @@ class CashExitFlow(val amount: Amount<Currency>, val issueRef: OpaqueBytes, prog
progressTracker.currentStep = GENERATING_TX progressTracker.currentStep = GENERATING_TX
val builder: TransactionBuilder = TransactionBuilder(notary = null as Party?) val builder: TransactionBuilder = TransactionBuilder(notary = null as Party?)
val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef) val issuer = serviceHub.myInfo.legalIdentity.ref(issueRef)
val exitStates = serviceHub.vaultService.unconsumedStatesForSpending<Cash.State>(amount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference)) val exitStates = Cash.unconsumedCashStatesForSpending(serviceHub, amount, setOf(issuer.party), builder.notary, builder.lockId, setOf(issuer.reference))
val signers = try { val signers = try {
Cash().generateExit( Cash().generateExit(
builder, builder,

View File

@ -1,6 +1,7 @@
package net.corda.flows package net.corda.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.InsufficientBalanceException import net.corda.core.contracts.InsufficientBalanceException
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
@ -26,7 +27,7 @@ open class CashPaymentFlow(
val recipient: Party, val recipient: Party,
val anonymous: Boolean, val anonymous: Boolean,
progressTracker: ProgressTracker, progressTracker: ProgressTracker,
val issuerConstraint: Set<Party>? = null) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) { val issuerConstraint: Set<Party> = emptySet()) : AbstractCashFlow<AbstractCashFlow.Result>(progressTracker) {
/** A straightforward constructor that constructs spends using cash states of any issuer. */ /** A straightforward constructor that constructs spends using cash states of any issuer. */
constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, true, tracker()) constructor(amount: Amount<Currency>, recipient: Party) : this(amount, recipient, true, tracker())
/** A straightforward constructor that constructs spends using cash states of any issuer. */ /** A straightforward constructor that constructs spends using cash states of any issuer. */
@ -45,7 +46,7 @@ open class CashPaymentFlow(
val builder: TransactionBuilder = TransactionBuilder(null as Party?) val builder: TransactionBuilder = TransactionBuilder(null as Party?)
// TODO: Have some way of restricting this to states the caller controls // TODO: Have some way of restricting this to states the caller controls
val (spendTX, keysForSigning) = try { val (spendTX, keysForSigning) = try {
serviceHub.vaultService.generateSpend( Cash.generateSpend(serviceHub,
builder, builder,
amount, amount,
anonymousRecipient, anonymousRecipient,

View File

@ -1,8 +1,12 @@
package net.corda.flows package net.corda.flows
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.sumCashBy import net.corda.contracts.asset.sumCashBy
import net.corda.core.contracts.* import net.corda.core.contracts.Amount
import net.corda.core.contracts.OwnableState
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.withoutIssuer
import net.corda.core.flows.* import net.corda.core.flows.*
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
@ -138,7 +142,8 @@ object TwoPartyTradeFlow {
// Put together a proposed transaction that performs the trade, and sign it. // Put together a proposed transaction that performs the trade, and sign it.
progressTracker.currentStep = SIGNING progressTracker.currentStep = SIGNING
val (ptx, cashSigningPubKeys) = assembleSharedTX(assetForSale, tradeRequest) val (ptx, cashSigningPubKeys) = assembleSharedTX(assetForSale, tradeRequest)
val partSignedTx = signWithOurKeys(cashSigningPubKeys, ptx) // Now sign the transaction with whatever keys we need to move the cash.
val partSignedTx = serviceHub.signInitialTransaction(ptx, cashSigningPubKeys)
// Send the signed transaction to the seller, who must then sign it themselves and commit // Send the signed transaction to the seller, who must then sign it themselves and commit
// it to the ledger by sending it to the notary. // it to the ledger by sending it to the notary.
@ -164,17 +169,13 @@ object TwoPartyTradeFlow {
} }
} }
private fun signWithOurKeys(cashSigningPubKeys: List<PublicKey>, ptx: TransactionBuilder): SignedTransaction {
// Now sign the transaction with whatever keys we need to move the cash.
return serviceHub.signInitialTransaction(ptx, cashSigningPubKeys)
}
@Suspendable @Suspendable
private fun assembleSharedTX(assetForSale: StateAndRef<OwnableState>, tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> { private fun assembleSharedTX(assetForSale: StateAndRef<OwnableState>, tradeRequest: SellerTradeInfo): Pair<TransactionBuilder, List<PublicKey>> {
val ptx = TransactionBuilder(notary) val ptx = TransactionBuilder(notary)
// Add input and output states for the movement of cash, by using the Cash contract to generate the states // Add input and output states for the movement of cash, by using the Cash contract to generate the states
val (tx, cashSigningPubKeys) = serviceHub.vaultService.generateSpend(ptx, tradeRequest.price, tradeRequest.sellerOwner) val (tx, cashSigningPubKeys) = Cash.generateSpend(serviceHub, ptx, tradeRequest.price, tradeRequest.sellerOwner)
// Add inputs/outputs/a command for the movement of the asset. // Add inputs/outputs/a command for the movement of the asset.
tx.addInputState(assetForSale) tx.addInputState(assetForSale)

View File

@ -1,21 +1,19 @@
package net.corda.contracts package net.corda.contracts
import net.corda.contracts.asset.* import net.corda.contracts.asset.*
import net.corda.testing.contracts.fillWithSomeTestCash
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.utilities.days
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService import net.corda.core.node.services.VaultService
import net.corda.core.utilities.seconds
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.node.utilities.configureDatabase import net.corda.core.utilities.days
import net.corda.core.utilities.seconds
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.fillWithSomeTestCash
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestDatabaseProperties
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.junit.runners.Parameterized import org.junit.runners.Parameterized
@ -212,40 +210,22 @@ class CommercialPaperTestsGeneric {
@Test @Test
fun `issue move and then redeem`() { fun `issue move and then redeem`() {
initialiseTestSerialization() initialiseTestSerialization()
val dataSourcePropsAlice = makeTestDataSourceProperties() val aliceDatabaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(ALICE_KEY))
val databaseAlice = configureDatabase(dataSourcePropsAlice, makeTestDatabaseProperties()) val databaseAlice = aliceDatabaseAndServices.first
aliceServices = aliceDatabaseAndServices.second
aliceVaultService = aliceServices.vaultService
databaseAlice.transaction { databaseAlice.transaction {
aliceServices = object : MockServices(ALICE_KEY) {
override val vaultService: VaultService = makeVaultService(dataSourcePropsAlice)
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
vaultService.notifyAll(txs.map { it.tx })
}
}
alicesVault = aliceServices.fillWithSomeTestCash(9000.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1) alicesVault = aliceServices.fillWithSomeTestCash(9000.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1)
aliceVaultService = aliceServices.vaultService aliceVaultService = aliceServices.vaultService
} }
val dataSourcePropsBigCorp = makeTestDataSourceProperties() val bigCorpDatabaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(BIG_CORP_KEY))
val databaseBigCorp = configureDatabase(dataSourcePropsBigCorp, makeTestDatabaseProperties()) val databaseBigCorp = bigCorpDatabaseAndServices.first
bigCorpServices = bigCorpDatabaseAndServices.second
bigCorpVaultService = bigCorpServices.vaultService
databaseBigCorp.transaction { databaseBigCorp.transaction {
bigCorpServices = object : MockServices(BIG_CORP_KEY) {
override val vaultService: VaultService = makeVaultService(dataSourcePropsBigCorp)
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
vaultService.notifyAll(txs.map { it.tx })
}
}
bigCorpVault = bigCorpServices.fillWithSomeTestCash(13000.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1) bigCorpVault = bigCorpServices.fillWithSomeTestCash(13000.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1)
bigCorpVaultService = bigCorpServices.vaultService bigCorpVaultService = bigCorpServices.vaultService
} }
@ -266,7 +246,7 @@ class CommercialPaperTestsGeneric {
// Alice pays $9000 to BigCorp to own some of their debt. // Alice pays $9000 to BigCorp to own some of their debt.
moveTX = run { moveTX = run {
val builder = TransactionBuilder(DUMMY_NOTARY) val builder = TransactionBuilder(DUMMY_NOTARY)
aliceVaultService.generateSpend(builder, 9000.DOLLARS, AnonymousParty(bigCorpServices.key.public)) Cash.generateSpend(aliceServices, builder, 9000.DOLLARS, AnonymousParty(bigCorpServices.key.public))
CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(aliceServices.key.public)) CommercialPaper().generateMove(builder, issueTx.tx.outRef(0), AnonymousParty(aliceServices.key.public))
val ptx = aliceServices.signInitialTransaction(builder) val ptx = aliceServices.signInitialTransaction(builder)
val ptx2 = bigCorpServices.addSignature(ptx) val ptx2 = bigCorpServices.addSignature(ptx)
@ -288,7 +268,7 @@ class CommercialPaperTestsGeneric {
fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> { fun makeRedeemTX(time: Instant): Pair<SignedTransaction, UUID> {
val builder = TransactionBuilder(DUMMY_NOTARY) val builder = TransactionBuilder(DUMMY_NOTARY)
builder.setTimeWindow(time, 30.seconds) builder.setTimeWindow(time, 30.seconds)
CommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), bigCorpVaultService) CommercialPaper().generateRedeem(builder, moveTX.tx.outRef(1), bigCorpServices)
val ptx = aliceServices.signInitialTransaction(builder) val ptx = aliceServices.signInitialTransaction(builder)
val ptx2 = bigCorpServices.addSignature(ptx) val ptx2 = bigCorpServices.addSignature(ptx)
val stx = notaryServices.addSignature(ptx2) val stx = notaryServices.addSignature(ptx2)

View File

@ -6,26 +6,25 @@ import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.VaultQueryService import net.corda.core.node.services.VaultQueryService
import net.corda.core.node.services.VaultService import net.corda.core.node.services.VaultService
import net.corda.core.node.services.queryBy import net.corda.core.node.services.queryBy
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.OpaqueBytes
import net.corda.node.services.database.HibernateConfiguration import net.corda.node.services.database.HibernateConfiguration
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.vault.HibernateVaultQueryImpl import net.corda.node.services.vault.HibernateVaultQueryImpl
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.DummyState import net.corda.testing.contracts.DummyState
import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.testing.contracts.fillWithSomeTestCash
import net.corda.testing.node.MockKeyManagementService
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestDatabaseProperties import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.security.KeyPair import java.security.KeyPair
@ -55,25 +54,11 @@ class CashTests : TestDependencyInjectionBase() {
@Before @Before
fun setUp() { fun setUp() {
LogHelper.setLevel(NodeVaultService::class) LogHelper.setLevel(NodeVaultService::class)
val dataSourceProps = makeTestDataSourceProperties() val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY))
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties()) database = databaseAndServices.first
miniCorpServices = databaseAndServices.second
database.transaction { database.transaction {
val hibernateConfig = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties())
miniCorpServices = object : MockServices(MINI_CORP_KEY) {
override val keyManagementService: MockKeyManagementService = MockKeyManagementService(identityService, MINI_CORP_KEY, MEGA_CORP_KEY, OUR_KEY)
override val vaultService: VaultService = makeVaultService(dataSourceProps)
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
vaultService.notifyAll(txs.map { it.tx })
}
override val vaultQueryService: VaultQueryService = HibernateVaultQueryImpl(hibernateConfig, vaultService.updatesPublisher)
}
miniCorpServices.fillWithSomeTestCash(howMuch = 100.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, miniCorpServices.fillWithSomeTestCash(howMuch = 100.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
issuedBy = MEGA_CORP.ref(1), issuerKey = MEGA_CORP_KEY, ownedBy = OUR_IDENTITY_1) issuedBy = MEGA_CORP.ref(1), issuerKey = MEGA_CORP_KEY, ownedBy = OUR_IDENTITY_1)
miniCorpServices.fillWithSomeTestCash(howMuch = 400.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1, miniCorpServices.fillWithSomeTestCash(howMuch = 400.DOLLARS, atLeastThisManyStates = 1, atMostThisManyStates = 1,
@ -88,6 +73,11 @@ class CashTests : TestDependencyInjectionBase() {
resetTestSerialization() resetTestSerialization()
} }
@After
fun tearDown() {
database.close()
}
@Test @Test
fun trivial() { fun trivial() {
transaction { transaction {
@ -485,7 +475,7 @@ class CashTests : TestDependencyInjectionBase() {
fun makeSpend(amount: Amount<Currency>, dest: AbstractParty): WireTransaction { fun makeSpend(amount: Amount<Currency>, dest: AbstractParty): WireTransaction {
val tx = TransactionBuilder(DUMMY_NOTARY) val tx = TransactionBuilder(DUMMY_NOTARY)
database.transaction { database.transaction {
vault.generateSpend(tx, amount, dest) Cash.generateSpend(miniCorpServices, tx, amount, dest)
} }
return tx.toWireTransaction() return tx.toWireTransaction()
} }
@ -586,7 +576,7 @@ class CashTests : TestDependencyInjectionBase() {
database.transaction { database.transaction {
val tx = TransactionBuilder(DUMMY_NOTARY) val tx = TransactionBuilder(DUMMY_NOTARY)
vault.generateSpend(tx, 80.DOLLARS, ALICE, setOf(MINI_CORP)) Cash.generateSpend(miniCorpServices, tx, 80.DOLLARS, ALICE, setOf(MINI_CORP))
assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0]) assertEquals(vaultStatesUnconsumed.elementAt(2).ref, tx.inputStates()[0])
} }

View File

@ -0,0 +1,94 @@
package net.corda.nodeapi.internal.serialization
import net.corda.core.serialization.ClassWhitelist
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence
import net.corda.nodeapi.internal.serialization.amqp.AmqpHeaderV1_0
import net.corda.nodeapi.internal.serialization.amqp.DeserializationInput
import net.corda.nodeapi.internal.serialization.amqp.SerializationOutput
import net.corda.nodeapi.internal.serialization.amqp.SerializerFactory
import java.util.concurrent.ConcurrentHashMap
private const val AMQP_ENABLED = false
abstract class AbstractAMQPSerializationScheme : SerializationScheme {
private val serializerFactoriesForContexts = ConcurrentHashMap<Pair<ClassWhitelist, ClassLoader>, SerializerFactory>()
protected abstract fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory
protected abstract fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory
private fun getSerializerFactory(context: SerializationContext): SerializerFactory {
return serializerFactoriesForContexts.computeIfAbsent(Pair(context.whitelist, context.deserializationClassLoader)) {
when (context.useCase) {
SerializationContext.UseCase.Checkpoint ->
throw IllegalStateException("AMQP should not be used for checkpoint serialization.")
SerializationContext.UseCase.RPCClient ->
rpcClientSerializerFactory(context)
SerializationContext.UseCase.RPCServer ->
rpcServerSerializerFactory(context)
else -> SerializerFactory(context.whitelist) // TODO pass class loader also
}
}
}
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
val serializerFactory = getSerializerFactory(context)
return DeserializationInput(serializerFactory).deserialize(byteSequence, clazz)
}
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
val serializerFactory = getSerializerFactory(context)
return SerializationOutput(serializerFactory).serialize(obj)
}
protected fun canDeserializeVersion(byteSequence: ByteSequence): Boolean = AMQP_ENABLED && byteSequence == AmqpHeaderV1_0
}
// TODO: This will eventually cover server RPC as well and move to node module, but for now this is not implemented
class AMQPServerSerializationScheme : AbstractAMQPSerializationScheme() {
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException()
}
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
return (canDeserializeVersion(byteSequence) &&
(target == SerializationContext.UseCase.P2P || target == SerializationContext.UseCase.Storage))
}
}
// TODO: This will eventually cover client RPC as well and move to client module, but for now this is not implemented
class AMQPClientSerializationScheme : AbstractAMQPSerializationScheme() {
override fun rpcClientSerializerFactory(context: SerializationContext): SerializerFactory {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun rpcServerSerializerFactory(context: SerializationContext): SerializerFactory {
throw UnsupportedOperationException()
}
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
return (canDeserializeVersion(byteSequence) &&
(target == SerializationContext.UseCase.P2P || target == SerializationContext.UseCase.Storage))
}
}
val AMQP_P2P_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0,
SerializationDefaults.javaClass.classLoader,
GlobalTransientClassWhiteList(BuiltInExceptionsWhitelist()),
emptyMap(),
true,
SerializationContext.UseCase.P2P)
val AMQP_STORAGE_CONTEXT = SerializationContextImpl(AmqpHeaderV1_0,
SerializationDefaults.javaClass.classLoader,
AllButBlacklisted,
emptyMap(),
true,
SerializationContext.UseCase.Storage)

View File

@ -54,6 +54,8 @@ data class SerializationContextImpl(override val preferedSerializationVersion: B
} }
} }
private const val HEADER_SIZE: Int = 8
open class SerializationFactoryImpl : SerializationFactory { open class SerializationFactoryImpl : SerializationFactory {
private val creator: List<StackTraceElement> = Exception().stackTrace.asList() private val creator: List<StackTraceElement> = Exception().stackTrace.asList()
@ -63,8 +65,8 @@ open class SerializationFactoryImpl : SerializationFactory {
private val schemes: ConcurrentHashMap<Pair<ByteSequence, SerializationContext.UseCase>, SerializationScheme> = ConcurrentHashMap() private val schemes: ConcurrentHashMap<Pair<ByteSequence, SerializationContext.UseCase>, SerializationScheme> = ConcurrentHashMap()
private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): SerializationScheme { private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): SerializationScheme {
// truncate sequence to 8 bytes // truncate sequence to 8 bytes, and make sure it's a copy to avoid holding onto large ByteArrays
return schemes.computeIfAbsent(byteSequence.take(8).copy() to target) { return schemes.computeIfAbsent(byteSequence.take(HEADER_SIZE).copy() to target) {
for (scheme in registeredSchemes) { for (scheme in registeredSchemes) {
if (scheme.canDeserializeVersion(it.first, it.second)) { if (scheme.canDeserializeVersion(it.first, it.second)) {
return@computeIfAbsent scheme return@computeIfAbsent scheme
@ -162,11 +164,12 @@ abstract class AbstractKryoSerializationScheme : SerializationScheme {
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T { override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
val pool = getPool(context) val pool = getPool(context)
Input(byteSequence.bytes, byteSequence.offset, byteSequence.size).use { input -> val headerSize = KryoHeaderV0_1.size
val header = OpaqueBytes(input.readBytes(8)) val header = byteSequence.take(headerSize)
if (header != KryoHeaderV0_1) { if (header != KryoHeaderV0_1) {
throw KryoException("Serialized bytes header does not match expected format.") throw KryoException("Serialized bytes header does not match expected format.")
} }
Input(byteSequence.bytes, byteSequence.offset + headerSize, byteSequence.size - headerSize).use { input ->
return pool.run { kryo -> return pool.run { kryo ->
withContext(kryo, context) { withContext(kryo, context) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.serialization.amqp
import net.corda.core.internal.getStackTraceAsString import net.corda.core.internal.getStackTraceAsString
import net.corda.core.serialization.SerializedBytes import net.corda.core.serialization.SerializedBytes
import net.corda.core.utilities.ByteSequence
import org.apache.qpid.proton.amqp.Binary import org.apache.qpid.proton.amqp.Binary
import org.apache.qpid.proton.amqp.DescribedType import org.apache.qpid.proton.amqp.DescribedType
import org.apache.qpid.proton.amqp.UnsignedByte import org.apache.qpid.proton.amqp.UnsignedByte
@ -26,17 +27,6 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory = S
internal companion object { internal companion object {
val BYTES_NEEDED_TO_PEEK: Int = 23 val BYTES_NEEDED_TO_PEEK: Int = 23
private fun subArraysEqual(a: ByteArray, aOffset: Int, length: Int, b: ByteArray, bOffset: Int): Boolean {
if (aOffset + length > a.size || bOffset + length > b.size) throw IndexOutOfBoundsException()
var bytesRemaining = length
var aPos = aOffset
var bPos = bOffset
while (bytesRemaining-- > 0) {
if (a[aPos++] != b[bPos++]) return false
}
return true
}
fun peekSize(bytes: ByteArray): Int { fun peekSize(bytes: ByteArray): Int {
// There's an 8 byte header, and then a 0 byte plus descriptor followed by constructor // There's an 8 byte header, and then a 0 byte plus descriptor followed by constructor
val eighth = bytes[8].toInt() val eighth = bytes[8].toInt()
@ -69,15 +59,16 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory = S
@Throws(NotSerializableException::class) @Throws(NotSerializableException::class)
private fun <T : Any> getEnvelope(bytes: SerializedBytes<T>): Envelope { private fun getEnvelope(bytes: ByteSequence): Envelope {
// Check that the lead bytes match expected header // Check that the lead bytes match expected header
if (!subArraysEqual(bytes.bytes, 0, 8, AmqpHeaderV1_0.bytes, 0)) { val headerSize = AmqpHeaderV1_0.size
if (bytes.take(headerSize) != AmqpHeaderV1_0) {
throw NotSerializableException("Serialization header does not match.") throw NotSerializableException("Serialization header does not match.")
} }
val data = Data.Factory.create() val data = Data.Factory.create()
val size = data.decode(ByteBuffer.wrap(bytes.bytes, 8, bytes.size - 8)) val size = data.decode(ByteBuffer.wrap(bytes.bytes, bytes.offset + headerSize, bytes.size - headerSize))
if (size.toInt() != bytes.size - 8) { if (size.toInt() != bytes.size - headerSize) {
throw NotSerializableException("Unexpected size of data") throw NotSerializableException("Unexpected size of data")
} }
@ -103,7 +94,7 @@ class DeserializationInput(internal val serializerFactory: SerializerFactory = S
* be deserialized and a schema describing the types of the objects. * be deserialized and a schema describing the types of the objects.
*/ */
@Throws(NotSerializableException::class) @Throws(NotSerializableException::class)
fun <T : Any> deserialize(bytes: SerializedBytes<T>, clazz: Class<T>): T { fun <T : Any> deserialize(bytes: ByteSequence, clazz: Class<T>): T {
return des { return des {
val envelope = getEnvelope(bytes) val envelope = getEnvelope(bytes)
clazz.cast(readObjectOrNull(envelope.obj, envelope.schema, clazz)) clazz.cast(readObjectOrNull(envelope.obj, envelope.schema, clazz))

View File

@ -11,20 +11,14 @@ import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.sequence import net.corda.core.utilities.sequence
import net.corda.node.serialization.KryoServerSerializationScheme import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.persistence.NodeAttachmentService import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.testing.ALICE
import net.corda.testing.ALICE_PUBKEY import net.corda.testing.ALICE_PUBKEY
import net.corda.testing.BOB
import net.corda.testing.BOB_PUBKEY
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.bouncycastle.cert.X509CertificateHolder
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
import java.security.cert.CertPath
import java.security.cert.CertificateFactory
import java.time.Instant import java.time.Instant
import kotlin.test.assertEquals import kotlin.test.assertEquals
import kotlin.test.assertTrue import kotlin.test.assertTrue
@ -150,26 +144,6 @@ class KryoTests {
assertEquals(-1, readRubbishStream.read()) assertEquals(-1, readRubbishStream.read())
} }
@Test
fun `serialize - deserialize X509CertififcateHolder`() {
val expected: X509CertificateHolder = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
val serialized = expected.serialize(factory, context).bytes
val actual: X509CertificateHolder = serialized.deserialize(factory, context)
assertEquals(expected, actual)
}
@Test
fun `serialize - deserialize X509CertPath`() {
val certFactory = CertificateFactory.getInstance("X509")
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name, BOB_PUBKEY)
val expected = certFactory.generateCertPath(listOf(certificate.cert, rootCACert.cert))
val serialized = expected.serialize(factory, context).bytes
val actual: CertPath = serialized.deserialize(factory, context)
assertEquals(expected, actual)
}
@CordaSerializable @CordaSerializable
private data class Person(val name: String, val birthday: Instant?) private data class Person(val name: String, val birthday: Instant?)

View File

@ -87,7 +87,6 @@ processSmokeTestResources {
// build/reports/project/dependencies/index.html for green highlighted parts of the tree. // build/reports/project/dependencies/index.html for green highlighted parts of the tree.
dependencies { dependencies {
compile project(':finance')
compile project(':node-schemas') compile project(':node-schemas')
compile project(':node-api') compile project(':node-api')
compile project(':client:rpc') compile project(':client:rpc')
@ -150,6 +149,7 @@ dependencies {
testCompile "com.pholser:junit-quickcheck-core:$quickcheck_version" testCompile "com.pholser:junit-quickcheck-core:$quickcheck_version"
testCompile project(':test-utils') testCompile project(':test-utils')
testCompile project(':client:jfx') testCompile project(':client:jfx')
testCompile project(':finance')
// sample test schemas // sample test schemas
testCompile project(path: ':finance', configuration: 'testArtifacts') testCompile project(path: ':finance', configuration: 'testArtifacts')

View File

@ -1,10 +1,8 @@
package net.corda.services.messaging package net.corda.services.messaging
import net.corda.core.crypto.Crypto
import net.corda.core.internal.copyTo import net.corda.core.internal.copyTo
import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectories
import net.corda.core.crypto.CertificateType
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.X509Utilities
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.node.utilities.* import net.corda.node.utilities.*
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER

View File

@ -479,7 +479,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
private fun makeVaultObservers() { private fun makeVaultObservers() {
VaultSoftLockManager(services.vaultService, smm) VaultSoftLockManager(services.vaultService, smm)
ScheduledActivityObserver(services) ScheduledActivityObserver(services)
HibernateObserver(services.vaultService.rawUpdates, HibernateConfiguration(services.schemaService, configuration.database ?: Properties())) HibernateObserver(services.vaultService.rawUpdates, HibernateConfiguration(services.schemaService, configuration.database ?: Properties(), services.identityService))
} }
private fun makeInfo(): NodeInfo { private fun makeInfo(): NodeInfo {
@ -766,7 +766,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
override val networkMapCache by lazy { InMemoryNetworkMapCache(this) } override val networkMapCache by lazy { InMemoryNetworkMapCache(this) }
override val vaultService by lazy { NodeVaultService(this, configuration.dataSourceProperties, configuration.database) } override val vaultService by lazy { NodeVaultService(this, configuration.dataSourceProperties, configuration.database) }
override val vaultQueryService by lazy { override val vaultQueryService by lazy {
HibernateVaultQueryImpl(HibernateConfiguration(schemaService, configuration.database ?: Properties()), vaultService.updatesPublisher) HibernateVaultQueryImpl(HibernateConfiguration(schemaService, configuration.database ?: Properties(), identityService), vaultService.updatesPublisher)
} }
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because // Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with // the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with

View File

@ -332,6 +332,7 @@ open class Node(override val configuration: FullNodeConfiguration,
private fun initialiseSerialization() { private fun initialiseSerialization() {
SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply { SerializationDefaults.SERIALIZATION_FACTORY = SerializationFactoryImpl().apply {
registerScheme(KryoServerSerializationScheme()) registerScheme(KryoServerSerializationScheme())
registerScheme(AMQPServerSerializationScheme())
} }
SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT SerializationDefaults.P2P_CONTEXT = KRYO_P2P_CONTEXT
SerializationDefaults.RPC_SERVER_CONTEXT = KRYO_RPC_SERVER_CONTEXT SerializationDefaults.RPC_SERVER_CONTEXT = KRYO_RPC_SERVER_CONTEXT

View File

@ -4,9 +4,10 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigParseOptions
import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigRenderOptions
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SignatureScheme
import net.corda.core.internal.copyTo import net.corda.core.internal.copyTo
import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectories
import net.corda.core.crypto.*
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor

View File

@ -1,7 +1,9 @@
package net.corda.node.services.database package net.corda.node.services.database
import net.corda.core.internal.castIfPossible import net.corda.core.internal.castIfPossible
import net.corda.core.node.services.IdentityService
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.converters.AbstractPartyToX500NameAsStringConverter
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.services.api.SchemaService import net.corda.node.services.api.SchemaService
import net.corda.node.utilities.DatabaseTransactionManager import net.corda.node.utilities.DatabaseTransactionManager
@ -18,7 +20,7 @@ import java.sql.Connection
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class HibernateConfiguration(val schemaService: SchemaService, val databaseProperties: Properties) { class HibernateConfiguration(val schemaService: SchemaService, val databaseProperties: Properties, val identitySvc: IdentityService) {
companion object { companion object {
val logger = loggerFor<HibernateConfiguration>() val logger = loggerFor<HibernateConfiguration>()
} }
@ -58,6 +60,7 @@ class HibernateConfiguration(val schemaService: SchemaService, val databasePrope
val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", HibernateConfiguration.NodeDatabaseConnectionProvider::class.java.name) val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", HibernateConfiguration.NodeDatabaseConnectionProvider::class.java.name)
.setProperty("hibernate.hbm2ddl.auto", if (databaseProperties.getProperty("initDatabase","true") == "true") "update" else "validate") .setProperty("hibernate.hbm2ddl.auto", if (databaseProperties.getProperty("initDatabase","true") == "true") "update" else "validate")
.setProperty("hibernate.format_sql", "true") .setProperty("hibernate.format_sql", "true")
schemas.forEach { schema -> schemas.forEach { schema ->
// TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session // TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session
schema.mappedTypes.forEach { config.addAnnotatedClass(it) } schema.mappedTypes.forEach { config.addAnnotatedClass(it) }
@ -76,6 +79,9 @@ class HibernateConfiguration(val schemaService: SchemaService, val databasePrope
return Identifier.toIdentifier(tablePrefix + default.text, default.isQuoted) return Identifier.toIdentifier(tablePrefix + default.text, default.isQuoted)
} }
}) })
// register custom converters
applyAttributeConverter(AbstractPartyToX500NameAsStringConverter(identitySvc))
build() build()
} }

View File

@ -1,10 +1,14 @@
package net.corda.node.services.keys package net.corda.node.services.keys
import net.corda.core.crypto.* import net.corda.core.crypto.ContentSignerBuilder
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert
import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.days import net.corda.core.utilities.days
import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.X509Utilities
import org.bouncycastle.operator.ContentSigner import org.bouncycastle.operator.ContentSigner
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
@ -31,7 +35,7 @@ fun freshCertificate(identityService: IdentityService,
revocationEnabled: Boolean = false): AnonymousPartyAndPath { revocationEnabled: Boolean = false): AnonymousPartyAndPath {
val issuerCertificate = issuer.certificate val issuerCertificate = issuer.certificate
val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate) val window = X509Utilities.getCertificateValidityWindow(Duration.ZERO, 3650.days, issuerCertificate)
val ourCertificate = Crypto.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window) val ourCertificate = X509Utilities.createCertificate(CertificateType.IDENTITY, issuerCertificate.subject, issuerSigner, issuer.name, subjectPublicKey, window)
val certFactory = CertificateFactory.getInstance("X509") val certFactory = CertificateFactory.getInstance("X509")
val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates) val ourCertPath = certFactory.generateCertPath(listOf(ourCertificate.cert) + issuer.certPath.certificates)
val anonymisedIdentity = AnonymousPartyAndPath(subjectPublicKey, ourCertPath) val anonymisedIdentity = AnonymousPartyAndPath(subjectPublicKey, ourCertPath)

View File

@ -2,13 +2,13 @@ package net.corda.node.services.messaging
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import io.netty.handler.ssl.SslHandler import io.netty.handler.ssl.SslHandler
import net.corda.core.*
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.* import net.corda.core.crypto.AddressFormatException
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS import net.corda.core.crypto.newSecureRandom
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA import net.corda.core.crypto.parsePublicKeyBase58
import net.corda.core.internal.concurrent.openFuture import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.noneOrSingle import net.corda.core.internal.noneOrSingle
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
@ -22,6 +22,9 @@ import net.corda.node.services.messaging.NodeLoginModule.Companion.NODE_ROLE
import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.PEER_ROLE
import net.corda.node.services.messaging.NodeLoginModule.Companion.RPC_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.RPC_ROLE
import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE import net.corda.node.services.messaging.NodeLoginModule.Companion.VERIFIER_ROLE
import net.corda.node.utilities.X509Utilities
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS
import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA
import net.corda.node.utilities.getX509Certificate import net.corda.node.utilities.getX509Certificate
import net.corda.node.utilities.loadKeyStore import net.corda.node.utilities.loadKeyStore
import net.corda.nodeapi.* import net.corda.nodeapi.*

View File

@ -8,13 +8,13 @@ import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultQueryException import net.corda.core.node.services.VaultQueryException
import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.QueryCriteria.CommonQueryCriteria import net.corda.core.node.services.vault.QueryCriteria.CommonQueryCriteria
import net.corda.core.schemas.CommonSchemaV1
import net.corda.core.schemas.PersistentState import net.corda.core.schemas.PersistentState
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toHexString
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toHexString
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.core.schemas.CommonSchemaV1
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import java.util.* import java.util.*
import javax.persistence.Tuple import javax.persistence.Tuple

View File

@ -8,28 +8,32 @@ import io.requery.kotlin.eq
import io.requery.kotlin.notNull import io.requery.kotlin.notNull
import io.requery.query.RowExpression import io.requery.query.RowExpression
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.OnLedgerAsset
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.containsAny import net.corda.core.crypto.containsAny
import net.corda.core.crypto.toBase58String import net.corda.core.crypto.toBase58String
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.internal.tee import net.corda.core.internal.tee
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.StatesNotAvailableException import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService import net.corda.core.node.services.VaultService
import net.corda.core.node.services.vault.IQueryCriteriaParser
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.node.services.vault.SortAttribute
import net.corda.core.schemas.PersistentState
import net.corda.core.serialization.SerializationDefaults.STORAGE_CONTEXT import net.corda.core.serialization.SerializationDefaults.STORAGE_CONTEXT
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.core.utilities.* import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.toNonEmptySet
import net.corda.core.utilities.trace
import net.corda.node.services.database.RequeryConfiguration import net.corda.node.services.database.RequeryConfiguration
import net.corda.node.services.database.parserTransactionIsolationLevel import net.corda.node.services.database.parserTransactionIsolationLevel
import net.corda.node.services.statemachine.FlowStateMachineImpl import net.corda.node.services.statemachine.FlowStateMachineImpl
@ -42,10 +46,8 @@ import net.corda.node.utilities.wrapWithDatabaseTransaction
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.security.PublicKey import java.security.PublicKey
import java.sql.SQLException
import java.util.* import java.util.*
import java.util.concurrent.locks.ReentrantLock import javax.persistence.criteria.Predicate
import kotlin.concurrent.withLock
/** /**
* Currently, the node vault service is a very simple RDBMS backed implementation. It will change significantly when * Currently, the node vault service is a very simple RDBMS backed implementation. It will change significantly when
@ -79,6 +81,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
// For use during publishing only. // For use during publishing only.
val updatesPublisher: rx.Observer<Vault.Update<ContractState>> get() = _updatesPublisher.bufferUntilDatabaseCommit().tee(_rawUpdatesPublisher) val updatesPublisher: rx.Observer<Vault.Update<ContractState>> get() = _updatesPublisher.bufferUntilDatabaseCommit().tee(_rawUpdatesPublisher)
} }
private val mutex = ThreadBox(InnerState()) private val mutex = ThreadBox(InnerState())
private fun recordUpdate(update: Vault.Update<ContractState>): Vault.Update<ContractState> { private fun recordUpdate(update: Vault.Update<ContractState>): Vault.Update<ContractState> {
@ -334,123 +337,110 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
} }
} }
// coin selection retry loop counter, sleep (msecs) and lock for selecting states // TODO We shouldn't need to rewrite the query if we could modify the defaults.
val MAX_RETRIES = 5 private class QueryEditor<out T : ContractState>(val services: ServiceHub,
val RETRY_SLEEP = 100 val lockId: UUID,
val spendLock: ReentrantLock = ReentrantLock() val contractType: Class<out T>) : IQueryCriteriaParser {
var alreadyHasVaultQuery: Boolean = false
var modifiedCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(contractStateTypes = setOf(contractType),
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)),
status = Vault.StateStatus.UNCONSUMED)
@Suspendable override fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection<Predicate> {
override fun <T : ContractState> unconsumedStatesForSpending(amount: Amount<Currency>, onlyFromIssuerParties: Set<AbstractParty>?, notary: Party?, lockId: UUID, withIssuerRefs: Set<OpaqueBytes>?): List<StateAndRef<T>> { modifiedCriteria = criteria
return emptyList()
val issuerKeysStr = onlyFromIssuerParties?.fold("") { left, right -> left + "('${right.owningKey.toBase58String()}')," }?.dropLast(1)
val issuerRefsStr = withIssuerRefs?.fold("") { left, right -> left + "('${right.bytes.toHexString()}')," }?.dropLast(1)
val stateAndRefs = mutableListOf<StateAndRef<T>>()
// TODO: Need to provide a database provider independent means of performing this function.
// We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins:
// 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the
// running total of such an accumulator
// 2) H2 uses session variables to perform this accumulator function:
// http://www.h2database.com/html/functions.html#set
// 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries)
for (retryCount in 1..MAX_RETRIES) {
spendLock.withLock {
val statement = configuration.jdbcSession().createStatement()
try {
statement.execute("CALL SET(@t, 0);")
// we select spendable states irrespective of lock but prioritised by unlocked ones (Eg. null)
// the softLockReserve update will detect whether we try to lock states locked by others
val selectJoin = """
SELECT vs.transaction_id, vs.output_index, vs.contract_state, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id
FROM vault_states AS vs, contract_cash_states AS ccs
WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index
AND vs.state_status = 0
AND ccs.ccy_code = '${amount.token}' and @t < ${amount.quantity}
AND (vs.lock_id = '$lockId' OR vs.lock_id is null)
""" +
(if (notary != null)
" AND vs.notary_key = '${notary.owningKey.toBase58String()}'" else "") +
(if (issuerKeysStr != null)
" AND ccs.issuer_key IN ($issuerKeysStr)" else "") +
(if (issuerRefsStr != null)
" AND ccs.issuer_ref IN ($issuerRefsStr)" else "")
// Retrieve spendable state refs
val rs = statement.executeQuery(selectJoin)
stateAndRefs.clear()
log.debug(selectJoin)
var totalPennies = 0L
while (rs.next()) {
val txHash = SecureHash.parse(rs.getString(1))
val index = rs.getInt(2)
val stateRef = StateRef(txHash, index)
val state = rs.getBytes(3).deserialize<TransactionState<T>>(context = STORAGE_CONTEXT)
val pennies = rs.getLong(4)
totalPennies = rs.getLong(5)
val rowLockId = rs.getString(6)
stateAndRefs.add(StateAndRef(state, stateRef))
log.trace { "ROW: $rowLockId ($lockId): $stateRef : $pennies ($totalPennies)" }
}
if (stateAndRefs.isNotEmpty() && totalPennies >= amount.quantity) {
// we should have a minimum number of states to satisfy our selection `amount` criteria
log.trace("Coin selection for $amount retrieved ${stateAndRefs.count()} states totalling $totalPennies pennies: $stateAndRefs")
// update database
softLockReserve(lockId, (stateAndRefs.map { it.ref }).toNonEmptySet())
return stateAndRefs
}
log.trace("Coin selection requested $amount but retrieved $totalPennies pennies with state refs: ${stateAndRefs.map { it.ref }}")
// retry as more states may become available
} catch (e: SQLException) {
log.error("""Failed retrieving unconsumed states for: amount [$amount], onlyFromIssuerParties [$onlyFromIssuerParties], notary [$notary], lockId [$lockId]
$e.
""")
} catch (e: StatesNotAvailableException) {
stateAndRefs.clear()
log.warn(e.message)
// retry only if there are locked states that may become available again (or consumed with change)
} finally {
statement.close()
}
}
log.warn("Coin selection failed on attempt $retryCount")
// TODO: revisit the back off strategy for contended spending.
if (retryCount != MAX_RETRIES) {
FlowStateMachineImpl.sleep(RETRY_SLEEP * retryCount.toLong())
}
} }
log.warn("Insufficient spendable states identified for $amount") override fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate> {
return stateAndRefs modifiedCriteria = criteria
return emptyList()
}
override fun parseCriteria(criteria: QueryCriteria.LinearStateQueryCriteria): Collection<Predicate> {
modifiedCriteria = criteria
return emptyList()
}
override fun <L : PersistentState> parseCriteria(criteria: QueryCriteria.VaultCustomQueryCriteria<L>): Collection<Predicate> {
modifiedCriteria = criteria
return emptyList()
}
override fun parseCriteria(criteria: QueryCriteria.VaultQueryCriteria): Collection<Predicate> {
modifiedCriteria = criteria.copy(contractStateTypes = setOf(contractType),
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)),
status = Vault.StateStatus.UNCONSUMED)
alreadyHasVaultQuery = true
return emptyList()
}
override fun parseOr(left: QueryCriteria, right: QueryCriteria): Collection<Predicate> {
parse(left)
val modifiedLeft = modifiedCriteria
parse(right)
val modifiedRight = modifiedCriteria
modifiedCriteria = modifiedLeft.or(modifiedRight)
return emptyList()
}
override fun parseAnd(left: QueryCriteria, right: QueryCriteria): Collection<Predicate> {
parse(left)
val modifiedLeft = modifiedCriteria
parse(right)
val modifiedRight = modifiedCriteria
modifiedCriteria = modifiedLeft.and(modifiedRight)
return emptyList()
}
override fun parse(criteria: QueryCriteria, sorting: Sort?): Collection<Predicate> {
val basicQuery = modifiedCriteria
criteria.visit(this)
modifiedCriteria = if (alreadyHasVaultQuery) modifiedCriteria else criteria.and(basicQuery)
return emptyList()
}
fun queryForEligibleStates(criteria: QueryCriteria): Vault.Page<T> {
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
parse(criteria, sorter)
return services.vaultQueryService.queryBy(contractType, modifiedCriteria, sorter)
}
} }
/**
* Generate a transaction that moves an amount of currency to the given pubkey.
*
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
* of given parties. This can be useful if the party you're trying to pay has expectations
* about which type of asset claims they are willing to accept.
*/
@Suspendable @Suspendable
override fun generateSpend(tx: TransactionBuilder, @Throws(StatesNotAvailableException::class)
amount: Amount<Currency>, override fun <T : FungibleAsset<U>, U : Any> tryLockFungibleStatesForSpending(lockId: UUID,
to: AbstractParty, eligibleStatesQuery: QueryCriteria,
onlyFromParties: Set<AbstractParty>?): Pair<TransactionBuilder, List<PublicKey>> { amount: Amount<U>,
// Retrieve unspent and unlocked cash states that meet our spending criteria. contractType: Class<out T>): List<StateAndRef<T>> {
val acceptableCoins = unconsumedStatesForSpending<Cash.State>(amount, onlyFromParties, tx.notary, tx.lockId) if (amount.quantity == 0L) {
return OnLedgerAsset.generateSpend(tx, amount, to, acceptableCoins, return emptyList()
{ state, quantity, owner -> deriveState(state, quantity, owner) }, }
{ Cash().generateMoveCommand() })
}
private fun deriveState(txState: TransactionState<Cash.State>, amount: Amount<Issued<Currency>>, owner: AbstractParty) // TODO This helper code re-writes the query to alter the defaults on things such as soft locks
= txState.copy(data = txState.data.copy(amount = amount, owner = owner)) // and then runs the query. Ideally we would not need to do this.
val results = QueryEditor(services, lockId, contractType).queryForEligibleStates(eligibleStatesQuery)
var claimedAmount = 0L
val claimedStates = mutableListOf<StateAndRef<T>>()
for (state in results.states) {
val issuedAssetToken = state.state.data.amount.token
if (issuedAssetToken.product == amount.token) {
claimedStates += state
claimedAmount += state.state.data.amount.quantity
if (claimedAmount > amount.quantity) {
break
}
}
}
if (claimedStates.isEmpty() || claimedAmount < amount.quantity) {
return emptyList()
}
softLockReserve(lockId, claimedStates.map { it.ref }.toNonEmptySet())
return claimedStates
}
// TODO : Persists this in DB. // TODO : Persists this in DB.
private val authorisedUpgrade = mutableMapOf<StateRef, Class<out UpgradedContract<*, *>>>() private val authorisedUpgrade = mutableMapOf<StateRef, Class<out UpgradedContract<*, *>>>()

View File

@ -105,8 +105,8 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
var participants: Set<CommonSchemaV1.Party>, var participants: Set<CommonSchemaV1.Party>,
/** [OwnableState] attributes */ /** [OwnableState] attributes */
@OneToOne(cascade = arrayOf(CascadeType.ALL)) @Column(name = "owner_id")
var owner: CommonSchemaV1.Party, var owner: AbstractParty,
/** [FungibleAsset] attributes /** [FungibleAsset] attributes
* *
@ -126,7 +126,7 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
var issuerRef: ByteArray var issuerRef: ByteArray
) : PersistentState() { ) : PersistentState() {
constructor(_owner: AbstractParty, _quantity: Long, _issuerParty: AbstractParty, _issuerRef: OpaqueBytes, _participants: List<AbstractParty>) : constructor(_owner: AbstractParty, _quantity: Long, _issuerParty: AbstractParty, _issuerRef: OpaqueBytes, _participants: List<AbstractParty>) :
this(owner = CommonSchemaV1.Party(_owner), this(owner = _owner,
quantity = _quantity, quantity = _quantity,
issuerParty = CommonSchemaV1.Party(_issuerParty), issuerParty = CommonSchemaV1.Party(_issuerParty),
issuerRef = _issuerRef.bytes, issuerRef = _issuerRef.bytes,

View File

@ -1,25 +1,33 @@
package net.corda.core.crypto package net.corda.node.utilities
import net.corda.core.crypto.*
import net.corda.core.utilities.days import net.corda.core.utilities.days
import net.corda.core.utilities.millis import net.corda.core.utilities.millis
import org.bouncycastle.asn1.ASN1Encodable import org.bouncycastle.asn1.ASN1EncodableVector
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x500.X500NameBuilder import org.bouncycastle.asn1.x509.*
import org.bouncycastle.asn1.x500.style.BCStyle import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.KeyPurposeId
import org.bouncycastle.asn1.x509.KeyUsage
import org.bouncycastle.asn1.x509.NameConstraints
import org.bouncycastle.cert.X509CertificateHolder import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.bc.BcX509ExtensionUtils
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder
import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder
import org.bouncycastle.util.io.pem.PemReader import org.bouncycastle.util.io.pem.PemReader
import java.io.FileReader import java.io.FileReader
import java.io.FileWriter import java.io.FileWriter
import java.io.InputStream import java.io.InputStream
import java.math.BigInteger
import java.nio.file.Path import java.nio.file.Path
import java.security.KeyPair import java.security.KeyPair
import java.security.PublicKey import java.security.PublicKey
import java.security.cert.* import java.security.cert.*
import java.security.cert.Certificate
import java.time.Duration import java.time.Duration
import java.time.Instant import java.time.Instant
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
@ -69,44 +77,13 @@ object X509Utilities {
return Pair(notBefore, notAfter) return Pair(notBefore, notAfter)
} }
/**
* Return a bogus X509 for dev purposes. Use [getX509Name] for something more real.
*/
@Deprecated("Full legal names should be specified in all configurations")
fun getDevX509Name(commonName: String): X500Name {
val nameBuilder = X500NameBuilder(BCStyle.INSTANCE)
nameBuilder.addRDN(BCStyle.CN, commonName)
nameBuilder.addRDN(BCStyle.O, "R3")
nameBuilder.addRDN(BCStyle.OU, "corda")
nameBuilder.addRDN(BCStyle.L, "London")
nameBuilder.addRDN(BCStyle.C, "GB")
return nameBuilder.build()
}
/**
* Generate a distinguished name from the provided values.
*
* @see [CoreTestUtils.getTestX509Name] for generating distinguished names for test cases.
*/
@JvmOverloads
@JvmStatic
fun getX509Name(myLegalName: String, nearestCity: String, email: String, country: String? = null): X500Name {
return X500NameBuilder(BCStyle.INSTANCE).let { builder ->
builder.addRDN(BCStyle.CN, myLegalName)
builder.addRDN(BCStyle.L, nearestCity)
country?.let { builder.addRDN(BCStyle.C, it) }
builder.addRDN(BCStyle.E, email)
builder.build()
}
}
/* /*
* Create a de novo root self-signed X509 v3 CA cert. * Create a de novo root self-signed X509 v3 CA cert.
*/ */
@JvmStatic @JvmStatic
fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder { fun createSelfSignedCACertificate(subject: X500Name, keyPair: KeyPair, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW): X509CertificateHolder {
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second) val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second)
return Crypto.createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window) return createCertificate(CertificateType.ROOT_CA, subject, keyPair, subject, keyPair.public, window)
} }
/** /**
@ -126,7 +103,7 @@ object X509Utilities {
validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW, validityWindow: Pair<Duration, Duration> = DEFAULT_VALIDITY_WINDOW,
nameConstraints: NameConstraints? = null): X509CertificateHolder { nameConstraints: NameConstraints? = null): X509CertificateHolder {
val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate) val window = getCertificateValidityWindow(validityWindow.first, validityWindow.second, issuerCertificate)
return Crypto.createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints) return createCertificate(certificateType, issuerCertificate.subject, issuerKeyPair, subject, subjectPublicKey, window, nameConstraints)
} }
fun validateCertificateChain(trustedRoot: X509CertificateHolder, vararg certificates: Certificate) { fun validateCertificateChain(trustedRoot: X509CertificateHolder, vararg certificates: Certificate) {
@ -168,54 +145,93 @@ object X509Utilities {
} }
} }
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme = DEFAULT_TLS_SIGNATURE_SCHEME) = Crypto.createCertificateSigningRequest(subject, keyPair, signatureScheme) /**
} * Build a partial X.509 certificate ready for signing.
*
* @param issuer name of the issuing entity.
* @param subject name of the certificate subject.
* @param subjectPublicKey public key of the certificate subject.
* @param validityWindow the time period the certificate is valid for.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
*/
fun createCertificate(certificateType: CertificateType, issuer: X500Name,
subject: X500Name, subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509v3CertificateBuilder {
/** val serial = BigInteger.valueOf(random63BitValue())
* Rebuild the distinguished name, adding a postfix to the common name. If no common name is present. val keyPurposes = DERSequence(ASN1EncodableVector().apply { certificateType.purposes.forEach { add(it) } })
* @throws IllegalArgumentException if the distinguished name does not contain a common name element. val subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(ASN1Sequence.getInstance(subjectPublicKey.encoded))
*/
fun X500Name.appendToCommonName(commonName: String): X500Name = mutateCommonName { attr -> attr.toString() + commonName }
/** val builder = JcaX509v3CertificateBuilder(issuer, serial, validityWindow.first, validityWindow.second, subject, subjectPublicKey)
* Rebuild the distinguished name, replacing the common name with the given value. If no common name is present, this .addExtension(Extension.subjectKeyIdentifier, false, BcX509ExtensionUtils().createSubjectKeyIdentifier(subjectPublicKeyInfo))
* adds one. .addExtension(Extension.basicConstraints, certificateType.isCA, BasicConstraints(certificateType.isCA))
* @throws IllegalArgumentException if the distinguished name does not contain a common name element. .addExtension(Extension.keyUsage, false, certificateType.keyUsage)
*/ .addExtension(Extension.extendedKeyUsage, false, keyPurposes)
fun X500Name.replaceCommonName(commonName: String): X500Name = mutateCommonName { _ -> commonName }
/** if (nameConstraints != null) {
* Rebuild the distinguished name, replacing the common name with a value generated from the provided function. builder.addExtension(Extension.nameConstraints, true, nameConstraints)
* }
* @param mutator a function to generate the new value from the previous one. return builder
* @throws IllegalArgumentException if the distinguished name does not contain a common name element. }
*/
private fun X500Name.mutateCommonName(mutator: (ASN1Encodable) -> String): X500Name { /**
val builder = X500NameBuilder(BCStyle.INSTANCE) * Build and sign an X.509 certificate with the given signer.
var matched = false *
this.rdNs.forEach { rdn -> * @param issuer name of the issuing entity.
rdn.typesAndValues.forEach { typeAndValue -> * @param issuerSigner content signer to sign the certificate with.
when (typeAndValue.type) { * @param subject name of the certificate subject.
BCStyle.CN -> { * @param subjectPublicKey public key of the certificate subject.
matched = true * @param validityWindow the time period the certificate is valid for.
builder.addRDN(typeAndValue.type, mutator(typeAndValue.value)) * @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
} */
else -> { fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerSigner: ContentSigner,
builder.addRDN(typeAndValue) subject: X500Name, subjectPublicKey: PublicKey,
} validityWindow: Pair<Date, Date>,
} nameConstraints: NameConstraints? = null): X509CertificateHolder {
val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
return builder.build(issuerSigner).apply {
require(isValidOn(Date()))
} }
} }
require(matched) { "Input X.500 name must include a common name (CN) attribute: ${this}" }
return builder.build() /**
* Build and sign an X.509 certificate with CA cert private key.
*
* @param issuer name of the issuing entity.
* @param issuerKeyPair the public & private key to sign the certificate with.
* @param subject name of the certificate subject.
* @param subjectPublicKey public key of the certificate subject.
* @param validityWindow the time period the certificate is valid for.
* @param nameConstraints any name constraints to impose on certificates signed by the generated certificate.
*/
fun createCertificate(certificateType: CertificateType, issuer: X500Name, issuerKeyPair: KeyPair,
subject: X500Name, subjectPublicKey: PublicKey,
validityWindow: Pair<Date, Date>,
nameConstraints: NameConstraints? = null): X509CertificateHolder {
val signatureScheme = Crypto.findSignatureScheme(issuerKeyPair.private)
val provider = Crypto.providerMap[signatureScheme.providerName]
val builder = createCertificate(certificateType, issuer, subject, subjectPublicKey, validityWindow, nameConstraints)
val signer = ContentSignerBuilder.build(signatureScheme, issuerKeyPair.private, provider)
return builder.build(signer).apply {
require(isValidOn(Date()))
require(isSignatureValid(JcaContentVerifierProviderBuilder().build(issuerKeyPair.public)))
}
}
/**
* Create certificate signing request using provided information.
*/
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair, signatureScheme: SignatureScheme): PKCS10CertificationRequest {
val signer = ContentSignerBuilder.build(signatureScheme, keyPair.private, Crypto.providerMap[signatureScheme.providerName])
return JcaPKCS10CertificationRequestBuilder(subject, keyPair.public).build(signer)
}
fun createCertificateSigningRequest(subject: X500Name, keyPair: KeyPair) = createCertificateSigningRequest(subject, keyPair, DEFAULT_TLS_SIGNATURE_SCHEME)
} }
val X500Name.commonName: String get() = getRDNs(BCStyle.CN).first().first.value.toString()
val X500Name.orgName: String? get() = getRDNs(BCStyle.O).firstOrNull()?.first?.value?.toString()
val X500Name.location: String get() = getRDNs(BCStyle.L).first().first.value.toString()
val X500Name.locationOrNull: String? get() = try { location } catch (e: Exception) { null }
val X509Certificate.subject: X500Name get() = X509CertificateHolder(encoded).subject
val X509CertificateHolder.cert: X509Certificate get() = JcaX509CertificateConverter().getCertificate(this)
class CertificateStream(val input: InputStream) { class CertificateStream(val input: InputStream) {
private val certificateFactory = CertificateFactory.getInstance("X.509") private val certificateFactory = CertificateFactory.getInstance("X.509")
@ -223,8 +239,6 @@ class CertificateStream(val input: InputStream) {
fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate fun nextCertificate(): X509Certificate = certificateFactory.generateCertificate(input) as X509Certificate
} }
data class CertificateAndKeyPair(val certificate: X509CertificateHolder, val keyPair: KeyPair)
enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) { enum class CertificateType(val keyUsage: KeyUsage, vararg val purposes: KeyPurposeId, val isCA: Boolean) {
ROOT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true), ROOT_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),
INTERMEDIATE_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true), INTERMEDIATE_CA(KeyUsage(KeyUsage.digitalSignature or KeyUsage.keyCertSign or KeyUsage.cRLSign), KeyPurposeId.id_kp_serverAuth, KeyPurposeId.id_kp_clientAuth, KeyPurposeId.anyExtendedKeyUsage, isCA = true),

View File

@ -1,7 +1,7 @@
package net.corda.node.utilities.registration package net.corda.node.utilities.registration
import com.google.common.net.MediaType import com.google.common.net.MediaType
import net.corda.core.crypto.CertificateStream import net.corda.node.utilities.CertificateStream
import org.apache.commons.io.IOUtils import org.apache.commons.io.IOUtils
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import java.io.IOException import java.io.IOException

View File

@ -1,17 +1,15 @@
package net.corda.node.utilities.registration package net.corda.node.utilities.registration
import net.corda.core.crypto.CertificateType
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_CA
import net.corda.core.crypto.X509Utilities.CORDA_CLIENT_TLS
import net.corda.core.crypto.X509Utilities.CORDA_ROOT_CA
import net.corda.core.crypto.cert import net.corda.core.crypto.cert
import net.corda.core.internal.* import net.corda.core.internal.*
import net.corda.core.utilities.seconds import net.corda.core.utilities.seconds
import net.corda.core.utilities.validateX500Name import net.corda.core.utilities.validateX500Name
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.* import net.corda.node.utilities.*
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_CA
import net.corda.node.utilities.X509Utilities.CORDA_CLIENT_TLS
import net.corda.node.utilities.X509Utilities.CORDA_ROOT_CA
import org.bouncycastle.openssl.jcajce.JcaPEMWriter import org.bouncycastle.openssl.jcajce.JcaPEMWriter
import org.bouncycastle.util.io.pem.PemObject import org.bouncycastle.util.io.pem.PemObject
import java.io.StringWriter import java.io.StringWriter

View File

@ -1,42 +1,52 @@
package net.corda.node.services.vault; package net.corda.node.services.vault;
import com.google.common.collect.*; import com.google.common.collect.ImmutableSet;
import net.corda.contracts.*; import kotlin.Pair;
import net.corda.contracts.asset.*; import net.corda.contracts.DealState;
import net.corda.contracts.asset.Cash;
import net.corda.core.contracts.*; import net.corda.core.contracts.*;
import net.corda.core.crypto.*; import net.corda.core.crypto.EncodingUtils;
import net.corda.core.identity.*; import net.corda.core.identity.AbstractParty;
import net.corda.core.messaging.*; import net.corda.core.messaging.DataFeed;
import net.corda.core.node.services.*; import net.corda.core.node.services.Vault;
import net.corda.core.node.services.VaultQueryException;
import net.corda.core.node.services.VaultQueryService;
import net.corda.core.node.services.VaultService;
import net.corda.core.node.services.vault.*; import net.corda.core.node.services.vault.*;
import net.corda.core.node.services.vault.QueryCriteria.*; import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria;
import net.corda.core.schemas.*; import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria;
import net.corda.core.transactions.*; import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
import net.corda.core.utilities.*; import net.corda.core.utilities.OpaqueBytes;
import net.corda.node.services.database.*; import net.corda.node.utilities.CordaPersistence;
import net.corda.node.services.schema.*; import net.corda.node.services.identity.*;
import net.corda.node.utilities.*; import net.corda.schemas.CashSchemaV1;
import net.corda.schemas.*; import net.corda.testing.TestConstants;
import net.corda.testing.*; import net.corda.testing.TestDependencyInjectionBase;
import net.corda.testing.contracts.*; import net.corda.testing.contracts.DummyLinearContract;
import net.corda.testing.node.*; import net.corda.testing.contracts.VaultFiller;
import net.corda.testing.schemas.*; import net.corda.testing.node.MockServices;
import org.jetbrains.annotations.*; import org.junit.After;
import org.junit.*; import org.junit.Before;
import org.junit.Test;
import rx.Observable; import rx.Observable;
import java.io.*; import java.io.IOException;
import java.lang.reflect.*; import java.lang.reflect.Field;
import java.security.KeyPair;
import java.util.*; import java.util.*;
import java.util.stream.*; import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import static net.corda.contracts.asset.CashKt.*; import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER;
import static net.corda.core.node.services.vault.QueryCriteriaUtils.*; import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER_KEY;
import static net.corda.core.utilities.ByteArrays.*; import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM;
import static net.corda.node.utilities.CordaPersistenceKt.*; import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE;
import static net.corda.core.utilities.ByteArrays.toHexString;
import static net.corda.testing.CoreTestUtils.*; import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.node.MockServicesKt.*; import static net.corda.testing.node.MockServicesKt.makeTestDatabaseAndMockServices;
import static org.assertj.core.api.Assertions.*; import static net.corda.testing.TestConstants.*;
import static org.assertj.core.api.Assertions.assertThat;
public class VaultQueryJavaTests extends TestDependencyInjectionBase { public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@ -47,38 +57,14 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@Before @Before
public void setUp() { public void setUp() {
Properties dataSourceProps = makeTestDataSourceProperties(SecureHash.randomSHA256().toString()); ArrayList<KeyPair> keys = new ArrayList<>();
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties()); keys.add(getMEGA_CORP_KEY());
database.transaction(statement -> { InMemoryIdentityService identityService = new InMemoryIdentityService(getMOCK_IDENTITIES(), Collections.emptyMap(), getDUMMY_CA().getCertificate());
Set<MappedSchema> customSchemas = new HashSet<>(Collections.singletonList(DummyLinearStateSchemaV1.INSTANCE)); Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(Collections.EMPTY_SET, keys);
HibernateConfiguration hibernateConfig = new HibernateConfiguration(new NodeSchemaService(customSchemas), makeTestDatabaseProperties()); database = databaseAndServices.getFirst();
services = new MockServices(getMEGA_CORP_KEY()) { services = databaseAndServices.getSecond();
@NotNull vaultSvc = services.getVaultService();
@Override vaultQuerySvc = services.getVaultQueryService();
public VaultService getVaultService() {
if (vaultSvc != null) return vaultSvc;
return makeVaultService(dataSourceProps, hibernateConfig);
}
@NotNull
@Override
public VaultQueryService getVaultQueryService() {
return new HibernateVaultQueryImpl(hibernateConfig, vaultSvc.getUpdatesPublisher());
}
@Override
public void recordTransactions(@NotNull Iterable<SignedTransaction> txs) {
for (SignedTransaction stx : txs) {
getValidatedTransactions().addTransaction(stx);
}
Stream<WireTransaction> wtxn = StreamSupport.stream(txs.spliterator(), false).map(SignedTransaction::getTx);
vaultSvc.notifyAll(wtxn.collect(Collectors.toList()));
}
};
vaultSvc = services.getVaultService();
vaultQuerySvc = services.getVaultQueryService();
return services;
});
} }
@After @After

View File

@ -148,8 +148,8 @@ class TwoPartyTradeFlowTests {
bobNode.disableDBCloseOnStop() bobNode.disableDBCloseOnStop()
val cashStates = bobNode.database.transaction { val cashStates = bobNode.database.transaction {
bobNode.services.fillWithSomeTestCash(2000.DOLLARS, notaryNode.info.notaryIdentity, 3, 3) bobNode.services.fillWithSomeTestCash(2000.DOLLARS, notaryNode.info.notaryIdentity, 3, 3)
} }
val alicesFakePaper = aliceNode.database.transaction { val alicesFakePaper = aliceNode.database.transaction {
fillUpForSeller(false, cpIssuer, aliceNode.info.legalIdentity, fillUpForSeller(false, cpIssuer, aliceNode.info.legalIdentity,
@ -168,7 +168,7 @@ class TwoPartyTradeFlowTests {
} }
val (bobStateMachine, aliceResult) = runBuyerAndSeller(notaryNode, aliceNode, bobNode, val (bobStateMachine, aliceResult) = runBuyerAndSeller(notaryNode, aliceNode, bobNode,
"alice's paper".outputStateAndRef()) "alice's paper".outputStateAndRef())
assertEquals(aliceResult.getOrThrow(), bobStateMachine.getOrThrow().resultFuture.getOrThrow()) assertEquals(aliceResult.getOrThrow(), bobStateMachine.getOrThrow().resultFuture.getOrThrow())
@ -533,11 +533,11 @@ class TwoPartyTradeFlowTests {
override fun call(): SignedTransaction { override fun call(): SignedTransaction {
send(buyer, Pair(notary.notaryIdentity, price)) send(buyer, Pair(notary.notaryIdentity, price))
return subFlow(Seller( return subFlow(Seller(
buyer, buyer,
notary, notary,
assetToSell, assetToSell,
price, price,
me.party)) me.party))
} }
} }

View File

@ -3,17 +3,21 @@ package net.corda.node.services.database
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.contracts.asset.DummyFungibleContract import net.corda.contracts.asset.DummyFungibleContract
import net.corda.contracts.asset.sumCash
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.toBase58String import net.corda.core.crypto.toBase58String
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultQueryService
import net.corda.core.node.services.VaultService import net.corda.core.node.services.VaultService
import net.corda.core.schemas.CommonSchemaV1 import net.corda.core.schemas.CommonSchemaV1
import net.corda.core.schemas.PersistentStateRef import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.vault.HibernateVaultQueryImpl
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.services.vault.VaultSchemaV1
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
@ -38,6 +42,7 @@ import org.junit.After
import org.junit.Assert import org.junit.Assert
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import java.math.BigDecimal
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
import javax.persistence.EntityManager import javax.persistence.EntityManager
@ -67,7 +72,8 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
database = configureDatabase(dataSourceProps, defaultDatabaseProperties) database = configureDatabase(dataSourceProps, defaultDatabaseProperties)
val customSchemas = setOf(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3) val customSchemas = setOf(VaultSchemaV1, CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3)
database.transaction { database.transaction {
hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), makeTestDatabaseProperties()) val identityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate)
hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), makeTestDatabaseProperties(), identityService)
services = object : MockServices(BOB_KEY) { services = object : MockServices(BOB_KEY) {
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig) override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
@ -126,7 +132,8 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
// execute query // execute query
val queryResults = entityManager.createQuery(criteriaQuery).resultList val queryResults = entityManager.createQuery(criteriaQuery).resultList
assertThat(queryResults.size).isEqualTo(6) val coins = queryResults.map { it.contractState.deserialize<TransactionState<Cash.State>>().data }.sumCash()
assertThat(coins.toDecimal() >= BigDecimal("50.00"))
} }
@Test @Test

View File

@ -1,6 +1,7 @@
package net.corda.node.services.events package net.corda.node.services.events
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.concurrent.CordaFuture
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.containsAny import net.corda.core.crypto.containsAny
import net.corda.core.flows.* import net.corda.core.flows.*
@ -15,10 +16,11 @@ import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
import net.corda.core.node.services.vault.Sort import net.corda.core.node.services.vault.Sort
import net.corda.core.node.services.vault.SortAttribute import net.corda.core.node.services.vault.SortAttribute
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.testing.DUMMY_NOTARY import net.corda.core.utilities.getOrThrow
import net.corda.node.services.network.NetworkMapService import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.statemachine.StateMachineManager import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.node.MockNetwork import net.corda.testing.node.MockNetwork
import org.junit.After import org.junit.After
@ -30,6 +32,10 @@ import java.time.Instant
import kotlin.test.assertEquals import kotlin.test.assertEquals
class ScheduledFlowTests { class ScheduledFlowTests {
companion object {
val PAGE_SIZE = 20
val SORTING = Sort(listOf(Sort.SortColumn(SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID), Sort.Direction.DESC)))
}
lateinit var mockNet: MockNetwork lateinit var mockNet: MockNetwork
lateinit var notaryNode: MockNetwork.MockNode lateinit var notaryNode: MockNetwork.MockNode
lateinit var nodeA: MockNetwork.MockNode lateinit var nodeA: MockNetwork.MockNode
@ -133,33 +139,50 @@ class ScheduledFlowTests {
@Test @Test
fun `run a whole batch of scheduled flows`() { fun `run a whole batch of scheduled flows`() {
val N = 100 val N = 100
val futures = mutableListOf<CordaFuture<*>>()
for (i in 0..N - 1) { for (i in 0..N - 1) {
nodeA.services.startFlow(InsertInitialStateFlow(nodeB.info.legalIdentity)) futures.add(nodeA.services.startFlow(InsertInitialStateFlow(nodeB.info.legalIdentity)).resultFuture)
nodeB.services.startFlow(InsertInitialStateFlow(nodeA.info.legalIdentity)) futures.add(nodeB.services.startFlow(InsertInitialStateFlow(nodeA.info.legalIdentity)).resultFuture)
} }
mockNet.waitQuiescent() mockNet.waitQuiescent()
val statesFromA = nodeA.database.transaction { // Check all of the flows completed successfully
futures.forEach { it.getOrThrow() }
// Convert the states into maps to make error reporting easier
val statesFromA: List<StateAndRef<ScheduledState>> = nodeA.database.transaction {
queryStatesWithPaging(nodeA.services.vaultQueryService) queryStatesWithPaging(nodeA.services.vaultQueryService)
} }
val statesFromB = nodeB.database.transaction { val statesFromB: List<StateAndRef<ScheduledState>> = nodeB.database.transaction {
queryStatesWithPaging(nodeB.services.vaultQueryService) queryStatesWithPaging(nodeB.services.vaultQueryService)
} }
assertEquals(2 * N, statesFromA.count(), "Expect all states to be present") assertEquals(2 * N, statesFromA.count(), "Expect all states to be present")
statesFromA.forEach { ref ->
if (ref !in statesFromB) {
throw IllegalStateException("State $ref is only present on node A.")
}
}
statesFromB.forEach { ref ->
if (ref !in statesFromA) {
throw IllegalStateException("State $ref is only present on node B.")
}
}
assertEquals(statesFromA, statesFromB, "Expect identical data on both nodes") assertEquals(statesFromA, statesFromB, "Expect identical data on both nodes")
assertTrue("Expect all states have run the scheduled task", statesFromB.all { it.state.data.processed }) assertTrue("Expect all states have run the scheduled task", statesFromB.all { it.state.data.processed })
} }
// Demonstrate Vault Query paging and sorting /**
val PAGE_SIZE = 20 * Query all states from the Vault, fetching results as a series of pages with ordered states in order to perform
val sorting = Sort(listOf(Sort.SortColumn(SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID), Sort.Direction.DESC))) * integration testing of that functionality.
*
* @return states ordered by the transaction ID.
*/
private fun queryStatesWithPaging(vaultQueryService: VaultQueryService): List<StateAndRef<ScheduledState>> { private fun queryStatesWithPaging(vaultQueryService: VaultQueryService): List<StateAndRef<ScheduledState>> {
var pageNumber = DEFAULT_PAGE_NUM var pageNumber = DEFAULT_PAGE_NUM
val states = mutableListOf<StateAndRef<ScheduledState>>() val states = mutableListOf<StateAndRef<ScheduledState>>()
do { do {
val pageSpec = PageSpecification(pageSize = PAGE_SIZE, pageNumber = pageNumber) val pageSpec = PageSpecification(pageSize = PAGE_SIZE, pageNumber = pageNumber)
val results = vaultQueryService.queryBy<ScheduledState>(VaultQueryCriteria(), pageSpec, sorting) val results = vaultQueryService.queryBy<ScheduledState>(VaultQueryCriteria(), pageSpec, SORTING)
states.addAll(results.states) states.addAll(results.states)
pageNumber++ pageNumber++
} while ((pageSpec.pageSize * (pageNumber)) <= results.totalStatesAvailable) } while ((pageSpec.pageSize * (pageNumber)) <= results.totalStatesAvailable)

View File

@ -1,12 +1,17 @@
package net.corda.node.services.network package net.corda.node.services.network
import net.corda.core.crypto.* import net.corda.core.crypto.CertificateAndKeyPair
import net.corda.core.identity.AnonymousPartyAndPath import net.corda.core.crypto.Crypto
import net.corda.core.crypto.cert
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.AnonymousPartyAndPath
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.X509Utilities
import net.corda.testing.* import net.corda.testing.*
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.junit.Test import org.junit.Test

View File

@ -10,9 +10,12 @@ import net.corda.core.schemas.QueryableState
import net.corda.testing.LogHelper import net.corda.testing.LogHelper
import net.corda.node.services.api.SchemaService import net.corda.node.services.api.SchemaService
import net.corda.node.services.database.HibernateConfiguration import net.corda.node.services.database.HibernateConfiguration
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.configureDatabase
import net.corda.testing.DUMMY_CA
import net.corda.testing.MEGA_CORP import net.corda.testing.MEGA_CORP
import net.corda.testing.MOCK_IDENTITIES
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDataSourceProperties
import net.corda.testing.node.makeTestDatabaseProperties import net.corda.testing.node.makeTestDatabaseProperties
import org.hibernate.annotations.Cascade import org.hibernate.annotations.Cascade
@ -102,7 +105,8 @@ class HibernateObserverTests {
} }
@Suppress("UNUSED_VARIABLE") @Suppress("UNUSED_VARIABLE")
val observer = HibernateObserver(rawUpdatesPublisher, HibernateConfiguration(schemaService, makeTestDatabaseProperties())) val identityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate)
val observer = HibernateObserver(rawUpdatesPublisher, HibernateConfiguration(schemaService, makeTestDatabaseProperties(), identityService))
database.transaction { database.transaction {
rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0))))) rawUpdatesPublisher.onNext(Vault.Update(emptySet(), setOf(StateAndRef(TransactionState(TestState(), MEGA_CORP), StateRef(SecureHash.sha256("dummy"), 0)))))
val parentRowCountResult = TransactionManager.current().connection.prepareStatement("select count(*) from Parents").executeQuery() val parentRowCountResult = TransactionManager.current().connection.prepareStatement("select count(*) from Parents").executeQuery()

View File

@ -1,18 +1,22 @@
package net.corda.node.services.vault package net.corda.node.services.vault
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.contracts.asset.sumCash
import net.corda.contracts.getCashBalance import net.corda.contracts.getCashBalance
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.services.StatesNotAvailableException import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultQueryService import net.corda.core.node.services.VaultQueryService
import net.corda.core.node.services.VaultService import net.corda.core.node.services.VaultService
import net.corda.core.node.services.queryBy import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.QueryCriteria.* import net.corda.core.node.services.vault.QueryCriteria.*
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
import net.corda.core.transactions.NotaryChangeWireTransaction import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
@ -20,20 +24,20 @@ import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toNonEmptySet import net.corda.core.utilities.toNonEmptySet
import net.corda.node.services.database.HibernateConfiguration import net.corda.node.services.database.HibernateConfiguration
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.fillWithSomeTestCash import net.corda.testing.contracts.fillWithSomeTestCash
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestDatabaseProperties
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.After import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import rx.observers.TestSubscriber import rx.observers.TestSubscriber
import java.math.BigDecimal
import java.util.* import java.util.*
import java.util.concurrent.CountDownLatch import java.util.concurrent.CountDownLatch
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -50,23 +54,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Before @Before
fun setUp() { fun setUp() {
LogHelper.setLevel(NodeVaultService::class) LogHelper.setLevel(NodeVaultService::class)
val dataSourceProps = makeTestDataSourceProperties() val databaseAndServices = makeTestDatabaseAndMockServices()
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties()) database = databaseAndServices.first
database.transaction { services = databaseAndServices.second
val hibernateConfig = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties())
services = object : MockServices() {
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
vaultService.notifyAll(txs.map { it.tx })
}
override val vaultQueryService : VaultQueryService = HibernateVaultQueryImpl(hibernateConfig, vaultService.updatesPublisher)
}
}
} }
@After @After
@ -75,6 +65,25 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
LogHelper.reset(NodeVaultService::class) LogHelper.reset(NodeVaultService::class)
} }
@Suspendable
private fun VaultService.unconsumedCashStatesForSpending(amount: Amount<Currency>,
onlyFromIssuerParties: Set<AbstractParty>? = null,
notary: Party? = null,
lockId: UUID = UUID.randomUUID(),
withIssuerRefs: Set<OpaqueBytes>? = null): List<StateAndRef<Cash.State>> {
val notaryName = if (notary != null) listOf(notary.name) else null
var baseCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(notaryName = notaryName)
if (onlyFromIssuerParties != null || withIssuerRefs != null) {
baseCriteria = baseCriteria.and(QueryCriteria.FungibleAssetQueryCriteria(
issuerPartyName = onlyFromIssuerParties?.toList(),
issuerRef = withIssuerRefs?.toList()))
}
return tryLockFungibleStatesForSpending(lockId, baseCriteria, amount, Cash.State::class.java)
}
@Test @Test
fun `states not local to instance`() { fun `states not local to instance`() {
database.transaction { database.transaction {
@ -308,7 +317,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
assertThat(unconsumedStates).hasSize(1) assertThat(unconsumedStates).hasSize(1)
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(100.DOLLARS, lockId = UUID.randomUUID()) val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(100.DOLLARS)
spendableStatesUSD.forEach(::println) spendableStatesUSD.forEach(::println)
assertThat(spendableStatesUSD).hasSize(1) assertThat(spendableStatesUSD).hasSize(1)
assertThat(spendableStatesUSD[0].state.data.amount.quantity).isEqualTo(100L * 100) assertThat(spendableStatesUSD[0].state.data.amount.quantity).isEqualTo(100L * 100)
@ -324,12 +333,13 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (DUMMY_CASH_ISSUER)) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (DUMMY_CASH_ISSUER))
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(1)), issuerKey = BOC_KEY) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(1)), issuerKey = BOC_KEY)
val spendableStatesUSD = vaultSvc.unconsumedStatesForSpending<Cash.State>(200.DOLLARS, lockId = UUID.randomUUID(), val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(200.DOLLARS,
onlyFromIssuerParties = setOf(DUMMY_CASH_ISSUER.party, BOC)).toList() onlyFromIssuerParties = setOf(DUMMY_CASH_ISSUER.party, BOC))
spendableStatesUSD.forEach(::println) spendableStatesUSD.forEach(::println)
assertThat(spendableStatesUSD).hasSize(2) assertThat(spendableStatesUSD).hasSize(2)
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer).isEqualTo(DUMMY_CASH_ISSUER) assertThat(spendableStatesUSD[0].state.data.amount.token.issuer).isIn(DUMMY_CASH_ISSUER, BOC.ref(1))
assertThat(spendableStatesUSD[1].state.data.amount.token.issuer).isEqualTo(BOC.ref(1)) assertThat(spendableStatesUSD[1].state.data.amount.token.issuer).isIn(DUMMY_CASH_ISSUER, BOC.ref(1))
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer).isNotEqualTo(spendableStatesUSD[1].state.data.amount.token.issuer)
} }
} }
@ -345,12 +355,13 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
assertThat(unconsumedStates).hasSize(4) assertThat(unconsumedStates).hasSize(4)
val spendableStatesUSD = vaultSvc.unconsumedStatesForSpending<Cash.State>(200.DOLLARS, lockId = UUID.randomUUID(), val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(200.DOLLARS,
onlyFromIssuerParties = setOf(BOC), withIssuerRefs = setOf(OpaqueBytes.of(1), OpaqueBytes.of(2))).toList() onlyFromIssuerParties = setOf(BOC), withIssuerRefs = setOf(OpaqueBytes.of(1), OpaqueBytes.of(2)))
assertThat(spendableStatesUSD).hasSize(2) assertThat(spendableStatesUSD).hasSize(2)
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.party).isEqualTo(BOC) assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.party).isEqualTo(BOC)
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.reference).isEqualTo(BOC.ref(1).reference) assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.reference).isIn(BOC.ref(1).reference, BOC.ref(2).reference)
assertThat(spendableStatesUSD[1].state.data.amount.token.issuer.reference).isEqualTo(BOC.ref(2).reference) assertThat(spendableStatesUSD[1].state.data.amount.token.issuer.reference).isIn(BOC.ref(1).reference, BOC.ref(2).reference)
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.reference).isNotEqualTo(spendableStatesUSD[1].state.data.amount.token.issuer.reference)
} }
} }
@ -363,9 +374,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
assertThat(unconsumedStates).hasSize(1) assertThat(unconsumedStates).hasSize(1)
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(110.DOLLARS, lockId = UUID.randomUUID()) val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(110.DOLLARS)
spendableStatesUSD.forEach(::println) spendableStatesUSD.forEach(::println)
assertThat(spendableStatesUSD).hasSize(1) assertThat(spendableStatesUSD).hasSize(0)
val criteriaLocked = VaultQueryCriteria(softLockingCondition = SoftLockingCondition(SoftLockingType.LOCKED_ONLY)) val criteriaLocked = VaultQueryCriteria(softLockingCondition = SoftLockingCondition(SoftLockingType.LOCKED_ONLY))
assertThat(vaultQuery.queryBy<Cash.State>(criteriaLocked).states).hasSize(0) assertThat(vaultQuery.queryBy<Cash.State>(criteriaLocked).states).hasSize(0)
} }
@ -380,7 +391,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
assertThat(unconsumedStates).hasSize(2) assertThat(unconsumedStates).hasSize(2)
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(1.DOLLARS, lockId = UUID.randomUUID()) val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(1.DOLLARS)
spendableStatesUSD.forEach(::println) spendableStatesUSD.forEach(::println)
assertThat(spendableStatesUSD).hasSize(1) assertThat(spendableStatesUSD).hasSize(1)
assertThat(spendableStatesUSD[0].state.data.amount.quantity).isGreaterThanOrEqualTo(100L) assertThat(spendableStatesUSD[0].state.data.amount.quantity).isGreaterThanOrEqualTo(100L)
@ -397,16 +408,30 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 10, 10, Random(0L)) services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 10, 10, Random(0L))
services.fillWithSomeTestCash(100.SWISS_FRANCS, DUMMY_NOTARY, 10, 10, Random(0L)) services.fillWithSomeTestCash(100.SWISS_FRANCS, DUMMY_NOTARY, 10, 10, Random(0L))
var unlockedStates = 30
val allStates = vaultQuery.queryBy<Cash.State>().states val allStates = vaultQuery.queryBy<Cash.State>().states
assertThat(allStates).hasSize(30) assertThat(allStates).hasSize(unlockedStates)
var lockedCount = 0
for (i in 1..5) { for (i in 1..5) {
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(20.DOLLARS, lockId = UUID.randomUUID()) val lockId = UUID.randomUUID()
val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(20.DOLLARS, lockId = lockId)
spendableStatesUSD.forEach(::println) spendableStatesUSD.forEach(::println)
assertThat(spendableStatesUSD.size <= unlockedStates)
unlockedStates -= spendableStatesUSD.size
val criteriaLocked = VaultQueryCriteria(softLockingCondition = SoftLockingCondition(SoftLockingType.SPECIFIED, listOf(lockId)))
val lockedStates = vaultQuery.queryBy<Cash.State>(criteriaLocked).states
if (spendableStatesUSD.isNotEmpty()) {
assertEquals(spendableStatesUSD.size, lockedStates.size)
val lockedTotal = lockedStates.map { it.state.data }.sumCash()
val foundAmount = spendableStatesUSD.map { it.state.data }.sumCash()
assertThat(foundAmount.toDecimal() >= BigDecimal("20.00"))
assertThat(lockedTotal == foundAmount)
lockedCount += lockedStates.size
}
} }
// note only 3 spend attempts succeed with a total of 8 states
val criteriaLocked = VaultQueryCriteria(softLockingCondition = SoftLockingCondition(SoftLockingType.LOCKED_ONLY)) val criteriaLocked = VaultQueryCriteria(softLockingCondition = SoftLockingCondition(SoftLockingType.LOCKED_ONLY))
assertThat(vaultQuery.queryBy<Cash.State>(criteriaLocked).states).hasSize(8) assertThat(vaultQuery.queryBy<Cash.State>(criteriaLocked).states).hasSize(lockedCount)
} }
} }
@ -484,7 +509,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
database.transaction { database.transaction {
val moveTx = TransactionBuilder(services.myInfo.legalIdentity).apply { val moveTx = TransactionBuilder(services.myInfo.legalIdentity).apply {
service.generateSpend(this, Amount(1000, GBP), thirdPartyIdentity) Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity)
}.toWireTransaction() }.toWireTransaction()
service.notify(moveTx) service.notify(moveTx)
} }
@ -530,7 +555,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
// Move cash // Move cash
val moveTx = database.transaction { val moveTx = database.transaction {
TransactionBuilder(newNotary).apply { TransactionBuilder(newNotary).apply {
service.generateSpend(this, Amount(1000, GBP), thirdPartyIdentity) Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity)
}.toWireTransaction() }.toWireTransaction()
} }

View File

@ -1,12 +1,14 @@
package net.corda.node.services.vault package net.corda.node.services.vault
import net.corda.contracts.* import net.corda.contracts.CommercialPaper
import net.corda.contracts.Commodity
import net.corda.contracts.DealState
import net.corda.contracts.asset.Cash import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.toBase58String import net.corda.core.crypto.toBase58String
import net.corda.core.utilities.days
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.services.* import net.corda.core.node.services.*
import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.*
@ -17,7 +19,9 @@ import net.corda.core.utilities.NonEmptySet
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.toHexString import net.corda.core.utilities.toHexString
import net.corda.node.services.database.HibernateConfiguration import net.corda.node.services.database.HibernateConfiguration
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.core.utilities.*
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase import net.corda.node.utilities.configureDatabase
import net.corda.schemas.CashSchemaV1 import net.corda.schemas.CashSchemaV1
@ -27,7 +31,7 @@ import net.corda.schemas.SampleCashSchemaV3
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.* import net.corda.testing.contracts.*
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestDatabaseProperties import net.corda.testing.node.makeTestDatabaseProperties
import net.corda.testing.schemas.DummyLinearStateSchemaV1 import net.corda.testing.schemas.DummyLinearStateSchemaV1
import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions
@ -54,24 +58,9 @@ class VaultQueryTests : TestDependencyInjectionBase() {
@Before @Before
fun setUp() { fun setUp() {
val dataSourceProps = makeTestDataSourceProperties() val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(MEGA_CORP_KEY))
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties()) database = databaseAndServices.first
database.transaction { services = databaseAndServices.second
val customSchemas = setOf(CommercialPaperSchemaV1, DummyLinearStateSchemaV1)
val hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), makeTestDatabaseProperties())
services = object : MockServices(MEGA_CORP_KEY) {
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
vaultService.notifyAll(txs.map { it.tx })
}
override val vaultQueryService : VaultQueryService = HibernateVaultQueryImpl(hibernateConfig, vaultService.updatesPublisher)
}
}
} }
@After @After
@ -97,7 +86,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
_database.transaction { _database.transaction {
// create new states // create new states
services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 10, 10, Random(0L)) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
val linearStatesXYZ = services.fillWithSomeTestLinearStates(1, "XYZ") val linearStatesXYZ = services.fillWithSomeTestLinearStates(1, "XYZ")
val linearStatesJKL = services.fillWithSomeTestLinearStates(2, "JKL") val linearStatesJKL = services.fillWithSomeTestLinearStates(2, "JKL")
services.fillWithSomeTestLinearStates(3, "ABC") services.fillWithSomeTestLinearStates(3, "ABC")
@ -239,15 +228,15 @@ class VaultQueryTests : TestDependencyInjectionBase() {
fun `unconsumed cash states sorted by state ref`() { fun `unconsumed cash states sorted by state ref`() {
database.transaction { database.transaction {
var stateRefs : MutableList<StateRef> = mutableListOf() val stateRefs: MutableList<StateRef> = mutableListOf()
val issuedStates = services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L)) val issuedStates = services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
val issuedStateRefs = issuedStates.states.map { it.ref }.toList() val issuedStateRefs = issuedStates.states.map { it.ref }.toList()
stateRefs.addAll(issuedStateRefs) stateRefs.addAll(issuedStateRefs)
val spentStates = services.consumeCash(25.DOLLARS) val spentStates = services.consumeCash(25.DOLLARS)
var consumedStateRefs = spentStates.consumed.map { it.ref }.toList() val consumedStateRefs = spentStates.consumed.map { it.ref }.toList()
var producedStateRefs = spentStates.produced.map { it.ref }.toList() val producedStateRefs = spentStates.produced.map { it.ref }.toList()
stateRefs.addAll(consumedStateRefs.plus(producedStateRefs)) stateRefs.addAll(consumedStateRefs.plus(producedStateRefs))
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF) val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
@ -271,8 +260,9 @@ class VaultQueryTests : TestDependencyInjectionBase() {
fun `unconsumed cash states sorted by state ref txnId and index`() { fun `unconsumed cash states sorted by state ref txnId and index`() {
database.transaction { database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L)) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
services.consumeCash(10.DOLLARS) val consumed = mutableSetOf<SecureHash>()
services.consumeCash(10.DOLLARS) services.consumeCash(10.DOLLARS).consumed.forEach { consumed += it.ref.txhash }
services.consumeCash(10.DOLLARS).consumed.forEach { consumed += it.ref.txhash }
val sortAttributeTxnId = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID) val sortAttributeTxnId = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID)
val sortAttributeIndex = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_INDEX) val sortAttributeIndex = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_INDEX)
@ -283,13 +273,11 @@ class VaultQueryTests : TestDependencyInjectionBase() {
results.statesMetadata.forEach { results.statesMetadata.forEach {
println(" ${it.ref}") println(" ${it.ref}")
assertThat(it.status).isEqualTo(Vault.StateStatus.UNCONSUMED)
} }
val sorted = results.states.sortedBy { it.ref.toString() }
// explicit sort order asc by txnId and then index: assertThat(results.states).isEqualTo(sorted)
// order by assertThat(results.states).allSatisfy { !consumed.contains(it.ref.txhash) }
// vaultschem1_.transaction_id asc,
// vaultschem1_.output_index asc
assertThat(results.states).hasSize(9) // -2 CONSUMED + 1 NEW UNCONSUMED (change)
} }
} }
@ -411,7 +399,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
} }
} }
val CASH_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(20)) } val CASH_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(21)) }
val CASH_NOTARY: Party get() = Party(X500Name("CN=Cash Notary Service,O=R3,OU=corda,L=Zurich,C=CH"), CASH_NOTARY_KEY.public) val CASH_NOTARY: Party get() = Party(X500Name("CN=Cash Notary Service,O=R3,OU=corda,L=Zurich,C=CH"), CASH_NOTARY_KEY.public)
@Test @Test
@ -870,7 +858,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
fun `aggregate functions count by contract type and state status`() { fun `aggregate functions count by contract type and state status`() {
database.transaction { database.transaction {
// create new states // create new states
services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 10, 10, Random(0L)) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
val linearStatesXYZ = services.fillWithSomeTestLinearStates(1, "XYZ") val linearStatesXYZ = services.fillWithSomeTestLinearStates(1, "XYZ")
val linearStatesJKL = services.fillWithSomeTestLinearStates(2, "JKL") val linearStatesJKL = services.fillWithSomeTestLinearStates(2, "JKL")
services.fillWithSomeTestLinearStates(3, "ABC") services.fillWithSomeTestLinearStates(3, "ABC")
@ -896,14 +884,14 @@ class VaultQueryTests : TestDependencyInjectionBase() {
services.consumeLinearStates(linearStatesXYZ.states.toList()) services.consumeLinearStates(linearStatesXYZ.states.toList())
services.consumeLinearStates(linearStatesJKL.states.toList()) services.consumeLinearStates(linearStatesJKL.states.toList())
services.consumeDeals(dealStates.states.filter { it.state.data.ref == "456" }) services.consumeDeals(dealStates.states.filter { it.state.data.ref == "456" })
services.consumeCash(50.DOLLARS) val cashUpdates = services.consumeCash(50.DOLLARS)
// UNCONSUMED states (default) // UNCONSUMED states (default)
// count fungible assets // count fungible assets
val countCriteriaUnconsumed = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.UNCONSUMED) val countCriteriaUnconsumed = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.UNCONSUMED)
val fungibleStateCountUnconsumed = vaultQuerySvc.queryBy<FungibleAsset<*>>(countCriteriaUnconsumed).otherResults.single() as Long val fungibleStateCountUnconsumed = vaultQuerySvc.queryBy<FungibleAsset<*>>(countCriteriaUnconsumed).otherResults.single() as Long
assertThat(fungibleStateCountUnconsumed).isEqualTo(5L) assertThat(fungibleStateCountUnconsumed.toInt()).isEqualTo(10 - cashUpdates.consumed.size + cashUpdates.produced.size)
// count linear states // count linear states
val linearStateCountUnconsumed = vaultQuerySvc.queryBy<LinearState>(countCriteriaUnconsumed).otherResults.single() as Long val linearStateCountUnconsumed = vaultQuerySvc.queryBy<LinearState>(countCriteriaUnconsumed).otherResults.single() as Long
@ -918,7 +906,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
// count fungible assets // count fungible assets
val countCriteriaConsumed = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.CONSUMED) val countCriteriaConsumed = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.CONSUMED)
val fungibleStateCountConsumed = vaultQuerySvc.queryBy<FungibleAsset<*>>(countCriteriaConsumed).otherResults.single() as Long val fungibleStateCountConsumed = vaultQuerySvc.queryBy<FungibleAsset<*>>(countCriteriaConsumed).otherResults.single() as Long
assertThat(fungibleStateCountConsumed).isEqualTo(6L) assertThat(fungibleStateCountConsumed.toInt()).isEqualTo(cashUpdates.consumed.size)
// count linear states // count linear states
val linearStateCountConsumed = vaultQuerySvc.queryBy<LinearState>(countCriteriaConsumed).otherResults.single() as Long val linearStateCountConsumed = vaultQuerySvc.queryBy<LinearState>(countCriteriaConsumed).otherResults.single() as Long
@ -962,7 +950,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
fun `states consumed after time`() { fun `states consumed after time`() {
database.transaction { database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 3, 3, Random(0L)) services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(10) services.fillWithSomeTestLinearStates(10)
services.fillWithSomeTestDeals(listOf("123", "456", "789")) services.fillWithSomeTestDeals(listOf("123", "456", "789"))

View File

@ -11,17 +11,15 @@ import net.corda.core.node.services.VaultService
import net.corda.core.node.services.queryBy import net.corda.core.node.services.queryBy
import net.corda.core.node.services.vault.QueryCriteria import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.node.services.database.HibernateConfiguration import net.corda.node.services.database.HibernateConfiguration
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.utilities.CordaPersistence import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
import net.corda.testing.* import net.corda.testing.*
import net.corda.testing.contracts.* import net.corda.testing.contracts.*
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDatabaseAndMockServices
import net.corda.testing.node.makeTestDatabaseProperties
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After import org.junit.After
@ -44,23 +42,9 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
@Before @Before
fun setUp() { fun setUp() {
LogHelper.setLevel(VaultWithCashTest::class) LogHelper.setLevel(VaultWithCashTest::class)
val dataSourceProps = makeTestDataSourceProperties() val databaseAndServices = makeTestDatabaseAndMockServices()
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties()) database = databaseAndServices.first
database.transaction { services = databaseAndServices.second
val hibernateConfig = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties())
services = object : MockServices() {
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
vaultService.notifyAll(txs.map { it.tx })
}
override val vaultQueryService : VaultQueryService = HibernateVaultQueryImpl(hibernateConfig, vaultService.updatesPublisher)
}
}
} }
@After @After
@ -103,7 +87,7 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
// A tx that spends our money. // A tx that spends our money.
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY) val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY)
vault.generateSpend(spendTXBuilder, 80.DOLLARS, BOB) Cash.generateSpend(services, spendTXBuilder, 80.DOLLARS, BOB)
val spendPTX = services.signInitialTransaction(spendTXBuilder, freshKey) val spendPTX = services.signInitialTransaction(spendTXBuilder, freshKey)
val spendTX = notaryServices.addSignature(spendPTX) val spendTX = notaryServices.addSignature(spendPTX)
@ -151,7 +135,7 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
database.transaction { database.transaction {
try { try {
val txn1Builder = TransactionBuilder(DUMMY_NOTARY) val txn1Builder = TransactionBuilder(DUMMY_NOTARY)
vault.generateSpend(txn1Builder, 60.DOLLARS, BOB) Cash.generateSpend(services, txn1Builder, 60.DOLLARS, BOB)
val ptxn1 = notaryServices.signInitialTransaction(txn1Builder) val ptxn1 = notaryServices.signInitialTransaction(txn1Builder)
val txn1 = services.addSignature(ptxn1, freshKey) val txn1 = services.addSignature(ptxn1, freshKey)
println("txn1: ${txn1.id} spent ${((txn1.tx.outputs[0].data) as Cash.State).amount}") println("txn1: ${txn1.id} spent ${((txn1.tx.outputs[0].data) as Cash.State).amount}")
@ -187,7 +171,7 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
database.transaction { database.transaction {
try { try {
val txn2Builder = TransactionBuilder(DUMMY_NOTARY) val txn2Builder = TransactionBuilder(DUMMY_NOTARY)
vault.generateSpend(txn2Builder, 80.DOLLARS, BOB) Cash.generateSpend(services, txn2Builder, 80.DOLLARS, BOB)
val ptxn2 = notaryServices.signInitialTransaction(txn2Builder) val ptxn2 = notaryServices.signInitialTransaction(txn2Builder)
val txn2 = services.addSignature(ptxn2, freshKey) val txn2 = services.addSignature(ptxn2, freshKey)
println("txn2: ${txn2.id} spent ${((txn2.tx.outputs[0].data) as Cash.State).amount}") println("txn2: ${txn2.id} spent ${((txn2.tx.outputs[0].data) as Cash.State).amount}")
@ -299,7 +283,7 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
database.transaction { database.transaction {
// A tx that spends our money. // A tx that spends our money.
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY) val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY)
vault.generateSpend(spendTXBuilder, 80.DOLLARS, BOB) Cash.generateSpend(services, spendTXBuilder, 80.DOLLARS, BOB)
val spendPTX = notaryServices.signInitialTransaction(spendTXBuilder) val spendPTX = notaryServices.signInitialTransaction(spendTXBuilder)
val spendTX = services.addSignature(spendPTX, freshKey) val spendTX = services.addSignature(spendPTX, freshKey)
services.recordTransactions(spendTX) services.recordTransactions(spendTX)

View File

@ -1,19 +1,28 @@
package net.corda.core.crypto package net.corda.node.utilities
import net.corda.core.crypto.Crypto
import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512 import net.corda.core.crypto.Crypto.EDDSA_ED25519_SHA512
import net.corda.core.crypto.Crypto.generateKeyPair import net.corda.core.crypto.Crypto.generateKeyPair
import net.corda.core.crypto.X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME import net.corda.core.crypto.cert
import net.corda.core.crypto.X509Utilities.createSelfSignedCACertificate import net.corda.core.crypto.commonName
import net.corda.core.crypto.getX509Name
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.toTypedArray import net.corda.core.internal.toTypedArray
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.node.serialization.KryoServerSerializationScheme
import net.corda.node.services.config.createKeystoreForCordaNode import net.corda.node.services.config.createKeystoreForCordaNode
import net.corda.node.utilities.* import net.corda.nodeapi.internal.serialization.AllWhitelist
import net.corda.testing.MEGA_CORP import net.corda.nodeapi.internal.serialization.KryoHeaderV0_1
import net.corda.testing.getTestX509Name import net.corda.nodeapi.internal.serialization.SerializationContextImpl
import net.corda.nodeapi.internal.serialization.SerializationFactoryImpl
import net.corda.testing.*
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.BasicConstraints import org.bouncycastle.asn1.x509.BasicConstraints
import org.bouncycastle.asn1.x509.Extension import org.bouncycastle.asn1.x509.Extension
import org.bouncycastle.asn1.x509.KeyUsage import org.bouncycastle.asn1.x509.KeyUsage
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder import org.bouncycastle.operator.jcajce.JcaContentVerifierProviderBuilder
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -27,7 +36,9 @@ import java.nio.file.Path
import java.security.KeyStore import java.security.KeyStore
import java.security.PrivateKey import java.security.PrivateKey
import java.security.SecureRandom import java.security.SecureRandom
import java.security.cert.CertPath
import java.security.cert.Certificate import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate import java.security.cert.X509Certificate
import java.util.* import java.util.*
import java.util.stream.Stream import java.util.stream.Stream
@ -42,8 +53,8 @@ class X509UtilitiesTest {
@Test @Test
fun `create valid self-signed CA certificate`() { fun `create valid self-signed CA certificate`() {
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey) val caCert = X509Utilities.createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
assertTrue { caCert.subject.commonName == "Test Cert" } // using our subject common name assertTrue { caCert.subject.commonName == "Test Cert" } // using our subject common name
assertEquals(caCert.issuer, caCert.subject) //self-signed assertEquals(caCert.issuer, caCert.subject) //self-signed
caCert.isValidOn(Date()) // throws on verification problems caCert.isValidOn(Date()) // throws on verification problems
@ -57,8 +68,8 @@ class X509UtilitiesTest {
@Test @Test
fun `load and save a PEM file certificate`() { fun `load and save a PEM file certificate`() {
val tmpCertificateFile = tempFile("cacert.pem") val tmpCertificateFile = tempFile("cacert.pem")
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey) val caCert = X509Utilities.createSelfSignedCACertificate(getTestX509Name("Test Cert"), caKey)
X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile) X509Utilities.saveCertificateAsPEMFile(caCert, tmpCertificateFile)
val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile) val readCertificate = X509Utilities.loadCertificateFromPEMFile(tmpCertificateFile)
assertEquals(caCert, readCertificate) assertEquals(caCert, readCertificate)
@ -66,10 +77,10 @@ class X509UtilitiesTest {
@Test @Test
fun `create valid server certificate chain`() { fun `create valid server certificate chain`() {
val caKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) val caKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val caCert = createSelfSignedCACertificate(getTestX509Name("Test CA Cert"), caKey) val caCert = X509Utilities.createSelfSignedCACertificate(getTestX509Name("Test CA Cert"), caKey)
val subject = getTestX509Name("Server Cert") val subject = getTestX509Name("Server Cert")
val keyPair = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) val keyPair = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subject, keyPair.public) val serverCert = X509Utilities.createCertificate(CertificateType.TLS, caCert, caKey, subject, keyPair.public)
assertTrue { serverCert.subject.toString().contains("CN=Server Cert") } // using our subject common name assertTrue { serverCert.subject.toString().contains("CN=Server Cert") } // using our subject common name
assertEquals(caCert.issuer, serverCert.issuer) // Issued by our CA cert assertEquals(caCert.issuer, serverCert.issuer) // Issued by our CA cert
@ -86,7 +97,7 @@ class X509UtilitiesTest {
val tmpKeyStore = tempFile("keystore.jks") val tmpKeyStore = tempFile("keystore.jks")
val keyPair = generateKeyPair(EDDSA_ED25519_SHA512) val keyPair = generateKeyPair(EDDSA_ED25519_SHA512)
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair) val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
assertTrue(Arrays.equals(selfSignCert.subjectPublicKeyInfo.encoded, keyPair.public.encoded)) assertTrue(Arrays.equals(selfSignCert.subjectPublicKeyInfo.encoded, keyPair.public.encoded))
@ -111,7 +122,7 @@ class X509UtilitiesTest {
fun `signing EdDSA key with EcDSA certificate`() { fun `signing EdDSA key with EcDSA certificate`() {
val tmpKeyStore = tempFile("keystore.jks") val tmpKeyStore = tempFile("keystore.jks")
val ecDSAKey = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) val ecDSAKey = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val ecDSACert = createSelfSignedCACertificate(X500Name("CN=Test"), ecDSAKey) val ecDSACert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Test"), ecDSAKey)
val edDSAKeypair = generateKeyPair(EDDSA_ED25519_SHA512) val edDSAKeypair = generateKeyPair(EDDSA_ED25519_SHA512)
val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public) val edDSACert = X509Utilities.createCertificate(CertificateType.TLS, ecDSACert, ecDSAKey, X500Name("CN=TestEdDSA"), edDSAKeypair.public)
@ -155,8 +166,8 @@ class X509UtilitiesTest {
// Now sign something with private key and verify against certificate public key // Now sign something with private key and verify against certificate public key
val testData = "12345".toByteArray() val testData = "12345".toByteArray()
val caSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData) val caSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaPrivateKey, testData)
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) } assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, rootCaCert.publicKey, caSignature, testData) }
// Load back generated intermediate CA Cert and private key // Load back generated intermediate CA Cert and private key
val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA) as X509Certificate val intermediateCaCert = keyStore.getCertificate(X509Utilities.CORDA_INTERMEDIATE_CA) as X509Certificate
@ -165,8 +176,8 @@ class X509UtilitiesTest {
intermediateCaCert.verify(rootCaCert.publicKey) intermediateCaCert.verify(rootCaCert.publicKey)
// Now sign something with private key and verify against certificate public key // Now sign something with private key and verify against certificate public key
val intermediateSignature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData) val intermediateSignature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCertPrivateKey, testData)
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) } assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, intermediateCaCert.publicKey, intermediateSignature, testData) }
} }
@Test @Test
@ -209,9 +220,9 @@ class X509UtilitiesTest {
assertTrue { sslCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.commonName) } assertTrue { sslCertAndKey.certificate.subject.toString().contains(MEGA_CORP.name.commonName) }
// Now sign something with private key and verify against certificate public key // Now sign something with private key and verify against certificate public key
val testData = "123456".toByteArray() val testData = "123456".toByteArray()
val signature = Crypto.doSign(DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData) val signature = Crypto.doSign(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, serverCertAndKey.keyPair.private, testData)
val publicKey = Crypto.toSupportedPublicKey(serverCertAndKey.certificate.subjectPublicKeyInfo) val publicKey = Crypto.toSupportedPublicKey(serverCertAndKey.certificate.subjectPublicKeyInfo)
assertTrue { Crypto.isValid(DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) } assertTrue { Crypto.isValid(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME, publicKey, signature, testData) }
} }
@Test @Test
@ -343,11 +354,11 @@ class X509UtilitiesTest {
trustStoreFilePath: Path, trustStoreFilePath: Path,
trustStorePassword: String trustStorePassword: String
): KeyStore { ): KeyStore {
val rootCAKey = generateKeyPair(DEFAULT_TLS_SIGNATURE_SCHEME) val rootCAKey = generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = createSelfSignedCACertificate(X509Utilities.getX509Name("Corda Node Root CA","London","demo@r3.com",null), rootCAKey) val rootCACert = X509Utilities.createSelfSignedCACertificate(getX509Name("Corda Node Root CA", "London", "demo@r3.com", null), rootCAKey)
val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME) val intermediateCAKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X509Utilities.getX509Name("Corda Node Intermediate CA","London","demo@r3.com",null), intermediateCAKeyPair.public) val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, getX509Name("Corda Node Intermediate CA", "London", "demo@r3.com", null), intermediateCAKeyPair.public)
val keyPass = keyPassword.toCharArray() val keyPass = keyPassword.toCharArray()
val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword) val keyStore = loadOrCreateKeyStore(keyStoreFilePath, storePassword)
@ -374,7 +385,7 @@ class X509UtilitiesTest {
@Test @Test
fun `Get correct private key type from Keystore`() { fun `Get correct private key type from Keystore`() {
val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256) val keyPair = generateKeyPair(Crypto.ECDSA_SECP256R1_SHA256)
val selfSignCert = createSelfSignedCACertificate(X500Name("CN=Test"), keyPair) val selfSignCert = X509Utilities.createSelfSignedCACertificate(X500Name("CN=Test"), keyPair)
val keyStore = loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword") val keyStore = loadOrCreateKeyStore(tempFile("testKeystore.jks"), "keystorepassword")
keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.cert)) keyStore.setKeyEntry("Key", keyPair.private, "keypassword".toCharArray(), arrayOf(selfSignCert.cert))
@ -384,4 +395,38 @@ class X509UtilitiesTest {
assertTrue(keyFromKeystore is java.security.interfaces.ECPrivateKey) // by default JKS returns SUN EC key assertTrue(keyFromKeystore is java.security.interfaces.ECPrivateKey) // by default JKS returns SUN EC key
assertTrue(keyFromKeystoreCasted is org.bouncycastle.jce.interfaces.ECPrivateKey) assertTrue(keyFromKeystoreCasted is org.bouncycastle.jce.interfaces.ECPrivateKey)
} }
@Test
fun `serialize - deserialize X509CertififcateHolder`() {
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
val context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
val expected: X509CertificateHolder = X509Utilities.createSelfSignedCACertificate(ALICE.name, Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME))
val serialized = expected.serialize(factory, context).bytes
val actual: X509CertificateHolder = serialized.deserialize(factory, context)
assertEquals(expected, actual)
}
@Test
fun `serialize - deserialize X509CertPath`() {
val factory = SerializationFactoryImpl().apply { registerScheme(KryoServerSerializationScheme()) }
val context = SerializationContextImpl(KryoHeaderV0_1,
javaClass.classLoader,
AllWhitelist,
emptyMap(),
true,
SerializationContext.UseCase.P2P)
val certFactory = CertificateFactory.getInstance("X509")
val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val rootCACert = X509Utilities.createSelfSignedCACertificate(ALICE.name, rootCAKey)
val certificate = X509Utilities.createCertificate(CertificateType.TLS, rootCACert, rootCAKey, BOB.name, BOB_PUBKEY)
val expected = certFactory.generateCertPath(listOf(certificate.cert, rootCACert.cert))
val serialized = expected.serialize(factory, context).bytes
val actual: CertPath = serialized.deserialize(factory, context)
assertEquals(expected, actual)
}
} }

View File

@ -3,9 +3,13 @@ package net.corda.node.utilities.registration
import com.nhaarman.mockito_kotlin.any import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.eq import com.nhaarman.mockito_kotlin.eq
import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.mock
import net.corda.core.crypto.* import net.corda.core.crypto.Crypto
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.cert
import net.corda.core.crypto.commonName
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.core.internal.toTypedArray import net.corda.core.internal.toTypedArray
import net.corda.node.utilities.X509Utilities
import net.corda.node.utilities.loadKeyStore import net.corda.node.utilities.loadKeyStore
import net.corda.testing.ALICE import net.corda.testing.ALICE
import net.corda.testing.getTestX509Name import net.corda.testing.getTestX509Name

View File

@ -3,7 +3,6 @@ package net.corda.irs.contract
import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import net.corda.contracts.* import net.corda.contracts.*
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.containsAny import net.corda.core.crypto.containsAny
import net.corda.core.flows.FlowLogicRefFactory import net.corda.core.flows.FlowLogicRefFactory
@ -460,189 +459,137 @@ class InterestRateSwap : Contract {
fixingCalendar, index, indexSource, indexTenor) fixingCalendar, index, indexSource, indexTenor)
} }
override fun verify(tx: LedgerTransaction) = verifyClause(tx, AllOf(Clauses.TimeWindow(), Clauses.Group()), tx.commands.select<Commands>()) // These functions may make more sense to use for basket types, but for now let's leave them here
private fun checkLegDates(legs: List<CommonLeg>) {
requireThat {
"Effective date is before termination date" using legs.all { it.effectiveDate < it.terminationDate }
"Effective dates are in alignment" using legs.all { it.effectiveDate == legs[0].effectiveDate }
"Termination dates are in alignment" using legs.all { it.terminationDate == legs[0].terminationDate }
}
}
interface Clauses { private fun checkLegAmounts(legs: List<CommonLeg>) {
/** requireThat {
* Common superclass for IRS contract clauses, which defines behaviour on match/no-match, and provides "The notional is non zero" using legs.any { it.notional.quantity > (0).toLong() }
* helper functions for the clauses. "The notional for all legs must be the same" using legs.all { it.notional == legs[0].notional }
*/ }
abstract class AbstractIRSClause : Clause<State, Commands, UniqueIdentifier>() { for (leg: CommonLeg in legs) {
// These functions may make more sense to use for basket types, but for now let's leave them here if (leg is FixedLeg) {
fun checkLegDates(legs: List<CommonLeg>) {
requireThat { requireThat {
"Effective date is before termination date" using legs.all { it.effectiveDate < it.terminationDate } // TODO: Confirm: would someone really enter a swap with a negative fixed rate?
"Effective dates are in alignment" using legs.all { it.effectiveDate == legs[0].effectiveDate } "Fixed leg rate must be positive" using leg.fixedRate.isPositive()
"Termination dates are in alignment" using legs.all { it.terminationDate == legs[0].terminationDate }
}
}
fun checkLegAmounts(legs: List<CommonLeg>) {
requireThat {
"The notional is non zero" using legs.any { it.notional.quantity > (0).toLong() }
"The notional for all legs must be the same" using legs.all { it.notional == legs[0].notional }
}
for (leg: CommonLeg in legs) {
if (leg is FixedLeg) {
requireThat {
// TODO: Confirm: would someone really enter a swap with a negative fixed rate?
"Fixed leg rate must be positive" using leg.fixedRate.isPositive()
}
}
}
}
// TODO: After business rules discussion, add further checks to the schedules and rates
fun checkSchedules(@Suppress("UNUSED_PARAMETER") legs: List<CommonLeg>): Boolean = true
fun checkRates(@Suppress("UNUSED_PARAMETER") legs: List<CommonLeg>): Boolean = true
/**
* Compares two schedules of Floating Leg Payments, returns the difference (i.e. omissions in either leg or changes to the values).
*/
fun getFloatingLegPaymentsDifferences(payments1: Map<LocalDate, Event>, payments2: Map<LocalDate, Event>): List<Pair<LocalDate, Pair<FloatingRatePaymentEvent, FloatingRatePaymentEvent>>> {
val diff1 = payments1.filter { payments1[it.key] != payments2[it.key] }
val diff2 = payments2.filter { payments1[it.key] != payments2[it.key] }
return (diff1.keys + diff2.keys).map {
it to Pair(diff1[it] as FloatingRatePaymentEvent, diff2[it] as FloatingRatePaymentEvent)
} }
} }
} }
}
class Group : GroupClauseVerifier<State, Commands, UniqueIdentifier>(AnyOf(Agree(), Fix(), Pay(), Mature())) { /**
// Group by Trade ID for in / out states * Compares two schedules of Floating Leg Payments, returns the difference (i.e. omissions in either leg or changes to the values).
override fun groupStates(tx: LedgerTransaction): List<LedgerTransaction.InOutGroup<State, UniqueIdentifier>> { */
return tx.groupStates { state -> state.linearId } private fun getFloatingLegPaymentsDifferences(payments1: Map<LocalDate, Event>, payments2: Map<LocalDate, Event>): List<Pair<LocalDate, Pair<FloatingRatePaymentEvent, FloatingRatePaymentEvent>>> {
} val diff1 = payments1.filter { payments1[it.key] != payments2[it.key] }
val diff2 = payments2.filter { payments1[it.key] != payments2[it.key] }
return (diff1.keys + diff2.keys).map {
it to Pair(diff1[it] as FloatingRatePaymentEvent, diff2[it] as FloatingRatePaymentEvent)
}
}
private fun verifyAgreeCommand(inputs: List<State>, outputs: List<State>) {
val irs = outputs.filterIsInstance<State>().single()
requireThat {
"There are no in states for an agreement" using inputs.isEmpty()
"There are events in the fix schedule" using (irs.calculation.fixedLegPaymentSchedule.isNotEmpty())
"There are events in the float schedule" using (irs.calculation.floatingLegPaymentSchedule.isNotEmpty())
"All notionals must be non zero" using (irs.fixedLeg.notional.quantity > 0 && irs.floatingLeg.notional.quantity > 0)
"The fixed leg rate must be positive" using (irs.fixedLeg.fixedRate.isPositive())
"The currency of the notionals must be the same" using (irs.fixedLeg.notional.token == irs.floatingLeg.notional.token)
"All leg notionals must be the same" using (irs.fixedLeg.notional == irs.floatingLeg.notional)
"The effective date is before the termination date for the fixed leg" using (irs.fixedLeg.effectiveDate < irs.fixedLeg.terminationDate)
"The effective date is before the termination date for the floating leg" using (irs.floatingLeg.effectiveDate < irs.floatingLeg.terminationDate)
"The effective dates are aligned" using (irs.floatingLeg.effectiveDate == irs.fixedLeg.effectiveDate)
"The termination dates are aligned" using (irs.floatingLeg.terminationDate == irs.fixedLeg.terminationDate)
"The fixing period date offset cannot be negative" using (irs.floatingLeg.fixingPeriodOffset >= 0)
// TODO: further tests
}
checkLegAmounts(listOf(irs.fixedLeg, irs.floatingLeg))
checkLegDates(listOf(irs.fixedLeg, irs.floatingLeg))
}
private fun verifyFixCommand(inputs: List<State>, outputs: List<State>, command: AuthenticatedObject<Commands.Refix>) {
val irs = outputs.filterIsInstance<State>().single()
val prevIrs = inputs.filterIsInstance<State>().single()
val paymentDifferences = getFloatingLegPaymentsDifferences(prevIrs.calculation.floatingLegPaymentSchedule, irs.calculation.floatingLegPaymentSchedule)
// Having both of these tests are "redundant" as far as verify() goes, however, by performing both
// we can relay more information back to the user in the case of failure.
requireThat {
"There is at least one difference in the IRS floating leg payment schedules" using !paymentDifferences.isEmpty()
"There is only one change in the IRS floating leg payment schedule" using (paymentDifferences.size == 1)
} }
class TimeWindow : Clause<ContractState, Commands, Unit>() { val (oldFloatingRatePaymentEvent, newFixedRatePaymentEvent) = paymentDifferences.single().second // Ignore the date of the changed rate (we checked that earlier).
override fun verify(tx: LedgerTransaction, val fixValue = command.value.fix
inputs: List<ContractState>, // Need to check that everything is the same apart from the new fixed rate entry.
outputs: List<ContractState>, requireThat {
commands: List<AuthenticatedObject<Commands>>, "The fixed leg parties are constant" using (irs.fixedLeg.fixedRatePayer == prevIrs.fixedLeg.fixedRatePayer) // Although superseded by the below test, this is included for a regression issue
groupingKey: Unit?): Set<Commands> { "The fixed leg is constant" using (irs.fixedLeg == prevIrs.fixedLeg)
requireNotNull(tx.timeWindow) { "must be have a time-window)" } "The floating leg is constant" using (irs.floatingLeg == prevIrs.floatingLeg)
// We return an empty set because we don't process any commands "The common values are constant" using (irs.common == prevIrs.common)
return emptySet() "The fixed leg payment schedule is constant" using (irs.calculation.fixedLegPaymentSchedule == prevIrs.calculation.fixedLegPaymentSchedule)
"The expression is unchanged" using (irs.calculation.expression == prevIrs.calculation.expression)
"There is only one changed payment in the floating leg" using (paymentDifferences.size == 1)
"There changed payment is a floating payment" using (oldFloatingRatePaymentEvent.rate is ReferenceRate)
"The new payment is a fixed payment" using (newFixedRatePaymentEvent.rate is FixedRate)
"The changed payments dates are aligned" using (oldFloatingRatePaymentEvent.date == newFixedRatePaymentEvent.date)
"The new payment has the correct rate" using (newFixedRatePaymentEvent.rate.ratioUnit!!.value == fixValue.value)
"The fixing is for the next required date" using (prevIrs.calculation.nextFixingDate() == fixValue.of.forDay)
"The fix payment has the same currency as the notional" using (newFixedRatePaymentEvent.flow.token == irs.floatingLeg.notional.token)
// "The fixing is not in the future " by (fixCommand) // The oracle should not have signed this .
}
}
private fun verifyPayCommand() {
requireThat {
"Payments not supported / verifiable yet" using false
}
}
private fun verifyMatureCommand(inputs: List<State>, outputs: List<State>) {
val irs = inputs.filterIsInstance<State>().single()
requireThat {
"No more fixings to be applied" using (irs.calculation.nextFixingDate() == null)
"The irs is fully consumed and there is no id matched output state" using outputs.isEmpty()
}
}
override fun verify(tx: LedgerTransaction) {
requireNotNull(tx.timeWindow) { "must be have a time-window)" }
val groups: List<LedgerTransaction.InOutGroup<State, UniqueIdentifier>> = tx.groupStates { state -> state.linearId }
var atLeastOneCommandProcessed = false
for ((inputs, outputs, key) in groups) {
val agreeCommand = tx.commands.select<Commands.Agree>().firstOrNull()
if (agreeCommand != null) {
verifyAgreeCommand(inputs, outputs)
atLeastOneCommandProcessed = true
}
val fixCommand = tx.commands.select<Commands.Refix>().firstOrNull()
if (fixCommand != null) {
verifyFixCommand(inputs, outputs, fixCommand)
atLeastOneCommandProcessed = true
}
val payCommand = tx.commands.select<Commands.Pay>().firstOrNull()
if (payCommand != null) {
verifyPayCommand()
atLeastOneCommandProcessed = true
}
val matureCommand = tx.commands.select<Commands.Mature>().firstOrNull()
if (matureCommand != null) {
verifyMatureCommand(inputs, outputs)
atLeastOneCommandProcessed = true
} }
} }
require(atLeastOneCommandProcessed) { "At least one command needs to present" }
class Agree : AbstractIRSClause() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Agree::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<State>,
outputs: List<State>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Agree>()
val irs = outputs.filterIsInstance<State>().single()
requireThat {
"There are no in states for an agreement" using inputs.isEmpty()
"There are events in the fix schedule" using (irs.calculation.fixedLegPaymentSchedule.isNotEmpty())
"There are events in the float schedule" using (irs.calculation.floatingLegPaymentSchedule.isNotEmpty())
"All notionals must be non zero" using (irs.fixedLeg.notional.quantity > 0 && irs.floatingLeg.notional.quantity > 0)
"The fixed leg rate must be positive" using (irs.fixedLeg.fixedRate.isPositive())
"The currency of the notionals must be the same" using (irs.fixedLeg.notional.token == irs.floatingLeg.notional.token)
"All leg notionals must be the same" using (irs.fixedLeg.notional == irs.floatingLeg.notional)
"The effective date is before the termination date for the fixed leg" using (irs.fixedLeg.effectiveDate < irs.fixedLeg.terminationDate)
"The effective date is before the termination date for the floating leg" using (irs.floatingLeg.effectiveDate < irs.floatingLeg.terminationDate)
"The effective dates are aligned" using (irs.floatingLeg.effectiveDate == irs.fixedLeg.effectiveDate)
"The termination dates are aligned" using (irs.floatingLeg.terminationDate == irs.fixedLeg.terminationDate)
"The rates are valid" using checkRates(listOf(irs.fixedLeg, irs.floatingLeg))
"The schedules are valid" using checkSchedules(listOf(irs.fixedLeg, irs.floatingLeg))
"The fixing period date offset cannot be negative" using (irs.floatingLeg.fixingPeriodOffset >= 0)
// TODO: further tests
}
checkLegAmounts(listOf(irs.fixedLeg, irs.floatingLeg))
checkLegDates(listOf(irs.fixedLeg, irs.floatingLeg))
return setOf(command.value)
}
}
class Fix : AbstractIRSClause() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Refix::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<State>,
outputs: List<State>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Refix>()
val irs = outputs.filterIsInstance<State>().single()
val prevIrs = inputs.filterIsInstance<State>().single()
val paymentDifferences = getFloatingLegPaymentsDifferences(prevIrs.calculation.floatingLegPaymentSchedule, irs.calculation.floatingLegPaymentSchedule)
// Having both of these tests are "redundant" as far as verify() goes, however, by performing both
// we can relay more information back to the user in the case of failure.
requireThat {
"There is at least one difference in the IRS floating leg payment schedules" using !paymentDifferences.isEmpty()
"There is only one change in the IRS floating leg payment schedule" using (paymentDifferences.size == 1)
}
val (oldFloatingRatePaymentEvent, newFixedRatePaymentEvent) = paymentDifferences.single().second // Ignore the date of the changed rate (we checked that earlier).
val fixValue = command.value.fix
// Need to check that everything is the same apart from the new fixed rate entry.
requireThat {
"The fixed leg parties are constant" using (irs.fixedLeg.fixedRatePayer == prevIrs.fixedLeg.fixedRatePayer) // Although superseded by the below test, this is included for a regression issue
"The fixed leg is constant" using (irs.fixedLeg == prevIrs.fixedLeg)
"The floating leg is constant" using (irs.floatingLeg == prevIrs.floatingLeg)
"The common values are constant" using (irs.common == prevIrs.common)
"The fixed leg payment schedule is constant" using (irs.calculation.fixedLegPaymentSchedule == prevIrs.calculation.fixedLegPaymentSchedule)
"The expression is unchanged" using (irs.calculation.expression == prevIrs.calculation.expression)
"There is only one changed payment in the floating leg" using (paymentDifferences.size == 1)
"There changed payment is a floating payment" using (oldFloatingRatePaymentEvent.rate is ReferenceRate)
"The new payment is a fixed payment" using (newFixedRatePaymentEvent.rate is FixedRate)
"The changed payments dates are aligned" using (oldFloatingRatePaymentEvent.date == newFixedRatePaymentEvent.date)
"The new payment has the correct rate" using (newFixedRatePaymentEvent.rate.ratioUnit!!.value == fixValue.value)
"The fixing is for the next required date" using (prevIrs.calculation.nextFixingDate() == fixValue.of.forDay)
"The fix payment has the same currency as the notional" using (newFixedRatePaymentEvent.flow.token == irs.floatingLeg.notional.token)
// "The fixing is not in the future " by (fixCommand) // The oracle should not have signed this .
}
return setOf(command.value)
}
}
class Pay : AbstractIRSClause() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Pay::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<State>,
outputs: List<State>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Pay>()
requireThat {
"Payments not supported / verifiable yet" using false
}
return setOf(command.value)
}
}
class Mature : AbstractIRSClause() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Mature::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<State>,
outputs: List<State>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Mature>()
val irs = inputs.filterIsInstance<State>().single()
requireThat {
"No more fixings to be applied" using (irs.calculation.nextFixingDate() == null)
"The irs is fully consumed and there is no id matched output state" using outputs.isEmpty()
}
return setOf(command.value)
}
}
} }
interface Commands : CommandData { interface Commands : CommandData {

View File

@ -34,7 +34,7 @@ class DummyIssueAndMove(private val notary: Party, private val counterpartyNode:
// Move ownership of the asset to the counterparty // Move ownership of the asset to the counterparty
val moveTxBuilder = TransactionBuilder(notary = notary) val moveTxBuilder = TransactionBuilder(notary = notary)
val (_, keys) = vaultService.generateSpend(moveTxBuilder, Amount(amount.quantity, GBP), counterpartyNode) val (_, keys) = Cash.generateSpend(serviceHub, moveTxBuilder, Amount(amount.quantity, GBP), counterpartyNode)
// We don't check signatures because we know that the notary's signature is missing // We don't check signatures because we know that the notary's signature is missing
signInitialTransaction(moveTxBuilder, keys) signInitialTransaction(moveTxBuilder, keys)
} }

View File

@ -1,7 +1,6 @@
package net.corda.vega.contracts package net.corda.vega.contracts
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import java.math.BigDecimal import java.math.BigDecimal
@ -10,39 +9,13 @@ import java.math.BigDecimal
* Specifies the contract between two parties that trade an OpenGamma IRS. Currently can only agree to trade. * Specifies the contract between two parties that trade an OpenGamma IRS. Currently can only agree to trade.
*/ */
data class OGTrade(override val legalContractReference: SecureHash = SecureHash.sha256("OGTRADE.KT")) : Contract { data class OGTrade(override val legalContractReference: SecureHash = SecureHash.sha256("OGTRADE.KT")) : Contract {
override fun verify(tx: LedgerTransaction) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select<Commands>()) override fun verify(tx: LedgerTransaction) {
requireNotNull(tx.timeWindow) { "must have a time-window" }
interface Commands : CommandData { val groups: List<LedgerTransaction.InOutGroup<IRSState, UniqueIdentifier>> = tx.groupStates { state -> state.linearId }
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to trade var atLeastOneCommandProcessed = false
} for ((inputs, outputs, key) in groups) {
val command = tx.commands.select<Commands.Agree>().firstOrNull()
interface Clauses { if (command != null) {
class TimeWindowed : Clause<ContractState, Commands, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: Unit?): Set<Commands> {
requireNotNull(tx.timeWindow) { "must have a time-window" }
// We return an empty set because we don't process any commands
return emptySet()
}
}
class Group : GroupClauseVerifier<IRSState, Commands, UniqueIdentifier>(AnyOf(Agree())) {
override fun groupStates(tx: LedgerTransaction): List<LedgerTransaction.InOutGroup<IRSState, UniqueIdentifier>>
// Group by Trade ID for in / out states
= tx.groupStates { state -> state.linearId }
}
class Agree : Clause<IRSState, Commands, UniqueIdentifier>() {
override fun verify(tx: LedgerTransaction,
inputs: List<IRSState>,
outputs: List<IRSState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Agree>()
require(inputs.isEmpty()) { "Inputs must be empty" } require(inputs.isEmpty()) { "Inputs must be empty" }
require(outputs.size == 1) { "" } require(outputs.size == 1) { "" }
require(outputs[0].buyer != outputs[0].seller) require(outputs[0].buyer != outputs[0].seller)
@ -51,9 +24,13 @@ data class OGTrade(override val legalContractReference: SecureHash = SecureHash.
require(outputs[0].swap.startDate.isBefore(outputs[0].swap.endDate)) require(outputs[0].swap.startDate.isBefore(outputs[0].swap.endDate))
require(outputs[0].swap.notional > BigDecimal(0)) require(outputs[0].swap.notional > BigDecimal(0))
require(outputs[0].swap.tradeDate.isBefore(outputs[0].swap.endDate)) require(outputs[0].swap.tradeDate.isBefore(outputs[0].swap.endDate))
atLeastOneCommandProcessed = true
return setOf(command.value)
} }
} }
require(atLeastOneCommandProcessed) { "At least one command needs to present" }
}
interface Commands : CommandData {
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to trade
} }
} }

View File

@ -1,7 +1,6 @@
package net.corda.vega.contracts package net.corda.vega.contracts
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.contracts.clauses.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
@ -11,71 +10,34 @@ import net.corda.core.transactions.LedgerTransaction
* of the portfolio arbitrarily. * of the portfolio arbitrarily.
*/ */
data class PortfolioSwap(override val legalContractReference: SecureHash = SecureHash.sha256("swordfish")) : Contract { data class PortfolioSwap(override val legalContractReference: SecureHash = SecureHash.sha256("swordfish")) : Contract {
override fun verify(tx: LedgerTransaction) = verifyClause(tx, AllOf(Clauses.TimeWindowed(), Clauses.Group()), tx.commands.select<Commands>()) override fun verify(tx: LedgerTransaction) {
requireNotNull(tx.timeWindow) { "must have a time-window)" }
interface Commands : CommandData { val groups: List<LedgerTransaction.InOutGroup<PortfolioState, UniqueIdentifier>> = tx.groupStates { state -> state.linearId }
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to portfolio for ((inputs, outputs, key) in groups) {
class Update : TypeOnlyCommandData(), Commands // Both sides re-agree to portfolio val agreeCommand = tx.commands.select<Commands.Agree>().firstOrNull()
} if (agreeCommand != null) {
interface Clauses {
class TimeWindowed : Clause<ContractState, Commands, Unit>() {
override fun verify(tx: LedgerTransaction,
inputs: List<ContractState>,
outputs: List<ContractState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: Unit?): Set<Commands> {
requireNotNull(tx.timeWindow) { "must have a time-window)" }
// We return an empty set because we don't process any commands
return emptySet()
}
}
class Group : GroupClauseVerifier<PortfolioState, Commands, UniqueIdentifier>(FirstOf(Agree(), Update())) {
override fun groupStates(tx: LedgerTransaction): List<LedgerTransaction.InOutGroup<PortfolioState, UniqueIdentifier>>
// Group by Trade ID for in / out states
= tx.groupStates { state -> state.linearId }
}
class Update : Clause<PortfolioState, Commands, UniqueIdentifier>() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Update::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<PortfolioState>,
outputs: List<PortfolioState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Update>()
requireThat {
"there is only one input" using (inputs.size == 1)
"there is only one output" using (outputs.size == 1)
"the valuer hasn't changed" using (inputs[0].valuer == outputs[0].valuer)
"the linear id hasn't changed" using (inputs[0].linearId == outputs[0].linearId)
}
return setOf(command.value)
}
}
class Agree : Clause<PortfolioState, Commands, UniqueIdentifier>() {
override val requiredCommands: Set<Class<out CommandData>> = setOf(Commands.Agree::class.java)
override fun verify(tx: LedgerTransaction,
inputs: List<PortfolioState>,
outputs: List<PortfolioState>,
commands: List<AuthenticatedObject<Commands>>,
groupingKey: UniqueIdentifier?): Set<Commands> {
val command = tx.commands.requireSingleCommand<Commands.Agree>()
requireThat { requireThat {
"there are no inputs" using (inputs.isEmpty()) "there are no inputs" using (inputs.isEmpty())
"there is one output" using (outputs.size == 1) "there is one output" using (outputs.size == 1)
"valuer must be a party" using (outputs[0].participants.contains(outputs[0].valuer)) "valuer must be a party" using (outputs[0].participants.contains(outputs[0].valuer))
} }
} else {
val updateCommand = tx.commands.select<Commands.Update>().firstOrNull()
if (updateCommand != null) {
requireThat {
"there is only one input" using (inputs.size == 1)
"there is only one output" using (outputs.size == 1)
"the valuer hasn't changed" using (inputs[0].valuer == outputs[0].valuer)
"the linear id hasn't changed" using (inputs[0].linearId == outputs[0].linearId)
}
return setOf(command.value) }
} }
} }
} }
interface Commands : CommandData {
class Agree : TypeOnlyCommandData(), Commands // Both sides agree to portfolio
class Update : TypeOnlyCommandData(), Commands // Both sides re-agree to portfolio
}
} }

View File

@ -40,8 +40,9 @@ dependencies {
task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) { task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [ ext.rpcUsers = [['username': "demo", 'password': "demo", 'permissions': [
'StartFlow.net.corda.flows.IssuerFlow$IssuanceRequester', 'StartFlow.net.corda.flows.CashIssueFlow',
"StartFlow.net.corda.traderdemo.flow.SellerFlow" 'StartFlow.net.corda.traderdemo.flow.CommercialPaperIssueFlow',
'StartFlow.net.corda.traderdemo.flow.SellerFlow'
]]] ]]]
directory "./build/nodes" directory "./build/nodes"
@ -74,7 +75,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
name "CN=BankOfCorda,O=R3,L=New York,C=US" name "CN=BankOfCorda,O=R3,L=New York,C=US"
advertisedServices = [] advertisedServices = []
p2pPort 10011 p2pPort 10011
rpcPort 10012
cordapps = [] cordapps = []
rpcUsers = ext.rpcUsers
} }
} }
@ -102,11 +105,11 @@ publishing {
} }
} }
task runBuyer(type: JavaExec) { task runBank(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath classpath = sourceSets.main.runtimeClasspath
main = 'net.corda.traderdemo.TraderDemoKt' main = 'net.corda.traderdemo.TraderDemoKt'
args '--role' args '--role'
args 'BUYER' args 'BANK'
} }
task runSeller(type: JavaExec) { task runSeller(type: JavaExec) {

View File

@ -6,6 +6,7 @@ import net.corda.core.utilities.millis
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.internal.concurrent.transpose import net.corda.core.internal.concurrent.transpose
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.flows.CashIssueFlow
import net.corda.testing.DUMMY_BANK_A import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_BANK_B import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
@ -17,6 +18,7 @@ import net.corda.testing.BOC
import net.corda.testing.driver.poll import net.corda.testing.driver.poll
import net.corda.testing.node.NodeBasedTest import net.corda.testing.node.NodeBasedTest
import net.corda.traderdemo.flow.BuyerFlow import net.corda.traderdemo.flow.BuyerFlow
import net.corda.traderdemo.flow.CommercialPaperIssueFlow
import net.corda.traderdemo.flow.SellerFlow import net.corda.traderdemo.flow.SellerFlow
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Test import org.junit.Test
@ -25,15 +27,13 @@ import java.util.concurrent.Executors
class TraderDemoTest : NodeBasedTest() { class TraderDemoTest : NodeBasedTest() {
@Test @Test
fun `runs trader demo`() { fun `runs trader demo`() {
val permissions = setOf( val demoUser = User("demo", "demo", setOf(startFlowPermission<SellerFlow>()))
startFlowPermission<IssuerFlow.IssuanceRequester>(), val bankUser = User("user1", "test", permissions = setOf(startFlowPermission<CashIssueFlow>(),
startFlowPermission<SellerFlow>()) startFlowPermission<CommercialPaperIssueFlow>()))
val demoUser = listOf(User("demo", "demo", permissions)) val (nodeA, nodeB, bankNode, notaryNode) = listOf(
val user = User("user1", "test", permissions = setOf(startFlowPermission<IssuerFlow.IssuanceRequester>())) startNode(DUMMY_BANK_A.name, rpcUsers = listOf(demoUser)),
val (nodeA, nodeB) = listOf( startNode(DUMMY_BANK_B.name, rpcUsers = listOf(demoUser)),
startNode(DUMMY_BANK_A.name, rpcUsers = demoUser), startNode(BOC.name, rpcUsers = listOf(bankUser)),
startNode(DUMMY_BANK_B.name, rpcUsers = demoUser),
startNode(BOC.name, rpcUsers = listOf(user)),
startNode(DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type))) startNode(DUMMY_NOTARY.name, advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
).transpose().getOrThrow() ).transpose().getOrThrow()
@ -41,19 +41,24 @@ class TraderDemoTest : NodeBasedTest() {
val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map { val (nodeARpc, nodeBRpc) = listOf(nodeA, nodeB).map {
val client = CordaRPCClient(it.configuration.rpcAddress!!, initialiseSerialization = false) val client = CordaRPCClient(it.configuration.rpcAddress!!, initialiseSerialization = false)
client.start(demoUser[0].username, demoUser[0].password).proxy client.start(demoUser.username, demoUser.password).proxy
}
val nodeBankRpc = let {
val client = CordaRPCClient(bankNode.configuration.rpcAddress!!, initialiseSerialization = false)
client.start(bankUser.username, bankUser.password).proxy
} }
val clientA = TraderDemoClientApi(nodeARpc) val clientA = TraderDemoClientApi(nodeARpc)
val clientB = TraderDemoClientApi(nodeBRpc) val clientB = TraderDemoClientApi(nodeBRpc)
val clientBank = TraderDemoClientApi(nodeBankRpc)
val originalACash = clientA.cashCount // A has random number of issued amount val originalACash = clientA.cashCount // A has random number of issued amount
val expectedBCash = clientB.cashCount + 1 val expectedBCash = clientB.cashCount + 1
val expectedPaper = listOf(clientA.commercialPaperCount + 1, clientB.commercialPaperCount) val expectedPaper = listOf(clientA.commercialPaperCount + 1, clientB.commercialPaperCount)
// TODO: Enable anonymisation // TODO: Enable anonymisation
clientA.runBuyer(amount = 100.DOLLARS, anonymous = false) clientBank.runIssuer(amount = 100.DOLLARS, buyerName = nodeA.info.legalIdentity.name, sellerName = nodeB.info.legalIdentity.name, notaryName = notaryNode.info.legalIdentity.name)
clientB.runSeller(counterparty = nodeA.info.legalIdentity.name, amount = 5.DOLLARS) clientB.runSeller(buyerName = nodeA.info.legalIdentity.name, amount = 5.DOLLARS)
assertThat(clientA.cashCount).isGreaterThan(originalACash) assertThat(clientA.cashCount).isGreaterThan(originalACash)
assertThat(clientB.cashCount).isEqualTo(expectedBCash) assertThat(clientB.cashCount).isEqualTo(expectedBCash)

View File

@ -4,8 +4,10 @@ import joptsimple.OptionParser
import net.corda.client.rpc.CordaRPCClient import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.DOLLARS import net.corda.core.contracts.DOLLARS
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.testing.DUMMY_BANK_A
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.testing.DUMMY_BANK_A
import net.corda.testing.DUMMY_BANK_B
import net.corda.testing.DUMMY_NOTARY
import org.slf4j.Logger import org.slf4j.Logger
import kotlin.system.exitProcess import kotlin.system.exitProcess
@ -18,12 +20,18 @@ fun main(args: Array<String>) {
private class TraderDemo { private class TraderDemo {
enum class Role { enum class Role {
BUYER, BANK,
SELLER SELLER
} }
companion object { companion object {
val logger: Logger = loggerFor<TraderDemo>() val logger: Logger = loggerFor<TraderDemo>()
val buyerName = DUMMY_BANK_A.name
val sellerName = DUMMY_BANK_B.name
val notaryName = DUMMY_NOTARY.name
val buyerRpcPort = 10006
val sellerRpcPort = 10009
val bankRpcPort = 10012
} }
fun main(args: Array<String>) { fun main(args: Array<String>) {
@ -41,15 +49,15 @@ private class TraderDemo {
// What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role // What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role
// will contact the buyer and actually make something happen. // will contact the buyer and actually make something happen.
val role = options.valueOf(roleArg)!! val role = options.valueOf(roleArg)!!
if (role == Role.BUYER) { if (role == Role.BANK) {
val host = NetworkHostAndPort("localhost", 10006) val bankHost = NetworkHostAndPort("localhost", bankRpcPort)
CordaRPCClient(host).start("demo", "demo").use { CordaRPCClient(bankHost).use("demo", "demo") {
TraderDemoClientApi(it.proxy).runBuyer() TraderDemoClientApi(it.proxy).runIssuer(1100.DOLLARS, buyerName, sellerName, notaryName)
} }
} else { } else {
val host = NetworkHostAndPort("localhost", 10009) val sellerHost = NetworkHostAndPort("localhost", sellerRpcPort)
CordaRPCClient(host).use("demo", "demo") { CordaRPCClient(sellerHost).use("demo", "demo") {
TraderDemoClientApi(it.proxy).runSeller(1000.DOLLARS, DUMMY_BANK_A.name) TraderDemoClientApi(it.proxy).runSeller(1000.DOLLARS, buyerName)
} }
} }
} }

View File

@ -16,11 +16,11 @@ import net.corda.core.node.services.vault.builder
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.flows.IssuerFlow.IssuanceRequester import net.corda.flows.CashIssueFlow
import net.corda.node.services.vault.VaultSchemaV1 import net.corda.node.services.vault.VaultSchemaV1
import net.corda.testing.BOC
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.contracts.calculateRandomlySizedAmounts import net.corda.testing.contracts.calculateRandomlySizedAmounts
import net.corda.traderdemo.flow.CommercialPaperIssueFlow
import net.corda.traderdemo.flow.SellerFlow import net.corda.traderdemo.flow.SellerFlow
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name
import java.util.* import java.util.*
@ -47,25 +47,42 @@ class TraderDemoClientApi(val rpc: CordaRPCOps) {
return rpc.vaultQueryBy<CommercialPaper.State>(countCriteria).otherResults.single() as Long return rpc.vaultQueryBy<CommercialPaper.State>(countCriteria).otherResults.single() as Long
} }
fun runBuyer(amount: Amount<Currency> = 30000.DOLLARS, anonymous: Boolean = false) { fun runIssuer(amount: Amount<Currency> = 1100.0.DOLLARS, buyerName: X500Name, sellerName: X500Name, notaryName: X500Name) {
val bankOfCordaParty = rpc.partyFromX500Name(BOC.name) val ref = OpaqueBytes.of(1)
?: throw IllegalStateException("Unable to locate ${BOC.name} in Network Map Service") val buyer = rpc.partyFromX500Name(buyerName) ?: throw IllegalStateException("Don't know $buyerName")
val seller = rpc.partyFromX500Name(sellerName) ?: throw IllegalStateException("Don't know $sellerName")
val notaryLegalIdentity = rpc.partyFromX500Name(DUMMY_NOTARY.name) val notaryLegalIdentity = rpc.partyFromX500Name(DUMMY_NOTARY.name)
?: throw IllegalStateException("Unable to locate ${DUMMY_NOTARY.name} in Network Map Service") ?: throw IllegalStateException("Unable to locate ${DUMMY_NOTARY.name} in Network Map Service")
val notaryNode = rpc.nodeIdentityFromParty(notaryLegalIdentity) val notaryNode = rpc.nodeIdentityFromParty(notaryLegalIdentity)
?: throw IllegalStateException("Unable to locate notary node in network map cache") ?: throw IllegalStateException("Unable to locate notary node in network map cache")
val me = rpc.nodeIdentity()
val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random()) val amounts = calculateRandomlySizedAmounts(amount, 3, 10, Random())
// issuer random amounts of currency totaling 30000.DOLLARS in parallel val anonymous = false
// issue random amounts of currency up to the requested amount, in parallel
val resultFutures = amounts.map { pennies -> val resultFutures = amounts.map { pennies ->
rpc.startFlow(::IssuanceRequester, Amount(pennies, amount.token), me.legalIdentity, OpaqueBytes.of(1), bankOfCordaParty, notaryNode.notaryIdentity, anonymous).returnValue rpc.startFlow(::CashIssueFlow, amount.copy(quantity = pennies), OpaqueBytes.of(1), buyer, notaryNode.notaryIdentity, anonymous).returnValue
} }
resultFutures.transpose().getOrThrow() resultFutures.transpose().getOrThrow()
println("Cash issued to buyer")
// The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an
// attachment. Make sure we have the transaction prospectus attachment loaded into our store.
//
// This can also be done via an HTTP upload, but here we short-circuit and do it from code.
if (!rpc.attachmentExists(SellerFlow.PROSPECTUS_HASH)) {
javaClass.classLoader.getResourceAsStream("bank-of-london-cp.jar").use {
val id = rpc.uploadAttachment(it)
check(SellerFlow.PROSPECTUS_HASH == id)
}
}
// The line below blocks and waits for the future to resolve.
val stx = rpc.startFlow(::CommercialPaperIssueFlow, amount, ref, seller, notaryNode.notaryIdentity).returnValue.getOrThrow()
println("Commercial paper issued to seller")
} }
fun runSeller(amount: Amount<Currency> = 1000.0.DOLLARS, counterparty: X500Name) { fun runSeller(amount: Amount<Currency> = 1000.0.DOLLARS, buyerName: X500Name) {
val otherParty = rpc.partyFromX500Name(counterparty) ?: throw IllegalStateException("Don't know $counterparty") val otherParty = rpc.partyFromX500Name(buyerName) ?: throw IllegalStateException("Don't know $buyerName")
// The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash.
// //
// The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an // The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an

View File

@ -0,0 +1,76 @@
package net.corda.traderdemo.flow
import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.CommercialPaper
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.core.contracts.Amount
import net.corda.core.contracts.`issued by`
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.days
import net.corda.core.utilities.seconds
import java.time.Instant
import java.util.*
/**
* Flow for the Bank of Corda node to issue some commercial paper to the seller's node, to sell to the buyer.
*/
@InitiatingFlow
@StartableByRPC
class CommercialPaperIssueFlow(val amount: Amount<Currency>,
val issueRef: OpaqueBytes,
val recipient: Party,
val notary: Party,
override val progressTracker: ProgressTracker) : FlowLogic<SignedTransaction>() {
constructor(amount: Amount<Currency>, issueRef: OpaqueBytes, recipient: Party, notary: Party) : this(amount, issueRef, recipient, notary, tracker())
companion object {
val PROSPECTUS_HASH = SecureHash.parse("decd098666b9657314870e192ced0c3519c2c9d395507a238338f8d003929de9")
object ISSUING : ProgressTracker.Step("Issuing and timestamping some commercial paper")
fun tracker() = ProgressTracker(ISSUING)
}
@Suspendable
override fun call(): SignedTransaction {
progressTracker.currentStep = ISSUING
val me = serviceHub.myInfo.legalIdentity
val issuance: SignedTransaction = run {
val tx = CommercialPaper().generateIssue(me.ref(issueRef), amount `issued by` me.ref(issueRef),
Instant.now() + 10.days, notary)
// TODO: Consider moving these two steps below into generateIssue.
// Attach the prospectus.
tx.addAttachment(serviceHub.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
// Requesting a time-window to be set, all CP must have a validation window.
tx.setTimeWindow(Instant.now(), 30.seconds)
// Sign it as ourselves.
val stx = serviceHub.signInitialTransaction(tx)
subFlow(FinalityFlow(stx)).single()
}
// Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works.
val move: SignedTransaction = run {
val builder = TransactionBuilder(notary)
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), recipient)
val stx = serviceHub.signInitialTransaction(builder)
subFlow(FinalityFlow(stx)).single()
}
return move
}
}

View File

@ -2,25 +2,17 @@ package net.corda.traderdemo.flow
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.CommercialPaper import net.corda.contracts.CommercialPaper
import net.corda.contracts.asset.DUMMY_CASH_ISSUER import net.corda.core.contracts.Amount
import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.utilities.days
import net.corda.core.flows.FinalityFlow
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatingFlow import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.utilities.seconds
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker import net.corda.core.utilities.ProgressTracker
import net.corda.flows.TwoPartyTradeFlow import net.corda.flows.TwoPartyTradeFlow
import org.bouncycastle.asn1.x500.X500Name
import java.time.Instant
import java.util.* import java.util.*
@InitiatingFlow @InitiatingFlow
@ -51,7 +43,8 @@ class SellerFlow(val otherParty: Party,
val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0] val notary: NodeInfo = serviceHub.networkMapCache.notaryNodes[0]
val cpOwnerKey = serviceHub.keyManagementService.freshKey() val cpOwnerKey = serviceHub.keyManagementService.freshKey()
val commercialPaper = selfIssueSomeCommercialPaper(serviceHub.myInfo.legalIdentity, notary) val commercialPaper = serviceHub.vaultQueryService.queryBy(CommercialPaper.State::class.java).states.first()
progressTracker.currentStep = TRADING progressTracker.currentStep = TRADING
@ -66,39 +59,4 @@ class SellerFlow(val otherParty: Party,
progressTracker.getChildProgressTracker(TRADING)!!) progressTracker.getChildProgressTracker(TRADING)!!)
return subFlow(seller) return subFlow(seller)
} }
@Suspendable
fun selfIssueSomeCommercialPaper(ownedBy: AbstractParty, notaryNode: NodeInfo): StateAndRef<CommercialPaper.State> {
// Make a fake company that's issued its own paper.
val party = Party(X500Name("CN=BankOfCorda,O=R3,L=New York,C=US"), serviceHub.legalIdentityKey)
val issuance: SignedTransaction = run {
val tx = CommercialPaper().generateIssue(party.ref(1, 2, 3), 1100.DOLLARS `issued by` DUMMY_CASH_ISSUER,
Instant.now() + 10.days, notaryNode.notaryIdentity)
// TODO: Consider moving these two steps below into generateIssue.
// Attach the prospectus.
tx.addAttachment(serviceHub.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
// Requesting a time-window to be set, all CP must have a validation window.
tx.setTimeWindow(Instant.now(), 30.seconds)
// Sign it as ourselves.
val stx = serviceHub.signInitialTransaction(tx)
subFlow(FinalityFlow(stx)).single()
}
// Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works.
val move: SignedTransaction = run {
val builder = TransactionBuilder(notaryNode.notaryIdentity)
CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy)
val stx = serviceHub.signInitialTransaction(builder)
subFlow(FinalityFlow(stx)).single()
}
return move.tx.outRef(0)
}
} }

View File

@ -22,7 +22,7 @@
<Logger name="BasicInfo" additivity="false"> <Logger name="BasicInfo" additivity="false">
<AppenderRef ref="Console-Appender-Println"/> <AppenderRef ref="Console-Appender-Println"/>
</Logger> </Logger>
<Logger name="org.hibernate.SQL" level="debug" additivity="false"> <Logger name="org.hibernate.SQL" level="info" additivity="false">
<AppenderRef ref="Console-Appender"/> <AppenderRef ref="Console-Appender"/>
</Logger> </Logger>
</Loggers> </Loggers>

View File

@ -11,13 +11,15 @@ import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.IdentityService import net.corda.core.node.services.IdentityService
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.VerifierType import net.corda.node.services.config.VerifierType
import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.utilities.CertificateType
import net.corda.node.utilities.X509Utilities
import net.corda.nodeapi.config.SSLConfiguration import net.corda.nodeapi.config.SSLConfiguration
import net.corda.testing.node.MockServices import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestDataSourceProperties import net.corda.testing.node.makeTestDataSourceProperties
@ -67,9 +69,9 @@ val ALICE_PUBKEY: PublicKey get() = ALICE_KEY.public
val BOB_PUBKEY: PublicKey get() = BOB_KEY.public val BOB_PUBKEY: PublicKey get() = BOB_KEY.public
val CHARLIE_PUBKEY: PublicKey get() = CHARLIE_KEY.public val CHARLIE_PUBKEY: PublicKey get() = CHARLIE_KEY.public
val MEGA_CORP_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(X509Utilities.getX509Name("MegaCorp","London","demo@r3.com",null), MEGA_CORP_PUBKEY) val MEGA_CORP_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(getX509Name("MegaCorp", "London", "demo@r3.com", null), MEGA_CORP_PUBKEY)
val MEGA_CORP: Party get() = MEGA_CORP_IDENTITY.party val MEGA_CORP: Party get() = MEGA_CORP_IDENTITY.party
val MINI_CORP_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(X509Utilities.getX509Name("MiniCorp","London","demo@r3.com",null), MINI_CORP_PUBKEY) val MINI_CORP_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(getX509Name("MiniCorp", "London", "demo@r3.com", null), MINI_CORP_PUBKEY)
val MINI_CORP: Party get() = MINI_CORP_IDENTITY.party val MINI_CORP: Party get() = MINI_CORP_IDENTITY.party
val BOC_KEY: KeyPair by lazy { generateKeyPair() } val BOC_KEY: KeyPair by lazy { generateKeyPair() }
@ -80,7 +82,7 @@ val BOC_PARTY_REF = BOC.ref(OpaqueBytes.of(1)).reference
val BIG_CORP_KEY: KeyPair by lazy { generateKeyPair() } val BIG_CORP_KEY: KeyPair by lazy { generateKeyPair() }
val BIG_CORP_PUBKEY: PublicKey get() = BIG_CORP_KEY.public val BIG_CORP_PUBKEY: PublicKey get() = BIG_CORP_KEY.public
val BIG_CORP_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(X509Utilities.getX509Name("BigCorporation","London","demo@r3.com",null), BIG_CORP_PUBKEY) val BIG_CORP_IDENTITY: PartyAndCertificate get() = getTestPartyAndCertificate(getX509Name("BigCorporation", "London", "demo@r3.com", null), BIG_CORP_PUBKEY)
val BIG_CORP: Party get() = BIG_CORP_IDENTITY.party val BIG_CORP: Party get() = BIG_CORP_IDENTITY.party
val BIG_CORP_PARTY_REF = BIG_CORP.ref(OpaqueBytes.of(1)).reference val BIG_CORP_PARTY_REF = BIG_CORP.ref(OpaqueBytes.of(1)).reference

View File

@ -63,6 +63,8 @@ fun initialiseTestSerialization() {
(SerializationDefaults.SERIALIZATION_FACTORY as TestSerializationFactory).delegate = SerializationFactoryImpl().apply { (SerializationDefaults.SERIALIZATION_FACTORY as TestSerializationFactory).delegate = SerializationFactoryImpl().apply {
registerScheme(KryoClientSerializationScheme()) registerScheme(KryoClientSerializationScheme())
registerScheme(KryoServerSerializationScheme()) registerScheme(KryoServerSerializationScheme())
registerScheme(AMQPClientSerializationScheme())
registerScheme(AMQPServerSerializationScheme())
} }
(SerializationDefaults.P2P_CONTEXT as TestSerializationContext).delegate = KRYO_P2P_CONTEXT (SerializationDefaults.P2P_CONTEXT as TestSerializationContext).delegate = KRYO_P2P_CONTEXT
(SerializationDefaults.RPC_SERVER_CONTEXT as TestSerializationContext).delegate = KRYO_RPC_SERVER_CONTEXT (SerializationDefaults.RPC_SERVER_CONTEXT as TestSerializationContext).delegate = KRYO_RPC_SERVER_CONTEXT

View File

@ -4,7 +4,9 @@ package net.corda.testing
import net.corda.core.contracts.Command import net.corda.core.contracts.Command
import net.corda.core.contracts.TypeOnlyCommandData import net.corda.core.contracts.TypeOnlyCommandData
import net.corda.core.crypto.* import net.corda.core.crypto.CertificateAndKeyPair
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.generateKeyPair
import net.corda.core.crypto.testing.DummyPublicKey import net.corda.core.crypto.testing.DummyPublicKey
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate import net.corda.core.identity.PartyAndCertificate
@ -12,6 +14,7 @@ import net.corda.core.internal.concurrent.transpose
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.node.utilities.X509Utilities
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.testing.driver.DriverDSLExposedInterface import net.corda.testing.driver.DriverDSLExposedInterface
import org.bouncycastle.asn1.x500.X500Name import org.bouncycastle.asn1.x500.X500Name

View File

@ -1,12 +1,9 @@
package net.corda.testing.contracts package net.corda.testing.contracts
import net.corda.core.contracts.CommandData
import net.corda.core.contracts.Contract import net.corda.core.contracts.Contract
import net.corda.core.contracts.LinearState import net.corda.core.contracts.LinearState
import net.corda.core.contracts.UniqueIdentifier import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.contracts.clauses.Clause import net.corda.core.contracts.requireThat
import net.corda.core.contracts.clauses.FilterOn
import net.corda.core.contracts.clauses.verifyClause
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.containsAny import net.corda.core.crypto.containsAny
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
@ -22,10 +19,17 @@ import java.time.ZoneOffset.UTC
class DummyLinearContract : Contract { class DummyLinearContract : Contract {
override val legalContractReference: SecureHash = SecureHash.sha256("Test") override val legalContractReference: SecureHash = SecureHash.sha256("Test")
val clause: Clause<State, CommandData, Unit> = LinearState.ClauseVerifier() override fun verify(tx: LedgerTransaction) {
override fun verify(tx: LedgerTransaction) = verifyClause(tx, val inputs = tx.inputs.map { it.state.data }.filterIsInstance<State>()
FilterOn(clause, { states -> states.filterIsInstance<State>() }), val outputs = tx.outputs.map { it.data }.filterIsInstance<State>()
emptyList())
val inputIds = inputs.map { it.linearId }.distinct()
val outputIds = outputs.map { it.linearId }.distinct()
requireThat {
"LinearStates are not merged" using (inputIds.count() == inputs.count())
"LinearStates are not split" using (outputIds.count() == outputs.count())
}
}
data class State( data class State(
override val linearId: UniqueIdentifier = UniqueIdentifier(), override val linearId: UniqueIdentifier = UniqueIdentifier(),

View File

@ -13,9 +13,9 @@ import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.Vault import net.corda.core.node.services.Vault
import net.corda.core.toFuture import net.corda.core.toFuture
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.OpaqueBytes
import net.corda.testing.CHARLIE import net.corda.testing.CHARLIE
import net.corda.testing.DUMMY_NOTARY import net.corda.testing.DUMMY_NOTARY
import net.corda.testing.DUMMY_NOTARY_KEY import net.corda.testing.DUMMY_NOTARY_KEY
@ -228,10 +228,11 @@ fun ServiceHub.evolveLinearState(linearState: StateAndRef<LinearState>) : StateA
@JvmOverloads @JvmOverloads
fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE): Vault.Update<ContractState> { fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE): Vault.Update<ContractState> {
val update = vaultService.rawUpdates.toFuture() val update = vaultService.rawUpdates.toFuture()
val services = this
// A tx that spends our money. // A tx that spends our money.
val spendTX = TransactionBuilder(DUMMY_NOTARY).apply { val spendTX = TransactionBuilder(DUMMY_NOTARY).apply {
vaultService.generateSpend(this, amount, to) Cash.generateSpend(services, this, amount, to)
signWith(DUMMY_NOTARY_KEY) signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction(checkSufficientSignatures = false) }.toSignedTransaction(checkSufficientSignatures = false)

View File

@ -11,12 +11,12 @@ import net.corda.cordform.CordformNode
import net.corda.cordform.NodeDefinition import net.corda.cordform.NodeDefinition
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.concurrent.firstOf import net.corda.core.concurrent.firstOf
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.appendToCommonName import net.corda.core.crypto.appendToCommonName
import net.corda.core.crypto.commonName import net.corda.core.crypto.commonName
import net.corda.core.crypto.getX509Name
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.*
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.internal.concurrent.*
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.times import net.corda.core.internal.times
import net.corda.core.messaging.CordaRPCOps import net.corda.core.messaging.CordaRPCOps
@ -53,9 +53,12 @@ import java.time.Instant
import java.time.ZoneOffset.UTC import java.time.ZoneOffset.UTC
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.util.* import java.util.*
import java.util.concurrent.* import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.MILLISECONDS
import java.util.concurrent.TimeUnit.SECONDS import java.util.concurrent.TimeUnit.SECONDS
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -566,7 +569,7 @@ class DriverDSL(
val rpcAddress = portAllocation.nextHostAndPort() val rpcAddress = portAllocation.nextHostAndPort()
val webAddress = portAllocation.nextHostAndPort() val webAddress = portAllocation.nextHostAndPort()
// TODO: Derive name from the full picked name, don't just wrap the common name // TODO: Derive name from the full picked name, don't just wrap the common name
val name = providedName ?: X509Utilities.getX509Name("${oneOf(names).commonName}-${p2pAddress.port}","London","demo@r3.com",null) val name = providedName ?: getX509Name("${oneOf(names).commonName}-${p2pAddress.port}", "London", "demo@r3.com", null)
val networkMapServiceConfigLookup = networkMapServiceConfigLookup(listOf(object : NodeDefinition { val networkMapServiceConfigLookup = networkMapServiceConfigLookup(listOf(object : NodeDefinition {
override fun getName() = name.toString() override fun getName() = name.toString()
override fun getConfig() = configOf("p2pAddress" to p2pAddress.toString()) override fun getConfig() = configOf("p2pAddress" to p2pAddress.toString())

View File

@ -3,8 +3,8 @@ package net.corda.testing.node
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture import com.google.common.util.concurrent.SettableFuture
import net.corda.core.crypto.getX509Name
import net.corda.core.internal.ThreadBox import net.corda.core.internal.ThreadBox
import net.corda.core.crypto.X509Utilities
import net.corda.core.messaging.AllPossibleRecipients import net.corda.core.messaging.AllPossibleRecipients
import net.corda.core.messaging.MessageRecipientGroup import net.corda.core.messaging.MessageRecipientGroup
import net.corda.core.messaging.MessageRecipients import net.corda.core.messaging.MessageRecipients
@ -128,7 +128,7 @@ class InMemoryMessagingNetwork(
id: Int, id: Int,
executor: AffinityExecutor, executor: AffinityExecutor,
advertisedServices: List<ServiceEntry>, advertisedServices: List<ServiceEntry>,
description: X500Name = X509Utilities.getX509Name("In memory node $id","London","demo@r3.com",null), description: X500Name = getX509Name("In memory node $id", "London", "demo@r3.com", null),
database: CordaPersistence) database: CordaPersistence)
: MessagingServiceBuilder<InMemoryMessaging> { : MessagingServiceBuilder<InMemoryMessaging> {
val peerHandle = PeerHandle(id, description) val peerHandle = PeerHandle(id, description)

View File

@ -9,6 +9,7 @@ import net.corda.core.messaging.DataFeed
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.* import net.corda.core.node.services.*
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
@ -24,11 +25,14 @@ import net.corda.node.services.persistence.InMemoryStateMachineRecordedTransacti
import net.corda.node.services.schema.HibernateObserver import net.corda.node.services.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.vault.HibernateVaultQueryImpl
import net.corda.node.services.vault.NodeVaultService import net.corda.node.services.vault.NodeVaultService
import net.corda.testing.DUMMY_CA import net.corda.node.utilities.CordaPersistence
import net.corda.testing.MEGA_CORP import net.corda.node.utilities.configureDatabase
import net.corda.testing.MOCK_IDENTITIES import net.corda.schemas.CashSchemaV1
import net.corda.testing.getTestPartyAndCertificate import net.corda.schemas.CommercialPaperSchemaV1
import net.corda.testing.*
import net.corda.testing.schemas.DummyLinearStateSchemaV1
import org.bouncycastle.operator.ContentSigner import org.bouncycastle.operator.ContentSigner
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
@ -84,7 +88,7 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
lateinit var hibernatePersister: HibernateObserver lateinit var hibernatePersister: HibernateObserver
fun makeVaultService(dataSourceProps: Properties, hibernateConfig: HibernateConfiguration = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties())): VaultService { fun makeVaultService(dataSourceProps: Properties, hibernateConfig: HibernateConfiguration = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties(), identityService)): VaultService {
val vaultService = NodeVaultService(this, dataSourceProps, makeTestDatabaseProperties()) val vaultService = NodeVaultService(this, dataSourceProps, makeTestDatabaseProperties())
hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig) hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig)
return vaultService return vaultService
@ -212,4 +216,30 @@ fun makeTestDatabaseProperties(): Properties {
return props return props
} }
fun makeTestDatabaseAndMockServices(customSchemas: Set<MappedSchema> = setOf(CommercialPaperSchemaV1, DummyLinearStateSchemaV1, CashSchemaV1), keys: List<KeyPair> = listOf(MEGA_CORP_KEY)): Pair<CordaPersistence, MockServices> {
val dataSourceProps = makeTestDataSourceProperties()
val databaseProperties = makeTestDatabaseProperties()
val database = configureDatabase(dataSourceProps, databaseProperties)
val mockService = database.transaction {
val identityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate)
val hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), databaseProperties, identityService)
object : MockServices(*(keys.toTypedArray())) {
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
vaultService.notifyAll(txs.map { it.tx })
}
override val vaultQueryService: VaultQueryService = HibernateVaultQueryImpl(hibernateConfig, vaultService.updatesPublisher)
override fun jdbcSession(): Connection = database.createSession()
}
}
return Pair(database, mockService)
}
val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor") val MOCK_VERSION_INFO = VersionInfo(1, "Mock release", "Mock revision", "Mock Vendor")

View File

@ -1,10 +1,13 @@
package net.corda.testing.node package net.corda.testing.node
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.appendToCommonName import net.corda.core.crypto.appendToCommonName
import net.corda.core.crypto.commonName import net.corda.core.crypto.commonName
import net.corda.core.internal.concurrent.* import net.corda.core.crypto.getX509Name
import net.corda.core.internal.concurrent.flatMap
import net.corda.core.internal.concurrent.fork
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectories
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
@ -120,13 +123,13 @@ abstract class NodeBasedTest : TestDependencyInjectionBase() {
val nodeAddresses = getFreeLocalPorts("localhost", clusterSize).map { it.toString() } val nodeAddresses = getFreeLocalPorts("localhost", clusterSize).map { it.toString() }
val masterNodeFuture = startNode( val masterNodeFuture = startNode(
X509Utilities.getX509Name("${notaryName.commonName}-0","London","demo@r3.com",null), getX509Name("${notaryName.commonName}-0", "London", "demo@r3.com", null),
advertisedServices = setOf(serviceInfo), advertisedServices = setOf(serviceInfo),
configOverrides = mapOf("notaryNodeAddress" to nodeAddresses[0])) configOverrides = mapOf("notaryNodeAddress" to nodeAddresses[0]))
val remainingNodesFutures = (1 until clusterSize).map { val remainingNodesFutures = (1 until clusterSize).map {
startNode( startNode(
X509Utilities.getX509Name("${notaryName.commonName}-$it","London","demo@r3.com",null), getX509Name("${notaryName.commonName}-$it", "London", "demo@r3.com", null),
advertisedServices = setOf(serviceInfo), advertisedServices = setOf(serviceInfo),
configOverrides = mapOf( configOverrides = mapOf(
"notaryNodeAddress" to nodeAddresses[it], "notaryNodeAddress" to nodeAddresses[it],

View File

@ -1,6 +1,6 @@
package net.corda.demobench.model package net.corda.demobench.model
import net.corda.core.crypto.X509Utilities.getX509Name import net.corda.core.crypto.getX509Name
import net.corda.demobench.plugin.PluginController import net.corda.demobench.plugin.PluginController
import net.corda.demobench.pty.R3Pty import net.corda.demobench.pty.R3Pty
import tornadofx.* import tornadofx.*

View File

@ -1,8 +1,8 @@
package net.corda.demobench.model package net.corda.demobench.model
import net.corda.core.crypto.X509Utilities.getX509Name import net.corda.core.crypto.getX509Name
import net.corda.testing.DUMMY_NOTARY
import net.corda.nodeapi.User import net.corda.nodeapi.User
import net.corda.testing.DUMMY_NOTARY
import org.junit.Test import org.junit.Test
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths

View File

@ -3,7 +3,6 @@ package net.corda.verifier
import net.corda.client.mock.* import net.corda.client.mock.*
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.entropyToKeyPair import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.crypto.sha256 import net.corda.core.crypto.sha256
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
@ -12,6 +11,7 @@ import net.corda.core.identity.Party
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import net.corda.testing.contracts.DummyContract import net.corda.testing.contracts.DummyContract
import net.corda.testing.getTestX509Name
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.math.BigInteger import java.math.BigInteger
import java.security.PublicKey import java.security.PublicKey
@ -179,7 +179,7 @@ fun commandGenerator(partiesToPickFrom: Collection<Party>): Generator<Pair<Comma
} }
val partyGenerator: Generator<Party> = Generator.int().combine(publicKeyGenerator) { n, key -> val partyGenerator: Generator<Party> = Generator.int().combine(publicKeyGenerator) { n, key ->
Party(X509Utilities.getDevX509Name("Party$n"), key) Party(getTestX509Name("Party$n"), key)
} }
fun <A> pickOneOrMaybeNew(from: Collection<A>, generator: Generator<A>): Generator<A> { fun <A> pickOneOrMaybeNew(from: Collection<A>, generator: Generator<A>): Generator<A> {

View File

@ -2,15 +2,13 @@ package net.corda.verifier
import com.typesafe.config.Config import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import net.corda.core.concurrent.* import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.X509Utilities
import net.corda.core.crypto.commonName import net.corda.core.crypto.commonName
import net.corda.core.internal.div
import net.corda.core.crypto.random63BitValue import net.corda.core.crypto.random63BitValue
import net.corda.core.internal.concurrent.* import net.corda.core.internal.concurrent.*
import net.corda.core.internal.div
import net.corda.core.transactions.LedgerTransaction import net.corda.core.transactions.LedgerTransaction
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.testing.driver.ProcessUtilities
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.services.config.configureDevKeyAndTrustStores import net.corda.node.services.config.configureDevKeyAndTrustStores
import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER import net.corda.nodeapi.ArtemisMessagingComponent.Companion.NODE_USER
@ -20,6 +18,7 @@ import net.corda.nodeapi.VerifierApi
import net.corda.nodeapi.config.NodeSSLConfiguration import net.corda.nodeapi.config.NodeSSLConfiguration
import net.corda.nodeapi.config.SSLConfiguration import net.corda.nodeapi.config.SSLConfiguration
import net.corda.testing.driver.* import net.corda.testing.driver.*
import net.corda.testing.getTestX509Name
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ActiveMQClient import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import org.apache.activemq.artemis.api.core.client.ClientProducer import org.apache.activemq.artemis.api.core.client.ClientProducer
@ -250,7 +249,7 @@ data class VerifierDriverDSL(
val id = verifierCount.andIncrement val id = verifierCount.andIncrement
val jdwpPort = if (driverDSL.isDebug) driverDSL.debugPortAllocation.nextPort() else null val jdwpPort = if (driverDSL.isDebug) driverDSL.debugPortAllocation.nextPort() else null
val processFuture = driverDSL.executorService.fork { val processFuture = driverDSL.executorService.fork {
val verifierName = X509Utilities.getDevX509Name("verifier$id") val verifierName = getTestX509Name("verifier$id")
val baseDirectory = driverDSL.driverDirectory / verifierName.commonName val baseDirectory = driverDSL.driverDirectory / verifierName.commonName
val config = createConfiguration(baseDirectory, address) val config = createConfiguration(baseDirectory, address)
val configFilename = "verifier.conf" val configFilename = "verifier.conf"