mirror of
https://github.com/corda/corda.git
synced 2025-04-14 14:37:22 +00:00
Resolve merge conflict
This commit is contained in:
commit
f061235cc8
@ -21,7 +21,7 @@ import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.internal.concurrent.doneFuture
|
||||
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.*
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
@ -117,23 +117,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
|
||||
* state is only _potentially_ modifiable as the contract code may forbid them from performing any actions.
|
||||
* If the querying node is a participant in a state then it is classed as [RELEVANT].
|
||||
*
|
||||
* 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
|
||||
* [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.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* NOTE: Default behaviour is for ALL STATES to be returned as this is how Corda behaved before the introduction of
|
||||
* this query criterion.
|
||||
*/
|
||||
@CordaSerializable
|
||||
enum class StateModificationStatus {
|
||||
MODIFIABLE, NOT_MODIFIABLE, ALL
|
||||
enum class RelevancyStatus {
|
||||
RELEVANT, NOT_RELEVANT, ALL
|
||||
}
|
||||
|
||||
@CordaSerializable
|
||||
@ -171,7 +170,7 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
||||
val notary: AbstractParty?,
|
||||
val lockId: String?,
|
||||
val lockUpdateTime: Instant?,
|
||||
val isModifiable: Vault.StateModificationStatus?
|
||||
val isRelevant: Vault.RelevancyStatus?
|
||||
) {
|
||||
constructor(ref: StateRef,
|
||||
contractStateClassName: String,
|
||||
|
@ -83,7 +83,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
|
||||
abstract class CommonQueryCriteria : QueryCriteria() {
|
||||
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>>?
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
return parser.parseCriteria(this)
|
||||
@ -100,7 +100,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
val notary: List<AbstractParty>? = null,
|
||||
val softLockingCondition: SoftLockingCondition? = null,
|
||||
val timeCondition: TimeCondition? = null,
|
||||
override val isModifiable: Vault.StateModificationStatus = Vault.StateModificationStatus.ALL
|
||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
) : CommonQueryCriteria() {
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
super.visit(parser)
|
||||
@ -135,14 +135,14 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
val externalId: List<String>? = null,
|
||||
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||
override val isModifiable: Vault.StateModificationStatus = Vault.StateModificationStatus.ALL
|
||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
) : CommonQueryCriteria() {
|
||||
constructor(
|
||||
participants: List<AbstractParty>? = null,
|
||||
linearId: List<UniqueIdentifier>? = null,
|
||||
status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||
isRelevant: Vault.StateModificationStatus
|
||||
isRelevant: Vault.RelevancyStatus
|
||||
) : this(participants, linearId?.map { it.id }, linearId?.mapNotNull { it.externalId }, status, contractStateTypes, isRelevant)
|
||||
|
||||
constructor(
|
||||
@ -185,7 +185,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
val issuerRef: List<OpaqueBytes>? = null,
|
||||
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||
override val isModifiable: Vault.StateModificationStatus = Vault.StateModificationStatus.ALL
|
||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
) : CommonQueryCriteria() {
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
super.visit(parser)
|
||||
@ -225,7 +225,7 @@ sealed class QueryCriteria : GenericQueryCriteria<QueryCriteria, IQueryCriteriaP
|
||||
val expression: CriteriaExpression<L, Boolean>,
|
||||
override val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
|
||||
override val contractStateTypes: Set<Class<out ContractState>>? = null,
|
||||
override val isModifiable: Vault.StateModificationStatus = Vault.StateModificationStatus.ALL
|
||||
override val isRelevant: Vault.RelevancyStatus = Vault.RelevancyStatus.ALL
|
||||
) : CommonQueryCriteria() {
|
||||
override fun visit(parser: IQueryCriteriaParser): Collection<Predicate> {
|
||||
super.visit(parser)
|
||||
|
@ -105,7 +105,7 @@ internal class UseRefState(val linearId: UniqueIdentifier) : FlowLogic<SignedTra
|
||||
val notary = serviceHub.networkMapCache.notaryIdentities.first()
|
||||
val query = QueryCriteria.LinearStateQueryCriteria(
|
||||
linearId = listOf(linearId),
|
||||
isRelevant = Vault.StateModificationStatus.ALL
|
||||
isRelevant = Vault.RelevancyStatus.ALL
|
||||
)
|
||||
val referenceState = serviceHub.vaultService.queryBy<ContractState>(query).states.single()
|
||||
return subFlow(FinalityFlow(
|
||||
|
@ -73,11 +73,11 @@ The proposed data structure for the new constraint type is as follows:
|
||||
|
||||
```kotlin
|
||||
data class SignatureAttachmentConstraint(
|
||||
val key: CompositeKey
|
||||
val key: PublicKey
|
||||
) : AttachmentConstraint
|
||||
```
|
||||
|
||||
Therefore if a state advertises this constraint, along with a class name of `com.foo.Bar` then the definition of Bar must reside in an attachment with signatures sufficient to meet the given composite key. Multiple signers of a JAR is useful for decentralised administration of an app that wishes to have a threat model in which one of the app developers may go bad, but not a majority of them. For example there could be a 2-of-3 threshold of {app developer, auditor, R3} in which R3 is legally bound to only sign an upgrade if the auditor is unavailable e.g. has gone bankrupt. However, we anticipate that most constraints will be one-of-one for now.
|
||||
Therefore if a state advertises this constraint, along with a class name of `com.foo.Bar` then the definition of Bar must reside in an attachment with signatures sufficient to meet the given public key. Note that the `key` may be a `CompositeKey` which is fulfilled by multiple signers. Multiple signers of a JAR is useful for decentralised administration of an app that wishes to have a threat model in which one of the app developers may go bad, but not a majority of them. For example there could be a 2-of-3 threshold of {app developer, auditor, R3} in which R3 is legally bound to only sign an upgrade if the auditor is unavailable e.g. has gone bankrupt. However, we anticipate that most constraints will be one-of-one for now.
|
||||
|
||||
We will add a `signers` field to the `ContractAttachment` class that will be filled out at load time if the JAR is signed. The signers will be computed by checking the certificate chain for every file in the JAR, and any unsigned files will cause an exception to be thrown.
|
||||
|
||||
@ -147,9 +147,9 @@ There are some further issues to think through here:
|
||||
3. Indirecting through package names increases centralisation somewhat, because now the zone operator has to agree to you taking ownership of a part of the namespace. This is also a privacy leak, it may expose what apps are being used on the network. *However* what it really exposes is application *developers* and not actual apps, and the zone op doesn't get to veto specific apps once they approved an app developer. More problematically unless an additional indirection is added to the network parameters, every change to the package ownership list requires a "hard fork" acceptance of new parameters.
|
||||
|
||||
|
||||
### Using X.500 names in the constraint instead of CompositeKey
|
||||
### Using X.500 names in the constraint instead of PublicKey
|
||||
|
||||
We advertise a `CompositeKey` in the constraint and *not* a set of `CordaX500Name` objects. This means that apps can be developed by entities that aren't in the network map (i.e. not a part of your zone), and it enables threshold keys, *but* the downside is there's no way to rotate or revoke a compromised key beyond adjusting the states themselves. We lose the indirection-through-identity.
|
||||
We advertise a `PublicKey` (which may be a `CompositeKey`) in the constraint and *not* a set of `CordaX500Name` objects. This means that apps can be developed by entities that aren't in the network map (i.e. not a part of your zone), and it enables threshold keys, *but* the downside is there's no way to rotate or revoke a compromised key beyond adjusting the states themselves. We lose the indirection-through-identity.
|
||||
|
||||
We could introduce such an indirection. This would disconnect the constraint from a particular public key. However then each zone an app is deployed to requires a new JAR signature by the creator, using a certificate issued by the zone operator. Because JARs can be signed by multiple certificates, this is OK, a JAR can be resigned N times if it's to be used in N zones. But it means that effectively zone operators get a power of veto over application developers, increasing centralisation and it increases required logistical efforts.
|
||||
|
||||
|
@ -32,7 +32,7 @@ private fun generateCashSumCriteria(currency: Currency): QueryCriteria {
|
||||
|
||||
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).
|
||||
val ccyCriteria = QueryCriteria.VaultCustomQueryCriteria(ccyIndex, isModifiable = Vault.StateModificationStatus.MODIFIABLE)
|
||||
val ccyCriteria = QueryCriteria.VaultCustomQueryCriteria(ccyIndex, isRelevant = Vault.RelevancyStatus.RELEVANT)
|
||||
return sumCriteria.and(ccyCriteria)
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ private fun generateCashSumsCriteria(): QueryCriteria {
|
||||
orderBy = Sort.Direction.DESC)
|
||||
}
|
||||
// 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> {
|
||||
|
@ -44,13 +44,13 @@ class CashSelectionH2Impl : AbstractCashSelection() {
|
||||
connection.createStatement().use { it.execute("CALL SET(@t, CAST(0 AS BIGINT));") }
|
||||
|
||||
// state_status = 0 -> UNCONSUMED.
|
||||
// is_modifiable = 0 -> MODIFIABLE.
|
||||
// is_relevant = 0 -> RELEVANT.
|
||||
val selectJoin = """
|
||||
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
|
||||
WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index
|
||||
AND vs.state_status = 0
|
||||
AND vs.is_modifiable = 0
|
||||
AND vs.is_relevant = 0
|
||||
AND ccs.ccy_code = ? and @t < ?
|
||||
AND (vs.lock_id = ? OR vs.lock_id is null)
|
||||
""" +
|
||||
|
@ -42,7 +42,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
|
||||
// 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 {
|
||||
// state_status = 0 -> UNCONSUMED.
|
||||
// is_modifiable = 0 -> MODIFIABLE.
|
||||
// is_relevant = 0 -> RELEVANT.
|
||||
val selectJoin = """SELECT nested.transaction_id, nested.output_index, nested.pennies,
|
||||
nested.total+nested.pennies as total_pennies, nested.lock_id
|
||||
FROM
|
||||
@ -52,7 +52,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
|
||||
FROM vault_states AS vs, contract_cash_states AS ccs
|
||||
WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index
|
||||
AND vs.state_status = 0
|
||||
AND vs.is_modifiable = 0
|
||||
AND vs.is_relevant = 0
|
||||
AND ccs.ccy_code = ?
|
||||
AND (vs.lock_id = ? OR vs.lock_id is null)
|
||||
""" +
|
||||
|
@ -59,7 +59,7 @@ class CashSelectionSQLServerImpl : AbstractCashSelection(maxRetries = 16, retryS
|
||||
withIssuerRefs: Set<OpaqueBytes>, withResultSet: (ResultSet) -> Boolean): Boolean {
|
||||
val sb = StringBuilder()
|
||||
// state_status = 0 -> UNCONSUMED.
|
||||
// is_modifiable = 0 -> MODIFIABLE.
|
||||
// is_relevant = 0 -> RELEVANT.
|
||||
sb.append( """
|
||||
;WITH CTE AS
|
||||
(
|
||||
@ -74,7 +74,7 @@ class CashSelectionSQLServerImpl : AbstractCashSelection(maxRetries = 16, retryS
|
||||
ON vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index
|
||||
WHERE
|
||||
vs.state_status = 0
|
||||
AND vs.is_modifiable = 0
|
||||
AND vs.is_relevant = 0
|
||||
AND ccs.ccy_code = ?
|
||||
AND (vs.lock_id = ? OR vs.lock_id IS NULL)
|
||||
"""
|
||||
|
@ -497,16 +497,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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -153,30 +153,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 = now,
|
||||
isModifiable = if (isModifiable) Vault.StateModificationStatus.MODIFIABLE else Vault.StateModificationStatus.NOT_MODIFIABLE,
|
||||
lockId = uuid,
|
||||
lockUpdateTime = if (uuid == null) null else now
|
||||
recordedTime = clock.instant(),
|
||||
isRelevant = if (isRelevant) Vault.RelevancyStatus.RELEVANT else Vault.RelevancyStatus.NOT_RELEVANT
|
||||
)
|
||||
stateToAdd.stateRef = PersistentStateRef(stateAndRef.key)
|
||||
session.save(stateToAdd)
|
||||
@ -234,7 +232,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 {
|
||||
@ -267,7 +265,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
|
||||
}
|
||||
@ -468,13 +466,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)
|
||||
|
||||
@ -498,7 +496,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.
|
||||
@ -581,7 +579,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())}" }
|
||||
|
@ -70,9 +70,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>
|
||||
|
@ -539,17 +539,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
|
||||
@ -761,19 +761,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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user