mirror of
https://github.com/corda/corda.git
synced 2024-12-18 20:47:57 +00:00
Revert back to using "relevancy" as "modifiable" states are not permissible by definition on an immutable ledger. (#3847)
This commit is contained in:
parent
f81428eb53
commit
36bfe268af
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -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(
|
||||||
|
@ -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> {
|
||||||
|
@ -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)
|
||||||
""" +
|
""" +
|
||||||
|
@ -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)
|
||||||
""" +
|
""" +
|
||||||
|
@ -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)
|
||||||
"""
|
"""
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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())}" }
|
||||||
|
@ -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)
|
||||||
|
@ -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>
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user