CORDA-2432 - Update documentation for filtering by state relevancy in vault queries (#4597)

* [CORDA-2432] Update documentation for filtering by state relevancy in vault queries

* Add missing file to fix
This commit is contained in:
JamesHR3 2019-01-16 22:10:56 +00:00 committed by Katelyn Baker
parent 3b1929371c
commit 131fcc65de
7 changed files with 128 additions and 14 deletions

View File

@ -86,7 +86,7 @@ There are four implementations of this interface which can be chained together t
1. ``VaultQueryCriteria`` provides filterable criteria on attributes within the Vault states table: status (UNCONSUMED,
CONSUMED), state reference(s), contract state type(s), notaries, soft locked states, timestamps (RECORDED, CONSUMED),
state constraints (see :ref:`Constraint Types <implicit_constraint_types>`).
state constraints (see :ref:`Constraint Types <implicit_constraint_types>`), relevancy (ALL, RELEVANT, NON_RELEVANT).
.. note:: Sensible defaults are defined for frequently used attributes (status = UNCONSUMED, always include soft
locked states).
@ -330,6 +330,14 @@ pages available:
:end-before: DOCEND VaultQueryExample24
:dedent: 8
Query for only relevant states in the vault:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample25
:end-before: DOCEND VaultQueryExample25
:dedent: 8
**LinearState and DealState queries using** ``LinearStateQueryCriteria``:
Query for unconsumed linear states for given linear ids:
@ -364,6 +372,14 @@ Query for unconsumed deal states with deals parties:
:end-before: DOCEND VaultQueryExample11
:dedent: 12
Query for only relevant linear states in the vault:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample26
:end-before: DOCEND VaultQueryExample26
:dedent: 8
**FungibleAsset and DealState queries using** ``FungibleAssetQueryCriteria``:
Query for fungible assets for a given currency:
@ -392,6 +408,14 @@ Query for fungible assets for a specific issuer party:
:end-before: DOCEND VaultQueryExample14
:dedent: 12
Query for only relevant fungible states in the vault:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample27
:end-before: DOCEND VaultQueryExample27
:dedent: 12
**Aggregate Function queries using** ``VaultCustomQueryCriteria``:
.. note:: Query results for aggregate functions are contained in the ``otherResults`` attribute of a results Page.

View File

@ -333,6 +333,15 @@ into shared business logic, but it makes perfect sense to put into a user-specif
If your flows could benefit from being extended in this way, read ":doc:`flow-overriding`" to learn more.
Step 10. Possibly update Vault state queries
--------------------------------------------
Queries made on a node's vault can filter by the relevancy of those states to the node in Corda 4. As this functionality does not exist in
Corda 3, apps targeting that release will continue to receive all states in any vault queries. In Corda 4, the default is to return all
states in the vault, to maintain backwards compatibility. However, it may make sense to migrate queries expecting just those states relevant
to the node in question to query for only relevant states. See :doc:`api-vault-query.rst` for more details on how to do this. Not doing this
may result in queries returning more states than expected if the node is using Observer node functionality (see ":doc:`tutorial-observer-nodes.rst`").
Step 10. Explore other new features that may be useful
------------------------------------------------------

View File

@ -320,6 +320,9 @@ Version 4.0
* Logging for P2P and RPC has been separated, to make it easier to enable all P2P or RPC logging without hand-picking loggers for individual classes.
* Vault Query Criteria have been enhanced to allow filtering by state relevancy. Queries can request all states, just relevant ones, or just non relevant ones. The default is to return all states, to maintain backwards compatibility.
Note that this means apps running on nodes using Observer node functionality should update their queries to request only relevant states if they are only expecting to see states in which they participate.
Version 3.3
-----------

View File

@ -16,7 +16,6 @@ import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.*
import net.corda.core.utilities.*
import net.corda.node.cordapp.CordappLoader
import net.corda.node.services.api.SchemaService
import net.corda.node.services.api.VaultServiceInternal
import net.corda.node.services.schema.PersistentStateService

View File

@ -7,6 +7,7 @@ import net.corda.core.crypto.NullKeys
import net.corda.core.crypto.generateKeyPair
import net.corda.core.identity.*
import net.corda.core.internal.NotaryChangeTransactionBuilder
import net.corda.core.internal.cordapp.CordappResolver
import net.corda.core.internal.packageName
import net.corda.core.node.NotaryInfo
import net.corda.core.node.StatesToRecord
@ -681,7 +682,7 @@ class NodeVaultServiceTest {
fun observerMode() {
fun countCash(): Long {
return database.transaction {
vaultService.queryBy(Cash.State::class.java, QueryCriteria.VaultQueryCriteria(), PageSpecification(1)).totalStatesAvailable
vaultService.queryBy(Cash.State::class.java, QueryCriteria.VaultQueryCriteria(relevancyStatus = Vault.RelevancyStatus.ALL), PageSpecification(1)).totalStatesAvailable
}
}
val currentCashStates = countCash()
@ -775,7 +776,7 @@ class NodeVaultServiceTest {
services.recordTransactions(StatesToRecord.NONE, listOf(createTx(7, bankOfCorda.party)))
// Test one.
// RelevancyStatus is RELEVANT by default. This should return two states.
// RelevancyStatus is ALL by default. This should return five states.
val resultOne = vaultService.queryBy<DummyState>().states.getNumbers()
assertEquals(setOf(1, 3, 4, 5, 6), resultOne)
@ -786,7 +787,7 @@ class NodeVaultServiceTest {
assertEquals(setOf(4, 5), resultTwo)
// Test three.
// RelevancyStatus set to ALL.
// RelevancyStatus set to RELEVANT.
val criteriaThree = VaultQueryCriteria(relevancyStatus = Vault.RelevancyStatus.RELEVANT)
val resultThree = vaultService.queryBy<DummyState>(criteriaThree).states.getNumbers()
assertEquals(setOf(1, 3, 6), resultThree)
@ -855,6 +856,35 @@ class NodeVaultServiceTest {
})
}
@Test
fun `V3 vault queries return all states by default`() {
fun createTx(number: Int, vararg participants: Party): SignedTransaction {
return services.signInitialTransaction(TransactionBuilder(DUMMY_NOTARY).apply {
addOutputState(DummyState(number, participants.toList()), DummyContract.PROGRAM_ID)
addCommand(DummyCommandData, listOf(megaCorp.publicKey))
})
}
fun List<StateAndRef<DummyState>>.getNumbers() = map { it.state.data.magicNumber }.toSet()
CordappResolver.withCordapp(targetPlatformVersion = 3) {
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx(1, megaCorp.party)))
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx(2, miniCorp.party)))
services.recordTransactions(StatesToRecord.ONLY_RELEVANT, listOf(createTx(3, miniCorp.party, megaCorp.party)))
services.recordTransactions(StatesToRecord.ALL_VISIBLE, listOf(createTx(4, miniCorp.party)))
services.recordTransactions(StatesToRecord.ALL_VISIBLE, listOf(createTx(5, bankOfCorda.party)))
services.recordTransactions(StatesToRecord.ALL_VISIBLE, listOf(createTx(6, megaCorp.party, bankOfCorda.party)))
services.recordTransactions(StatesToRecord.NONE, listOf(createTx(7, bankOfCorda.party)))
// Test one.
// RelevancyStatus is ALL by default. This should return five states.
val resultOne = vaultService.queryBy<DummyState>().states.getNumbers()
assertEquals(setOf(1, 3, 4, 5, 6), resultOne)
}
// We should never see 2 or 7.
}
@Test
@Ignore
fun `trackByCriteria filters updates and snapshots`() {

View File

@ -7,6 +7,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.packageName
import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.*
import net.corda.core.node.services.Vault.ConstraintInfo.Type.*
import net.corda.core.node.services.vault.*
@ -831,6 +832,45 @@ abstract class VaultQueryTestsBase : VaultQueryParties {
}
}
@Test
fun `state relevancy queries`() {
database.transaction {
vaultFiller.fillWithSomeTestDeals(listOf("123", "456", "789"), includeMe = true)
vaultFiller.fillWithSomeTestDeals(listOf("ABC", "DEF", "GHI"), includeMe = false)
vaultFillerCashNotary.fillWithSomeTestCash(100.DOLLARS, notaryServices, 10, DUMMY_CASH_ISSUER, statesToRecord = StatesToRecord.ALL_VISIBLE)
vaultFillerCashNotary.fillWithSomeTestCash(100.DOLLARS, notaryServices, 10, DUMMY_CASH_ISSUER, charlie.party, statesToRecord = StatesToRecord.ALL_VISIBLE)
vaultFiller.fillWithSomeTestLinearStates(1, "XYZ", includeMe = true)
vaultFiller.fillWithSomeTestLinearStates(2, "JKL", includeMe = false)
val dealStates = vaultService.queryBy<DummyDealContract.State>().states
assertThat(dealStates).hasSize(6)
//DOCSTART VaultQueryExample25
val relevancyAllCriteria = VaultQueryCriteria(relevancyStatus = Vault.RelevancyStatus.RELEVANT)
val allDealStateCount = vaultService.queryBy<DummyDealContract.State>(relevancyAllCriteria).states
//DOCEND VaultQueryExample25
assertThat(allDealStateCount).hasSize(3)
val cashStates = vaultService.queryBy<Cash.State>().states
assertThat(cashStates).hasSize(20)
//DOCSTART VaultQueryExample27
val allCashCriteria = FungibleStateQueryCriteria(relevancyStatus = Vault.RelevancyStatus.RELEVANT)
val allCashStates = vaultService.queryBy<Cash.State>(allCashCriteria).states
//DOCEND VaultQueryExample27
assertThat(allCashStates).hasSize(10)
val linearStates = vaultService.queryBy<DummyLinearContract.State>().states
assertThat(linearStates).hasSize(3)
//DOCSTART VaultQueryExample26
val allLinearStateCriteria = LinearStateQueryCriteria(relevancyStatus = Vault.RelevancyStatus.RELEVANT)
val allLinearStates = vaultService.queryBy<DummyLinearContract.State>(allLinearStateCriteria).states
//DOCEND VaultQueryExample26
assertThat(allLinearStates).hasSize(1)
}
}
@Test
fun `logical operator EQUAL`() {
database.transaction {

View File

@ -7,6 +7,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.node.ServiceHub
import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.Vault
import net.corda.core.toFuture
import net.corda.core.transactions.SignedTransaction
@ -72,20 +73,23 @@ class VaultFiller @JvmOverloads constructor(
@JvmOverloads
fun fillWithSomeTestDeals(dealIds: List<String>,
issuerServices: ServiceHub = services,
participants: List<AbstractParty> = emptyList()): Vault<DealState> {
participants: List<AbstractParty> = emptyList(),
includeMe: Boolean = true): Vault<DealState> {
val myKey: PublicKey = services.myInfo.chooseIdentity().owningKey
val me = AnonymousParty(myKey)
val participantsToUse = if (includeMe) participants.plus(me) else participants
val transactions: List<SignedTransaction> = dealIds.map {
// Issue a deal state
val dummyIssue = TransactionBuilder(notary = defaultNotary.party).apply {
addOutputState(DummyDealContract.State(ref = it, participants = participants.plus(me)), DUMMY_DEAL_PROGRAM_ID)
addOutputState(DummyDealContract.State(ref = it, participants = participantsToUse), DUMMY_DEAL_PROGRAM_ID)
addCommand(dummyCommand())
}
val stx = issuerServices.signInitialTransaction(dummyIssue)
return@map services.addSignature(stx, defaultNotary.publicKey)
}
services.recordTransactions(transactions)
val statesToRecord = if (includeMe) StatesToRecord.ONLY_RELEVANT else StatesToRecord.ALL_VISIBLE
services.recordTransactions(statesToRecord, transactions)
// Get all the StateAndRefs of all the generated transactions.
val states = transactions.flatMap { stx ->
stx.tx.outputs.indices.map { i -> stx.tx.outRef<DealState>(i) }
@ -103,17 +107,19 @@ class VaultFiller @JvmOverloads constructor(
linearNumber: Long = 0L,
linearBoolean: Boolean = false,
linearTimestamp: Instant = now(),
constraint: AttachmentConstraint = AutomaticPlaceholderConstraint): Vault<LinearState> {
constraint: AttachmentConstraint = AutomaticPlaceholderConstraint,
includeMe: Boolean = true): Vault<LinearState> {
val myKey: PublicKey = services.myInfo.chooseIdentity().owningKey
val me = AnonymousParty(myKey)
val issuerKey = defaultNotary.keyPair
val signatureMetadata = SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(issuerKey.public).schemeNumberID)
val participantsToUse = if (includeMe) participants.plus(me) else participants
val transactions: List<SignedTransaction> = (1..numberToCreate).map {
// Issue a Linear state
val dummyIssue = TransactionBuilder(notary = defaultNotary.party).apply {
addOutputState(DummyLinearContract.State(
linearId = uniqueIdentifier ?: UniqueIdentifier(externalId),
participants = participants.plus(me),
participants = participantsToUse,
linearString = linearString,
linearNumber = linearNumber,
linearBoolean = linearBoolean,
@ -123,7 +129,8 @@ class VaultFiller @JvmOverloads constructor(
}
return@map services.signInitialTransaction(dummyIssue).withAdditionalSignature(issuerKey, signatureMetadata)
}
services.recordTransactions(transactions)
val statesToRecord = if (includeMe) StatesToRecord.ONLY_RELEVANT else StatesToRecord.ALL_VISIBLE
services.recordTransactions(statesToRecord, transactions)
// Get all the StateAndRefs of all the generated transactions.
val states = transactions.flatMap { stx ->
stx.tx.outputs.indices.map { i -> stx.tx.outRef<LinearState>(i) }
@ -174,7 +181,8 @@ class VaultFiller @JvmOverloads constructor(
thisManyStates: Int,
issuedBy: PartyAndReference,
owner: AbstractParty? = null,
rng: Random? = null) = fillWithSomeTestCash(howMuch, issuerServices, thisManyStates, thisManyStates, issuedBy, owner, rng)
rng: Random? = null,
statesToRecord: StatesToRecord = StatesToRecord.ONLY_RELEVANT) = fillWithSomeTestCash(howMuch, issuerServices, thisManyStates, thisManyStates, issuedBy, owner, rng, statesToRecord)
/**
* Creates a random set of between (by default) 3 and 10 cash states that add up to the given amount and adds them
@ -190,7 +198,8 @@ class VaultFiller @JvmOverloads constructor(
atMostThisManyStates: Int,
issuedBy: PartyAndReference,
owner: AbstractParty? = null,
rng: Random? = null): Vault<Cash.State> {
rng: Random? = null,
statesToRecord: StatesToRecord = StatesToRecord.ONLY_RELEVANT): Vault<Cash.State> {
val amounts = calculateRandomlySizedAmounts(howMuch, atLeastThisManyStates, atMostThisManyStates, rng ?: rngFactory())
// We will allocate one state to one transaction, for simplicities sake.
val cash = Cash()
@ -199,7 +208,7 @@ class VaultFiller @JvmOverloads constructor(
cash.generateIssue(issuance, Amount(pennies, Issued(issuedBy, howMuch.token)), owner ?: services.myInfo.singleIdentity(), altNotary)
return@map issuerServices.signInitialTransaction(issuance, issuedBy.party.owningKey)
}
services.recordTransactions(transactions)
services.recordTransactions(statesToRecord, transactions)
// Get all the StateRefs of all the generated transactions.
val states = transactions.flatMap { stx ->
stx.tx.outputs.indices.map { i -> stx.tx.outRef<Cash.State>(i) }