Revert back to using "relevancy" as "modifiable" states are not permissible by definition on an immutable ledger. (#3847)

This commit is contained in:
josecoll 2018-08-28 11:04:40 +02:00 committed by GitHub
parent f81428eb53
commit 36bfe268af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 55 additions and 56 deletions

View File

@ -11,7 +11,7 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.identity.AbstractParty import net.corda.core.identity.AbstractParty
import net.corda.core.internal.concurrent.doneFuture import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.services.Vault.StateModificationStatus.* import net.corda.core.node.services.Vault.RelevancyStatus.*
import net.corda.core.node.services.Vault.StateStatus import net.corda.core.node.services.Vault.StateStatus
import net.corda.core.node.services.vault.* import net.corda.core.node.services.vault.*
import net.corda.core.serialization.CordaSerializable import net.corda.core.serialization.CordaSerializable
@ -107,23 +107,22 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
} }
/** /**
* If the querying node is a participant in a state then it is classed as [MODIFIABLE], although technically the * If the querying node is a participant in a state then it is classed as [RELEVANT].
* state is only _potentially_ modifiable as the contract code may forbid them from performing any actions.
* *
* If the querying node is not a participant in a state then it is classed as [NOT_MODIFIABLE]. These types of * If the querying node is not a participant in a state then it is classed as [NOT_RELEVANT]. These types of
* states can still be recorded in the vault if the transaction containing them was recorded with the * states can still be recorded in the vault if the transaction containing them was recorded with the
* [StatesToRecord.ALL_VISIBLE] flag. This will typically happen for things like reference data which can be * [StatesToRecord.ALL_VISIBLE] flag. This will typically happen for things like reference data which can be
* referenced in transactions as a [ReferencedStateAndRef] but cannot be modified by any party but the maintainer. * referenced in transactions as a [ReferencedStateAndRef] but cannot be modified by any party but the maintainer.
* *
* If both [MODIFIABLE] and [NOT_MODIFIABLE] states are required to be returned from a query, then the [ALL] flag * If both [RELEVANT] and [NOT_RELEVANT] states are required to be returned from a query, then the [ALL] flag
* can be used. * can be used.
* *
* NOTE: Default behaviour is for ALL STATES to be returned as this is how Corda behaved before the introduction of * NOTE: Default behaviour is for ALL STATES to be returned as this is how Corda behaved before the introduction of
* this query criterion. * this query criterion.
*/ */
@CordaSerializable @CordaSerializable
enum class StateModificationStatus { enum class RelevancyStatus {
MODIFIABLE, NOT_MODIFIABLE, ALL RELEVANT, NOT_RELEVANT, ALL
} }
@CordaSerializable @CordaSerializable
@ -161,7 +160,7 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
val notary: AbstractParty?, val notary: AbstractParty?,
val lockId: String?, val lockId: String?,
val lockUpdateTime: Instant?, val lockUpdateTime: Instant?,
val isModifiable: Vault.StateModificationStatus? val isRelevant: Vault.RelevancyStatus?
) { ) {
constructor(ref: StateRef, constructor(ref: StateRef,
contractStateClassName: String, contractStateClassName: String,

View File

@ -73,7 +73,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
abstract class CommonQueryCriteria : QueryCriteria() { abstract class CommonQueryCriteria : QueryCriteria() {
abstract val status: Vault.StateStatus abstract val status: Vault.StateStatus
open val isModifiable: Vault.StateModificationStatus = Vault.StateModificationStatus.ALL open val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
abstract val contractStateTypes: Set<Class<out ContractState>>? abstract val contractStateTypes: Set<Class<out ContractState>>?
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> { override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
return parser.parseCriteria(this) return parser.parseCriteria(this)
@ -90,7 +90,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
val notary: List<AbstractParty>? = null, val notary: List<AbstractParty>? = null,
val softLockingCondition: SoftLockingCondition? = null, val softLockingCondition: SoftLockingCondition? = null,
val timeCondition: TimeCondition? = null, val timeCondition: TimeCondition? = null,
override val isModifiable: Vault.StateModificationStatus = Vault.StateModificationStatus.ALL override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
) : CommonQueryCriteria() { ) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> { override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
super.visit(parser) super.visit(parser)
@ -125,14 +125,14 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
val externalId: List<String>? = null, val externalId: List<String>? = null,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
override val contractStateTypes: Set<Class<out ContractState>>? = null, override val contractStateTypes: Set<Class<out ContractState>>? = null,
override val isModifiable: Vault.StateModificationStatus = Vault.StateModificationStatus.ALL override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
) : CommonQueryCriteria() { ) : CommonQueryCriteria() {
constructor( constructor(
participants: List<AbstractParty>? = null, participants: List<AbstractParty>? = null,
linearId: List<UniqueIdentifier>? = null, linearId: List<UniqueIdentifier>? = null,
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
contractStateTypes: Set<Class<out ContractState>>? = null, contractStateTypes: Set<Class<out ContractState>>? = null,
isRelevant: Vault.StateModificationStatus isRelevant: Vault.RelevancyStatus
) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, isRelevant) ) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, isRelevant)
constructor( constructor(
@ -175,7 +175,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
val issuerRef: List<OpaqueBytes>? = null, val issuerRef: List<OpaqueBytes>? = null,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
override val contractStateTypes: Set<Class<out ContractState>>? = null, override val contractStateTypes: Set<Class<out ContractState>>? = null,
override val isModifiable: Vault.StateModificationStatus = Vault.StateModificationStatus.ALL override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
) : CommonQueryCriteria() { ) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> { override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
super.visit(parser) super.visit(parser)
@ -215,7 +215,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
val expression: CriteriaExpression<L, Boolean>, val expression: CriteriaExpression<L, Boolean>,
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED, override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
override val contractStateTypes: Set<Class<out ContractState>>? = null, override val contractStateTypes: Set<Class<out ContractState>>? = null,
override val isModifiable: Vault.StateModificationStatus = Vault.StateModificationStatus.ALL override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
) : CommonQueryCriteria() { ) : CommonQueryCriteria() {
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> { override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
super.visit(parser) super.visit(parser)

View File

@ -105,7 +105,7 @@ internal class UseRefState(val linearId: UniqueIdentifier) : FlowLogic<SignedTra
val notary = serviceHub.networkMapCache.notaryIdentities.first() val notary = serviceHub.networkMapCache.notaryIdentities.first()
val query = QueryCriteria.LinearStateQueryCriteria( val query = QueryCriteria.LinearStateQueryCriteria(
linearId = listOf(linearId), linearId = listOf(linearId),
isRelevant = Vault.StateModificationStatus.ALL isRelevant = Vault.RelevancyStatus.ALL
) )
val referenceState = serviceHub.vaultService.queryBy<ContractState>(query).states.single() val referenceState = serviceHub.vaultService.queryBy<ContractState>(query).states.single()
return subFlow(FinalityFlow( return subFlow(FinalityFlow(

View File

@ -22,7 +22,7 @@ private fun generateCashSumCriteria(currency: Currency): QueryCriteria {
val ccyIndex = builder { CashSchemaV1.PersistentCashState::currency.equal(currency.currencyCode) } val ccyIndex = builder { CashSchemaV1.PersistentCashState::currency.equal(currency.currencyCode) }
// This query should only return cash states the calling node is a participant of (meaning they can be modified/spent). // This query should only return cash states the calling node is a participant of (meaning they can be modified/spent).
val ccyCriteria = QueryCriteria.VaultCustomQueryCriteria(ccyIndex, isModifiable = Vault.StateModificationStatus.MODIFIABLE) val ccyCriteria = QueryCriteria.VaultCustomQueryCriteria(ccyIndex, isRelevant = Vault.RelevancyStatus.RELEVANT)
return sumCriteria.and(ccyCriteria) return sumCriteria.and(ccyCriteria)
} }
@ -32,7 +32,7 @@ private fun generateCashSumsCriteria(): QueryCriteria {
orderBy = Sort.Direction.DESC) orderBy = Sort.Direction.DESC)
} }
// This query should only return cash states the calling node is a participant of (meaning they can be modified/spent). // This query should only return cash states the calling node is a participant of (meaning they can be modified/spent).
return QueryCriteria.VaultCustomQueryCriteria(sum, isModifiable = Vault.StateModificationStatus.MODIFIABLE) return QueryCriteria.VaultCustomQueryCriteria(sum, isRelevant = Vault.RelevancyStatus.RELEVANT)
} }
private fun rowsToAmount(currency: Currency, rows: Vault.Page<FungibleAsset<*>>): Amount<Currency> { private fun rowsToAmount(currency: Currency, rows: Vault.Page<FungibleAsset<*>>): Amount<Currency> {

View File

@ -34,13 +34,13 @@ class CashSelectionH2Impl : AbstractCashSelection() {
connection.createStatement().use { it.execute("CALL SET(@t, CAST(0 AS BIGINT));") } connection.createStatement().use { it.execute("CALL SET(@t, CAST(0 AS BIGINT));") }
// state_status = 0 -> UNCONSUMED. // state_status = 0 -> UNCONSUMED.
// is_modifiable = 0 -> MODIFIABLE. // is_relevant = 0 -> RELEVANT.
val selectJoin = """ val selectJoin = """
SELECT vs.transaction_id, vs.output_index, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id SELECT vs.transaction_id, vs.output_index, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id
FROM vault_states AS vs, contract_cash_states AS ccs FROM vault_states AS vs, contract_cash_states AS ccs
WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index
AND vs.state_status = 0 AND vs.state_status = 0
AND vs.is_modifiable = 0 AND vs.is_relevant = 0
AND ccs.ccy_code = ? and @t < ? AND ccs.ccy_code = ? and @t < ?
AND (vs.lock_id = ? OR vs.lock_id is null) AND (vs.lock_id = ? OR vs.lock_id is null)
""" + """ +

View File

@ -32,7 +32,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
// 3) Currently (version 9.6), FOR UPDATE cannot be specified with window functions // 3) Currently (version 9.6), FOR UPDATE cannot be specified with window functions
override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>, withResultSet: (ResultSet) -> Boolean): Boolean { override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>, withResultSet: (ResultSet) -> Boolean): Boolean {
// state_status = 0 -> UNCONSUMED. // state_status = 0 -> UNCONSUMED.
// is_modifiable = 0 -> MODIFIABLE. // is_relevant = 0 -> RELEVANT.
val selectJoin = """SELECT nested.transaction_id, nested.output_index, nested.pennies, val selectJoin = """SELECT nested.transaction_id, nested.output_index, nested.pennies,
nested.total+nested.pennies as total_pennies, nested.lock_id nested.total+nested.pennies as total_pennies, nested.lock_id
FROM FROM
@ -42,7 +42,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
FROM vault_states AS vs, contract_cash_states AS ccs FROM vault_states AS vs, contract_cash_states AS ccs
WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index
AND vs.state_status = 0 AND vs.state_status = 0
AND vs.is_modifiable = 0 AND vs.is_relevant = 0
AND ccs.ccy_code = ? AND ccs.ccy_code = ?
AND (vs.lock_id = ? OR vs.lock_id is null) AND (vs.lock_id = ? OR vs.lock_id is null)
""" + """ +

View File

@ -43,7 +43,7 @@ class CashSelectionSQLServerImpl : AbstractCashSelection() {
override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>, withResultSet: (ResultSet) -> Boolean): Boolean { override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?, onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>, withResultSet: (ResultSet) -> Boolean): Boolean {
val sb = StringBuilder() val sb = StringBuilder()
// state_status = 0 -> UNCONSUMED. // state_status = 0 -> UNCONSUMED.
// is_modifiable = 0 -> MODIFIABLE. // is_relevant = 0 -> RELEVANT.
sb.append( """ sb.append( """
;WITH CTE AS ;WITH CTE AS
( (
@ -58,7 +58,7 @@ class CashSelectionSQLServerImpl : AbstractCashSelection() {
ON vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index ON vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index
WHERE WHERE
vs.state_status = 0 vs.state_status = 0
AND vs.is_modifiable = 0 AND vs.is_relevant = 0
AND ccs.ccy_code = ? AND ccs.ccy_code = ?
AND (vs.lock_id = ? OR vs.lock_id IS NULL) AND (vs.lock_id = ? OR vs.lock_id IS NULL)
""" """

View File

@ -487,16 +487,16 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
} }
// state relevance. // state relevance.
if (criteria.isModifiable != Vault.StateModificationStatus.ALL) { if (criteria.isRelevant != Vault.RelevancyStatus.ALL) {
val predicateID = Pair(VaultSchemaV1.VaultStates::isModifiable.name, EqualityComparisonOperator.EQUAL) val predicateID = Pair(VaultSchemaV1.VaultStates::isRelevant.name, EqualityComparisonOperator.EQUAL)
if (commonPredicates.containsKey(predicateID)) { if (commonPredicates.containsKey(predicateID)) {
val existingStatus = ((commonPredicates[predicateID] as ComparisonPredicate).rightHandOperand as LiteralExpression).literal val existingStatus = ((commonPredicates[predicateID] as ComparisonPredicate).rightHandOperand as LiteralExpression).literal
if (existingStatus != criteria.isModifiable) { if (existingStatus != criteria.isRelevant) {
log.warn("Overriding previous attribute [${VaultSchemaV1.VaultStates::isModifiable.name}] value $existingStatus with ${criteria.status}") log.warn("Overriding previous attribute [${VaultSchemaV1.VaultStates::isRelevant.name}] value $existingStatus with ${criteria.status}")
commonPredicates.replace(predicateID, criteriaBuilder.equal(vaultStates.get<Vault.StateModificationStatus>(VaultSchemaV1.VaultStates::isModifiable.name), criteria.isModifiable)) commonPredicates.replace(predicateID, criteriaBuilder.equal(vaultStates.get<Vault.RelevancyStatus>(VaultSchemaV1.VaultStates::isRelevant.name), criteria.isRelevant))
} }
} else { } else {
commonPredicates[predicateID] = criteriaBuilder.equal(vaultStates.get<Vault.StateModificationStatus>(VaultSchemaV1.VaultStates::isModifiable.name), criteria.isModifiable) commonPredicates[predicateID] = criteriaBuilder.equal(vaultStates.get<Vault.RelevancyStatus>(VaultSchemaV1.VaultStates::isRelevant.name), criteria.isRelevant)
} }
} }

View File

@ -113,28 +113,28 @@ class NodeVaultService(
// For EVERY state to be committed to the vault, this checks whether it is spendable by the recording // For EVERY state to be committed to the vault, this checks whether it is spendable by the recording
// node. The behaviour is as follows: // node. The behaviour is as follows:
// //
// 1) All vault updates marked as MODIFIABLE will, of, course all have isModifiable = true. // 1) All vault updates marked as RELEVANT will, of, course all have isRelevant = true.
// 2) For ALL_VISIBLE updates, those which are not modifiable will have isModifiable = false. // 2) For ALL_VISIBLE updates, those which are not relevant according to the relevancy rules will have isRelevant = false.
// //
// This is useful when it comes to querying for fungible states, when we do not want non-modifiable states // This is useful when it comes to querying for fungible states, when we do not want non-relevant states
// included in the result. // included in the result.
// //
// The same functionality could be obtained by passing in a list of participants to the vault query, // The same functionality could be obtained by passing in a list of participants to the vault query,
// however this: // however this:
// //
// * requires a join on the participants table which results in slow queries // * requires a join on the participants table which results in slow queries
// * states may flip from being non-modifiable to modifiable // * states may flip from being non-relevant to relevant
// * it's more complicated for CorDapp developers // * it's more complicated for CorDapp developers
// //
// Adding a new column in the "VaultStates" table was considered the best approach. // Adding a new column in the "VaultStates" table was considered the best approach.
val keys = stateOnly.participants.map { it.owningKey } val keys = stateOnly.participants.map { it.owningKey }
val isModifiable = isModifiable(stateOnly, keyManagementService.filterMyKeys(keys).toSet()) val isRelevant = isRelevant(stateOnly, keyManagementService.filterMyKeys(keys).toSet())
val stateToAdd = VaultSchemaV1.VaultStates( val stateToAdd = VaultSchemaV1.VaultStates(
notary = stateAndRef.value.state.notary, notary = stateAndRef.value.state.notary,
contractStateClassName = stateAndRef.value.state.data.javaClass.name, contractStateClassName = stateAndRef.value.state.data.javaClass.name,
stateStatus = Vault.StateStatus.UNCONSUMED, stateStatus = Vault.StateStatus.UNCONSUMED,
recordedTime = clock.instant(), recordedTime = clock.instant(),
isModifiable = if (isModifiable) Vault.StateModificationStatus.MODIFIABLE else Vault.StateModificationStatus.NOT_MODIFIABLE isRelevant = if (isRelevant) Vault.RelevancyStatus.RELEVANT else Vault.RelevancyStatus.NOT_RELEVANT
) )
stateToAdd.stateRef = PersistentStateRef(stateAndRef.key) stateToAdd.stateRef = PersistentStateRef(stateAndRef.key)
session.save(stateToAdd) session.save(stateToAdd)
@ -188,7 +188,7 @@ class NodeVaultService(
val ourNewStates = when (statesToRecord) { val ourNewStates = when (statesToRecord) {
StatesToRecord.NONE -> throw AssertionError("Should not reach here") StatesToRecord.NONE -> throw AssertionError("Should not reach here")
StatesToRecord.ONLY_RELEVANT -> tx.outputs.withIndex().filter { StatesToRecord.ONLY_RELEVANT -> tx.outputs.withIndex().filter {
isModifiable(it.value.data, keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } }).toSet()) isRelevant(it.value.data, keyManagementService.filterMyKeys(tx.outputs.flatMap { it.data.participants.map { it.owningKey } }).toSet())
} }
StatesToRecord.ALL_VISIBLE -> tx.outputs.withIndex() StatesToRecord.ALL_VISIBLE -> tx.outputs.withIndex()
}.map { tx.outRef<ContractState>(it.index) } }.map { tx.outRef<ContractState>(it.index) }
@ -217,7 +217,7 @@ class NodeVaultService(
val myKeys by lazy { keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } }) } val myKeys by lazy { keyManagementService.filterMyKeys(ltx.outputs.flatMap { it.data.participants.map { it.owningKey } }) }
val (consumedStateAndRefs, producedStates) = ltx.inputs.zip(ltx.outputs).filter { (_, output) -> val (consumedStateAndRefs, producedStates) = ltx.inputs.zip(ltx.outputs).filter { (_, output) ->
if (statesToRecord == StatesToRecord.ONLY_RELEVANT) { if (statesToRecord == StatesToRecord.ONLY_RELEVANT) {
isModifiable(output.data, myKeys.toSet()) isRelevant(output.data, myKeys.toSet())
} else { } else {
true true
} }
@ -396,13 +396,13 @@ class NodeVaultService(
} }
// Enrich QueryCriteria with additional default attributes (such as soft locks). // Enrich QueryCriteria with additional default attributes (such as soft locks).
// We only want to return MODIFIABLE states here. // We only want to return RELEVANT states here.
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF) val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC))) val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
val enrichedCriteria = QueryCriteria.VaultQueryCriteria( val enrichedCriteria = QueryCriteria.VaultQueryCriteria(
contractStateTypes = setOf(contractStateType), contractStateTypes = setOf(contractStateType),
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)), softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)),
isModifiable = Vault.StateModificationStatus.MODIFIABLE isRelevant = Vault.RelevancyStatus.RELEVANT
) )
val results = queryBy(contractStateType, enrichedCriteria.and(eligibleStatesQuery), sorter) val results = queryBy(contractStateType, enrichedCriteria.and(eligibleStatesQuery), sorter)
@ -426,7 +426,7 @@ class NodeVaultService(
} }
@VisibleForTesting @VisibleForTesting
internal fun isModifiable(state: ContractState, myKeys: Set<PublicKey>): Boolean { internal fun isRelevant(state: ContractState, myKeys: Set<PublicKey>): Boolean {
val keysToCheck = when (state) { val keysToCheck = when (state) {
// Sometimes developers forget to add the owning key to participants for OwnableStates. // Sometimes developers forget to add the owning key to participants for OwnableStates.
// TODO: This logic should probably be moved to OwnableState so we can just do a simple intersection here. // TODO: This logic should probably be moved to OwnableState so we can just do a simple intersection here.
@ -510,7 +510,7 @@ class NodeVaultService(
vaultState.notary, vaultState.notary,
vaultState.lockId, vaultState.lockId,
vaultState.lockUpdateTime, vaultState.lockUpdateTime,
vaultState.isModifiable)) vaultState.isRelevant))
} else { } else {
// TODO: improve typing of returned other results // TODO: improve typing of returned other results
log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" } log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" }

