mirror of
https://github.com/corda/corda.git
synced 2024-12-21 05:53:23 +00:00
CORDA-1997 Added constraint type information to vault states table. (#3975)
* Added constraint type information to vault states table. * Added Vault Query criteria support for constraint data. * Added documentation and changelog entry. * Added missing @CordaSerializable. * Fix minor bug in test setup and parsing code. * Use binary encoding data types instead of serialize/deserialize. * Optimized storage of constraints data. Additional assertions on Vault Query constraint data contents (to validate encoding/decoding). Tested with CompositeKey containing 10 keys. * Addressing PR review feedback. * Query by constraints type and data. * Revert back accidentally removed code for contractStateType filtering. * Incorporating final PR review feedback. Use @JvmOverloads on constructor. * Make sure constraintInfo is class evolution friendly.
This commit is contained in:
parent
beb4dc008f
commit
7edc18f85d
@ -5,6 +5,7 @@ import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.DoNotImplement
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.Crypto
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowException
|
||||
import net.corda.core.flows.FlowLogic
|
||||
@ -18,6 +19,7 @@ import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.toFuture
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.toHexString
|
||||
import rx.Observable
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
@ -125,6 +127,44 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
RELEVANT, NOT_RELEVANT, ALL
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract constraint information associated with a [ContractState].
|
||||
* See [AttachmentConstraint]
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class ConstraintInfo(val constraint: AttachmentConstraint) {
|
||||
@CordaSerializable
|
||||
enum class Type {
|
||||
ALWAYS_ACCEPT, HASH, CZ_WHITELISTED, SIGNATURE
|
||||
}
|
||||
fun type(): Type {
|
||||
return when (constraint::class.java) {
|
||||
AlwaysAcceptAttachmentConstraint::class.java -> Type.ALWAYS_ACCEPT
|
||||
HashAttachmentConstraint::class.java -> Type.HASH
|
||||
WhitelistedByZoneAttachmentConstraint::class.java -> Type.CZ_WHITELISTED
|
||||
SignatureAttachmentConstraint::class.java -> Type.SIGNATURE
|
||||
else -> throw IllegalArgumentException("Invalid constraint type: $constraint")
|
||||
}
|
||||
}
|
||||
fun data(): ByteArray? {
|
||||
return when (type()) {
|
||||
Type.HASH -> (constraint as HashAttachmentConstraint).attachmentId.bytes
|
||||
Type.SIGNATURE -> (constraint as SignatureAttachmentConstraint).key.encoded
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
fun constraintInfo(type: Type, data: ByteArray?): ConstraintInfo {
|
||||
return when (type) {
|
||||
Type.ALWAYS_ACCEPT -> ConstraintInfo(AlwaysAcceptAttachmentConstraint)
|
||||
Type.HASH -> ConstraintInfo(HashAttachmentConstraint(SecureHash.parse(data!!.toHexString())))
|
||||
Type.CZ_WHITELISTED -> ConstraintInfo(WhitelistedByZoneAttachmentConstraint)
|
||||
Type.SIGNATURE -> ConstraintInfo(SignatureAttachmentConstraint(Crypto.decodePublicKey(data!!)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
enum class UpdateType {
|
||||
GENERAL, NOTARY_CHANGE, CONTRACT_UPGRADE
|
||||
@ -151,7 +191,7 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
val otherResults: List<Any>)
|
||||
|
||||
@CordaSerializable
|
||||
data class StateMetadata constructor(
|
||||
data class StateMetadata @JvmOverloads constructor(
|
||||
val ref: StateRef,
|
||||
val contractStateClassName: String,
|
||||
val recordedTime: Instant,
|
||||
@ -160,18 +200,9 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
val notary: AbstractParty?,
|
||||
val lockId: String?,
|
||||
val lockUpdateTime: Instant?,
|
||||
val relevancyStatus: Vault.RelevancyStatus?
|
||||
val relevancyStatus: Vault.RelevancyStatus? = null,
|
||||
val constraintInfo: ConstraintInfo? = null
|
||||
) {
|
||||
constructor(ref: StateRef,
|
||||
contractStateClassName: String,
|
||||
recordedTime: Instant,
|
||||
consumedTime: Instant?,
|
||||
status: Vault.StateStatus,
|
||||
notary: AbstractParty?,
|
||||
lockId: String?,
|
||||
lockUpdateTime: Instant?
|
||||
) : this(ref, contractStateClassName, recordedTime, consumedTime, status, notary, lockId, lockUpdateTime, null)
|
||||
|
||||
fun copy(
|
||||
ref: StateRef = this.ref,
|
||||
contractStateClassName: String = this.contractStateClassName,
|
||||
@ -184,6 +215,19 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
): StateMetadata {
|
||||
return StateMetadata(ref, contractStateClassName, recordedTime, consumedTime, status, notary, lockId, lockUpdateTime, null)
|
||||
}
|
||||
fun copy(
|
||||
ref: StateRef = this.ref,
|
||||
contractStateClassName: String = this.contractStateClassName,
|
||||
recordedTime: Instant = this.recordedTime,
|
||||
consumedTime: Instant? = this.consumedTime,
|
||||
status: Vault.StateStatus = this.status,
|
||||
notary: AbstractParty? = this.notary,
|
||||
lockId: String? = this.lockId,
|
||||
lockUpdateTime: Instant? = this.lockUpdateTime,
|
||||
relevancyStatus: Vault.RelevancyStatus?
|
||||
): StateMetadata {
|
||||
return StateMetadata(ref, contractStateClassName, recordedTime, consumedTime, status, notary, lockId, lockUpdateTime, relevancyStatus, ConstraintInfo(AlwaysAcceptAttachmentConstraint))
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -194,6 +238,12 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum permissible size of contract constraint type data (for storage in vault states database table).
|
||||
* Maximum value equates to a CompositeKey with 10 EDDSA_ED25519_SHA512 keys stored in.
|
||||
*/
|
||||
const val MAX_CONSTRAINT_DATA_SIZE = 563
|
||||
|
||||
/**
|
||||
* A [VaultService] is responsible for securely and safely persisting the current state of a vault to storage. The
|
||||
* vault service vends immutable snapshots of the current vault for working with: if you build a transaction based
|
||||
|
@ -74,6 +74,8 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
abstract class CommonQueryCriteria : QueryCriteria() {
|
||||
abstract val status: Vault.StateStatus
|
||||
open val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
open val constraintTypes: Set<Vault.ConstraintInfo.Type> = emptySet()
|
||||
open val constraints: Set<Vault.ConstraintInfo> = emptySet()
|
||||
abstract val contractStateTypes: Set<Class<out ContractState>>?
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
return parser.parseCriteria(this)
|
||||
@ -90,7 +92,9 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
val notary: List<AbstractParty>? = null,
|
||||
val softLockingCondition: SoftLockingCondition? = null,
|
||||
val timeCondition: TimeCondition? = null,
|
||||
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
override val relevancyStatus: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL,
|
||||
override val constraintTypes: Set<Vault.ConstraintInfo.Type> = emptySet(),
|
||||
override val constraints: Set<Vault.ConstraintInfo> = emptySet()
|
||||
) : CommonQueryCriteria() {
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
super.visit(parser)
|
||||
|
@ -184,7 +184,8 @@ data class Sort(val columns: Collection<SortColumn>) : BaseSort() {
|
||||
STATE_STATUS("stateStatus"),
|
||||
RECORDED_TIME("recordedTime"),
|
||||
CONSUMED_TIME("consumedTime"),
|
||||
LOCK_ID("lockId")
|
||||
LOCK_ID("lockId"),
|
||||
CONSTRAINT_TYPE("constraintType")
|
||||
}
|
||||
|
||||
enum class LinearStateAttribute(val attributeName: String) : Attribute {
|
||||
|
@ -51,6 +51,8 @@ upgrade approach is that you can upgrade states regardless of their constraint,
|
||||
anticipate a need to do so. But it requires everyone to sign, requires everyone to manually authorise the upgrade,
|
||||
consumes notary and ledger resources, and is just in general more complex.
|
||||
|
||||
.. _implicit_constraint_types:
|
||||
|
||||
How constraints work
|
||||
--------------------
|
||||
|
||||
|
@ -78,7 +78,8 @@ and/or composition and a rich set of operators to include:
|
||||
There are four implementations of this interface which can be chained together to define advanced filters.
|
||||
|
||||
1. ``VaultQueryCriteria`` provides filterable criteria on attributes within the Vault states table: status (UNCONSUMED,
|
||||
CONSUMED), state reference(s), contract state type(s), notaries, soft locked states, timestamps (RECORDED, CONSUMED).
|
||||
CONSUMED), state reference(s), contract state type(s), notaries, soft locked states, timestamps (RECORDED, CONSUMED),
|
||||
state constraints (see :ref:`Constraint Types <implicit_constraint_types>`).
|
||||
|
||||
.. note:: Sensible defaults are defined for frequently used attributes (status = UNCONSUMED, always include soft
|
||||
locked states).
|
||||
@ -260,6 +261,22 @@ Query for unconsumed states for several contract state types:
|
||||
:end-before: DOCEND VaultQueryExample3
|
||||
:dedent: 12
|
||||
|
||||
Query for unconsumed states for specified contract state constraint types and sorted in ascending alphabetical order:
|
||||
|
||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART VaultQueryExample30
|
||||
:end-before: DOCEND VaultQueryExample30
|
||||
:dedent: 12
|
||||
|
||||
Query for unconsumed states for specified contract state constraints (type and data):
|
||||
|
||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
||||
:language: kotlin
|
||||
:start-after: DOCSTART VaultQueryExample31
|
||||
:end-before: DOCEND VaultQueryExample31
|
||||
:dedent: 12
|
||||
|
||||
Query for unconsumed states for a given notary:
|
||||
|
||||
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
|
||||
|
@ -9,6 +9,8 @@ Unreleased
|
||||
|
||||
* Introduce minimum and target platform version for CorDapps.
|
||||
|
||||
* Vault storage of contract state constraints metadata and associated vault query functions to retrieve and sort by constraint type.
|
||||
|
||||
* New overload for ``CordaRPCClient.start()`` method allowing to specify target legal identity to use for RPC call.
|
||||
|
||||
* Case insensitive vault queries can be specified via a boolean on applicable SQL criteria builder operators. By default queries will be case sensitive.
|
||||
|
@ -225,6 +225,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
|
||||
private val rootEntities = mutableMapOf<Class<out PersistentState>, Root<*>>(Pair(VaultSchemaV1.VaultStates::class.java, vaultStates))
|
||||
private val aggregateExpressions = mutableListOf<Expression<*>>()
|
||||
private val commonPredicates = mutableMapOf<Pair<String, Operator>, Predicate>() // schema attribute Name, operator -> predicate
|
||||
private val constraintPredicates = mutableSetOf<Predicate>()
|
||||
|
||||
var stateTypes: Vault.StateStatus = Vault.StateStatus.UNCONSUMED
|
||||
|
||||
@ -508,7 +509,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
|
||||
else
|
||||
aggregateExpressions
|
||||
criteriaQuery.multiselect(selections)
|
||||
val combinedPredicates = commonPredicates.values.plus(predicateSet)
|
||||
val combinedPredicates = commonPredicates.values.plus(predicateSet).plus(constraintPredicates)
|
||||
criteriaQuery.where(*combinedPredicates.toTypedArray())
|
||||
|
||||
return predicateSet
|
||||
@ -561,6 +562,38 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
|
||||
}
|
||||
}
|
||||
|
||||
// contract constraint types
|
||||
if (criteria.constraintTypes.isNotEmpty()) {
|
||||
val predicateID = Pair(VaultSchemaV1.VaultStates::constraintType.name, IN)
|
||||
if (commonPredicates.containsKey(predicateID)) {
|
||||
val existingTypes = (commonPredicates[predicateID]!!.expressions[0] as InPredicate<*>).values.map { (it as LiteralExpression).literal }.toSet()
|
||||
if (existingTypes != criteria.constraintTypes) {
|
||||
log.warn("Enriching previous attribute [${VaultSchemaV1.VaultStates::constraintType.name}] values [$existingTypes] with [${criteria.constraintTypes}]")
|
||||
commonPredicates.replace(predicateID, criteriaBuilder.and(vaultStates.get<Vault.ConstraintInfo.Type>(VaultSchemaV1.VaultStates::constraintType.name).`in`(criteria.constraintTypes.plus(existingTypes))))
|
||||
}
|
||||
} else {
|
||||
commonPredicates[predicateID] = criteriaBuilder.and(vaultStates.get<Vault.ConstraintInfo.Type>(VaultSchemaV1.VaultStates::constraintType.name).`in`(criteria.constraintTypes))
|
||||
}
|
||||
}
|
||||
|
||||
// contract constraint information (type and data)
|
||||
if (criteria.constraints.isNotEmpty()) {
|
||||
criteria.constraints.forEach { constraint ->
|
||||
val predicateConstraintType = criteriaBuilder.equal(vaultStates.get<Vault.ConstraintInfo>(VaultSchemaV1.VaultStates::constraintType.name), constraint.type())
|
||||
if (constraint.data() != null) {
|
||||
val predicateConstraintData = criteriaBuilder.equal(vaultStates.get<Vault.ConstraintInfo>(VaultSchemaV1.VaultStates::constraintData.name), constraint.data())
|
||||
val compositePredicate = criteriaBuilder.and(predicateConstraintType, predicateConstraintData)
|
||||
if (constraintPredicates.isNotEmpty()) {
|
||||
val previousPredicate = constraintPredicates.last()
|
||||
constraintPredicates.clear()
|
||||
constraintPredicates.add(criteriaBuilder.or(previousPredicate, compositePredicate))
|
||||
}
|
||||
else constraintPredicates.add(compositePredicate)
|
||||
}
|
||||
else constraintPredicates.add(criteriaBuilder.or(predicateConstraintType))
|
||||
}
|
||||
}
|
||||
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,12 @@ import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.node.services.Vault.ConstraintInfo.Companion.constraintInfo
|
||||
import net.corda.core.schemas.PersistentStateRef
|
||||
import net.corda.core.serialization.SerializationDefaults.STORAGE_CONTEXT
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.*
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.node.services.api.SchemaService
|
||||
@ -132,12 +136,15 @@ class NodeVaultService(
|
||||
// Adding a new column in the "VaultStates" table was considered the best approach.
|
||||
val keys = stateOnly.participants.map { it.owningKey }
|
||||
val isRelevant = isRelevant(stateOnly, keyManagementService.filterMyKeys(keys).toSet())
|
||||
val constraintInfo = Vault.ConstraintInfo(stateAndRef.value.state.constraint)
|
||||
val stateToAdd = VaultSchemaV1.VaultStates(
|
||||
notary = stateAndRef.value.state.notary,
|
||||
contractStateClassName = stateAndRef.value.state.data.javaClass.name,
|
||||
stateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
recordedTime = clock.instant(),
|
||||
relevancyStatus = if (isRelevant) Vault.RelevancyStatus.RELEVANT else Vault.RelevancyStatus.NOT_RELEVANT
|
||||
relevancyStatus = if (isRelevant) Vault.RelevancyStatus.RELEVANT else Vault.RelevancyStatus.NOT_RELEVANT,
|
||||
constraintType = constraintInfo.type(),
|
||||
constraintData = constraintInfo.data()
|
||||
)
|
||||
stateToAdd.stateRef = PersistentStateRef(stateAndRef.key)
|
||||
session.save(stateToAdd)
|
||||
@ -513,7 +520,9 @@ class NodeVaultService(
|
||||
vaultState.notary,
|
||||
vaultState.lockId,
|
||||
vaultState.lockUpdateTime,
|
||||
vaultState.relevancyStatus))
|
||||
vaultState.relevancyStatus,
|
||||
constraintInfo(vaultState.constraintType, vaultState.constraintData)
|
||||
))
|
||||
} else {
|
||||
// TODO: improve typing of returned other results
|
||||
log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" }
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
|
||||
import net.corda.core.contracts.UniqueIdentifier
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.MAX_CONSTRAINT_DATA_SIZE
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.schemas.MappedSchema
|
||||
import net.corda.core.schemas.PersistentState
|
||||
@ -66,7 +67,16 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
|
||||
|
||||
/** refers to the last time a lock was taken (reserved) or updated (released, re-reserved) */
|
||||
@Column(name = "lock_timestamp", nullable = true)
|
||||
var lockUpdateTime: Instant? = null
|
||||
var lockUpdateTime: Instant? = null,
|
||||
|
||||
/** refers to constraint type (none, hash, whitelisted, signature) associated with a contract state */
|
||||
@Column(name = "constraint_type", nullable = false)
|
||||
var constraintType: Vault.ConstraintInfo.Type,
|
||||
|
||||
/** associated constraint type data (if any) */
|
||||
@Column(name = "constraint_data", length = MAX_CONSTRAINT_DATA_SIZE, nullable = true)
|
||||
@Type(type = "corda-wrapper-binary")
|
||||
var constraintData: ByteArray? = null
|
||||
) : PersistentState()
|
||||
|
||||
@Entity
|
||||
|
@ -9,4 +9,5 @@
|
||||
<include file="migration/vault-schema.changelog-v4.xml"/>
|
||||
<include file="migration/vault-schema.changelog-pkey.xml"/>
|
||||
<include file="migration/vault-schema.changelog-v5.xml"/>
|
||||
<include file="migration/vault-schema.changelog-v6.xml"/>
|
||||
</databaseChangeLog>
|
||||
|
@ -0,0 +1,17 @@
|
||||
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
|
||||
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
|
||||
<changeSet author="R3.Corda" id="add_is_constraint_information_columns">
|
||||
<addColumn tableName="vault_states">
|
||||
<column name="constraint_type" type="INT" defaultValue="0">
|
||||
<constraints nullable="false"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
<addColumn tableName="vault_states">
|
||||
<column name="constraint_data" type="varbinary(563)">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</addColumn>
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
@ -9,6 +9,7 @@ import net.corda.core.internal.packageName
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.node.services.vault.QueryCriteria.*
|
||||
import net.corda.core.node.services.Vault.ConstraintInfo.Type.*
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.*
|
||||
@ -472,6 +473,125 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `query by contract states constraint type`() {
|
||||
database.transaction {
|
||||
// insert states with different constraint types
|
||||
vaultFiller.fillWithSomeTestLinearStates(1).states.first().state.constraint
|
||||
vaultFiller.fillWithSomeTestLinearStates(1, constraint = AlwaysAcceptAttachmentConstraint).states.first().state.constraint
|
||||
vaultFiller.fillWithSomeTestLinearStates(1, constraint = WhitelistedByZoneAttachmentConstraint).states.first().state.constraint
|
||||
// hash constraint
|
||||
val linearStateHash = vaultFiller.fillWithSomeTestLinearStates(1, constraint = HashAttachmentConstraint(SecureHash.randomSHA256()))
|
||||
val constraintHash = linearStateHash.states.first().state.constraint as HashAttachmentConstraint
|
||||
// signature constraint (single key)
|
||||
val linearStateSignature = vaultFiller.fillWithSomeTestLinearStates(1, constraint = SignatureAttachmentConstraint(alice.publicKey))
|
||||
val constraintSignature = linearStateSignature.states.first().state.constraint as SignatureAttachmentConstraint
|
||||
// signature constraint (composite key)
|
||||
val compositeKey = CompositeKey.Builder().addKeys(alice.publicKey, bob.publicKey, charlie.publicKey, bankOfCorda.publicKey, bigCorp.publicKey, megaCorp.publicKey, miniCorp.publicKey, cashNotary.publicKey, dummyNotary.publicKey, dummyCashIssuer.publicKey).build()
|
||||
val linearStateSignatureCompositeKey = vaultFiller.fillWithSomeTestLinearStates(1, constraint = SignatureAttachmentConstraint(compositeKey))
|
||||
val constraintSignatureCompositeKey = linearStateSignatureCompositeKey.states.first().state.constraint as SignatureAttachmentConstraint
|
||||
|
||||
// default Constraint Type is ALL
|
||||
val results = vaultService.queryBy<LinearState>()
|
||||
assertThat(results.states).hasSize(6)
|
||||
|
||||
// search for states with Vault.ConstraintInfo.Type = ALWAYS_ACCEPT
|
||||
val constraintTypeCriteria1 = VaultQueryCriteria(constraintTypes = setOf(ALWAYS_ACCEPT))
|
||||
val constraintResults1 = vaultService.queryBy<LinearState>(constraintTypeCriteria1)
|
||||
assertThat(constraintResults1.states).hasSize(1)
|
||||
|
||||
// search for states with [Vault.ConstraintInfo.Type] = HASH
|
||||
val constraintTypeCriteria2 = VaultQueryCriteria(constraintTypes = setOf(HASH))
|
||||
val constraintResults2 = vaultService.queryBy<LinearState>(constraintTypeCriteria2)
|
||||
assertThat(constraintResults2.states).hasSize(2)
|
||||
assertThat(constraintResults2.states.map { it.state.constraint }).containsOnlyOnce(constraintHash)
|
||||
|
||||
// search for states with [Vault.ConstraintInfo.Type] either HASH or CZ_WHITELISED
|
||||
// DOCSTART VaultQueryExample30
|
||||
val constraintTypeCriteria = VaultQueryCriteria(constraintTypes = setOf(HASH, CZ_WHITELISTED))
|
||||
val sortAttribute = SortAttribute.Standard(Sort.VaultStateAttribute.CONSTRAINT_TYPE)
|
||||
val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
|
||||
val constraintResults = vaultService.queryBy<LinearState>(constraintTypeCriteria, sorter)
|
||||
// DOCEND VaultQueryExample30
|
||||
assertThat(constraintResults.states).hasSize(3)
|
||||
|
||||
// search for states with [Vault.ConstraintInfo.Type] = SIGNATURE
|
||||
val constraintTypeCriteria4 = VaultQueryCriteria(constraintTypes = setOf(SIGNATURE))
|
||||
val constraintResults4 = vaultService.queryBy<LinearState>(constraintTypeCriteria4)
|
||||
assertThat(constraintResults4.states).hasSize(2)
|
||||
assertThat(constraintResults4.states.map { it.state.constraint }).containsAll(listOf(constraintSignature, constraintSignatureCompositeKey))
|
||||
|
||||
// search for states with [Vault.ConstraintInfo.Type] = SIGNATURE or CZ_WHITELISED
|
||||
val constraintTypeCriteria5 = VaultQueryCriteria(constraintTypes = setOf(SIGNATURE, CZ_WHITELISTED))
|
||||
val constraintResults5 = vaultService.queryBy<LinearState>(constraintTypeCriteria5)
|
||||
assertThat(constraintResults5.states).hasSize(3)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `query by contract states constraint type and data`() {
|
||||
database.transaction {
|
||||
// insert states with different constraint types
|
||||
vaultFiller.fillWithSomeTestLinearStates(1).states.first().state.constraint
|
||||
val alwaysAcceptConstraint = vaultFiller.fillWithSomeTestLinearStates(1, constraint = AlwaysAcceptAttachmentConstraint).states.first().state.constraint
|
||||
vaultFiller.fillWithSomeTestLinearStates(1, constraint = WhitelistedByZoneAttachmentConstraint)
|
||||
// hash constraint
|
||||
val linearStateHash = vaultFiller.fillWithSomeTestLinearStates(1, constraint = HashAttachmentConstraint(SecureHash.randomSHA256()))
|
||||
val constraintHash = linearStateHash.states.first().state.constraint as HashAttachmentConstraint
|
||||
// signature constraint (single key)
|
||||
val linearStateSignature = vaultFiller.fillWithSomeTestLinearStates(1, constraint = SignatureAttachmentConstraint(alice.publicKey))
|
||||
val constraintSignature = linearStateSignature.states.first().state.constraint as SignatureAttachmentConstraint
|
||||
// signature constraint (composite key)
|
||||
val compositeKey = CompositeKey.Builder().addKeys(alice.publicKey, bob.publicKey, charlie.publicKey, bankOfCorda.publicKey, bigCorp.publicKey, megaCorp.publicKey, miniCorp.publicKey, cashNotary.publicKey, dummyNotary.publicKey, dummyCashIssuer.publicKey).build()
|
||||
val linearStateSignatureCompositeKey = vaultFiller.fillWithSomeTestLinearStates(1, constraint = SignatureAttachmentConstraint(compositeKey))
|
||||
val constraintSignatureCompositeKey = linearStateSignatureCompositeKey.states.first().state.constraint as SignatureAttachmentConstraint
|
||||
|
||||
// default Constraint Type is ALL
|
||||
val results = vaultService.queryBy<LinearState>()
|
||||
assertThat(results.states).hasSize(6)
|
||||
|
||||
// search for states with AlwaysAcceptAttachmentConstraint
|
||||
val constraintCriteria1 = VaultQueryCriteria(constraints = setOf(Vault.ConstraintInfo(AlwaysAcceptAttachmentConstraint)))
|
||||
val constraintResults1 = vaultService.queryBy<LinearState>(constraintCriteria1)
|
||||
assertThat(constraintResults1.states).hasSize(1)
|
||||
assertThat(constraintResults1.states.first().state.constraint).isEqualTo(alwaysAcceptConstraint)
|
||||
|
||||
// search for states for a specific HashAttachmentConstraint
|
||||
val constraintsCriteria2 = VaultQueryCriteria(constraints = setOf(Vault.ConstraintInfo(constraintHash)))
|
||||
val constraintResults2 = vaultService.queryBy<LinearState>(constraintsCriteria2)
|
||||
assertThat(constraintResults2.states).hasSize(1)
|
||||
assertThat(constraintResults2.states.first().state.constraint).isEqualTo(constraintHash)
|
||||
|
||||
// search for states with a specific SignatureAttachmentConstraint constraint
|
||||
val constraintCriteria3 = VaultQueryCriteria(constraints = setOf(Vault.ConstraintInfo(constraintSignatureCompositeKey)))
|
||||
val constraintResults3 = vaultService.queryBy<LinearState>(constraintCriteria3)
|
||||
assertThat(constraintResults3.states).hasSize(1)
|
||||
assertThat(constraintResults3.states.first().state.constraint).isEqualTo(constraintSignatureCompositeKey)
|
||||
|
||||
// search for states for given set of mixed constraint types
|
||||
// DOCSTART VaultQueryExample31
|
||||
val constraintCriteria = VaultQueryCriteria(constraints = setOf(Vault.ConstraintInfo(constraintSignature),
|
||||
Vault.ConstraintInfo(constraintSignatureCompositeKey), Vault.ConstraintInfo(constraintHash)))
|
||||
val constraintResults = vaultService.queryBy<LinearState>(constraintCriteria)
|
||||
// DOCEND VaultQueryExample31
|
||||
assertThat(constraintResults.states).hasSize(3)
|
||||
assertThat(constraintResults.states.map { it.state.constraint }).containsAll(listOf(constraintHash, constraintSignature, constraintSignatureCompositeKey))
|
||||
|
||||
// exercise enriched query
|
||||
|
||||
// Base criteria
|
||||
val baseCriteria = VaultQueryCriteria(constraints = setOf(Vault.ConstraintInfo(AlwaysAcceptAttachmentConstraint)))
|
||||
|
||||
// Enrich and override QueryCriteria with additional default attributes
|
||||
val enrichedCriteria = VaultQueryCriteria(constraints = setOf(Vault.ConstraintInfo(constraintSignature)))
|
||||
|
||||
// Execute query
|
||||
val enrichedResults = services.vaultService.queryBy<LinearState>(baseCriteria and enrichedCriteria).states
|
||||
assertThat(enrichedResults).hasSize(2)
|
||||
assertThat(enrichedResults.map { it.state.constraint }).containsAll(listOf(constraintSignature, alwaysAcceptConstraint))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `consumed states`() {
|
||||
database.transaction {
|
||||
@ -2211,6 +2331,33 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sorted, enriched and overridden composite query with constraints handles defaults correctly`() {
|
||||
database.transaction {
|
||||
vaultFiller.fillWithSomeTestLinearStates(1, constraint = WhitelistedByZoneAttachmentConstraint)
|
||||
vaultFiller.fillWithSomeTestLinearStates(1, constraint = SignatureAttachmentConstraint(alice.publicKey))
|
||||
vaultFiller.fillWithSomeTestLinearStates(1, constraint = HashAttachmentConstraint( SecureHash.randomSHA256()))
|
||||
vaultFiller.fillWithSomeTestLinearStates(1, constraint = AlwaysAcceptAttachmentConstraint)
|
||||
|
||||
// Base criteria
|
||||
val baseCriteria = VaultQueryCriteria(constraintTypes = setOf(ALWAYS_ACCEPT))
|
||||
|
||||
// Enrich and override QueryCriteria with additional default attributes (contract constraints)
|
||||
val enrichedCriteria = VaultQueryCriteria(constraintTypes = setOf(SIGNATURE, HASH, ALWAYS_ACCEPT)) // enrich
|
||||
|
||||
// Sorting
|
||||
val sortAttribute = SortAttribute.Standard(Sort.VaultStateAttribute.CONSTRAINT_TYPE)
|
||||
val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
|
||||
|
||||
// Execute query
|
||||
val results = services.vaultService.queryBy<LinearState>(baseCriteria and enrichedCriteria, sorter).states
|
||||
assertThat(results).hasSize(3)
|
||||
assertThat(results[0].state.constraint is AlwaysAcceptAttachmentConstraint)
|
||||
assertThat(results[1].state.constraint is HashAttachmentConstraint)
|
||||
assertThat(results[2].state.constraint is SignatureAttachmentConstraint)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun unconsumedCashStatesForSpending_single_issuer_reference() {
|
||||
database.transaction {
|
||||
|
@ -102,7 +102,8 @@ class VaultFiller @JvmOverloads constructor(
|
||||
linearString: String = "",
|
||||
linearNumber: Long = 0L,
|
||||
linearBoolean: Boolean = false,
|
||||
linearTimestamp: Instant = now()): Vault<LinearState> {
|
||||
linearTimestamp: Instant = now(),
|
||||
constraint: AttachmentConstraint = AutomaticHashConstraint): Vault<LinearState> {
|
||||
val myKey: PublicKey = services.myInfo.chooseIdentity().owningKey
|
||||
val me = AnonymousParty(myKey)
|
||||
val issuerKey = defaultNotary.keyPair
|
||||
@ -116,7 +117,8 @@ class VaultFiller @JvmOverloads constructor(
|
||||
linearString = linearString,
|
||||
linearNumber = linearNumber,
|
||||
linearBoolean = linearBoolean,
|
||||
linearTimestamp = linearTimestamp), DUMMY_LINEAR_CONTRACT_PROGRAM_ID)
|
||||
linearTimestamp = linearTimestamp), DUMMY_LINEAR_CONTRACT_PROGRAM_ID,
|
||||
constraint = constraint)
|
||||
addCommand(dummyCommand())
|
||||
}
|
||||
return@map services.signInitialTransaction(dummyIssue).withAdditionalSignature(issuerKey, signatureMetadata)
|
||||
|
Loading…
Reference in New Issue
Block a user