mirror of
https://github.com/corda/corda.git
synced 2025-06-21 00:23:09 +00:00
Merge branch 'master' into tudor_merge_os_master
This commit is contained in:
@ -48,20 +48,4 @@ interface Cordapp {
|
||||
val jarPath: URL
|
||||
val cordappClasses: List<String>
|
||||
val jarHash: SecureHash.SHA256
|
||||
|
||||
/**
|
||||
* CorDapp's information, including vendor and version.
|
||||
*
|
||||
* @property shortName Cordapp's shortName
|
||||
* @property vendor Cordapp's vendor
|
||||
* @property version Cordapp's version
|
||||
*/
|
||||
@DoNotImplement
|
||||
interface Info {
|
||||
val shortName: String
|
||||
val vendor: String
|
||||
val version: String
|
||||
|
||||
fun hasUnknownFields(): Boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,7 +102,11 @@ fun PublicKey.isValid(content: ByteArray, signature: DigitalSignature): Boolean
|
||||
/** Render a public key to its hash (in Base58) of its serialised form using the DL prefix. */
|
||||
fun PublicKey.toStringShort(): String = "DL" + this.toSHA256Bytes().toBase58()
|
||||
|
||||
/** Return a [Set] of the contained keys if this is a [CompositeKey]; otherwise, return a [Set] with a single element (this [PublicKey]). */
|
||||
/**
|
||||
* Return a [Set] of the contained leaf keys if this is a [CompositeKey].
|
||||
* Otherwise, return a [Set] with a single element (this [PublicKey]).
|
||||
* <i>Note that leaf keys cannot be of type [CompositeKey].</i>
|
||||
*/
|
||||
val PublicKey.keys: Set<PublicKey> get() = (this as? CompositeKey)?.leafKeys ?: setOf(this)
|
||||
|
||||
/** Return true if [otherKey] fulfils the requirements of this [PublicKey]. */
|
||||
@ -110,7 +114,12 @@ fun PublicKey.isFulfilledBy(otherKey: PublicKey): Boolean = isFulfilledBy(setOf(
|
||||
/** Return true if [otherKeys] fulfil the requirements of this [PublicKey]. */
|
||||
fun PublicKey.isFulfilledBy(otherKeys: Iterable<PublicKey>): Boolean = (this as? CompositeKey)?.isFulfilledBy(otherKeys) ?: (this in otherKeys)
|
||||
|
||||
/** Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey]. */
|
||||
/**
|
||||
* Checks whether any of the given [keys] matches a leaf on the [CompositeKey] tree or a single [PublicKey].
|
||||
*
|
||||
* <i>Note that this function checks against leaves, which cannot be of type [CompositeKey]. Due to that, if any of the
|
||||
* [otherKeys] is a [CompositeKey], this function will not find a match.</i>
|
||||
*/
|
||||
fun PublicKey.containsAny(otherKeys: Iterable<PublicKey>): Boolean {
|
||||
return if (this is CompositeKey) keys.intersect(otherKeys).isNotEmpty()
|
||||
else this in otherKeys
|
||||
|
@ -11,9 +11,11 @@ import net.i2p.crypto.eddsa.EdDSASecurityProvider
|
||||
import org.bouncycastle.asn1.ASN1ObjectIdentifier
|
||||
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
import org.bouncycastle.jcajce.provider.asymmetric.ec.AlgorithmParametersSpi
|
||||
import org.bouncycastle.jcajce.provider.util.AsymmetricKeyInfoConverter
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider
|
||||
import java.security.SecureRandom
|
||||
import java.security.Security
|
||||
|
||||
internal val cordaSecurityProvider = CordaSecurityProvider().also {
|
||||
@ -29,6 +31,8 @@ internal val cordaBouncyCastleProvider = BouncyCastleProvider().apply {
|
||||
override fun generatePublic(keyInfo: SubjectPublicKeyInfo) = decodePublicKey(EDDSA_ED25519_SHA512, keyInfo.encoded)
|
||||
override fun generatePrivate(keyInfo: PrivateKeyInfo) = decodePrivateKey(EDDSA_ED25519_SHA512, keyInfo.encoded)
|
||||
})
|
||||
// Required due to [X509CRL].verify() reported issues in network-services after BC 1.60 update.
|
||||
put("AlgorithmParameters.SHA256WITHECDSA", AlgorithmParametersSpi::class.java.name)
|
||||
}.also {
|
||||
// This registration is needed for reading back EdDSA key from java keystore.
|
||||
// TODO: Find a way to make JKS work with bouncy castle provider or implement our own provide so we don't have to register bouncy castle provider.
|
||||
@ -46,4 +50,4 @@ internal val bouncyCastlePQCProvider = BouncyCastlePQCProvider().apply {
|
||||
internal val providerMap = listOf(cordaBouncyCastleProvider, cordaSecurityProvider, bouncyCastlePQCProvider).map { it.name to it }.toMap()
|
||||
|
||||
@DeleteForDJVM
|
||||
internal fun platformSecureRandomFactory() = platformSecureRandom() // To minimise diff of CryptoUtils against open-source.
|
||||
internal fun platformSecureRandomFactory(): SecureRandom = platformSecureRandom() // To minimise diff of CryptoUtils against open-source.
|
||||
|
@ -21,13 +21,13 @@ import net.corda.core.CordaRuntimeException
|
||||
* the exception is handled. This ID is propagated to counterparty flows, even when the [FlowException] is
|
||||
* downgraded to an [UnexpectedFlowEndException]. This is so the error conditions may be correlated later on.
|
||||
*/
|
||||
open class FlowException(message: String?, cause: Throwable?) :
|
||||
open class FlowException(message: String?, cause: Throwable?, var originalErrorId: Long? = null) :
|
||||
CordaException(message, cause), IdentifiableException {
|
||||
constructor(message: String?, cause: Throwable?) : this(message, cause, null)
|
||||
constructor(message: String?) : this(message, null)
|
||||
constructor(cause: Throwable?) : this(cause?.toString(), cause)
|
||||
constructor() : this(null, null)
|
||||
|
||||
var originalErrorId: Long? = null
|
||||
override fun getErrorId(): Long? = originalErrorId
|
||||
}
|
||||
// DOCEND 1
|
||||
|
@ -24,22 +24,22 @@ import java.security.cert.X509Certificate
|
||||
// also note that IDs are numbered from 1 upwards, matching numbering of other enum types in ASN.1 specifications.
|
||||
// TODO: Link to the specification once it has a permanent URL
|
||||
enum class CertRole(val validParents: NonEmptySet<CertRole?>, val isIdentity: Boolean, val isWellKnown: Boolean) : ASN1Encodable {
|
||||
/** Intermediate CA (Doorman service). */
|
||||
INTERMEDIATE_CA(NonEmptySet.of(null), false, false),
|
||||
/** Signing certificate for the Doorman CA. */
|
||||
DOORMAN_CA(NonEmptySet.of(null), false, false),
|
||||
/** Signing certificate for the network map. */
|
||||
NETWORK_MAP(NonEmptySet.of(null), false, false),
|
||||
/** Well known (publicly visible) identity of a service (such as notary). */
|
||||
SERVICE_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA), true, true),
|
||||
SERVICE_IDENTITY(NonEmptySet.of(DOORMAN_CA), true, true),
|
||||
/** Node level CA from which the TLS and well known identity certificates are issued. */
|
||||
NODE_CA(NonEmptySet.of(INTERMEDIATE_CA), false, false),
|
||||
NODE_CA(NonEmptySet.of(DOORMAN_CA), false, false),
|
||||
/** Transport layer security certificate for a node. */
|
||||
TLS(NonEmptySet.of(NODE_CA), false, false),
|
||||
/** Well known (publicly visible) identity of a legal entity. */
|
||||
// TODO: at the moment, Legal Identity certs are issued by Node CA only. However, [INTERMEDIATE_CA] is also added
|
||||
// TODO: at the moment, Legal Identity certs are issued by Node CA only. However, [DOORMAN_CA] is also added
|
||||
// as a valid parent of [LEGAL_IDENTITY] for backwards compatibility purposes (eg. if we decide TLS has its
|
||||
// own Root CA and Intermediate CA directly issues Legal Identities; thus, there won't be a requirement for
|
||||
// Node CA). Consider removing [INTERMEDIATE_CA] from [validParents] when the model is finalised.
|
||||
LEGAL_IDENTITY(NonEmptySet.of(INTERMEDIATE_CA, NODE_CA), true, true),
|
||||
// own Root CA and Doorman CA directly issues Legal Identities; thus, there won't be a requirement for
|
||||
// Node CA). Consider removing [DOORMAN_CA] from [validParents] when the model is finalised.
|
||||
LEGAL_IDENTITY(NonEmptySet.of(DOORMAN_CA, NODE_CA), true, true),
|
||||
/** Confidential (limited visibility) identity of a legal entity. */
|
||||
CONFIDENTIAL_LEGAL_IDENTITY(NonEmptySet.of(LEGAL_IDENTITY), true, false);
|
||||
|
||||
|
@ -4,17 +4,41 @@ import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.verify
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||
import net.corda.core.serialization.SerializedBytes
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import java.security.cert.X509Certificate
|
||||
import java.security.cert.*
|
||||
|
||||
// TODO: Rename this to DigitalSignature.WithCert once we're happy for it to be public API. The methods will need documentation
|
||||
// and the correct exceptions will be need to be annotated
|
||||
/** A digital signature with attached certificate of the public key. */
|
||||
class DigitalSignatureWithCert(val by: X509Certificate, bytes: ByteArray) : DigitalSignature(bytes) {
|
||||
/** A digital signature with attached certificate of the public key and (optionally) the remaining chain of the certificates from the certificate path. */
|
||||
class DigitalSignatureWithCert(val by: X509Certificate, val parentCertsChain: List<X509Certificate>, bytes: ByteArray) : DigitalSignature(bytes) {
|
||||
@DeprecatedConstructorForDeserialization(1)
|
||||
constructor(by: X509Certificate, bytes: ByteArray) : this(by, emptyList(), bytes)
|
||||
|
||||
val fullCertChain: List<X509Certificate> get() = listOf(by) + parentCertsChain
|
||||
val fullCertPath: CertPath get() = CertificateFactory.getInstance("X.509").generateCertPath(fullCertChain)
|
||||
|
||||
fun verify(content: ByteArray): Boolean = by.publicKey.verify(content, this)
|
||||
fun verify(content: OpaqueBytes): Boolean = verify(content.bytes)
|
||||
|
||||
init {
|
||||
if (parentCertsChain.isNotEmpty()) {
|
||||
val parameters = PKIXParameters(setOf(TrustAnchor(parentCertsChain.last(), null))).apply { isRevocationEnabled = false }
|
||||
try {
|
||||
CertPathValidator.getInstance("PKIX").validate(fullCertPath, parameters)
|
||||
} catch (e: CertPathValidatorException) {
|
||||
throw IllegalArgumentException(
|
||||
"""Cert path failed to validate.
|
||||
Reason: ${e.reason}
|
||||
Offending cert index: ${e.index}
|
||||
Cert path: $fullCertPath
|
||||
""", e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/** Similar to [SignedData] but instead of just attaching the public key, the certificate for the key is attached instead. */
|
||||
|
@ -17,6 +17,7 @@ import net.corda.core.serialization.SerializeAsTokenContext
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.unwrap
|
||||
import java.nio.file.FileAlreadyExistsException
|
||||
import java.util.*
|
||||
@ -75,7 +76,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
||||
return if (toFetch.isEmpty()) {
|
||||
Result(fromDisk, emptyList())
|
||||
} else {
|
||||
logger.info("Requesting ${toFetch.size} dependency(s) for verification from ${otherSideSession.counterparty.name}")
|
||||
logger.debug { "Requesting ${toFetch.size} dependency(s) for verification from ${otherSideSession.counterparty.name}" }
|
||||
|
||||
// TODO: Support "large message" response streaming so response sizes are not limited by RAM.
|
||||
// We can then switch to requesting items in large batches to minimise the latency penalty.
|
||||
@ -93,7 +94,7 @@ sealed class FetchDataFlow<T : NamedByHash, in W : Any>(
|
||||
}
|
||||
// Check for a buggy/malicious peer answering with something that we didn't ask for.
|
||||
val downloaded = validateFetchResponse(UntrustworthyData(maybeItems), toFetch)
|
||||
logger.info("Fetched ${downloaded.size} elements from ${otherSideSession.counterparty.name}")
|
||||
logger.debug { "Fetched ${downloaded.size} elements from ${otherSideSession.counterparty.name}" }
|
||||
maybeWriteToDisk(downloaded)
|
||||
Result(fromDisk, downloaded)
|
||||
}
|
||||
|
@ -4,21 +4,11 @@ package net.corda.core.internal
|
||||
|
||||
import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.cordapp.CordappConfig
|
||||
import net.corda.core.cordapp.CordappContext
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.UntrustworthyData
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.MDC
|
||||
import rx.Observable
|
||||
import rx.Observer
|
||||
import rx.subjects.PublishSubject
|
||||
|
@ -29,12 +29,6 @@ fun <K, V> Caffeine<in K, in V>.buildNamed(name: String): Cache<K, V> {
|
||||
return this.build<K, V>()
|
||||
}
|
||||
|
||||
fun <K, V> Caffeine<in K, in V>.buildNamed(name: String, loadFunc: (K) -> V): LoadingCache<K, V> {
|
||||
checkCacheName(name)
|
||||
return this.build<K, V>(loadFunc)
|
||||
}
|
||||
|
||||
|
||||
fun <K, V> Caffeine<in K, in V>.buildNamed(name: String, loader: CacheLoader<K, V>): LoadingCache<K, V> {
|
||||
checkCacheName(name)
|
||||
return this.build<K, V>(loader)
|
||||
|
@ -24,25 +24,29 @@ data class CordappImpl(
|
||||
override val customSchemas: Set<MappedSchema>,
|
||||
override val allFlows: List<Class<out FlowLogic<*>>>,
|
||||
override val jarPath: URL,
|
||||
val info: Info,
|
||||
override val jarHash: SecureHash.SHA256) : Cordapp {
|
||||
override val name: String = jarPath.toPath().fileName.toString().removeSuffix(".jar")
|
||||
override val name: String = jarName(jarPath)
|
||||
|
||||
companion object {
|
||||
fun jarName(url: URL): String = url.toPath().fileName.toString().removeSuffix(".jar")
|
||||
}
|
||||
|
||||
/**
|
||||
* An exhaustive list of all classes relevant to the node within this CorDapp
|
||||
*
|
||||
* TODO: Also add [SchedulableFlow] as a Cordapp class
|
||||
*/
|
||||
override val cordappClasses = ((rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames)
|
||||
override val cordappClasses: List<String> = (rpcFlows + initiatedFlows + services + serializationWhitelists.map { javaClass }).map { it.name } + contractClassNames
|
||||
|
||||
data class Info(override val shortName: String, override val vendor: String, override val version: String): Cordapp.Info {
|
||||
// TODO Why a seperate Info class and not just have the fields directly in CordappImpl?
|
||||
data class Info(val shortName: String, val vendor: String, val version: String, val minimumPlatformVersion: Int, val targetPlatformVersion: Int) {
|
||||
companion object {
|
||||
private const val UNKNOWN_VALUE = "Unknown"
|
||||
|
||||
val UNKNOWN = Info(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE)
|
||||
val UNKNOWN = Info(UNKNOWN_VALUE, UNKNOWN_VALUE, UNKNOWN_VALUE, 1, 1)
|
||||
}
|
||||
|
||||
override fun hasUnknownFields(): Boolean {
|
||||
return setOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
|
||||
}
|
||||
fun hasUnknownFields(): Boolean = arrayOf(shortName, vendor, version).any { it == UNKNOWN_VALUE }
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,68 @@
|
||||
package net.corda.core.internal.cordapp
|
||||
|
||||
import net.corda.core.internal.VisibleForTesting
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* Provides a way to acquire information about the calling CorDapp.
|
||||
*/
|
||||
object CordappInfoResolver {
|
||||
private val logger = loggerFor<CordappInfoResolver>()
|
||||
private val cordappClasses: ConcurrentHashMap<String, Set<CordappImpl.Info>> = ConcurrentHashMap()
|
||||
|
||||
// TODO use the StackWalker API once we migrate to Java 9+
|
||||
private var cordappInfoResolver: () -> CordappImpl.Info? = {
|
||||
Exception().stackTrace
|
||||
.mapNotNull { cordappClasses[it.className] }
|
||||
// If there is more than one cordapp registered for a class name we can't determine the "correct" one and return null.
|
||||
.firstOrNull { it.size < 2 }?.single()
|
||||
}
|
||||
|
||||
/*
|
||||
* Associates class names with CorDapps or logs a warning when a CorDapp is already registered for a given class.
|
||||
* This could happen when trying to run different versions of the same CorDapp on the same node.
|
||||
*/
|
||||
@Synchronized
|
||||
fun register(classes: List<String>, cordapp: CordappImpl.Info) {
|
||||
classes.forEach {
|
||||
if (cordappClasses.containsKey(it)) {
|
||||
logger.warn("More than one CorDapp registered for $it.")
|
||||
cordappClasses[it] = cordappClasses[it]!! + cordapp
|
||||
} else {
|
||||
cordappClasses[it] = setOf(cordapp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This should only be used when making a change that would break compatibility with existing CorDapps. The change
|
||||
* can then be version-gated, meaning the old behaviour is used if the calling CorDapp's target version is lower
|
||||
* than the platform version that introduces the new behaviour.
|
||||
* In situations where a `[CordappProvider]` is available the CorDapp context should be obtained from there.
|
||||
*
|
||||
* @return Information about the CorDapp from which the invoker is called, null if called outside a CorDapp or the
|
||||
* calling CorDapp cannot be reliably determined..
|
||||
*/
|
||||
fun getCorDappInfo(): CordappImpl.Info? = cordappInfoResolver()
|
||||
|
||||
/**
|
||||
* Temporarily switch out the internal resolver for another one. For use in testing.
|
||||
*/
|
||||
@Synchronized
|
||||
@VisibleForTesting
|
||||
fun withCordappInfoResolution(tempResolver: () -> CordappImpl.Info?, block: () -> Unit) {
|
||||
val resolver = cordappInfoResolver
|
||||
cordappInfoResolver = tempResolver
|
||||
try {
|
||||
block()
|
||||
} finally {
|
||||
cordappInfoResolver = resolver
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun clear() {
|
||||
cordappClasses.clear()
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
package net.corda.core.internal.notary
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.NotarisationRequestSignature
|
||||
import net.corda.core.flows.NotaryError
|
||||
import net.corda.core.identity.Party
|
||||
|
||||
/**
|
||||
* A service that records input states of the given transaction and provides conflict information
|
||||
* if any of the inputs have already been used in another transaction.
|
||||
*/
|
||||
interface AsyncUniquenessProvider : UniquenessProvider {
|
||||
/** Commits all input states of the given transaction. */
|
||||
fun commitAsync(states: List<StateRef>, txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef>): CordaFuture<Result>
|
||||
|
||||
/** Commits all input states of the given transaction synchronously. Use [commitAsync] for better performance. */
|
||||
override fun commit(states: List<StateRef>, txId: SecureHash, callerIdentity: Party, requestSignature: NotarisationRequestSignature, timeWindow: TimeWindow?, references: List<StateRef>) {
|
||||
val result = commitAsync(states, txId, callerIdentity, requestSignature, timeWindow,references).get()
|
||||
if (result is Result.Failure) {
|
||||
throw NotaryInternalException(result.error)
|
||||
}
|
||||
}
|
||||
|
||||
/** The outcome of committing a transaction. */
|
||||
sealed class Result {
|
||||
/** Indicates that all input states have been committed successfully. */
|
||||
object Success : Result()
|
||||
/** Indicates that the transaction has not been committed. */
|
||||
data class Failure(val error: NotaryError) : Result()
|
||||
}
|
||||
}
|
||||
|
@ -67,4 +67,4 @@ abstract class TrustedAuthorityNotaryService : NotaryService() {
|
||||
}
|
||||
|
||||
// TODO: Sign multiple transactions at once by building their Merkle tree and then signing over its root.
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.Try
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.security.PublicKey
|
||||
@ -42,7 +41,8 @@ data class StateMachineInfo @JvmOverloads constructor(
|
||||
* An object representing information about the initiator of the flow. Note that this field is
|
||||
* superseded by the [invocationContext] property, which has more detail.
|
||||
*/
|
||||
@Deprecated("There is more info available using 'context'") val initiator: FlowInitiator,
|
||||
@Deprecated("There is more info available using 'invocationContext'")
|
||||
val initiator: FlowInitiator,
|
||||
/** A [DataFeed] of the current progress step as a human readable string, and updates to that string. */
|
||||
val progressTrackerStepAndUpdates: DataFeed<String, String>?,
|
||||
/** An [InvocationContext] describing why and by whom the flow was started. */
|
||||
@ -76,7 +76,8 @@ sealed class StateMachineUpdate {
|
||||
// DOCSTART 1
|
||||
/**
|
||||
* Data class containing information about the scheduled network parameters update. The info is emitted every time node
|
||||
* receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed] and [CordaRPCOps.acceptNewNetworkParameters].
|
||||
* receives network map with [ParametersUpdate] which wasn't seen before. For more information see: [CordaRPCOps.networkParametersFeed]
|
||||
* and [CordaRPCOps.acceptNewNetworkParameters].
|
||||
* @property hash new [NetworkParameters] hash
|
||||
* @property parameters new [NetworkParameters] data structure
|
||||
* @property description description of the update
|
||||
@ -96,12 +97,6 @@ data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRun
|
||||
|
||||
/** RPC operations that the node exposes to clients. */
|
||||
interface CordaRPCOps : RPCOps {
|
||||
/**
|
||||
* Returns the RPC protocol version, which is the same the node's Platform Version. Exists since version 1 so guaranteed
|
||||
* to be present.
|
||||
*/
|
||||
override val protocolVersion: Int get() = nodeInfo().platformVersion
|
||||
|
||||
/** Returns a list of currently in-progress state machine infos. */
|
||||
fun stateMachinesSnapshot(): List<StateMachineInfo>
|
||||
|
||||
@ -233,6 +228,9 @@ interface CordaRPCOps : RPCOps {
|
||||
@RPCReturnsObservables
|
||||
fun networkMapFeed(): DataFeed<List<NodeInfo>, NetworkMapCache.MapChange>
|
||||
|
||||
/** Returns the network parameters the node is operating under. */
|
||||
val networkParameters: NetworkParameters
|
||||
|
||||
/**
|
||||
* Returns [DataFeed] object containing information on currently scheduled parameters update (null if none are currently scheduled)
|
||||
* and observable with future update events. Any update that occurs before the deadline automatically cancels the current one.
|
||||
@ -406,38 +404,20 @@ interface CordaRPCOps : RPCOps {
|
||||
* This does not wait for flows to be completed.
|
||||
*/
|
||||
fun shutdown()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a [DataFeed] that keeps track on the count of pending flows.
|
||||
*/
|
||||
fun CordaRPCOps.pendingFlowsCount(): DataFeed<Int, Pair<Int, Int>> {
|
||||
/**
|
||||
* Shuts the node down. Returns immediately.
|
||||
* @param drainPendingFlows whether the node will wait for pending flows to be completed before exiting. While draining, new flows from RPC will be rejected.
|
||||
*/
|
||||
fun terminate(drainPendingFlows: Boolean = false)
|
||||
|
||||
val stateMachineState = stateMachinesFeed()
|
||||
var pendingFlowsCount = stateMachineState.snapshot.size
|
||||
var completedFlowsCount = 0
|
||||
val updates = PublishSubject.create<Pair<Int, Int>>()
|
||||
stateMachineState
|
||||
.updates
|
||||
.doOnNext { update ->
|
||||
when (update) {
|
||||
is StateMachineUpdate.Added -> {
|
||||
pendingFlowsCount++
|
||||
updates.onNext(completedFlowsCount to pendingFlowsCount)
|
||||
}
|
||||
is StateMachineUpdate.Removed -> {
|
||||
completedFlowsCount++
|
||||
updates.onNext(completedFlowsCount to pendingFlowsCount)
|
||||
if (completedFlowsCount == pendingFlowsCount) {
|
||||
updates.onCompleted()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.subscribe()
|
||||
if (completedFlowsCount == 0) {
|
||||
updates.onCompleted()
|
||||
}
|
||||
return DataFeed(pendingFlowsCount, updates)
|
||||
/**
|
||||
* Returns whether the node is waiting for pending flows to complete before shutting down.
|
||||
* Disabling draining mode cancels this state.
|
||||
*
|
||||
* @return whether the node will shutdown when the pending flows count reaches zero.
|
||||
*/
|
||||
fun isWaitingForShutdown(): Boolean
|
||||
}
|
||||
|
||||
inline fun <reified T : ContractState> CordaRPCOps.vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
|
||||
|
@ -28,4 +28,4 @@ interface AppServiceHub : ServiceHub {
|
||||
* TODO it is assumed here that the flow object has an appropriate classloader.
|
||||
*/
|
||||
fun <T> startTrackedFlow(flow: FlowLogic<T>): FlowProgressHandle<T>
|
||||
}
|
||||
}
|
||||
|
@ -140,4 +140,4 @@ interface IdentityService {
|
||||
fun partiesFromName(query: String, exactMatch: Boolean): Set<Party>
|
||||
}
|
||||
|
||||
class UnknownAnonymousPartyException(msg: String) : CordaException(msg)
|
||||
class UnknownAnonymousPartyException(message: String) : CordaException(message)
|
||||
|
@ -160,7 +160,7 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
val notary: AbstractParty?,
|
||||
val lockId: String?,
|
||||
val lockUpdateTime: Instant?,
|
||||
val isRelevant: Vault.RelevancyStatus?
|
||||
val relevancyStatus: Vault.RelevancyStatus?
|
||||
) {
|
||||
constructor(ref: StateRef,
|
||||
contractStateClassName: String,
|
||||
|
@ -73,7 +73,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
|
||||
abstract class CommonQueryCriteria : QueryCriteria() {
|
||||
abstract val status: Vault.StateStatus
|
||||
open val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
open val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
abstract val contractStateTypes: Set<Class<out ContractState>>?
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
return parser.parseCriteria(this)
|
||||
@ -90,7 +90,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
val notary: List<AbstractParty>? = null,
|
||||
val softLockingCondition: SoftLockingCondition? = null,
|
||||
val timeCondition: TimeCondition? = null,
|
||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
) : CommonQueryCriteria() {
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
super.visit(parser)
|
||||
@ -125,15 +125,15 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
val externalId: List<String>? = null,
|
||||
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
) : CommonQueryCriteria() {
|
||||
constructor(
|
||||
participants: List<AbstractParty>? = null,
|
||||
linearId: List<UniqueIdentifier>? = null,
|
||||
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||
isRelevant: Vault.RelevancyStatus
|
||||
) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, isRelevant)
|
||||
relevancyStatus: Vault.RelevancyStatus
|
||||
) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, relevancyStatus)
|
||||
|
||||
constructor(
|
||||
participants: List<AbstractParty>? = null,
|
||||
@ -175,7 +175,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
val issuerRef: List<OpaqueBytes>? = null,
|
||||
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
) : CommonQueryCriteria() {
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
super.visit(parser)
|
||||
@ -215,7 +215,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
val expression: CriteriaExpression<L, Boolean>,
|
||||
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
) : CommonQueryCriteria() {
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
super.visit(parser)
|
||||
|
@ -5,6 +5,10 @@ package net.corda.core.node.services.vault
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.internal.declaredField
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.node.services.vault.CollectionOperator.*
|
||||
import net.corda.core.node.services.vault.ColumnPredicate.*
|
||||
import net.corda.core.node.services.vault.EqualityComparisonOperator.*
|
||||
import net.corda.core.node.services.vault.LikenessOperator.*
|
||||
import net.corda.core.schemas.PersistentState
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import java.lang.reflect.Field
|
||||
@ -24,7 +28,9 @@ enum class BinaryLogicalOperator : Operator {
|
||||
|
||||
enum class EqualityComparisonOperator : Operator {
|
||||
EQUAL,
|
||||
NOT_EQUAL
|
||||
NOT_EQUAL,
|
||||
EQUAL_IGNORE_CASE,
|
||||
NOT_EQUAL_IGNORE_CASE
|
||||
}
|
||||
|
||||
enum class BinaryComparisonOperator : Operator {
|
||||
@ -41,12 +47,16 @@ enum class NullOperator : Operator {
|
||||
|
||||
enum class LikenessOperator : Operator {
|
||||
LIKE,
|
||||
NOT_LIKE
|
||||
NOT_LIKE,
|
||||
LIKE_IGNORE_CASE,
|
||||
NOT_LIKE_IGNORE_CASE
|
||||
}
|
||||
|
||||
enum class CollectionOperator : Operator {
|
||||
IN,
|
||||
NOT_IN
|
||||
NOT_IN,
|
||||
IN_IGNORE_CASE,
|
||||
NOT_IN_IGNORE_CASE
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
@ -251,27 +261,45 @@ object Builder {
|
||||
fun <R : Comparable<R>> Field.comparePredicate(operator: BinaryComparisonOperator, value: R) = info().comparePredicate(operator, value)
|
||||
fun <R : Comparable<R>> FieldInfo.comparePredicate(operator: BinaryComparisonOperator, value: R) = predicate(compare(operator, value))
|
||||
|
||||
fun <O, R> KProperty1<O, R?>.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value))
|
||||
fun <O, R> KProperty1<O, R?>.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value))
|
||||
@JvmOverloads
|
||||
fun <O, R> KProperty1<O, R?>.equal(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
|
||||
|
||||
@JvmOverloads
|
||||
fun <O, R> KProperty1<O, R?>.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.notEqual(value, exactMatch))
|
||||
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.lessThan(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN, value)
|
||||
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.lessThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value)
|
||||
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.greaterThan(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN, value)
|
||||
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.greaterThanOrEqual(value: R) = comparePredicate(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value)
|
||||
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to))
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.`in`(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection))
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.notIn(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection))
|
||||
|
||||
@JvmOverloads
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.`in`(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.`in`(collection, exactMatch))
|
||||
|
||||
@JvmOverloads
|
||||
fun <O, R : Comparable<R>> KProperty1<O, R?>.notIn(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.notIn(collection, exactMatch))
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||
fun <R> Field.equal(value: R) = info().equal(value)
|
||||
@JvmStatic
|
||||
fun <R> FieldInfo.equal(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value))
|
||||
fun <R> Field.equal(value: R, exactMatch: Boolean = true) = info().equal(value, exactMatch)
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||
fun <R> Field.notEqual(value: R) = info().notEqual(value)
|
||||
@JvmOverloads
|
||||
fun <R> FieldInfo.equal(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
|
||||
|
||||
@JvmStatic
|
||||
fun <R> FieldInfo.notEqual(value: R) = predicate(ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value))
|
||||
@JvmOverloads
|
||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||
fun <R> Field.notEqual(value: R, exactMatch: Boolean = true) = info().notEqual(value, exactMatch)
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <R> FieldInfo.notEqual(value: R, exactMatch: Boolean = true) = predicate(Builder.equal(value, exactMatch))
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||
@ -304,44 +332,77 @@ object Builder {
|
||||
fun <R : Comparable<R>> FieldInfo.between(from: R, to: R) = predicate(ColumnPredicate.Between(from, to))
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||
fun <R : Comparable<R>> Field.`in`(collection: Collection<R>) = info().`in`(collection)
|
||||
fun <R : Comparable<R>> FieldInfo.`in`(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection))
|
||||
fun <R : Comparable<R>> Field.`in`(collection: Collection<R>, exactMatch: Boolean = true) = info().`in`(collection, exactMatch)
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||
fun <R : Comparable<R>> Field.notIn(collection: Collection<R>) = info().notIn(collection)
|
||||
@JvmStatic
|
||||
fun <R : Comparable<R>> FieldInfo.notIn(collection: Collection<R>) = predicate(ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection))
|
||||
@JvmOverloads
|
||||
fun <R : Comparable<R>> FieldInfo.`in`(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.`in`(collection, exactMatch))
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||
fun <R : Comparable<R>> Field.notIn(collection: Collection<R>, exactMatch: Boolean = true) = info().notIn(collection, exactMatch)
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun <R : Comparable<R>> FieldInfo.notIn(collection: Collection<R>, exactMatch: Boolean = true) = predicate(Builder.notIn(collection, exactMatch))
|
||||
|
||||
@JvmOverloads
|
||||
fun <R> equal(value: R, exactMatch: Boolean = true) = EqualityComparison(if (exactMatch) EQUAL else EQUAL_IGNORE_CASE, value)
|
||||
|
||||
@JvmOverloads
|
||||
fun <R> notEqual(value: R, exactMatch: Boolean = true) = EqualityComparison(if (exactMatch) NOT_EQUAL else NOT_EQUAL_IGNORE_CASE, value)
|
||||
|
||||
fun <R> equal(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.EQUAL, value)
|
||||
fun <R> notEqual(value: R) = ColumnPredicate.EqualityComparison(EqualityComparisonOperator.NOT_EQUAL, value)
|
||||
fun <R : Comparable<R>> lessThan(value: R) = compare(BinaryComparisonOperator.LESS_THAN, value)
|
||||
|
||||
fun <R : Comparable<R>> lessThanOrEqual(value: R) = compare(BinaryComparisonOperator.LESS_THAN_OR_EQUAL, value)
|
||||
|
||||
fun <R : Comparable<R>> greaterThan(value: R) = compare(BinaryComparisonOperator.GREATER_THAN, value)
|
||||
|
||||
fun <R : Comparable<R>> greaterThanOrEqual(value: R) = compare(BinaryComparisonOperator.GREATER_THAN_OR_EQUAL, value)
|
||||
|
||||
fun <R : Comparable<R>> between(from: R, to: R) = ColumnPredicate.Between(from, to)
|
||||
fun <R : Comparable<R>> `in`(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.IN, collection)
|
||||
fun <R : Comparable<R>> notIn(collection: Collection<R>) = ColumnPredicate.CollectionExpression(CollectionOperator.NOT_IN, collection)
|
||||
fun like(string: String) = ColumnPredicate.Likeness(LikenessOperator.LIKE, string)
|
||||
fun notLike(string: String) = ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string)
|
||||
|
||||
@JvmOverloads
|
||||
fun <R : Comparable<R>> `in`(collection: Collection<R>, exactMatch: Boolean = true) = CollectionExpression(if (exactMatch) IN else IN_IGNORE_CASE, collection)
|
||||
|
||||
@JvmOverloads
|
||||
fun <R : Comparable<R>> notIn(collection: Collection<R>, exactMatch: Boolean = true) = CollectionExpression(if (exactMatch) NOT_IN else NOT_IN_IGNORE_CASE, collection)
|
||||
|
||||
@JvmOverloads
|
||||
fun like(string: String, exactMatch: Boolean = true) = Likeness(if (exactMatch) LIKE else LIKE_IGNORE_CASE, string)
|
||||
|
||||
@JvmOverloads
|
||||
fun notLike(string: String, exactMatch: Boolean = true) = Likeness(if (exactMatch) NOT_LIKE else NOT_LIKE_IGNORE_CASE, string)
|
||||
|
||||
fun <R> isNull() = ColumnPredicate.NullExpression<R>(NullOperator.IS_NULL)
|
||||
fun <R> isNotNull() = ColumnPredicate.NullExpression<R>(NullOperator.NOT_NULL)
|
||||
|
||||
@JvmOverloads
|
||||
fun <O> KProperty1<O, String?>.like(string: String, exactMatch: Boolean = true) = predicate(Builder.like(string, exactMatch))
|
||||
|
||||
fun <O> KProperty1<O, String?>.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string))
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||
fun Field.like(string: String) = info().like(string)
|
||||
@JvmStatic
|
||||
fun FieldInfo.like(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.LIKE, string))
|
||||
fun Field.like(string: String, exactMatch: Boolean = true) = info().like(string, exactMatch)
|
||||
|
||||
fun <O> KProperty1<O, String?>.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string))
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun FieldInfo.like(string: String, exactMatch: Boolean = true) = predicate(Builder.like(string, exactMatch))
|
||||
|
||||
@JvmOverloads
|
||||
fun <O> KProperty1<O, String?>.notLike(string: String, exactMatch: Boolean = true) = predicate(Builder.notLike(string, exactMatch))
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
@Deprecated("Does not support fields from a MappedSuperclass. Use equivalent on a FieldInfo.")
|
||||
fun Field.notLike(string: String) = info().notLike(string)
|
||||
fun Field.notLike(string: String, exactMatch: Boolean = true) = info().notLike(string, exactMatch)
|
||||
|
||||
@JvmStatic
|
||||
fun FieldInfo.notLike(string: String) = predicate(ColumnPredicate.Likeness(LikenessOperator.NOT_LIKE, string))
|
||||
@JvmOverloads
|
||||
fun FieldInfo.notLike(string: String, exactMatch: Boolean = true) = predicate(Builder.notLike(string, exactMatch))
|
||||
|
||||
fun <O, R> KProperty1<O, R?>.isNull() = predicate(ColumnPredicate.NullExpression(NullOperator.IS_NULL))
|
||||
@JvmStatic
|
||||
|
@ -207,7 +207,13 @@ interface SerializationContext {
|
||||
* The use case that we are serializing for, since it influences the implementations chosen.
|
||||
*/
|
||||
@KeepForDJVM
|
||||
enum class UseCase { P2P, RPCServer, RPCClient, Storage, Checkpoint, Testing }
|
||||
enum class UseCase {
|
||||
P2P,
|
||||
RPCServer,
|
||||
RPCClient,
|
||||
Storage,
|
||||
Testing
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -230,7 +236,6 @@ object SerializationDefaults {
|
||||
@DeleteForDJVM val RPC_SERVER_CONTEXT get() = effectiveSerializationEnv.rpcServerContext
|
||||
@DeleteForDJVM val RPC_CLIENT_CONTEXT get() = effectiveSerializationEnv.rpcClientContext
|
||||
@DeleteForDJVM val STORAGE_CONTEXT get() = effectiveSerializationEnv.storageContext
|
||||
@DeleteForDJVM val CHECKPOINT_CONTEXT get() = effectiveSerializationEnv.checkpointContext
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -0,0 +1,198 @@
|
||||
package net.corda.core.serialization.internal
|
||||
|
||||
import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.sequence
|
||||
import java.io.NotSerializableException
|
||||
|
||||
|
||||
object CheckpointSerializationDefaults {
|
||||
@DeleteForDJVM
|
||||
val CHECKPOINT_CONTEXT get() = effectiveSerializationEnv.checkpointContext
|
||||
val CHECKPOINT_SERIALIZATION_FACTORY get() = effectiveSerializationEnv.checkpointSerializationFactory
|
||||
}
|
||||
|
||||
/**
|
||||
* A class for serializing and deserializing objects at checkpoints, using Kryo serialization.
|
||||
*/
|
||||
@KeepForDJVM
|
||||
class CheckpointSerializationFactory(
|
||||
private val scheme: CheckpointSerializationScheme
|
||||
) {
|
||||
|
||||
val defaultContext: CheckpointSerializationContext get() = _currentContext.get() ?: effectiveSerializationEnv.checkpointContext
|
||||
|
||||
private val creator: List<StackTraceElement> = Exception().stackTrace.asList()
|
||||
|
||||
/**
|
||||
* Deserialize the bytes in to an object, using the prefixed bytes to determine the format.
|
||||
*
|
||||
* @param byteSequence The bytes to deserialize, including a format header prefix.
|
||||
* @param clazz The class or superclass or the object to be deserialized, or [Any] or [Object] if unknown.
|
||||
* @param context A context that configures various parameters to deserialization.
|
||||
*/
|
||||
fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: CheckpointSerializationContext): T {
|
||||
return withCurrentContext(context) { scheme.deserialize(byteSequence, clazz, context) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an object to bytes using the preferred serialization format version from the context.
|
||||
*
|
||||
* @param obj The object to be serialized.
|
||||
* @param context A context that configures various parameters to serialization, including the serialization format version.
|
||||
*/
|
||||
fun <T : Any> serialize(obj: T, context: CheckpointSerializationContext): SerializedBytes<T> {
|
||||
return withCurrentContext(context) { scheme.serialize(obj, context) }
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "${this.javaClass.name} scheme=$scheme ${creator.joinToString("\n")}"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other is CheckpointSerializationFactory && other.scheme == this.scheme
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = scheme.hashCode()
|
||||
|
||||
private val _currentContext = ThreadLocal<CheckpointSerializationContext?>()
|
||||
|
||||
/**
|
||||
* Change the current context inside the block to that supplied.
|
||||
*/
|
||||
fun <T> withCurrentContext(context: CheckpointSerializationContext?, block: () -> T): T {
|
||||
val priorContext = _currentContext.get()
|
||||
if (context != null) _currentContext.set(context)
|
||||
try {
|
||||
return block()
|
||||
} finally {
|
||||
if (context != null) _currentContext.set(priorContext)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val defaultFactory: CheckpointSerializationFactory get() = effectiveSerializationEnv.checkpointSerializationFactory
|
||||
}
|
||||
}
|
||||
|
||||
@KeepForDJVM
|
||||
@DoNotImplement
|
||||
interface CheckpointSerializationScheme {
|
||||
@Throws(NotSerializableException::class)
|
||||
fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: CheckpointSerializationContext): T
|
||||
|
||||
@Throws(NotSerializableException::class)
|
||||
fun <T : Any> serialize(obj: T, context: CheckpointSerializationContext): SerializedBytes<T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters to checkpoint serialization and deserialization.
|
||||
*/
|
||||
@KeepForDJVM
|
||||
@DoNotImplement
|
||||
interface CheckpointSerializationContext {
|
||||
/**
|
||||
* If non-null, apply this encoding (typically compression) when serializing.
|
||||
*/
|
||||
val encoding: SerializationEncoding?
|
||||
/**
|
||||
* The class loader to use for deserialization.
|
||||
*/
|
||||
val deserializationClassLoader: ClassLoader
|
||||
/**
|
||||
* A whitelist that contains (mostly for security purposes) which classes can be serialized and deserialized.
|
||||
*/
|
||||
val whitelist: ClassWhitelist
|
||||
/**
|
||||
* A whitelist that determines (mostly for security purposes) whether a particular encoding may be used when deserializing.
|
||||
*/
|
||||
val encodingWhitelist: EncodingWhitelist
|
||||
/**
|
||||
* A map of any addition properties specific to the particular use case.
|
||||
*/
|
||||
val properties: Map<Any, Any>
|
||||
/**
|
||||
* Duplicate references to the same object preserved in the wire format and when deserialized when this is true,
|
||||
* otherwise they appear as new copies of the object.
|
||||
*/
|
||||
val objectReferencesEnabled: Boolean
|
||||
|
||||
/**
|
||||
* Helper method to return a new context based on this context with the property added.
|
||||
*/
|
||||
fun withProperty(property: Any, value: Any): CheckpointSerializationContext
|
||||
|
||||
/**
|
||||
* Helper method to return a new context based on this context with object references disabled.
|
||||
*/
|
||||
fun withoutReferences(): CheckpointSerializationContext
|
||||
|
||||
/**
|
||||
* Helper method to return a new context based on this context with the deserialization class loader changed.
|
||||
*/
|
||||
fun withClassLoader(classLoader: ClassLoader): CheckpointSerializationContext
|
||||
|
||||
/**
|
||||
* Helper method to return a new context based on this context with the appropriate class loader constructed from the passed attachment identifiers.
|
||||
* (Requires the attachment storage to have been enabled).
|
||||
*/
|
||||
@Throws(MissingAttachmentsException::class)
|
||||
fun withAttachmentsClassLoader(attachmentHashes: List<SecureHash>): CheckpointSerializationContext
|
||||
|
||||
/**
|
||||
* Helper method to return a new context based on this context with the given class specifically whitelisted.
|
||||
*/
|
||||
fun withWhitelisted(clazz: Class<*>): CheckpointSerializationContext
|
||||
|
||||
/**
|
||||
* A shallow copy of this context but with the given (possibly null) encoding.
|
||||
*/
|
||||
fun withEncoding(encoding: SerializationEncoding?): CheckpointSerializationContext
|
||||
|
||||
/**
|
||||
* A shallow copy of this context but with the given encoding whitelist.
|
||||
*/
|
||||
fun withEncodingWhitelist(encodingWhitelist: EncodingWhitelist): CheckpointSerializationContext
|
||||
}
|
||||
|
||||
/*
|
||||
* The following extension methods are disambiguated from the AMQP-serialization methods by requiring that an
|
||||
* explicit [CheckpointSerializationContext] parameter be provided.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Convenience extension method for deserializing a ByteSequence, utilising the default factory.
|
||||
*/
|
||||
inline fun <reified T : Any> ByteSequence.checkpointDeserialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory,
|
||||
context: CheckpointSerializationContext): T {
|
||||
return serializationFactory.deserialize(this, T::class.java, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience extension method for deserializing SerializedBytes with type matching, utilising the default factory.
|
||||
*/
|
||||
inline fun <reified T : Any> SerializedBytes<T>.checkpointDeserialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory,
|
||||
context: CheckpointSerializationContext): T {
|
||||
return serializationFactory.deserialize(this, T::class.java, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience extension method for deserializing a ByteArray, utilising the default factory.
|
||||
*/
|
||||
inline fun <reified T : Any> ByteArray.checkpointDeserialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory,
|
||||
context: CheckpointSerializationContext): T {
|
||||
require(isNotEmpty()) { "Empty bytes" }
|
||||
return this.sequence().checkpointDeserialize(serializationFactory, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience extension method for serializing an object of type T, utilising the default factory.
|
||||
*/
|
||||
fun <T : Any> T.checkpointSerialize(serializationFactory: CheckpointSerializationFactory = CheckpointSerializationFactory.defaultFactory,
|
||||
context: CheckpointSerializationContext): SerializedBytes<T> {
|
||||
return serializationFactory.serialize(this, context)
|
||||
}
|
@ -12,11 +12,12 @@ import net.corda.core.serialization.SerializationFactory
|
||||
@KeepForDJVM
|
||||
interface SerializationEnvironment {
|
||||
val serializationFactory: SerializationFactory
|
||||
val checkpointSerializationFactory: CheckpointSerializationFactory
|
||||
val p2pContext: SerializationContext
|
||||
val rpcServerContext: SerializationContext
|
||||
val rpcClientContext: SerializationContext
|
||||
val storageContext: SerializationContext
|
||||
val checkpointContext: SerializationContext
|
||||
val checkpointContext: CheckpointSerializationContext
|
||||
}
|
||||
|
||||
@KeepForDJVM
|
||||
@ -26,18 +27,21 @@ open class SerializationEnvironmentImpl(
|
||||
rpcServerContext: SerializationContext? = null,
|
||||
rpcClientContext: SerializationContext? = null,
|
||||
storageContext: SerializationContext? = null,
|
||||
checkpointContext: SerializationContext? = null) : SerializationEnvironment {
|
||||
checkpointContext: CheckpointSerializationContext? = null,
|
||||
checkpointSerializationFactory: CheckpointSerializationFactory? = null) : SerializationEnvironment {
|
||||
// Those that are passed in as null are never inited:
|
||||
override lateinit var rpcServerContext: SerializationContext
|
||||
override lateinit var rpcClientContext: SerializationContext
|
||||
override lateinit var storageContext: SerializationContext
|
||||
override lateinit var checkpointContext: SerializationContext
|
||||
override lateinit var checkpointContext: CheckpointSerializationContext
|
||||
override lateinit var checkpointSerializationFactory: CheckpointSerializationFactory
|
||||
|
||||
init {
|
||||
rpcServerContext?.let { this.rpcServerContext = it }
|
||||
rpcClientContext?.let { this.rpcClientContext = it }
|
||||
storageContext?.let { this.storageContext = it }
|
||||
checkpointContext?.let { this.checkpointContext = it }
|
||||
checkpointSerializationFactory?.let { this.checkpointSerializationFactory = it }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,12 +3,15 @@ package net.corda.core.transactions
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.contracts.ComponentGroupEnum.*
|
||||
import net.corda.core.crypto.*
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import java.security.PublicKey
|
||||
import java.util.function.Predicate
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Implemented by [WireTransaction] and [FilteredTransaction]. A TraversableTransaction allows you to iterate
|
||||
@ -18,29 +21,29 @@ import java.util.function.Predicate
|
||||
*/
|
||||
abstract class TraversableTransaction(open val componentGroups: List<ComponentGroup>) : CoreTransaction() {
|
||||
/** Hashes of the ZIP/JAR files that are needed to interpret the contents of this wire transaction. */
|
||||
val attachments: List<SecureHash> = deserialiseComponentGroup(ComponentGroupEnum.ATTACHMENTS_GROUP, { SerializedBytes<SecureHash>(it).deserialize() })
|
||||
val attachments: List<SecureHash> = deserialiseComponentGroup(SecureHash::class, ATTACHMENTS_GROUP)
|
||||
|
||||
/** Pointers to the input states on the ledger, identified by (tx identity hash, output index). */
|
||||
override val inputs: List<StateRef> = deserialiseComponentGroup(ComponentGroupEnum.INPUTS_GROUP, { SerializedBytes<StateRef>(it).deserialize() })
|
||||
override val inputs: List<StateRef> = deserialiseComponentGroup(StateRef::class, INPUTS_GROUP)
|
||||
|
||||
/** Pointers to reference states, identified by (tx identity hash, output index). */
|
||||
override val references: List<StateRef> = deserialiseComponentGroup(ComponentGroupEnum.REFERENCES_GROUP, { SerializedBytes<StateRef>(it).deserialize() })
|
||||
override val references: List<StateRef> = deserialiseComponentGroup(StateRef::class, REFERENCES_GROUP)
|
||||
|
||||
override val outputs: List<TransactionState<ContractState>> = deserialiseComponentGroup(ComponentGroupEnum.OUTPUTS_GROUP, { SerializedBytes<TransactionState<ContractState>>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
|
||||
override val outputs: List<TransactionState<ContractState>> = deserialiseComponentGroup(TransactionState::class, OUTPUTS_GROUP, attachmentsContext = true)
|
||||
|
||||
/** Ordered list of ([CommandData], [PublicKey]) pairs that instruct the contracts what to do. */
|
||||
val commands: List<Command<*>> = deserialiseCommands()
|
||||
|
||||
override val notary: Party? = let {
|
||||
val notaries: List<Party> = deserialiseComponentGroup(ComponentGroupEnum.NOTARY_GROUP, { SerializedBytes<Party>(it).deserialize() })
|
||||
val notaries: List<Party> = deserialiseComponentGroup(Party::class, NOTARY_GROUP)
|
||||
check(notaries.size <= 1) { "Invalid Transaction. More than 1 notary party detected." }
|
||||
if (notaries.isNotEmpty()) notaries[0] else null
|
||||
notaries.firstOrNull()
|
||||
}
|
||||
|
||||
val timeWindow: TimeWindow? = let {
|
||||
val timeWindows: List<TimeWindow> = deserialiseComponentGroup(ComponentGroupEnum.TIMEWINDOW_GROUP, { SerializedBytes<TimeWindow>(it).deserialize() })
|
||||
val timeWindows: List<TimeWindow> = deserialiseComponentGroup(TimeWindow::class, TIMEWINDOW_GROUP)
|
||||
check(timeWindows.size <= 1) { "Invalid Transaction. More than 1 time-window detected." }
|
||||
if (timeWindows.isNotEmpty()) timeWindows[0] else null
|
||||
timeWindows.firstOrNull()
|
||||
}
|
||||
|
||||
/**
|
||||
@ -63,12 +66,16 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
||||
}
|
||||
|
||||
// Helper function to return a meaningful exception if deserialisation of a component fails.
|
||||
private fun <T> deserialiseComponentGroup(groupEnum: ComponentGroupEnum, deserialiseBody: (ByteArray) -> T): List<T> {
|
||||
private fun <T : Any> deserialiseComponentGroup(clazz: KClass<T>,
|
||||
groupEnum: ComponentGroupEnum,
|
||||
attachmentsContext: Boolean = false): List<T> {
|
||||
val factory = SerializationFactory.defaultFactory
|
||||
val context = factory.defaultContext.let { if (attachmentsContext) it.withAttachmentsClassLoader(attachments) else it }
|
||||
val group = componentGroups.firstOrNull { it.groupIndex == groupEnum.ordinal }
|
||||
return if (group != null && group.components.isNotEmpty()) {
|
||||
group.components.mapIndexed { internalIndex, component ->
|
||||
try {
|
||||
deserialiseBody(component.bytes)
|
||||
factory.deserialize(component, clazz.java, context)
|
||||
} catch (e: MissingAttachmentsException) {
|
||||
throw e
|
||||
} catch (e: Exception) {
|
||||
@ -87,11 +94,13 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
||||
// TODO: we could avoid deserialising unrelated signers.
|
||||
// However, current approach ensures the transaction is not malformed
|
||||
// and it will throw if any of the signers objects is not List of public keys).
|
||||
val signersList = deserialiseComponentGroup(ComponentGroupEnum.SIGNERS_GROUP, { SerializedBytes<List<PublicKey>>(it).deserialize() })
|
||||
val commandDataList = deserialiseComponentGroup(ComponentGroupEnum.COMMANDS_GROUP, { SerializedBytes<CommandData>(it).deserialize(context = SerializationFactory.defaultFactory.defaultContext.withAttachmentsClassLoader(attachments)) })
|
||||
val group = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal }
|
||||
val signersList: List<List<PublicKey>> = uncheckedCast(deserialiseComponentGroup(List::class, SIGNERS_GROUP))
|
||||
val commandDataList: List<CommandData> = deserialiseComponentGroup(CommandData::class, COMMANDS_GROUP, attachmentsContext = true)
|
||||
val group = componentGroups.firstOrNull { it.groupIndex == COMMANDS_GROUP.ordinal }
|
||||
return if (group is FilteredComponentGroup) {
|
||||
check(commandDataList.size <= signersList.size) { "Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects" }
|
||||
check(commandDataList.size <= signersList.size) {
|
||||
"Invalid Transaction. Less Signers (${signersList.size}) than CommandData (${commandDataList.size}) objects"
|
||||
}
|
||||
val componentHashes = group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }
|
||||
val leafIndices = componentHashes.map { group.partialMerkleTree.leafIndex(it) }
|
||||
if (leafIndices.isNotEmpty())
|
||||
@ -100,7 +109,9 @@ abstract class TraversableTransaction(open val componentGroups: List<ComponentGr
|
||||
} else {
|
||||
// It is a WireTransaction
|
||||
// or a FilteredTransaction with no Commands (in which case group is null).
|
||||
check(commandDataList.size == signersList.size) { "Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match" }
|
||||
check(commandDataList.size == signersList.size) {
|
||||
"Invalid Transaction. Sizes of CommandData (${commandDataList.size}) and Signers (${signersList.size}) do not match"
|
||||
}
|
||||
commandDataList.mapIndexed { index, commandData -> Command(commandData, signersList[index]) }
|
||||
}
|
||||
}
|
||||
@ -145,47 +156,47 @@ class FilteredTransaction internal constructor(
|
||||
var signersIncluded = false
|
||||
|
||||
fun <T : Any> filter(t: T, componentGroupIndex: Int, internalIndex: Int) {
|
||||
if (filtering.test(t)) {
|
||||
val group = filteredSerialisedComponents[componentGroupIndex]
|
||||
// Because the filter passed, we know there is a match. We also use first Vs single as the init function
|
||||
// of WireTransaction ensures there are no duplicated groups.
|
||||
val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex]
|
||||
if (group == null) {
|
||||
// As all of the helper Map structures, like availableComponentNonces, availableComponentHashes
|
||||
// and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be
|
||||
// a match on Map.get ensuring it will never return null.
|
||||
filteredSerialisedComponents[componentGroupIndex] = mutableListOf(serialisedComponent)
|
||||
filteredComponentNonces[componentGroupIndex] = mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
|
||||
filteredComponentHashes[componentGroupIndex] = mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
|
||||
} else {
|
||||
group.add(serialisedComponent)
|
||||
// If the group[componentGroupIndex] existed, then we guarantee that
|
||||
// filteredComponentNonces[componentGroupIndex] and filteredComponentHashes[componentGroupIndex] are not null.
|
||||
filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
|
||||
filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
|
||||
}
|
||||
// If at least one command is visible, then all command-signers should be visible as well.
|
||||
// This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details.
|
||||
if (componentGroupIndex == ComponentGroupEnum.COMMANDS_GROUP.ordinal && !signersIncluded) {
|
||||
signersIncluded = true
|
||||
val signersGroupIndex = ComponentGroupEnum.SIGNERS_GROUP.ordinal
|
||||
// There exist commands, thus the signers group is not empty.
|
||||
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
|
||||
filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList()
|
||||
filteredComponentNonces[signersGroupIndex] = wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()
|
||||
filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()
|
||||
}
|
||||
if (!filtering.test(t)) return
|
||||
|
||||
val group = filteredSerialisedComponents[componentGroupIndex]
|
||||
// Because the filter passed, we know there is a match. We also use first Vs single as the init function
|
||||
// of WireTransaction ensures there are no duplicated groups.
|
||||
val serialisedComponent = wtx.componentGroups.first { it.groupIndex == componentGroupIndex }.components[internalIndex]
|
||||
if (group == null) {
|
||||
// As all of the helper Map structures, like availableComponentNonces, availableComponentHashes
|
||||
// and groupsMerkleRoots, are computed lazily via componentGroups.forEach, there should always be
|
||||
// a match on Map.get ensuring it will never return null.
|
||||
filteredSerialisedComponents[componentGroupIndex] = mutableListOf(serialisedComponent)
|
||||
filteredComponentNonces[componentGroupIndex] = mutableListOf(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
|
||||
filteredComponentHashes[componentGroupIndex] = mutableListOf(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
|
||||
} else {
|
||||
group.add(serialisedComponent)
|
||||
// If the group[componentGroupIndex] existed, then we guarantee that
|
||||
// filteredComponentNonces[componentGroupIndex] and filteredComponentHashes[componentGroupIndex] are not null.
|
||||
filteredComponentNonces[componentGroupIndex]!!.add(wtx.availableComponentNonces[componentGroupIndex]!![internalIndex])
|
||||
filteredComponentHashes[componentGroupIndex]!!.add(wtx.availableComponentHashes[componentGroupIndex]!![internalIndex])
|
||||
}
|
||||
// If at least one command is visible, then all command-signers should be visible as well.
|
||||
// This is required for visibility purposes, see FilteredTransaction.checkAllCommandsVisible() for more details.
|
||||
if (componentGroupIndex == COMMANDS_GROUP.ordinal && !signersIncluded) {
|
||||
signersIncluded = true
|
||||
val signersGroupIndex = SIGNERS_GROUP.ordinal
|
||||
// There exist commands, thus the signers group is not empty.
|
||||
val signersGroupComponents = wtx.componentGroups.first { it.groupIndex == signersGroupIndex }
|
||||
filteredSerialisedComponents[signersGroupIndex] = signersGroupComponents.components.toMutableList()
|
||||
filteredComponentNonces[signersGroupIndex] = wtx.availableComponentNonces[signersGroupIndex]!!.toMutableList()
|
||||
filteredComponentHashes[signersGroupIndex] = wtx.availableComponentHashes[signersGroupIndex]!!.toMutableList()
|
||||
}
|
||||
}
|
||||
|
||||
fun updateFilteredComponents() {
|
||||
wtx.inputs.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.INPUTS_GROUP.ordinal, internalIndex) }
|
||||
wtx.outputs.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.OUTPUTS_GROUP.ordinal, internalIndex) }
|
||||
wtx.commands.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.COMMANDS_GROUP.ordinal, internalIndex) }
|
||||
wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.ATTACHMENTS_GROUP.ordinal, internalIndex) }
|
||||
if (wtx.notary != null) filter(wtx.notary, ComponentGroupEnum.NOTARY_GROUP.ordinal, 0)
|
||||
if (wtx.timeWindow != null) filter(wtx.timeWindow, ComponentGroupEnum.TIMEWINDOW_GROUP.ordinal, 0)
|
||||
wtx.references.forEachIndexed { internalIndex, it -> filter(it, ComponentGroupEnum.REFERENCES_GROUP.ordinal, internalIndex) }
|
||||
wtx.inputs.forEachIndexed { internalIndex, it -> filter(it, INPUTS_GROUP.ordinal, internalIndex) }
|
||||
wtx.outputs.forEachIndexed { internalIndex, it -> filter(it, OUTPUTS_GROUP.ordinal, internalIndex) }
|
||||
wtx.commands.forEachIndexed { internalIndex, it -> filter(it, COMMANDS_GROUP.ordinal, internalIndex) }
|
||||
wtx.attachments.forEachIndexed { internalIndex, it -> filter(it, ATTACHMENTS_GROUP.ordinal, internalIndex) }
|
||||
if (wtx.notary != null) filter(wtx.notary, NOTARY_GROUP.ordinal, 0)
|
||||
if (wtx.timeWindow != null) filter(wtx.timeWindow, TIMEWINDOW_GROUP.ordinal, 0)
|
||||
wtx.references.forEachIndexed { internalIndex, it -> filter(it, REFERENCES_GROUP.ordinal, internalIndex) }
|
||||
// It is highlighted that because there is no a signers property in TraversableTransaction,
|
||||
// one cannot specifically filter them in or out.
|
||||
// The above is very important to ensure someone won't filter out the signers component group if at least one
|
||||
@ -195,10 +206,17 @@ class FilteredTransaction internal constructor(
|
||||
// we decide to filter and attach this field to a FilteredTransaction.
|
||||
// An example would be to redact certain contract state types, but otherwise leave a transaction alone,
|
||||
// including the unknown new components.
|
||||
wtx.componentGroups.filter { it.groupIndex >= ComponentGroupEnum.values().size }.forEach { componentGroup -> componentGroup.components.forEachIndexed { internalIndex, component -> filter(component, componentGroup.groupIndex, internalIndex) } }
|
||||
wtx.componentGroups
|
||||
.filter { it.groupIndex >= values().size }
|
||||
.forEach { componentGroup -> componentGroup.components.forEachIndexed { internalIndex, component -> filter(component, componentGroup.groupIndex, internalIndex) } }
|
||||
}
|
||||
|
||||
fun createPartialMerkleTree(componentGroupIndex: Int) = PartialMerkleTree.build(MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!), filteredComponentHashes[componentGroupIndex]!!)
|
||||
fun createPartialMerkleTree(componentGroupIndex: Int): PartialMerkleTree {
|
||||
return PartialMerkleTree.build(
|
||||
MerkleTree.getMerkleTree(wtx.availableComponentHashes[componentGroupIndex]!!),
|
||||
filteredComponentHashes[componentGroupIndex]!!
|
||||
)
|
||||
}
|
||||
|
||||
fun createFilteredComponentGroups(): List<FilteredComponentGroup> {
|
||||
updateFilteredComponents()
|
||||
@ -223,8 +241,11 @@ class FilteredTransaction internal constructor(
|
||||
@Throws(FilteredTransactionVerificationException::class)
|
||||
fun verify() {
|
||||
verificationCheck(groupHashes.isNotEmpty()) { "At least one component group hash is required" }
|
||||
// Verify the top level Merkle tree (group hashes are its leaves, including allOnesHash for empty list or null components in WireTransaction).
|
||||
verificationCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { "Top level Merkle tree cannot be verified against transaction's id" }
|
||||
// Verify the top level Merkle tree (group hashes are its leaves, including allOnesHash for empty list or null
|
||||
// components in WireTransaction).
|
||||
verificationCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) {
|
||||
"Top level Merkle tree cannot be verified against transaction's id"
|
||||
}
|
||||
|
||||
// For completely blind verification (no components are included).
|
||||
if (filteredComponentGroups.isEmpty()) return
|
||||
@ -233,8 +254,12 @@ class FilteredTransaction internal constructor(
|
||||
filteredComponentGroups.forEach { (groupIndex, components, nonces, groupPartialTree) ->
|
||||
verificationCheck(groupIndex < groupHashes.size) { "There is no matching component group hash for group $groupIndex" }
|
||||
val groupMerkleRoot = groupHashes[groupIndex]
|
||||
verificationCheck(groupMerkleRoot == PartialMerkleTree.rootAndUsedHashes(groupPartialTree.root, mutableListOf())) { "Partial Merkle tree root and advertised full Merkle tree root for component group $groupIndex do not match" }
|
||||
verificationCheck(groupPartialTree.verify(groupMerkleRoot, components.mapIndexed { index, component -> componentHash(nonces[index], component) })) { "Visible components in group $groupIndex cannot be verified against their partial Merkle tree" }
|
||||
verificationCheck(groupMerkleRoot == PartialMerkleTree.rootAndUsedHashes(groupPartialTree.root, mutableListOf())) {
|
||||
"Partial Merkle tree root and advertised full Merkle tree root for component group $groupIndex do not match"
|
||||
}
|
||||
verificationCheck(groupPartialTree.verify(groupMerkleRoot, components.mapIndexed { index, component -> componentHash(nonces[index], component) })) {
|
||||
"Visible components in group $groupIndex cannot be verified against their partial Merkle tree"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -281,7 +306,9 @@ class FilteredTransaction internal constructor(
|
||||
val groupFullRoot = MerkleTree.getMerkleTree(group.components.mapIndexed { index, component -> componentHash(group.nonces[index], component) }).hash
|
||||
visibilityCheck(groupPartialRoot == groupFullRoot) { "Some components for group ${group.groupIndex} are not visible" }
|
||||
// Verify the top level Merkle tree from groupHashes.
|
||||
visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) { "Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id" }
|
||||
visibilityCheck(MerkleTree.getMerkleTree(groupHashes).hash == id) {
|
||||
"Transaction is malformed. Top level Merkle tree cannot be verified against transaction's id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -296,15 +323,17 @@ class FilteredTransaction internal constructor(
|
||||
*/
|
||||
@Throws(ComponentVisibilityException::class)
|
||||
fun checkCommandVisibility(publicKey: PublicKey) {
|
||||
val commandSigners = componentGroups.firstOrNull { it.groupIndex == ComponentGroupEnum.SIGNERS_GROUP.ordinal }
|
||||
val commandSigners = componentGroups.firstOrNull { it.groupIndex == SIGNERS_GROUP.ordinal }
|
||||
val expectedNumOfCommands = expectedNumOfCommands(publicKey, commandSigners)
|
||||
val receivedForThisKeyNumOfCommands = commands.filter { publicKey in it.signers }.size
|
||||
visibilityCheck(expectedNumOfCommands == receivedForThisKeyNumOfCommands) { "$expectedNumOfCommands commands were expected, but received $receivedForThisKeyNumOfCommands" }
|
||||
visibilityCheck(expectedNumOfCommands == receivedForThisKeyNumOfCommands) {
|
||||
"$expectedNumOfCommands commands were expected, but received $receivedForThisKeyNumOfCommands"
|
||||
}
|
||||
}
|
||||
|
||||
// Function to return number of expected commands to sign.
|
||||
private fun expectedNumOfCommands(publicKey: PublicKey, commandSigners: ComponentGroup?): Int {
|
||||
checkAllComponentsVisible(ComponentGroupEnum.SIGNERS_GROUP)
|
||||
checkAllComponentsVisible(SIGNERS_GROUP)
|
||||
if (commandSigners == null) return 0
|
||||
fun signersKeys (internalIndex: Int, opaqueBytes: OpaqueBytes): List<PublicKey> {
|
||||
try {
|
||||
@ -340,7 +369,10 @@ class FilteredTransaction internal constructor(
|
||||
*/
|
||||
@KeepForDJVM
|
||||
@CordaSerializable
|
||||
data class FilteredComponentGroup(override val groupIndex: Int, override val components: List<OpaqueBytes>, val nonces: List<SecureHash>, val partialMerkleTree: PartialMerkleTree) : ComponentGroup(groupIndex, components) {
|
||||
data class FilteredComponentGroup(override val groupIndex: Int,
|
||||
override val components: List<OpaqueBytes>,
|
||||
val nonces: List<SecureHash>,
|
||||
val partialMerkleTree: PartialMerkleTree) : ComponentGroup(groupIndex, components) {
|
||||
init {
|
||||
check(components.size == nonces.size) { "Size of transaction components and nonces do not match" }
|
||||
}
|
||||
|
@ -33,9 +33,10 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si
|
||||
fun open() = ByteArrayInputStream(_bytes, offset, size)
|
||||
|
||||
/**
|
||||
* Create a sub-sequence, that may be backed by a new byte array.
|
||||
* Create a sub-sequence of this sequence. A copy of the underlying array may be made, if a subclass overrides
|
||||
* [bytes] to do so, as [OpaqueBytes] does.
|
||||
*
|
||||
* @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array.
|
||||
* @param offset The offset within this sequence to start the new sequence. Note: not the offset within the backing array.
|
||||
* @param size The size of the intended sub sequence.
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanBePrivate")
|
||||
@ -43,7 +44,7 @@ sealed class ByteSequence(private val _bytes: ByteArray, val offset: Int, val si
|
||||
require(offset >= 0)
|
||||
require(offset + size <= this.size)
|
||||
// Intentionally use bytes rather than _bytes, to mirror the copy-or-not behaviour of that property.
|
||||
return if (offset == 0 && size == this.size) this else OpaqueBytesSubSequence(bytes, this.offset + offset, size)
|
||||
return if (offset == 0 && size == this.size) this else of(bytes, this.offset + offset, size)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -106,7 +106,7 @@ class CordappSmokeTest {
|
||||
class SendBackInitiatorFlowContext(private val otherPartySession: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
// An initiated flow calling getFlowContext on its initiator will get the context from the session-init
|
||||
// An initiated flow calling getFlowInfo on its initiator will get the context from the session-init
|
||||
val sessionInitContext = otherPartySession.getCounterpartyFlowInfo()
|
||||
otherPartySession.send(sessionInitContext)
|
||||
}
|
||||
|
@ -1,11 +1,14 @@
|
||||
package net.corda.core.flows;
|
||||
|
||||
import net.corda.core.serialization.internal.CheckpointSerializationDefaults;
|
||||
import net.corda.core.serialization.internal.CheckpointSerializationFactory;
|
||||
import net.corda.core.serialization.SerializationDefaults;
|
||||
import net.corda.core.serialization.SerializationFactory;
|
||||
import net.corda.testing.core.SerializationEnvironmentRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
import static net.corda.core.serialization.internal.CheckpointSerializationAPIKt.checkpointSerialize;
|
||||
import static net.corda.core.serialization.SerializationAPIKt.serialize;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
@ -28,10 +31,13 @@ public class SerializationApiInJavaTest {
|
||||
public void enforceSerializationDefaultsApi() {
|
||||
SerializationDefaults defaults = SerializationDefaults.INSTANCE;
|
||||
SerializationFactory factory = defaults.getSERIALIZATION_FACTORY();
|
||||
|
||||
CheckpointSerializationDefaults checkpointDefaults = CheckpointSerializationDefaults.INSTANCE;
|
||||
CheckpointSerializationFactory checkpointSerializationFactory = checkpointDefaults.getCHECKPOINT_SERIALIZATION_FACTORY();
|
||||
serialize("hello", factory, defaults.getP2P_CONTEXT());
|
||||
serialize("hello", factory, defaults.getRPC_SERVER_CONTEXT());
|
||||
serialize("hello", factory, defaults.getRPC_CLIENT_CONTEXT());
|
||||
serialize("hello", factory, defaults.getSTORAGE_CONTEXT());
|
||||
serialize("hello", factory, defaults.getCHECKPOINT_CONTEXT());
|
||||
checkpointSerialize("hello", checkpointSerializationFactory, checkpointDefaults.getCHECKPOINT_CONTEXT());
|
||||
}
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ internal class UseRefState(val linearId: UniqueIdentifier) : FlowLogic<SignedTra
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities.first()
|
||||
val query = QueryCriteria.LinearStateQueryCriteria(
|
||||
linearId = listOf(linearId),
|
||||
isRelevant = Vault.RelevancyStatus.ALL
|
||||
relevancyStatus = Vault.RelevancyStatus.ALL
|
||||
)
|
||||
val referenceState = serviceHub.vaultService.queryBy<ContractState>(query).states.single()
|
||||
return subFlow(FinalityFlow(
|
||||
|
@ -8,7 +8,7 @@ import kotlin.test.assertFailsWith
|
||||
class CertRoleTests {
|
||||
@Test
|
||||
fun `should deserialize valid value`() {
|
||||
val expected = CertRole.INTERMEDIATE_CA
|
||||
val expected = CertRole.DOORMAN_CA
|
||||
val actual = CertRole.getInstance(ASN1Integer(1L))
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
@ -0,0 +1,42 @@
|
||||
package net.corda.core.internal.cordapp
|
||||
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class CordappInfoResolverTest {
|
||||
|
||||
@Before
|
||||
@After
|
||||
fun clearCordappInfoResolver() {
|
||||
CordappInfoResolver.clear()
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun `The correct cordapp resolver is used after calling withCordappResolution`() {
|
||||
val defaultTargetVersion = 222
|
||||
|
||||
CordappInfoResolver.register(listOf(javaClass.name), CordappImpl.Info("test", "test", "2", 3, defaultTargetVersion))
|
||||
assertEquals(defaultTargetVersion, returnCallingTargetVersion())
|
||||
|
||||
val expectedTargetVersion = 555
|
||||
CordappInfoResolver.withCordappInfoResolution( { CordappImpl.Info("foo", "bar", "1", 2, expectedTargetVersion) })
|
||||
{
|
||||
val actualTargetVersion = returnCallingTargetVersion()
|
||||
assertEquals(expectedTargetVersion, actualTargetVersion)
|
||||
}
|
||||
assertEquals(defaultTargetVersion, returnCallingTargetVersion())
|
||||
}
|
||||
|
||||
@Test()
|
||||
fun `When more than one cordapp is registered for the same class, the resolver returns null`() {
|
||||
CordappInfoResolver.register(listOf(javaClass.name), CordappImpl.Info("test", "test", "2", 3, 222))
|
||||
CordappInfoResolver.register(listOf(javaClass.name), CordappImpl.Info("test1", "test1", "1", 2, 456))
|
||||
assertEquals(0, returnCallingTargetVersion())
|
||||
}
|
||||
|
||||
private fun returnCallingTargetVersion(): Int {
|
||||
return CordappInfoResolver.getCorDappInfo()?.targetPlatformVersion ?: 0
|
||||
}
|
||||
}
|
@ -3,9 +3,10 @@ package net.corda.core.utilities
|
||||
import com.esotericsoftware.kryo.KryoException
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.serialization.internal.checkpointDeserialize
|
||||
import net.corda.core.serialization.internal.checkpointSerialize
|
||||
import net.corda.node.serialization.kryo.KRYO_CHECKPOINT_CONTEXT
|
||||
import net.corda.node.serialization.kryo.kryoMagic
|
||||
import net.corda.serialization.internal.SerializationContextImpl
|
||||
import net.corda.serialization.internal.CheckpointSerializationContextImpl
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Rule
|
||||
@ -24,12 +25,11 @@ class KotlinUtilsTest {
|
||||
@Rule
|
||||
val expectedEx: ExpectedException = ExpectedException.none()
|
||||
|
||||
private val KRYO_CHECKPOINT_NOWHITELIST_CONTEXT = SerializationContextImpl(kryoMagic,
|
||||
private val KRYO_CHECKPOINT_NOWHITELIST_CONTEXT = CheckpointSerializationContextImpl(
|
||||
javaClass.classLoader,
|
||||
EmptyWhitelist,
|
||||
emptyMap(),
|
||||
true,
|
||||
SerializationContext.UseCase.Checkpoint,
|
||||
null)
|
||||
|
||||
@Test
|
||||
@ -44,7 +44,7 @@ class KotlinUtilsTest {
|
||||
fun `checkpointing a transient property with non-capturing lambda`() {
|
||||
val original = NonCapturingTransientProperty()
|
||||
val originalVal = original.transientVal
|
||||
val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT)
|
||||
val copy = original.checkpointSerialize(context = KRYO_CHECKPOINT_CONTEXT).checkpointDeserialize(context = KRYO_CHECKPOINT_CONTEXT)
|
||||
val copyVal = copy.transientVal
|
||||
assertThat(copyVal).isNotEqualTo(originalVal)
|
||||
assertThat(copy.transientVal).isEqualTo(copyVal)
|
||||
@ -55,14 +55,14 @@ class KotlinUtilsTest {
|
||||
expectedEx.expect(KryoException::class.java)
|
||||
expectedEx.expectMessage("is not annotated or on the whitelist, so cannot be used in serialization")
|
||||
val original = NonCapturingTransientProperty()
|
||||
original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
||||
original.checkpointSerialize(context = KRYO_CHECKPOINT_CONTEXT).checkpointDeserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `checkpointing a transient property with capturing lambda`() {
|
||||
val original = CapturingTransientProperty("Hello")
|
||||
val originalVal = original.transientVal
|
||||
val copy = original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_CONTEXT)
|
||||
val copy = original.checkpointSerialize(context = KRYO_CHECKPOINT_CONTEXT).checkpointDeserialize(context = KRYO_CHECKPOINT_CONTEXT)
|
||||
val copyVal = copy.transientVal
|
||||
assertThat(copyVal).isNotEqualTo(originalVal)
|
||||
assertThat(copy.transientVal).isEqualTo(copyVal)
|
||||
@ -76,7 +76,7 @@ class KotlinUtilsTest {
|
||||
|
||||
val original = CapturingTransientProperty("Hello")
|
||||
|
||||
original.serialize(context = KRYO_CHECKPOINT_CONTEXT).deserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
||||
original.checkpointSerialize(context = KRYO_CHECKPOINT_CONTEXT).checkpointDeserialize(context = KRYO_CHECKPOINT_NOWHITELIST_CONTEXT)
|
||||
}
|
||||
|
||||
private class NullTransientProperty {
|
||||
|
Reference in New Issue
Block a user