View File

@ -60,9 +60,9 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
@Column(name = "lock_id", nullable = true) @Column(name = "lock_id", nullable = true)
var lockId: String? = null, var lockId: String? = null,
/** Used to determine whether a state is modifiable by the recording node */ /** Used to determine whether a state abides by the relevancy rules of the recording node */
@Column(name = "is_modifiable", nullable = false) @Column(name = "is_relevant", nullable = false)
var isModifiable: Vault.StateModificationStatus, var isRelevant: Vault.RelevancyStatus,
/** refers to the last time a lock was taken (reserved) or updated (released, re-reserved) */ /** refers to the last time a lock was taken (reserved) or updated (released, re-reserved) */
@Column(name = "lock_timestamp", nullable = true) @Column(name = "lock_timestamp", nullable = true)

View File

@ -3,13 +3,13 @@
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"> xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd 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_modifiable_column"> <changeSet author="R3.Corda" id="add_is_relevant_column">
<addColumn tableName="vault_states"> <addColumn tableName="vault_states">
<column name="is_modifiable" type="INT"/> <column name="is_relevant" type="INT"/>
</addColumn> </addColumn>
<update tableName="vault_states"> <update tableName="vault_states">
<column name="is_modifiable" valueNumeric="0"/> <column name="is_relevant" valueNumeric="0"/>
</update> </update>
<addNotNullConstraint tableName="vault_states" columnName="is_modifiable" columnDataType="INT" /> <addNotNullConstraint tableName="vault_states" columnName="is_relevant" columnDataType="INT" />
</changeSet> </changeSet>
</databaseChangeLog> </databaseChangeLog>

