mirror of
https://github.com/corda/corda.git
synced 2025-06-17 14:48:16 +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:
@ -487,16 +487,16 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
|
||||
}
|
||||
|
||||
// state relevance.
|
||||
if (criteria.isModifiable != Vault.StateModificationStatus.ALL) {
|
||||
val predicateID = Pair(VaultSchemaV1.VaultStates::isModifiable.name, EqualityComparisonOperator.EQUAL)
|
||||
if (criteria.isRelevant != Vault.RelevancyStatus.ALL) {
|
||||
val predicateID = Pair(VaultSchemaV1.VaultStates::isRelevant.name, EqualityComparisonOperator.EQUAL)
|
||||
if (commonPredicates.containsKey(predicateID)) {
|
||||
val existingStatus = ((commonPredicates[predicateID] as ComparisonPredicate).rightHandOperand as LiteralExpression).literal
|
||||
if (existingStatus != criteria.isModifiable) {
|
||||
log.warn("Overriding previous attribute [${VaultSchemaV1.VaultStates::isModifiable.name}] value $existingStatus with ${criteria.status}")
|
||||
commonPredicates.replace(predicateID, criteriaBuilder.equal(vaultStates.get<Vault.StateModificationStatus>(VaultSchemaV1.VaultStates::isModifiable.name), criteria.isModifiable))
|
||||
if (existingStatus != criteria.isRelevant) {
|
||||
log.warn("Overriding previous attribute [${VaultSchemaV1.VaultStates::isRelevant.name}] value $existingStatus with ${criteria.status}")
|
||||
commonPredicates.replace(predicateID, criteriaBuilder.equal(vaultStates.get<Vault.RelevancyStatus>(VaultSchemaV1.VaultStates::isRelevant.name), criteria.isRelevant))
|
||||
}
|
||||
} 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
|
||||
// node. The behaviour is as follows:
|
||||
//
|
||||
// 1) All vault updates marked as MODIFIABLE will, of, course all have isModifiable = true.
|
||||
// 2) For ALL_VISIBLE updates, those which are not modifiable will have isModifiable = false.
|
||||
// 1) All vault updates marked as RELEVANT will, of, course all have isRelevant = true.
|
||||
// 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.
|
||||
//
|
||||
// The same functionality could be obtained by passing in a list of participants to the vault query,
|
||||
// however this:
|
||||
//
|
||||
// * 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
|
||||
//
|
||||
// Adding a new column in the "VaultStates" table was considered the best approach.
|
||||
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(
|
||||
notary = stateAndRef.value.state.notary,
|
||||
contractStateClassName = stateAndRef.value.state.data.javaClass.name,
|
||||
stateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
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)
|
||||
session.save(stateToAdd)
|
||||
@ -188,7 +188,7 @@ class NodeVaultService(
|
||||
val ourNewStates = when (statesToRecord) {
|
||||
StatesToRecord.NONE -> throw AssertionError("Should not reach here")
|
||||
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()
|
||||
}.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 (consumedStateAndRefs, producedStates) = ltx.inputs.zip(ltx.outputs).filter { (_, output) ->
|
||||
if (statesToRecord == StatesToRecord.ONLY_RELEVANT) {
|
||||
isModifiable(output.data, myKeys.toSet())
|
||||
isRelevant(output.data, myKeys.toSet())
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@ -396,13 +396,13 @@ class NodeVaultService(
|
||||
}
|
||||
|
||||
// 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 sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
|
||||
val enrichedCriteria = QueryCriteria.VaultQueryCriteria(
|
||||
contractStateTypes = setOf(contractStateType),
|
||||
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)
|
||||
|
||||
@ -426,7 +426,7 @@ class NodeVaultService(
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
internal fun isModifiable(state: ContractState, myKeys: Set<PublicKey>): Boolean {
|
||||
internal fun isRelevant(state: ContractState, myKeys: Set<PublicKey>): Boolean {
|
||||
val keysToCheck = when (state) {
|
||||
// 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.
|
||||
@ -510,7 +510,7 @@ class NodeVaultService(
|
||||
vaultState.notary,
|
||||
vaultState.lockId,
|
||||
vaultState.lockUpdateTime,
|
||||
vaultState.isModifiable))
|
||||
vaultState.isRelevant))
|
||||
} else {
|
||||
// TODO: improve typing of returned other results
|
||||
log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" }
|
||||
|
@ -60,9 +60,9 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
|
||||
@Column(name = "lock_id", nullable = true)
|
||||
var lockId: String? = null,
|
||||
|
||||
/** Used to determine whether a state is modifiable by the recording node */
|
||||
@Column(name = "is_modifiable", nullable = false)
|
||||
var isModifiable: Vault.StateModificationStatus,
|
||||
/** Used to determine whether a state abides by the relevancy rules of the recording node */
|
||||
@Column(name = "is_relevant", nullable = false)
|
||||
var isRelevant: Vault.RelevancyStatus,
|
||||
|
||||
/** refers to the last time a lock was taken (reserved) or updated (released, re-reserved) */
|
||||
@Column(name = "lock_timestamp", nullable = true)
|
||||
|
@ -3,13 +3,13 @@
|
||||
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
|
||||
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">
|
||||
<changeSet author="R3.Corda" id="add_is_modifiable_column">
|
||||
<changeSet author="R3.Corda" id="add_is_relevant_column">
|
||||
<addColumn tableName="vault_states">
|
||||
<column name="is_modifiable" type="INT"/>
|
||||
<column name="is_relevant" type="INT"/>
|
||||
</addColumn>
|
||||
<update tableName="vault_states">
|
||||
<column name="is_modifiable" valueNumeric="0"/>
|
||||
<column name="is_relevant" valueNumeric="0"/>
|
||||
</update>
|
||||
<addNotNullConstraint tableName="vault_states" columnName="is_modifiable" columnDataType="INT" />
|
||||
<addNotNullConstraint tableName="vault_states" columnName="is_relevant" columnDataType="INT" />
|
||||
</changeSet>
|
||||
</databaseChangeLog>
|
||||
|
@ -529,17 +529,17 @@ class NodeVaultServiceTest {
|
||||
val amount = Amount(1000, Issued(BOC.ref(1), GBP))
|
||||
val wellKnownCash = Cash.State(amount, identity.party)
|
||||
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 anonymousCash = Cash.State(amount, anonymousIdentity.party)
|
||||
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 thirdPartyCash = Cash.State(amount, thirdPartyIdentity)
|
||||
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
|
||||
@ -751,19 +751,19 @@ class NodeVaultServiceTest {
|
||||
services.recordTransactions(StatesToRecord.NONE, listOf(createTx(7, bankOfCorda.party)))
|
||||
|
||||
// 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()
|
||||
assertEquals(setOf(1, 3, 4, 5, 6), resultOne)
|
||||
|
||||
// Test two.
|
||||
// StateModificationStatus set to NOT_MODIFIABLE.
|
||||
val criteriaTwo = VaultQueryCriteria(isModifiable = Vault.StateModificationStatus.NOT_MODIFIABLE)
|
||||
// RelevancyStatus set to NOT_RELEVANT.
|
||||
val criteriaTwo = VaultQueryCriteria(isRelevant = Vault.RelevancyStatus.NOT_RELEVANT)
|
||||
val resultTwo = vaultService.queryBy<DummyState>(criteriaTwo).states.getNumbers()
|
||||
assertEquals(setOf(4, 5), resultTwo)
|
||||
|
||||
// Test three.
|
||||
// StateModificationStatus set to ALL.
|
||||
val criteriaThree = VaultQueryCriteria(isModifiable = Vault.StateModificationStatus.MODIFIABLE)
|
||||
// RelevancyStatus set to ALL.
|
||||
val criteriaThree = VaultQueryCriteria(isRelevant = Vault.RelevancyStatus.RELEVANT)
|
||||
val resultThree = vaultService.queryBy<DummyState>(criteriaThree).states.getNumbers()
|
||||
assertEquals(setOf(1, 3, 6), resultThree)
|
||||
|
||||
|
Reference in New Issue
Block a user