View File

@ -529,17 +529,17 @@ class NodeVaultServiceTest {
val amount = Amount(1000, Issued(BOC.ref(1), GBP)) val amount = Amount(1000, Issued(BOC.ref(1), GBP))
val wellKnownCash = Cash.State(amount, identity.party) val wellKnownCash = Cash.State(amount, identity.party)
val myKeys = services.keyManagementService.filterMyKeys(listOf(wellKnownCash.owner.owningKey)) val myKeys = services.keyManagementService.filterMyKeys(listOf(wellKnownCash.owner.owningKey))
assertTrue { service.isModifiable(wellKnownCash, myKeys.toSet()) } assertTrue { service.isRelevant(wellKnownCash, myKeys.toSet()) }
val anonymousIdentity = services.keyManagementService.freshKeyAndCert(identity, false) val anonymousIdentity = services.keyManagementService.freshKeyAndCert(identity, false)
val anonymousCash = Cash.State(amount, anonymousIdentity.party) val anonymousCash = Cash.State(amount, anonymousIdentity.party)
val anonymousKeys = services.keyManagementService.filterMyKeys(listOf(anonymousCash.owner.owningKey)) val anonymousKeys = services.keyManagementService.filterMyKeys(listOf(anonymousCash.owner.owningKey))
assertTrue { service.isModifiable(anonymousCash, anonymousKeys.toSet()) } assertTrue { service.isRelevant(anonymousCash, anonymousKeys.toSet()) }
val thirdPartyIdentity = AnonymousParty(generateKeyPair().public) val thirdPartyIdentity = AnonymousParty(generateKeyPair().public)
val thirdPartyCash = Cash.State(amount, thirdPartyIdentity) val thirdPartyCash = Cash.State(amount, thirdPartyIdentity)
val thirdPartyKeys = services.keyManagementService.filterMyKeys(listOf(thirdPartyCash.owner.owningKey)) val thirdPartyKeys = services.keyManagementService.filterMyKeys(listOf(thirdPartyCash.owner.owningKey))
assertFalse { service.isModifiable(thirdPartyCash, thirdPartyKeys.toSet()) } assertFalse { service.isRelevant(thirdPartyCash, thirdPartyKeys.toSet()) }
} }
// TODO: Unit test linear state relevancy checks // TODO: Unit test linear state relevancy checks
@ -751,19 +751,19 @@ class NodeVaultServiceTest {
services.recordTransactions(StatesToRecord.NONE, listOf(createTx(7, bankOfCorda.party))) services.recordTransactions(StatesToRecord.NONE, listOf(createTx(7, bankOfCorda.party)))
// Test one. // Test one.
// StateModificationStatus is MODIFIABLE by default. This should return two states. // RelevancyStatus is RELEVANT by default. This should return two states.
val resultOne = vaultService.queryBy<DummyState>().states.getNumbers() val resultOne = vaultService.queryBy<DummyState>().states.getNumbers()
assertEquals(setOf(1, 3, 4, 5, 6), resultOne) assertEquals(setOf(1, 3, 4, 5, 6), resultOne)
// Test two. // Test two.
// StateModificationStatus set to NOT_MODIFIABLE. // RelevancyStatus set to NOT_RELEVANT.
val criteriaTwo = VaultQueryCriteria(isModifiable = Vault.StateModificationStatus.NOT_MODIFIABLE) val criteriaTwo = VaultQueryCriteria(isRelevant = Vault.RelevancyStatus.NOT_RELEVANT)
val resultTwo = vaultService.queryBy<DummyState>(criteriaTwo).states.getNumbers() val resultTwo = vaultService.queryBy<DummyState>(criteriaTwo).states.getNumbers()
assertEquals(setOf(4, 5), resultTwo) assertEquals(setOf(4, 5), resultTwo)
// Test three. // Test three.
// StateModificationStatus set to ALL. // RelevancyStatus set to ALL.
val criteriaThree = VaultQueryCriteria(isModifiable = Vault.StateModificationStatus.MODIFIABLE) val criteriaThree = VaultQueryCriteria(isRelevant = Vault.RelevancyStatus.RELEVANT)
val resultThree = vaultService.queryBy<DummyState>(criteriaThree).states.getNumbers() val resultThree = vaultService.queryBy<DummyState>(criteriaThree).states.getNumbers()
assertEquals(setOf(1, 3, 6), resultThree) assertEquals(setOf(1, 3, 6), resultThree)