Vault Query API design (#522)

* Added queryBy(QueryCriteria) Vault API and Junit tests.

* Minor fix following rebase.

* Spit out Vault Query tests into separate source file.

* WIP

* Enable composition of QueryCriteria specifications.
Additional JUnit test cases to validate API.

* Added Deprecating annotations.
Added QueryCriteria for set of contractStateTypes

* Minor tweaks and additional JUnit test cases (chain of linear id)

* Added Java Junit tests and QueryCriteria builder support.

* Added API documentation (including coding snippets and examples).

* Added @JvmOverloads to QueryCriteria classes for easy of use from Java.

* Refactored QueryCriteria API to use composition via sealed data classes.

* Enable infix notation.

* Fixed typo.

* Clarified future work to enforce DB level permissioning.

* Moved PageSpec and Order from QueryCriteria to become parameters of Query itself.

* Moved PageSpec and Order from QueryCriteria to become parameters of Query itself.

* TokenType now specified as set of <Class> (was non extensible enum).

* Exposed new Vault Query API functions via RPC.

* Fixed compiler error in java test.

* Addressed a couple of minor PR review scomments from MH.

* Major updates following PR discussion and recommendations.

* All pagination and sorting arguments are optional (and constructed with sensible defaults).
Added Java helper functions for queryBy and trackBy interfaces.
Added Java trackBy unit tests.
Miscellaneous cleanup.

* Added Generic Index schema mapping and query support.

* Query criteria referencing Party now references a String (until Identity framework built out).
Added participants attribute to general query criteria.

* Fleshed our IndexCriteria including PR recommendation to define column aliases for index mappings.

* Removed all directly exposed API dependencies on requery.

* Updated documentation.

* Provide sensible defaults for all Query arguments.
Add RPC Java helpers and increase range of Vault Service helpers.

* Further improvements (upgrading notes) and updates to documentation.

* RST documentation updates.

* Updates to address RP latest set of review comments.

* Updates to address MH latest set of review comments.

* Updated to highlight use of VaultIndexQueryCriteria to directly reference a JPA-annotated entity (versus the indirect, explicitly mapped attribute to GenericIndexSchema approach)

* Aesthetic updates requested by MH

* Reverted Indexing approach: removed all references to VaultIndexedQueryCriteria and GenericVaultIndexSchemaV1 scheme.

* Final clean-up and minor updates prior to merge.

* Fixed compiler warnings (except deprecation warnings)

* Reverted all changes to Vault Schemas (except simple illustrative VaultLinearState used in VaultQueryTests)

* Reverted all changes to Vault Schemas (except simple illustrative VaultLinearState used in VaultQueryTests)

* Commented out @Deprecated annotations (as a hedge against us releasing M12 with the work half-done)

* Renamed RPC JavaHelper functions as RPCDispatcher does not allow more than one method with same name.
This commit is contained in:
josecoll 2017-05-05 15:14:43 +01:00 committed by GitHub
parent c062d48e6e
commit 8c3b9ac589
20 changed files with 1918 additions and 84 deletions

View File

@ -15,6 +15,9 @@ import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import org.bouncycastle.asn1.x500.X500Name
@ -65,10 +68,64 @@ interface CordaRPCOps : RPCOps {
@RPCReturnsObservables
fun stateMachinesAndUpdates(): Pair<List<StateMachineInfo>, Observable<StateMachineUpdate>>
/**
* Returns a snapshot of vault states for a given query criteria (and optional order and paging specification)
*
* Generic vault query function which takes a [QueryCriteria] object to define filters,
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
* and returns a [Vault.Page] object containing the following:
* 1. states as a List of <StateAndRef> (page number and size defined by [PageSpecification])
* 2. states metadata as a List of [Vault.StateMetadata] held in the Vault States table.
* 3. the [PageSpecification] used in the query
* 4. a total number of results available (for subsequent paging if necessary)
*
* Note: a default [PageSpecification] is applied to the query returning the 1st page (indexed from 0) with up to 200 entries.
* It is the responsibility of the Client to request further pages and/or specify a more suitable [PageSpecification].
*/
// DOCSTART VaultQueryByAPI
fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
paging: PageSpecification = PageSpecification(),
sorting: Sort = Sort(emptySet())): Vault.Page<T>
// DOCEND VaultQueryByAPI
/**
* Returns a snapshot (as per queryBy) and an observable of future updates to the vault for the given query criteria.
*
* Generic vault query function which takes a [QueryCriteria] object to define filters,
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
* and returns a [Vault.PageAndUpdates] object containing
* 1) a snapshot as a [Vault.Page] (described previously in [queryBy])
* 2) an [Observable] of [Vault.Update]
*
* Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function.
* the [QueryCriteria] applies to both snapshot and deltas (streaming updates).
*/
// DOCSTART VaultTrackByAPI
@RPCReturnsObservables
fun <T : ContractState> vaultTrackBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
paging: PageSpecification = PageSpecification(),
sorting: Sort = Sort(emptySet())): Vault.PageAndUpdates<T>
// DOCEND VaultTrackByAPI
// Note: cannot apply @JvmOverloads to interfaces nor interface implementations
// Java Helpers
// DOCSTART VaultQueryAPIJavaHelpers
fun <T : ContractState> vaultQueryByCriteria(criteria: QueryCriteria): Vault.Page<T> = vaultQueryBy(criteria = criteria)
fun <T : ContractState> vaultQueryByWithPagingSpec(criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> = vaultQueryBy(criteria, paging = paging)
fun <T : ContractState> vaultQueryByWithSorting(criteria: QueryCriteria, sorting: Sort): Vault.Page<T> = vaultQueryBy(criteria, sorting = sorting)
fun <T : ContractState> vaultTrackByCriteria(criteria: QueryCriteria): Vault.PageAndUpdates<T> = vaultTrackBy(criteria = criteria)
fun <T : ContractState> vaultTrackByWithPagingSpec(criteria: QueryCriteria, paging: PageSpecification): Vault.PageAndUpdates<T> = vaultTrackBy(criteria, paging = paging)
fun <T : ContractState> vaultTrackByWithSorting(criteria: QueryCriteria, sorting: Sort): Vault.PageAndUpdates<T> = vaultTrackBy(criteria, sorting = sorting)
// DOCEND VaultQueryAPIJavaHelpers
/**
* Returns a pair of head states in the vault and an observable of future updates to the vault.
*/
@RPCReturnsObservables
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("vaultTrackBy(QueryCriteria())"))
fun vaultAndUpdates(): Pair<List<StateAndRef<ContractState>>, Observable<Vault.Update>>
/**

View File

@ -5,6 +5,9 @@ import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.contracts.*
import net.corda.core.crypto.*
import net.corda.core.flows.FlowException
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.toFuture
@ -16,6 +19,7 @@ import java.io.InputStream
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.time.Instant
import java.util.*
/**
@ -88,8 +92,38 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
}
enum class StateStatus {
UNCONSUMED, CONSUMED
UNCONSUMED, CONSUMED, ALL
}
/**
* Returned in queries [VaultService.queryBy] and [VaultService.trackBy].
* A Page contains:
* 1) a [List] of actual [StateAndRef] requested by the specified [QueryCriteria] to a maximum of [MAX_PAGE_SIZE]
* 2) a [List] of associated [Vault.StateMetadata], one per [StateAndRef] result
* 3) the [PageSpecification] definition used to bound this result set
* 4) a total number of states that met the given [QueryCriteria]
* Note that this may be more than the specified [PageSpecification.pageSize], and should be used to perform
* further pagination (by issuing new queries).
*/
@CordaSerializable
data class Page<out T : ContractState>(val states: List<StateAndRef<T>>,
val statesMetadata: List<Vault.StateMetadata>,
val pageable: PageSpecification,
val totalStatesAvailable: Long)
@CordaSerializable
data class StateMetadata(val ref: StateRef,
val contractStateClassName: String,
val recordedTime: Instant,
val consumedTime: Instant?,
val status: Vault.StateStatus,
val notaryName: String,
val notaryKey: String,
val lockId: String?,
val lockUpdateTime: Instant?)
@CordaSerializable
data class PageAndUpdates<out T : ContractState> (val current: Vault.Page<T>, val future: Observable<Vault.Update>? = null)
}
/**
@ -129,12 +163,58 @@ interface VaultService {
* Atomically get the current vault and a stream of updates. Note that the Observable buffers updates until the
* first subscriber is registered so as to avoid racing with early updates.
*/
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("trackBy(QueryCriteria())"))
fun track(): Pair<Vault<ContractState>, Observable<Vault.Update>>
// DOCSTART VaultQueryAPI
/**
* Generic vault query function which takes a [QueryCriteria] object to define filters,
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
* and returns a [Vault.Page] object containing the following:
* 1. states as a List of <StateAndRef> (page number and size defined by [PageSpecification])
* 2. states metadata as a List of [Vault.StateMetadata] held in the Vault States table.
* 3. the [PageSpecification] used in the query
* 4. a total number of results available (for subsequent paging if necessary)
*
* Note: a default [PageSpecification] is applied to the query returning the 1st page (indexed from 0) with up to 200 entries.
* It is the responsibility of the Client to request further pages and/or specify a more suitable [PageSpecification].
*/
fun <T : ContractState> queryBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
paging: PageSpecification = PageSpecification(),
sorting: Sort = Sort(emptySet())): Vault.Page<T>
/**
* Generic vault query function which takes a [QueryCriteria] object to define filters,
* optional [PageSpecification] and optional [Sort] modification criteria (default unsorted),
* and returns a [Vault.PageAndUpdates] object containing
* 1) a snapshot as a [Vault.Page] (described previously in [queryBy])
* 2) an [Observable] of [Vault.Update]
*
* Notes: the snapshot part of the query adheres to the same behaviour as the [queryBy] function.
* the [QueryCriteria] applies to both snapshot and deltas (streaming updates).
*/
fun <T : ContractState> trackBy(criteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(),
paging: PageSpecification = PageSpecification(),
sorting: Sort = Sort(emptySet())): Vault.PageAndUpdates<T>
// DOCEND VaultQueryAPI
// Note: cannot apply @JvmOverloads to interfaces nor interface implementations
// Java Helpers
fun <T : ContractState> queryBy(): Vault.Page<T> = queryBy()
fun <T : ContractState> queryBy(criteria: QueryCriteria): Vault.Page<T> = queryBy(criteria)
fun <T : ContractState> queryBy(criteria: QueryCriteria, paging: PageSpecification): Vault.Page<T> = queryBy(criteria, paging)
fun <T : ContractState> queryBy(criteria: QueryCriteria, sorting: Sort): Vault.Page<T> = queryBy(criteria, sorting)
fun <T : ContractState> trackBy(): Vault.PageAndUpdates<T> = trackBy()
fun <T : ContractState> trackBy(criteria: QueryCriteria): Vault.PageAndUpdates<T> = trackBy(criteria)
fun <T : ContractState> trackBy(criteria: QueryCriteria, paging: PageSpecification): Vault.PageAndUpdates<T> = trackBy(criteria, paging)
fun <T : ContractState> trackBy(criteria: QueryCriteria, sorting: Sort): Vault.PageAndUpdates<T> = trackBy(criteria, Sort(emptySet()))
/**
* Return unconsumed [ContractState]s for a given set of [StateRef]s
* TODO: revisit and generalize this exposed API function.
*/
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("queryBy(VaultQueryCriteria(stateRefs = listOf(<StateRef>)))"))
fun statesForRefs(refs: List<StateRef>): Map<StateRef, TransactionState<*>?>
/**
@ -212,6 +292,8 @@ interface VaultService {
* Return [ContractState]s of a given [Contract] type and [Iterable] of [Vault.StateStatus].
* Optionally may specify whether to include [StateRef] that have been marked as soft locked (default is true)
*/
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("queryBy(QueryCriteria())"))
fun <T : ContractState> states(clazzes: Set<Class<T>>, statuses: EnumSet<Vault.StateStatus>, includeSoftLockedStates: Boolean = true): Iterable<StateAndRef<T>>
// DOCEND VaultStatesQuery
@ -257,17 +339,25 @@ interface VaultService {
fun <T : ContractState> unconsumedStatesForSpending(amount: Amount<Currency>, onlyFromIssuerParties: Set<AbstractParty>? = null, notary: Party? = null, lockId: UUID, withIssuerRefs: Set<OpaqueBytes>? = null): List<StateAndRef<T>>
}
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("queryBy(VaultQueryCriteria())"))
inline fun <reified T : ContractState> VaultService.unconsumedStates(includeSoftLockedStates: Boolean = true): Iterable<StateAndRef<T>> =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED), includeSoftLockedStates)
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("queryBy(VaultQueryCriteria(status = Vault.StateStatus.CONSUMED))"))
inline fun <reified T : ContractState> VaultService.consumedStates(): Iterable<StateAndRef<T>> =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.CONSUMED))
/** Returns the [linearState] heads only when the type of the state would be considered an 'instanceof' the given type. */
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("queryBy(LinearStateQueryCriteria(linearId = listOf(<UniqueIdentifier>)))"))
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() =
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
.associateBy { it.state.data.linearId }.mapValues { it.value }
// TODO: Remove this from the interface
// @Deprecated("This function will be removed in a future milestone", ReplaceWith("queryBy(LinearStateQueryCriteria(dealPartyName = listOf(<String>)))"))
inline fun <reified T : DealState> VaultService.dealsWith(party: AbstractParty) = linearHeadsOfType<T>().values.filter {
it.state.data.parties.any { it == party }
}

View File

@ -0,0 +1,85 @@
package net.corda.core.node.services.vault
import net.corda.core.contracts.Commodity
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.QueryCriteria.AndComposition
import net.corda.core.node.services.vault.QueryCriteria.OrComposition
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.OpaqueBytes
import java.time.Instant
import java.util.*
/**
* Indexing assumptions:
* QueryCriteria assumes underlying schema tables are correctly indexed for performance.
*/
@CordaSerializable
sealed class QueryCriteria {
/**
* VaultQueryCriteria: provides query by attributes defined in [VaultSchema.VaultStates]
*/
data class VaultQueryCriteria @JvmOverloads constructor (
val status: Vault.StateStatus = Vault.StateStatus.UNCONSUMED,
val stateRefs: List<StateRef>? = null,
val contractStateTypes: Set<Class<out ContractState>>? = null,
val notaryName: List<String>? = null,
val includeSoftlockedStates: Boolean? = true,
val timeCondition: Logical<TimeInstantType, Array<Instant>>? = null,
val participantIdentities: List<String>? = null) : QueryCriteria()
/**
* LinearStateQueryCriteria: provides query by attributes defined in [VaultSchema.VaultLinearState]
*/
data class LinearStateQueryCriteria @JvmOverloads constructor(
val linearId: List<UniqueIdentifier>? = null,
val latestOnly: Boolean? = true,
val dealRef: List<String>? = null,
val dealPartyName: List<String>? = null) : QueryCriteria()
/**
* FungibleStateQueryCriteria: provides query by attributes defined in [VaultSchema.VaultFungibleState]
*
* Valid TokenType implementations defined by Amount<T> are
* [Currency] as used in [Cash] contract state
* [Commodity] as used in [CommodityContract] state
*/
data class FungibleAssetQueryCriteria @JvmOverloads constructor(
val ownerIdentity: List<String>? = null,
val quantity: Logical<*,Long>? = null,
val tokenType: List<Class<out Any>>? = null,
val tokenValue: List<String>? = null,
val issuerPartyName: List<String>? = null,
val issuerRef: List<OpaqueBytes>? = null,
val exitKeyIdentity: List<String>? = null) : QueryCriteria()
/**
* VaultCustomQueryCriteria: provides query by custom attributes defined in a contracts
* [QueryableState] implementation.
* (see Persistence documentation for more information)
*
* Params
* [indexExpression] refers to a (composable) JPA Query like WHERE expression clauses of the form:
* [JPA entityAttributeName] [Operand] [Value]
*
* Refer to [CommercialPaper.State] for a concrete example.
*/
data class VaultCustomQueryCriteria<L,R>(val indexExpression: Logical<L,R>? = null) : QueryCriteria()
// enable composition of [QueryCriteria]
data class AndComposition(val a: QueryCriteria, val b: QueryCriteria): QueryCriteria()
data class OrComposition(val a: QueryCriteria, val b: QueryCriteria): QueryCriteria()
// timestamps stored in the vault states table [VaultSchema.VaultStates]
@CordaSerializable
enum class TimeInstantType {
RECORDED,
CONSUMED
}
}
infix fun QueryCriteria.and(criteria: QueryCriteria): QueryCriteria = AndComposition(this, criteria)
infix fun QueryCriteria.or(criteria: QueryCriteria): QueryCriteria = OrComposition(this, criteria)

View File

@ -0,0 +1,113 @@
package net.corda.core.node.services.vault
import net.corda.core.serialization.CordaSerializable
@CordaSerializable
enum class Operator {
AND,
OR,
EQUAL,
NOT_EQUAL,
LESS_THAN,
LESS_THAN_OR_EQUAL,
GREATER_THAN,
GREATER_THAN_OR_EQUAL,
IN,
NOT_IN,
LIKE,
NOT_LIKE,
BETWEEN,
IS_NULL,
NOT_NULL
}
interface Condition<L, R> {
val leftOperand: L
val operator: Operator
val rightOperand: R
}
interface AndOr<out Q> {
infix fun <V> and(condition: Condition<V, *>): Q
infix fun <V> or(condition: Condition<V, *>): Q
}
@CordaSerializable
sealed class Logical<L, R> : Condition<L, R>, AndOr<Logical<*, *>>
class LogicalExpression<L, R>(leftOperand: L,
operator: Operator,
rightOperand: R? = null) : Logical<L, R>() {
init {
if (rightOperand == null) {
check(operator in setOf(Operator.NOT_NULL, Operator.IS_NULL),
{ "Must use a unary operator (${Operator.IS_NULL}, ${Operator.NOT_NULL}) if right operand is null"} )
}
else {
check(operator !in setOf(Operator.NOT_NULL, Operator.IS_NULL),
{ "Cannot use a unary operator (${Operator.IS_NULL}, ${Operator.NOT_NULL}) if right operand is not null"} )
}
}
override fun <V> and(condition: Condition<V, *>): Logical<*, *> = LogicalExpression(this, Operator.AND, condition)
override fun <V> or(condition: Condition<V, *>): Logical<*, *> = LogicalExpression(this, Operator.OR, condition)
override val operator: Operator = operator
override val rightOperand: R = rightOperand as R
override val leftOperand: L = leftOperand
}
/**
* Pagination and Ordering
*
* Provide simple ability to specify an offset within a result set and the number of results to
* return from that offset (eg. page size) together with (optional) sorting criteria at column level.
*
* Note: it is the responsibility of the calling client to manage page windows.
*
* For advanced pagination it is recommended you utilise standard JPA query frameworks such as
* Spring Data's JPARepository which extends the [PagingAndSortingRepository] interface to provide
* paging and sorting capability:
* https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html
*/
val DEFAULT_PAGE_NUM = 0L
val DEFAULT_PAGE_SIZE = 200L
/**
* Note: this maximum size will be configurable in future (to allow for large JVM heap sized node configurations)
* Use [PageSpecification] to correctly handle a number of bounded pages of [MAX_PAGE_SIZE].
*/
val MAX_PAGE_SIZE = 512L
/**
* PageSpecification allows specification of a page number (starting from 0 as default) and page size (defaulting to
* [DEFAULT_PAGE_SIZE] with a maximum page size of [DEFAULT_PAGE_SIZE]
*/
@CordaSerializable
data class PageSpecification(val pageNumber: Long = DEFAULT_PAGE_NUM, val pageSize: Long = DEFAULT_PAGE_SIZE)
/**
* Sort allows specification of a set of entity attribute names and their associated directionality
* and null handling, to be applied upon processing a query specification.
*/
@CordaSerializable
data class Sort(val columns: Collection<SortColumn>) {
@CordaSerializable
enum class Direction {
ASC,
DESC
}
@CordaSerializable
enum class NullHandling {
NULLS_FIRST,
NULLS_LAST
}
/**
* [columnName] should reference a persisted entity attribute name as defined by the associated mapped schema
* (for example, [VaultSchema.VaultStates::txId.name])
*/
@CordaSerializable
data class SortColumn(val columnName: String, val direction: Sort.Direction = Sort.Direction.ASC,
val nullHandling: Sort.NullHandling = if (direction == Sort.Direction.ASC) Sort.NullHandling.NULLS_LAST else Sort.NullHandling.NULLS_FIRST)
}

View File

@ -46,7 +46,7 @@ Note the following:
* the vault performs fungible state spending (and in future, fungible state optimisation management including merging, splitting and re-issuance)
* vault extensions represent additional custom plugin code a developer may write to query specific custom contract state attributes.
* customer "Off Ledger" (private store) represents internal organisational data that may be joined with the vault data to perform additional reporting or processing
* a vault query API is exposed to developers using standard Corda RPC and CorDapp plugin mechanisms
* a :doc:`vault-query` API is exposed to developers using standard Corda RPC and CorDapp plugin mechanisms
* a vault update API is internally used by transaction recording flows.
* the vault database schemas are directly accessible via JDBC for customer joins and queries

339
docs/source/vault-query.rst Normal file
View File

@ -0,0 +1,339 @@
Vault Query
===========
Corda has been architected from the ground up to encourage usage of industry standard, proven query frameworks and libraries for accessing RDBMS backed transactional stores (including the Vault).
Corda provides a number of flexible query mechanisms for accessing the Vault:
- Vault Query API
- custom JPA_/JPQL_ queries
- custom 3rd party Data Access frameworks such as `Spring Data <http://projects.spring.io/spring-data>`_
The majority of query requirements can be satified by using the Vault Query API, which is exposed via the ``VaultService`` for use directly by flows:
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/node/services/Services.kt
:language: kotlin
:start-after: DOCSTART VaultQueryAPI
:end-before: DOCEND VaultQueryAPI
and via ``CordaRPCOps`` for use by RPC client applications:
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
:language: kotlin
:start-after: DOCSTART VaultQueryByAPI
:end-before: DOCEND VaultQueryByAPI
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
:language: kotlin
:start-after: DOCSTART VaultTrackByAPI
:end-before: DOCEND VaultTrackByAPI
Java helper methods are also provided with default values for arguments:
.. literalinclude:: ../../core/src/main/kotlin/net/corda/core/messaging/CordaRPCOps.kt
:language: kotlin
:start-after: DOCSTART VaultQueryAPIJavaHelpers
:end-before: DOCEND VaultQueryAPIJavaHelpers
The API provides both static (snapshot) and dynamic (snapshot with streaming updates) methods for a defined set of filter criteria.
- Use ``queryBy`` to obtain a only current snapshot of data (for a given ``QueryCriteria``)
- Use ``trackBy`` to obtain a both a current snapshot and a future stream of updates (for a given ``QueryCriteria``)
Simple pagination (page number and size) and sorting (directional ordering, null handling, custom property sort) is also specifiable.
Defaults are defined for Paging (pageNumber = 0, pageSize = 200) and Sorting (direction = ASC, nullHandling = NULLS_LAST).
The ``QueryCriteria`` interface provides a flexible mechanism for specifying different filtering criteria, including and/or composition and a rich set of logical operators. There are four implementations of this interface which can be chained together to define advanced filters.
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).
.. note:: Sensible defaults are defined for frequently used attributes (status = UNCONSUMED, includeSoftlockedStates = true).
2. ``FungibleAssetQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core ``FungibleAsset`` contract state interface, used to represent assets that are fungible, countable and issued by a specific party (eg. ``Cash.State`` and ``CommodityContract.State`` in the Corda finance module).
.. note:: Contract states that extend the ``FungibleAsset`` interface now automatically persist associated state attributes.
3. ``LinearStateQueryCriteria`` provides filterable criteria on attributes defined in the Corda Core ``LinearState`` and ``DealState`` contract state interfaces, used to represent entities that continuously supercede themselves, all of which share the same *linearId* (eg. trade entity states such as the ``IRSState`` defined in the SIMM valuation demo)
.. note:: Contract states that extend the ``LinearState`` or ``DealState`` interfaces now automatically persist associated state attributes.
4. ``VaultCustomQueryCriteria`` provides the means to specify one or many arbitrary expressions on attributes defined by a custom contract state that implements its own schema as described in the Persistence_ documentation and associated examples.
Custom criteria expressions are expressed as JPA Query like WHERE clauses as follows: [JPA entityAttributeName] [Operand] [Value]
An example is illustrated here:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample16
:end-before: DOCEND VaultQueryExample16
All ``QueryCriteria`` implementations are composable using ``and`` and ``or`` operators, as also illustrated above.
Additional notes
.. note:: Custom contract states that implement the ``Queryable`` interface may now extend the ``FungiblePersistentState``, ``LinearPersistentState`` or ``DealPersistentState`` classes when implementing their ``MappedSchema``. Previously, all custom contracts extended the root ``PersistentState`` class and defined repeated mappings of ``FungibleAsset``, ``LinearState`` and ``DealState`` attributes.
Examples of these ``QueryCriteria`` objects are presented below for Kotlin and Java.
The Vault Query API leverages the rich semantics of the underlying Requery_ persistence framework adopted by Corda.
.. _Requery: https://github.com/requery/requery/wiki
.. _Persistence: https://docs.corda.net/persistence.html
.. note:: Permissioning at the database level will be enforced at a later date to ensure authenticated, role-based, read-only access to underlying Corda tables.
.. note:: API's now provide ease of use calling semantics from both Java and Kotlin.
.. note:: Current queries by ``Party`` specify only a party name as the underlying identity framework is being re-designed. In future it may be possible to query on specific elements of a parties identity such as a ``CompositeKey`` hierarchy (parent and child nodes, weightings).
Example usage
-------------
Kotlin
^^^^^^
**General snapshot queries using** ``VaultQueryCriteria``
Query for all unconsumed states:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample1
:end-before: DOCEND VaultQueryExample1
Query for unconsumed states for some state references:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample2
:end-before: DOCEND VaultQueryExample2
Query for unconsumed states for several contract state types:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample3
:end-before: DOCEND VaultQueryExample3
Query for unconsumed states for a given notary:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample4
:end-before: DOCEND VaultQueryExample4
Query for unconsumed states for a given set of participants:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample5
:end-before: DOCEND VaultQueryExample5
Query for unconsumed states recorded between two time intervals:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample6
:end-before: DOCEND VaultQueryExample6
Query for all states with pagination specification:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample7
:end-before: DOCEND VaultQueryExample7
**LinearState and DealState queries using** ``LinearStateQueryCriteria``
Query for unconsumed linear states for given linear ids:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample8
:end-before: DOCEND VaultQueryExample8
.. note:: This example was previously executed using the deprecated extension method:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultDeprecatedQueryExample1
:end-before: DOCEND VaultDeprecatedQueryExample1
Query for all linear states associated with a linear id:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample9
:end-before: DOCEND VaultQueryExample9
.. note:: This example was previously executed using the deprecated method:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultDeprecatedQueryExample2
:end-before: DOCEND VaultDeprecatedQueryExample2
Query for unconsumed deal states with deals references:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample10
:end-before: DOCEND VaultQueryExample10
Query for unconsumed deal states with deals parties:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample11
:end-before: DOCEND VaultQueryExample11
**FungibleAsset and DealState queries using** ``FungibleAssetQueryCriteria``
Query for fungible assets for a given currency:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample12
:end-before: DOCEND VaultQueryExample12
Query for fungible assets for a given currency and minimum quantity:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample13
:end-before: DOCEND VaultQueryExample13
Query for fungible assets for a specifc issuer party:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample14
:end-before: DOCEND VaultQueryExample14
Query for consumed fungible assets with a specific exit key:
.. literalinclude:: ../../node/src/test/kotlin/net/corda/node/services/vault/VaultQueryTests.kt
:language: kotlin
:start-after: DOCSTART VaultQueryExample15
:end-before: DOCEND VaultQueryExample15
Java examples
^^^^^^^^^^^^^
Query for all consumed contract states:
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
:language: java
:start-after: DOCSTART VaultJavaQueryExample1
:end-before: DOCEND VaultJavaQueryExample1
.. note:: This example was previously executed using the deprecated method:
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
:language: java
:start-after: DOCSTART VaultDeprecatedJavaQueryExample1
:end-before: DOCEND VaultDeprecatedJavaQueryExample1
Query for all deal states:
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
:language: java
:start-after: DOCSTART VaultJavaQueryExample2
:end-before: DOCEND VaultJavaQueryExample2
.. note:: This example was previously executed using the deprecated method:
.. literalinclude:: ../../node/src/test/java/net/corda/node/services/vault/VaultQueryJavaTests.java
:language: java
:start-after: DOCSTART VaultDeprecatedJavaQueryExample2
:end-before: DOCEND VaultDeprecatedJavaQueryExample2
**Dynamic queries** (also using ``VaultQueryCriteria``) are an extension to the snapshot queries by returning an additional ``QueryResults`` return type in the form of an ``Observable<Vault.Update>``. Refer to `ReactiveX Observable <http://reactivex.io/documentation/observable.html>`_ for a detailed understanding and usage of this type.
Other use case scenarios
------------------------
For advanced use cases that require sophisticated pagination, sorting, grouping, and aggregation functions, it is recommended that the CorDapp developer utilise one of the many proven frameworks that ship with this capability out of the box. Namely, implementations of JPQL (JPA Query Language) such as **Hibernate** for advanced SQL access, and **Spring Data** for advanced pagination and ordering constructs.
The Corda Tutorials provide examples satisfying these additional Use Cases:
1. Template / Tutorial CorDapp service using Vault API Custom Query to access attributes of IOU State
2. Template / Tutorial CorDapp service query extension executing Named Queries via JPQL_
3. `Advanced pagination <https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html>`_ queries using Spring Data JPA_
.. _JPQL: http://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#hql
.. _JPA: https://docs.spring.io/spring-data/jpa/docs/current/reference/html
Upgrading from previous releases
---------------------------------
Here follows a selection of the most common upgrade scenarios:
1. ServiceHub usage to obtain Unconsumed states for a given contract state type
Previously:
.. container:: codeset
.. sourcecode:: kotlin
val yoStates = b.vault.unconsumedStates<Yo.State>()
This query returned an ``Iterable<StateAndRef<T>>``
Now:
.. container:: codeset
.. sourcecode:: kotlin
val yoStates = b.vault.queryBy<Yo.State>().states
The query returns a ``Vault.Page`` result containing:
- states as a ``List<StateAndRef<T : ContractState>>`` sized according to the default Page specification of ``DEFAULT_PAGE_NUM`` (0) and ``DEFAULT_PAGE_SIZE`` (200).
- states metadata as a ``List<Vault.StateMetadata>`` containing Vault State metadata held in the Vault states table.
- the ``PagingSpecification`` used in the query
- a ``total`` number of results available. This value can be used issue subsequent queries with appropriately specified ``PageSpecification`` (according to your paging needs and/or maximum memory capacity for holding large data sets). Note it is your responsibility to manage page numbers and sizes.
2. ServiceHub usage obtaining linear heads for a given contract state type
Previously:
.. container:: codeset
.. sourcecode:: kotlin
val iouStates = serviceHub.vaultService.linearHeadsOfType<IOUState>()
val iouToSettle = iouStates[linearId] ?: throw Exception("IOUState with linearId $linearId not found.")
Now:
.. container:: codeset
.. sourcecode:: kotlin
val criteria = QueryCriteria.LinearStateQueryCriteria(linearId = listOf(linearId))
val iouStates = serviceHub.vaultService.queryBy<IOUState>(criteria).states
val iouToSettle = iouStates.singleOrNull() ?: throw Exception("IOUState with linearId $linearId not found.")
3. RPC usage was limited to using the ``vaultAndUpdates`` RPC method, which returned a snapshot and streaming updates as an Observable.
In many cases, queries were not interested in the streaming updates.
Previously:
.. container:: codeset
.. sourcecode:: kotlin
val iouStates = services.vaultAndUpdates().first.filter { it.state.data is IOUState }
Now:
.. container:: codeset
.. sourcecode:: kotlin
val iouStates = services.vaultQueryBy<IOUState>()

View File

@ -10,6 +10,7 @@ description 'Corda finance modules'
dependencies {
compile project(':core')
compile project(':node-schemas')
testCompile project(':test-utils')
testCompile project(path: ':core', configuration: 'testArtifacts')

View File

@ -81,13 +81,14 @@ class CommercialPaper : Contract {
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
// DOCSTART VaultIndexedQueryCriteria
/** Object Relational Mapping support. */
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(CommercialPaperSchemaV1)
/** Object Relational Mapping support. */
override fun generateMappedObject(schema: MappedSchema): PersistentState {
return when (schema) {
is CommercialPaperSchemaV1 -> CommercialPaperSchemaV1.PersistentCommericalPaperState(
is CommercialPaperSchemaV1 -> CommercialPaperSchemaV1.PersistentCommercialPaperState(
issuanceParty = this.issuance.party.owningKey.toBase58String(),
issuanceRef = this.issuance.reference.bytes,
owner = this.owner.toBase58String(),
@ -100,6 +101,7 @@ class CommercialPaper : Contract {
else -> throw IllegalArgumentException("Unrecognised schema $schema")
}
}
// DOCEND VaultIndexedQueryCriteria
}
interface Clauses {

View File

@ -5,10 +5,8 @@ package net.corda.contracts.testing
import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
import net.corda.core.contracts.Amount
import net.corda.core.contracts.Issued
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.TransactionType
import net.corda.core.contracts.*
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.Vault
@ -20,12 +18,16 @@ import java.security.KeyPair
import java.security.PublicKey
import java.util.*
fun ServiceHub.fillWithSomeTestDeals(dealIds: List<String>) {
@JvmOverloads
fun ServiceHub.fillWithSomeTestDeals(dealIds: List<String>,
revisions: Int? = 0,
participants: List<PublicKey> = emptyList()) : Vault<DealState> {
val freshKey = keyManagementService.freshKey()
val transactions: List<SignedTransaction> = dealIds.map {
// Issue a deal state
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
addOutputState(DummyDealContract.State(ref = it, participants = listOf(freshKey.public)))
addOutputState(DummyDealContract.State(ref = it, participants = participants.plus(freshKey.public)))
signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}
@ -33,19 +35,40 @@ fun ServiceHub.fillWithSomeTestDeals(dealIds: List<String>) {
}
recordTransactions(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) }
}
return Vault(states)
}
fun ServiceHub.fillWithSomeTestLinearStates(numberToCreate: Int) {
@JvmOverloads
fun ServiceHub.fillWithSomeTestLinearStates(numberToCreate: Int,
uid: UniqueIdentifier = UniqueIdentifier(),
participants: List<PublicKey> = emptyList()) : Vault<LinearState> {
val freshKey = keyManagementService.freshKey()
for (i in 1..numberToCreate) {
// Issue a deal state
val transactions: List<SignedTransaction> = (1..numberToCreate).map {
// Issue a Linear state
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
addOutputState(DummyLinearContract.State(participants = listOf(freshKey.public)))
addOutputState(DummyLinearContract.State(linearId = uid, participants = participants.plus(freshKey.public)))
signWith(freshKey)
signWith(DUMMY_NOTARY_KEY)
}
recordTransactions(dummyIssue.toSignedTransaction())
return@map dummyIssue.toSignedTransaction(true)
}
recordTransactions(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) }
}
return Vault(states)
}
/**

View File

@ -4,6 +4,7 @@ import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Index
import javax.persistence.Table
/**
@ -17,7 +18,9 @@ object CashSchema
*/
object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) {
@Entity
@Table(name = "cash_states")
@Table(name = "cash_states",
indexes = arrayOf(Index(name = "ccy_code_idx", columnList = "ccy_code"),
Index(name = "pennies_idx", columnList = "pennies")))
class PersistentCashState(
@Column(name = "owner_key")
var owner: String,

View File

@ -5,6 +5,7 @@ import net.corda.core.schemas.PersistentState
import java.time.Instant
import javax.persistence.Column
import javax.persistence.Entity
import javax.persistence.Index
import javax.persistence.Table
/**
@ -16,10 +17,13 @@ object CommercialPaperSchema
* First version of a commercial paper contract ORM schema that maps all fields of the [CommercialPaper] contract state
* as it stood at the time of writing.
*/
object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCommericalPaperState::class.java)) {
object CommercialPaperSchemaV1 : MappedSchema(schemaFamily = CommercialPaperSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCommercialPaperState::class.java)) {
@Entity
@Table(name = "cp_states")
class PersistentCommericalPaperState(
@Table(name = "cp_states",
indexes = arrayOf(Index(name = "ccy_code_idx", columnList = "ccy_code"),
Index(name = "maturity_idx", columnList = "maturity_instant"),
Index(name = "face_value_idx", columnList = "face_value")))
class PersistentCommercialPaperState(
@Column(name = "issuance_key")
var issuanceParty: String,

View File

@ -9,6 +9,7 @@ dependencies {
compile project(':core')
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
testCompile "junit:junit:$junit_version"
testCompile project(':test-utils')
// Requery: SQL based query & persistence for Kotlin
kapt "io.requery:requery-processor:$requery_version"

View File

@ -4,6 +4,7 @@ import io.requery.*
import net.corda.core.node.services.Vault
import net.corda.core.schemas.requery.Requery
import java.time.Instant
import java.util.*
object VaultSchema {
@ -73,4 +74,20 @@ object VaultSchema {
@get:Column(name = "lock_timestamp", nullable = true)
var lockUpdateTime: Instant?
}
/**
* The following entity is for illustration purposes only as used by VaultQueryTests
*/
@Table(name = "vault_linear_states")
@Entity(model = "vault")
interface VaultLinearState : Persistable {
@get:Index("external_id_index")
@get:Column(name = "external_id")
var externalId: String
@get:Index("uuid_index")
@get:Column(name = "uuid", unique = true, nullable = false)
var uuid: UUID
}
}

View File

@ -29,7 +29,6 @@ import org.junit.Before
import org.junit.Test
import rx.Observable
import java.security.PublicKey
import sun.misc.MessageUtils.where
import java.time.Instant
import java.util.*
import java.util.concurrent.CountDownLatch

View File

@ -15,6 +15,9 @@ import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.node.services.api.ServiceHubInternal
@ -55,6 +58,23 @@ class CordaRPCOpsImpl(
}
}
override fun <T : ContractState> vaultQueryBy(criteria: QueryCriteria,
paging: PageSpecification,
sorting: Sort): Vault.Page<T> {
return database.transaction {
services.vaultService.queryBy<T>(criteria, paging, sorting)
}
}
@RPCReturnsObservables
override fun <T : ContractState> vaultTrackBy(criteria: QueryCriteria,
paging: PageSpecification,
sorting: Sort): Vault.PageAndUpdates<T> {
return database.transaction {
services.vaultService.trackBy<T>(criteria, paging, sorting)
}
}
override fun verifiedTransactions(): Pair<List<SignedTransaction>, Observable<SignedTransaction>> {
return database.transaction {
services.storageService.validatedTransactions.track()

View File

@ -15,18 +15,15 @@ import net.corda.contracts.clause.AbstractConserveAmount
import net.corda.core.ThreadBox
import net.corda.core.bufferUntilSubscribed
import net.corda.core.contracts.*
import net.corda.core.crypto.AbstractParty
import net.corda.core.crypto.CompositeKey
import net.corda.core.crypto.Party
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.containsAny
import net.corda.core.crypto.toBase58String
import net.corda.core.flows.FlowStateMachine
import net.corda.core.crypto.*
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.unconsumedStates
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
import net.corda.core.node.services.vault.Sort
import net.corda.core.serialization.*
import net.corda.core.tee
import net.corda.core.transactions.TransactionBuilder
@ -194,6 +191,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
.map { it ->
val stateRef = StateRef(SecureHash.parse(it.txId), it.index)
val state = it.contractState.deserialize<TransactionState<T>>(storageKryo())
Vault.StateMetadata(stateRef, it.contractStateClassName, it.recordedTime, it.consumedTime, it.stateStatus, it.notaryName, it.notaryKey, it.lockId, it.lockUpdateTime)
StateAndRef(state, stateRef)
}
}
@ -221,6 +219,26 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
return stateAndRefs.associateBy({ it.ref }, { it.state })
}
override fun <T : ContractState> queryBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): Vault.Page<T> {
TODO("Under construction")
// If [VaultQueryCriteria.PageSpecification] specified
// must return (CloseableIterator) result.get().iterator(skip, take)
// where
// skip = Max[(pageNumber - 1),0] * pageSize
// take = pageSize
}
override fun <T : ContractState> trackBy(criteria: QueryCriteria, paging: PageSpecification, sorting: Sort): Vault.PageAndUpdates<T> {
TODO("Under construction")
// return mutex.locked {
// Vault.PageAndUpdates(queryBy(criteria),
// _updatesPublisher.bufferUntilSubscribed().wrapWithDatabaseTransaction())
// }
}
override fun notifyAll(txns: Iterable<WireTransaction>) {
val ourKeys = services.keyManagementService.keys.keys
val netDelta = txns.fold(Vault.NoUpdate) { netDelta, txn -> netDelta + makeUpdate(txn, ourKeys) }
@ -520,4 +538,4 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
private fun stateRefArgs(stateRefs: Iterable<StateRef>): List<List<Any>> {
return stateRefs.map { listOf("'${it.txhash}'", it.index) }
}
}
}

View File

@ -0,0 +1,280 @@
package net.corda.node.services.vault;
import com.google.common.collect.*;
import kotlin.*;
import net.corda.contracts.asset.*;
import net.corda.core.contracts.*;
import net.corda.core.crypto.*;
import net.corda.core.node.services.*;
import net.corda.core.node.services.vault.*;
import net.corda.core.node.services.vault.QueryCriteria.*;
import net.corda.core.serialization.*;
import net.corda.core.transactions.*;
import net.corda.node.services.vault.schemas.*;
import net.corda.testing.node.*;
import org.jetbrains.annotations.*;
import org.jetbrains.exposed.sql.*;
import org.junit.*;
import rx.Observable;
import java.io.*;
import java.util.*;
import java.util.stream.*;
import static net.corda.contracts.asset.CashKt.*;
import static net.corda.contracts.testing.VaultFiller.*;
import static net.corda.core.node.services.vault.QueryCriteriaKt.*;
import static net.corda.core.node.services.vault.QueryCriteriaUtilsKt.*;
import static net.corda.core.utilities.TestConstants.*;
import static net.corda.node.utilities.DatabaseSupportKt.*;
import static net.corda.node.utilities.DatabaseSupportKt.transaction;
import static net.corda.testing.CoreTestUtils.*;
import static net.corda.testing.node.MockServicesKt.*;
import static org.assertj.core.api.Assertions.*;
@Ignore
public class VaultQueryJavaTests {
private MockServices services;
private VaultService vaultSvc;
private Closeable dataSource;
private Database database;
@Before
public void setUp() {
Properties dataSourceProps = makeTestDataSourceProperties(SecureHash.randomSHA256().toString());
Pair<Closeable, Database> dataSourceAndDatabase = configureDatabase(dataSourceProps);
dataSource = dataSourceAndDatabase.getFirst();
database = dataSourceAndDatabase.getSecond();
transaction(database, statement -> services = new MockServices() {
@NotNull
@Override
public VaultService getVaultService() {
return makeVaultService(dataSourceProps);
}
@Override
public void recordTransactions(@NotNull Iterable<SignedTransaction> txs) {
for (SignedTransaction stx : txs ) {
getStorageService().getValidatedTransactions().addTransaction(stx);
}
Stream<WireTransaction> wtxn = StreamSupport.stream(txs.spliterator(), false).map(txn -> txn.getTx());
getVaultService().notifyAll(wtxn.collect(Collectors.toList()));
}
});
vaultSvc = services.getVaultService();
}
@After
public void cleanUp() throws IOException {
dataSource.close();
}
/**
* Sample Vault Query API tests
*/
/**
* Static queryBy() tests
*/
@Test
public void consumedStates() {
transaction(database, tx -> {
fillWithSomeTestCash(services,
new Amount<>(100, Currency.getInstance("USD")),
getDUMMY_NOTARY(),
3,
3,
new Random(),
new OpaqueBytes("1".getBytes()),
null,
getDUMMY_CASH_ISSUER(),
getDUMMY_CASH_ISSUER_KEY() );
// DOCSTART VaultJavaQueryExample1
@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(Collections.singletonList(Cash.State.class));
Vault.StateStatus status = Vault.StateStatus.CONSUMED;
VaultQueryCriteria criteria = new VaultQueryCriteria(status, null, contractStateTypes);
Vault.Page<ContractState> results = vaultSvc.queryBy(criteria);
// DOCEND VaultJavaQueryExample1
assertThat(results.getStates()).hasSize(3);
return tx;
});
}
@Test
public void consumedDealStatesPagedSorted() {
transaction(database, tx -> {
UniqueIdentifier uid = new UniqueIdentifier();
fillWithSomeTestLinearStates(services, 10, uid);
List<String> dealIds = Arrays.asList("123", "456", "789");
fillWithSomeTestDeals(services, dealIds, 0);
// DOCSTART VaultJavaQueryExample2
Vault.StateStatus status = Vault.StateStatus.CONSUMED;
@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(Collections.singletonList(Cash.State.class));
QueryCriteria vaultCriteria = new VaultQueryCriteria(status, null, contractStateTypes);
List<UniqueIdentifier> linearIds = Arrays.asList(uid);
List<String> dealPartyNames = Arrays.asList(getMEGA_CORP().getName());
QueryCriteria dealCriteriaAll = new LinearStateQueryCriteria(linearIds, false, dealIds, dealPartyNames);
QueryCriteria compositeCriteria = and(dealCriteriaAll, vaultCriteria);
PageSpecification pageSpec = new PageSpecification(0, getMAX_PAGE_SIZE());
Sort.SortColumn sortByUid = new Sort.SortColumn(VaultLinearStateEntity.UUID.getName(), Sort.Direction.DESC, Sort.NullHandling.NULLS_LAST);
Sort sorting = new Sort(ImmutableSet.of(sortByUid));
Vault.Page<ContractState> results = vaultSvc.queryBy(compositeCriteria, pageSpec, sorting);
// DOCEND VaultJavaQueryExample2
assertThat(results.getStates()).hasSize(4);
return tx;
});
}
/**
* Dynamic trackBy() tests
*/
@Test
public void trackCashStates() {
transaction(database, tx -> {
fillWithSomeTestCash(services,
new Amount<>(100, Currency.getInstance("USD")),
getDUMMY_NOTARY(),
3,
3,
new Random(),
new OpaqueBytes("1".getBytes()),
null,
getDUMMY_CASH_ISSUER(),
getDUMMY_CASH_ISSUER_KEY() );
// DOCSTART VaultJavaQueryExample1
@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(Collections.singletonList(Cash.State.class));
VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, null, contractStateTypes);
Vault.PageAndUpdates<ContractState> results = vaultSvc.trackBy(criteria);
Vault.Page<ContractState> snapshot = results.getCurrent();
Observable<Vault.Update> updates = results.getFuture();
// DOCEND VaultJavaQueryExample1
assertThat(snapshot.getStates()).hasSize(3);
return tx;
});
}
@Test
public void trackDealStatesPagedSorted() {
transaction(database, tx -> {
UniqueIdentifier uid = new UniqueIdentifier();
fillWithSomeTestLinearStates(services, 10, uid);
List<String> dealIds = Arrays.asList("123", "456", "789");
fillWithSomeTestDeals(services, dealIds, 0);
// DOCSTART VaultJavaQueryExample2
@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(Collections.singletonList(DealState.class));
QueryCriteria vaultCriteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, null, contractStateTypes);
List<UniqueIdentifier> linearIds = Arrays.asList(uid);
List<String> dealPartyNames = Arrays.asList(getMEGA_CORP().getName());
QueryCriteria dealCriteriaAll = new LinearStateQueryCriteria(linearIds, false, dealIds, dealPartyNames);
QueryCriteria compositeCriteria = and(dealCriteriaAll, vaultCriteria);
PageSpecification pageSpec = new PageSpecification(0, getMAX_PAGE_SIZE());
Sort.SortColumn sortByUid = new Sort.SortColumn(VaultLinearStateEntity.UUID.getName(), Sort.Direction.DESC, Sort.NullHandling.NULLS_LAST);
Sort sorting = new Sort(ImmutableSet.of(sortByUid));
Vault.PageAndUpdates<ContractState> results = vaultSvc.trackBy(compositeCriteria, pageSpec, sorting);
Vault.Page<ContractState> snapshot = results.getCurrent();
Observable<Vault.Update> updates = results.getFuture();
// DOCEND VaultJavaQueryExample2
assertThat(snapshot.getStates()).hasSize(4);
return tx;
});
}
/**
* Deprecated usage
*/
@Test
public void consumedStatesDeprecated() {
transaction(database, tx -> {
fillWithSomeTestCash(services,
new Amount<>(100, Currency.getInstance("USD")),
getDUMMY_NOTARY(),
3,
3,
new Random(),
new OpaqueBytes("1".getBytes()),
null,
getDUMMY_CASH_ISSUER(),
getDUMMY_CASH_ISSUER_KEY() );
// DOCSTART VaultDeprecatedJavaQueryExample1
@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(Collections.singletonList(Cash.State.class));
EnumSet<Vault.StateStatus> status = EnumSet.of(Vault.StateStatus.CONSUMED);
// WARNING! unfortunately cannot use inlined reified Kotlin extension methods.
Iterable<StateAndRef<ContractState>> results = vaultSvc.states(contractStateTypes, status, true);
// DOCEND VaultDeprecatedJavaQueryExample1
assertThat(results).hasSize(3);
return tx;
});
}
@Test
public void consumedStatesForLinearIdDeprecated() {
transaction(database, tx -> {
UniqueIdentifier trackUid = new UniqueIdentifier();
fillWithSomeTestLinearStates(services, 1, trackUid);
fillWithSomeTestLinearStates(services, 4, new UniqueIdentifier());
// DOCSTART VaultDeprecatedJavaQueryExample2
@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(Collections.singletonList(LinearState.class));
EnumSet<Vault.StateStatus> status = EnumSet.of(Vault.StateStatus.CONSUMED);
// WARNING! unfortunately cannot use inlined reified Kotlin extension methods.
Iterable<StateAndRef<ContractState>> results = vaultSvc.states(contractStateTypes, status, true);
Stream<StateAndRef<ContractState>> trackedLinearState = StreamSupport.stream(results.spliterator(), false).filter(
state -> ((LinearState) state.component1().getData()).getLinearId() == trackUid);
// DOCEND VaultDeprecatedJavaQueryExample2
assertThat(results).hasSize(4);
assertThat(trackedLinearState).hasSize(1);
return tx;
});
}
}

View File

@ -35,7 +35,7 @@ import kotlin.test.assertNull
class NodeVaultServiceTest {
lateinit var services: MockServices
val vault: VaultService get() = services.vaultService
val vaultSvc: VaultService get() = services.vaultService
lateinit var dataSource: Closeable
lateinit var database: Database
@ -73,11 +73,11 @@ class NodeVaultServiceTest {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
val w1 = services.vaultService.unconsumedStates<Cash.State>()
val w1 = vaultSvc.unconsumedStates<Cash.State>()
assertThat(w1).hasSize(3)
val originalStorage = services.storageService
val originalVault = services.vaultService
val originalVault = vaultSvc
val services2 = object : MockServices() {
override val vaultService: VaultService get() = originalVault
@ -103,11 +103,11 @@ class NodeVaultServiceTest {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
val w1 = services.vaultService.unconsumedStates<Cash.State>().toList()
val w1 = vaultSvc.unconsumedStates<Cash.State>().toList()
assertThat(w1).hasSize(3)
val stateRefs = listOf(w1[1].ref, w1[2].ref)
val states = services.vaultService.statesForRefs(stateRefs)
val states = vaultSvc.statesForRefs(stateRefs)
assertThat(states).hasSize(2)
}
}
@ -118,32 +118,32 @@ class NodeVaultServiceTest {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
val unconsumedStates = services.vaultService.unconsumedStates<Cash.State>().toList()
val unconsumedStates = vaultSvc.unconsumedStates<Cash.State>().toList()
assertThat(unconsumedStates).hasSize(3)
val stateRefsToSoftLock = setOf(unconsumedStates[1].ref, unconsumedStates[2].ref)
// soft lock two of the three states
val softLockId = UUID.randomUUID()
services.vaultService.softLockReserve(softLockId, stateRefsToSoftLock)
vaultSvc.softLockReserve(softLockId, stateRefsToSoftLock)
// all softlocked states
assertThat(services.vaultService.softLockedStates<Cash.State>()).hasSize(2)
assertThat(vaultSvc.softLockedStates<Cash.State>()).hasSize(2)
// my softlocked states
assertThat(services.vaultService.softLockedStates<Cash.State>(softLockId)).hasSize(2)
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId)).hasSize(2)
// excluding softlocked states
val unlockedStates1 = services.vaultService.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
val unlockedStates1 = vaultSvc.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
assertThat(unlockedStates1).hasSize(1)
// soft lock release one of the states explicitly
services.vaultService.softLockRelease(softLockId, setOf(unconsumedStates[1].ref))
val unlockedStates2 = services.vaultService.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
vaultSvc.softLockRelease(softLockId, setOf(unconsumedStates[1].ref))
val unlockedStates2 = vaultSvc.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
assertThat(unlockedStates2).hasSize(2)
// soft lock release the rest by id
services.vaultService.softLockRelease(softLockId)
val unlockedStates = services.vaultService.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
vaultSvc.softLockRelease(softLockId)
val unlockedStates = vaultSvc.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
assertThat(unlockedStates).hasSize(3)
// should be back to original states
@ -162,7 +162,7 @@ class NodeVaultServiceTest {
val vaultStates =
database.transaction {
assertNull(vault.cashBalances[USD])
assertNull(vaultSvc.cashBalances[USD])
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
}
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
@ -172,8 +172,8 @@ class NodeVaultServiceTest {
backgroundExecutor.submit {
try {
database.transaction {
vault.softLockReserve(softLockId1, stateRefsToSoftLock)
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock)
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
}
println("SOFT LOCK STATES #1 succeeded")
} catch(e: Throwable) {
@ -188,8 +188,8 @@ class NodeVaultServiceTest {
try {
Thread.sleep(100) // let 1st thread soft lock them 1st
database.transaction {
vault.softLockReserve(softLockId2, stateRefsToSoftLock)
assertThat(vault.softLockedStates<Cash.State>(softLockId2)).hasSize(3)
vaultSvc.softLockReserve(softLockId2, stateRefsToSoftLock)
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId2)).hasSize(3)
}
println("SOFT LOCK STATES #2 succeeded")
} catch(e: Throwable) {
@ -201,10 +201,10 @@ class NodeVaultServiceTest {
countDown.await()
database.transaction {
val lockStatesId1 = vault.softLockedStates<Cash.State>(softLockId1)
val lockStatesId1 = vaultSvc.softLockedStates<Cash.State>(softLockId1)
println("SOFT LOCK #1 final states: $lockStatesId1")
assertThat(lockStatesId1.size).isIn(0, 3)
val lockStatesId2 = vault.softLockedStates<Cash.State>(softLockId2)
val lockStatesId2 = vaultSvc.softLockedStates<Cash.State>(softLockId2)
println("SOFT LOCK #2 final states: $lockStatesId2")
assertThat(lockStatesId2.size).isIn(0, 3)
}
@ -218,7 +218,7 @@ class NodeVaultServiceTest {
val vaultStates =
database.transaction {
assertNull(vault.cashBalances[USD])
assertNull(vaultSvc.cashBalances[USD])
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
}
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
@ -226,14 +226,14 @@ class NodeVaultServiceTest {
// lock 1st state with LockId1
database.transaction {
vault.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first()))
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(1)
vaultSvc.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first()))
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(1)
}
// attempt to lock all 3 states with LockId2
database.transaction {
assertThatExceptionOfType(StatesNotAvailableException::class.java).isThrownBy(
{ vault.softLockReserve(softLockId2, stateRefsToSoftLock) }
{ vaultSvc.softLockReserve(softLockId2, stateRefsToSoftLock) }
).withMessageContaining("only 2 rows available").withNoCause()
}
}
@ -245,7 +245,7 @@ class NodeVaultServiceTest {
val vaultStates =
database.transaction {
assertNull(vault.cashBalances[USD])
assertNull(vaultSvc.cashBalances[USD])
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
}
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
@ -253,14 +253,14 @@ class NodeVaultServiceTest {
// lock states with LockId1
database.transaction {
vault.softLockReserve(softLockId1, stateRefsToSoftLock)
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock)
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
}
// attempt to relock same states with LockId1
database.transaction {
vault.softLockReserve(softLockId1, stateRefsToSoftLock)
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock)
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
}
}
@ -271,7 +271,7 @@ class NodeVaultServiceTest {
val vaultStates =
database.transaction {
assertNull(vault.cashBalances[USD])
assertNull(vaultSvc.cashBalances[USD])
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
}
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
@ -279,14 +279,14 @@ class NodeVaultServiceTest {
// lock states with LockId1
database.transaction {
vault.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first()))
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(1)
vaultSvc.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first()))
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(1)
}
// attempt to lock all states with LockId1 (including previously already locked one)
database.transaction {
vault.softLockReserve(softLockId1, stateRefsToSoftLock)
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock)
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
}
}
@ -296,14 +296,14 @@ class NodeVaultServiceTest {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L))
val unconsumedStates = services.vaultService.unconsumedStates<Cash.State>().toList()
val unconsumedStates = vaultSvc.unconsumedStates<Cash.State>().toList()
assertThat(unconsumedStates).hasSize(1)
val spendableStatesUSD = (services.vaultService as NodeVaultService).unconsumedStatesForSpending<Cash.State>(100.DOLLARS, lockId = UUID.randomUUID())
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(100.DOLLARS, lockId = UUID.randomUUID())
spendableStatesUSD.forEach(::println)
assertThat(spendableStatesUSD).hasSize(1)
assertThat(spendableStatesUSD[0].state.data.amount.quantity).isEqualTo(100L * 100)
assertThat(services.vaultService.softLockedStates<Cash.State>()).hasSize(1)
assertThat(vaultSvc.softLockedStates<Cash.State>()).hasSize(1)
}
}
@ -314,7 +314,7 @@ class NodeVaultServiceTest {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (DUMMY_CASH_ISSUER))
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(1)), issuerKey = BOC_KEY)
val spendableStatesUSD = services.vaultService.unconsumedStatesForSpending<Cash.State>(200.DOLLARS, lockId = UUID.randomUUID(),
val spendableStatesUSD = vaultSvc.unconsumedStatesForSpending<Cash.State>(200.DOLLARS, lockId = UUID.randomUUID(),
onlyFromIssuerParties = setOf(DUMMY_CASH_ISSUER.party, BOC)).toList()
spendableStatesUSD.forEach(::println)
assertThat(spendableStatesUSD).hasSize(2)
@ -332,10 +332,10 @@ class NodeVaultServiceTest {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(2)), issuerKey = BOC_KEY, ref = OpaqueBytes.of(2))
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(3)), issuerKey = BOC_KEY, ref = OpaqueBytes.of(3))
val unconsumedStates = services.vaultService.unconsumedStates<Cash.State>().toList()
val unconsumedStates = vaultSvc.unconsumedStates<Cash.State>().toList()
assertThat(unconsumedStates).hasSize(4)
val spendableStatesUSD = services.vaultService.unconsumedStatesForSpending<Cash.State>(200.DOLLARS, lockId = UUID.randomUUID(),
val spendableStatesUSD = vaultSvc.unconsumedStatesForSpending<Cash.State>(200.DOLLARS, lockId = UUID.randomUUID(),
onlyFromIssuerParties = setOf(BOC), withIssuerRefs = setOf(OpaqueBytes.of(1), OpaqueBytes.of(2))).toList()
assertThat(spendableStatesUSD).hasSize(2)
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.party).isEqualTo(BOC)
@ -350,13 +350,13 @@ class NodeVaultServiceTest {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L))
val unconsumedStates = services.vaultService.unconsumedStates<Cash.State>().toList()
val unconsumedStates = vaultSvc.unconsumedStates<Cash.State>().toList()
assertThat(unconsumedStates).hasSize(1)
val spendableStatesUSD = (services.vaultService as NodeVaultService).unconsumedStatesForSpending<Cash.State>(110.DOLLARS, lockId = UUID.randomUUID())
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(110.DOLLARS, lockId = UUID.randomUUID())
spendableStatesUSD.forEach(::println)
assertThat(spendableStatesUSD).hasSize(1)
assertThat(services.vaultService.softLockedStates<Cash.State>()).hasSize(0)
assertThat(vaultSvc.softLockedStates<Cash.State>()).hasSize(0)
}
}
@ -366,14 +366,14 @@ class NodeVaultServiceTest {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 2, 2, Random(0L))
val unconsumedStates = services.vaultService.unconsumedStates<Cash.State>().toList()
val unconsumedStates = vaultSvc.unconsumedStates<Cash.State>().toList()
assertThat(unconsumedStates).hasSize(2)
val spendableStatesUSD = (services.vaultService as NodeVaultService).unconsumedStatesForSpending<Cash.State>(1.DOLLARS, lockId = UUID.randomUUID())
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(1.DOLLARS, lockId = UUID.randomUUID())
spendableStatesUSD.forEach(::println)
assertThat(spendableStatesUSD).hasSize(1)
assertThat(spendableStatesUSD[0].state.data.amount.quantity).isGreaterThanOrEqualTo(100L)
assertThat(services.vaultService.softLockedStates<Cash.State>()).hasSize(1)
assertThat(vaultSvc.softLockedStates<Cash.State>()).hasSize(1)
}
}
@ -385,15 +385,15 @@ class NodeVaultServiceTest {
services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 10, 10, Random(0L))
services.fillWithSomeTestCash(100.SWISS_FRANCS, DUMMY_NOTARY, 10, 10, Random(0L))
val allStates = services.vaultService.unconsumedStates<Cash.State>()
val allStates = vaultSvc.unconsumedStates<Cash.State>()
assertThat(allStates).hasSize(30)
for (i in 1..5) {
val spendableStatesUSD = (services.vaultService as NodeVaultService).unconsumedStatesForSpending<Cash.State>(20.DOLLARS, lockId = UUID.randomUUID())
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(20.DOLLARS, lockId = UUID.randomUUID())
spendableStatesUSD.forEach(::println)
}
// note only 3 spend attempts succeed with a total of 8 states
assertThat(services.vaultService.softLockedStates<Cash.State>()).hasSize(8)
assertThat(vaultSvc.softLockedStates<Cash.State>()).hasSize(8)
}
}
@ -411,10 +411,10 @@ class NodeVaultServiceTest {
services.recordTransactions(listOf(usefulTX))
services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 1")
services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 2")
services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 3")
assertEquals(3, services.vaultService.getTransactionNotes(usefulTX.id).count())
vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 1")
vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 2")
vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 3")
assertEquals(3, vaultSvc.getTransactionNotes(usefulTX.id).count())
// Issue more Money (GBP)
val anotherTX = TransactionType.General.Builder(null).apply {
@ -424,8 +424,8 @@ class NodeVaultServiceTest {
services.recordTransactions(listOf(anotherTX))
services.vaultService.addNoteToTransaction(anotherTX.id, "GPB Sample Note 1")
assertEquals(1, services.vaultService.getTransactionNotes(anotherTX.id).count())
vaultSvc.addNoteToTransaction(anotherTX.id, "GPB Sample Note 1")
assertEquals(1, vaultSvc.getTransactionNotes(anotherTX.id).count())
}
}
}

View File

@ -0,0 +1,782 @@
package net.corda.node.services.vault
import net.corda.contracts.CommercialPaper
import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.contracts.testing.fillWithSomeTestCash
import net.corda.contracts.testing.fillWithSomeTestDeals
import net.corda.contracts.testing.fillWithSomeTestLinearStates
import net.corda.core.contracts.*
import net.corda.core.crypto.Party
import net.corda.core.crypto.entropyToKeyPair
import net.corda.core.days
import net.corda.core.node.services.Vault
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.linearHeadsOfType
import net.corda.core.node.services.vault.*
import net.corda.core.node.services.vault.QueryCriteria.*
import net.corda.core.seconds
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.*
import net.corda.node.services.vault.schemas.VaultLinearStateEntity
import net.corda.node.services.vault.schemas.VaultSchema
import net.corda.node.utilities.configureDatabase
import net.corda.node.utilities.transaction
import net.corda.schemas.CashSchemaV1.PersistentCashState
import net.corda.schemas.CommercialPaperSchemaV1.PersistentCommercialPaperState
import net.corda.testing.*
import net.corda.testing.node.MockServices
import net.corda.testing.node.makeTestDataSourceProperties
import org.assertj.core.api.Assertions.assertThat
import org.jetbrains.exposed.sql.Database
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.io.Closeable
import java.math.BigInteger
import java.security.KeyPair
import java.time.temporal.ChronoUnit
import java.util.*
@Ignore
class VaultQueryTests {
lateinit var services: MockServices
val vaultSvc: VaultService get() = services.vaultService
lateinit var dataSource: Closeable
lateinit var database: Database
@Before
fun setUp() {
val dataSourceProps = makeTestDataSourceProperties()
val dataSourceAndDatabase = configureDatabase(dataSourceProps)
dataSource = dataSourceAndDatabase.first
database = dataSourceAndDatabase.second
database.transaction {
services = object : MockServices() {
override val vaultService: VaultService = makeVaultService(dataSourceProps)
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
}
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
vaultService.notifyAll(txs.map { it.tx })
}
}
}
}
@After
fun tearDown() {
dataSource.close()
}
/**
* Query API tests
*/
/** Generic Query tests
(combining both FungibleState and LinearState contract types) */
@Test
fun `unconsumed states`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(10)
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
// DOCSTART VaultQueryExample1
val criteria = VaultQueryCriteria() // default is UNCONSUMED
val result = vaultSvc.queryBy<ContractState>(criteria)
/**
* Query result returns a [Vault.Page] which contains:
* 1) actual states as a list of [StateAndRef]
* 2) state reference and associated vault metadata as a list of [Vault.StateMetadata]
* 3) [PageSpecification] used to delimit the size of items returned in the result set (defaults to [DEFAULT_PAGE_SIZE])
* 4) Total number of items available (to aid further pagination if required)
*/
val states = result.states
val metadata = result.statesMetadata
// DOCEND VaultQueryExample1
assertThat(states).hasSize(16)
assertThat(metadata).hasSize(16)
}
}
@Test
fun `unconsumed states for state refs`() {
database.transaction {
val issuedStates = services.fillWithSomeTestLinearStates(2)
val stateRefs = issuedStates.states.map { it.ref }.toList()
services.fillWithSomeTestLinearStates(8)
// DOCSTART VaultQueryExample2
val criteria = VaultQueryCriteria(stateRefs = listOf(stateRefs.first(), stateRefs.last()))
val results = vaultSvc.queryBy<LinearState>(criteria)
// DOCEND VaultQueryExample2
assertThat(results.states).hasSize(2)
assertThat(results.states.first().ref).isEqualTo(issuedStates.states.first().ref)
assertThat(results.states.last().ref).isEqualTo(issuedStates.states.last().ref)
}
}
@Test
fun `unconsumed states for contract state types`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(10)
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
// default State.Status is UNCONSUMED
// DOCSTART VaultQueryExample3
val criteria = VaultQueryCriteria(contractStateTypes = setOf(Cash.State::class.java, DealState::class.java))
val results = vaultSvc.queryBy<ContractState>(criteria)
// DOCEND VaultQueryExample3
assertThat(results.states).hasSize(6)
}
}
@Test
fun `consumed states`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(2, UniqueIdentifier("TEST")) // create 2 states with same UID
services.fillWithSomeTestLinearStates(8)
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
// services.consumeLinearStates(UniqueIdentifier("TEST"))
// services.consumeDeals("456")
// services.consumeCash(80.DOLLARS)
val criteria = VaultQueryCriteria(status = Vault.StateStatus.CONSUMED)
val results = vaultSvc.queryBy<ContractState>(criteria)
assertThat(results.states).hasSize(5)
}
}
@Test
fun `all states`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(2, UniqueIdentifier("TEST")) // create 2 results with same UID
services.fillWithSomeTestLinearStates(8)
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
// services.consumeLinearStates(UniqueIdentifier("TEST"))
// services.consumeDeals("456")
// services.consumeCash(80.DOLLARS)
val criteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val results = vaultSvc.queryBy<ContractState>(criteria)
assertThat(results.states).hasSize(16)
}
}
val CASH_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(20)) }
val CASH_NOTARY: Party get() = Party("Notary Service", CASH_NOTARY_KEY.public)
@Test
fun `unconsumed states by notary`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(10)
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
// DOCSTART VaultQueryExample4
val criteria = VaultQueryCriteria(notaryName = listOf(CASH_NOTARY.name))
val results = vaultSvc.queryBy<ContractState>(criteria)
// DOCEND VaultQueryExample4
assertThat(results.states).hasSize(3)
}
}
@Test
fun `unconsumed states by participants`() {
database.transaction {
services.fillWithSomeTestLinearStates(2, UniqueIdentifier("TEST"), participants = listOf(MEGA_CORP_PUBKEY, MINI_CORP_PUBKEY))
services.fillWithSomeTestDeals(listOf("456"), 3, participants = listOf(MEGA_CORP_PUBKEY, BIG_CORP_PUBKEY))
services.fillWithSomeTestDeals(listOf("123", "789"), participants = listOf(BIG_CORP_PUBKEY, MINI_CORP_PUBKEY))
// DOCSTART VaultQueryExample5
val criteria = VaultQueryCriteria(participantIdentities = listOf(MEGA_CORP.name, MINI_CORP.name))
val results = vaultSvc.queryBy<ContractState>(criteria)
// DOCEND VaultQueryExample5
assertThat(results.states).hasSize(3)
}
}
@Test
fun `unconsumed states excluding soft locks`() {
database.transaction {
val issuedStates = services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 3, 3, Random(0L))
vaultSvc.softLockReserve(UUID.randomUUID(), setOf(issuedStates.states.first().ref, issuedStates.states.last().ref))
val criteria = VaultQueryCriteria(includeSoftlockedStates = false)
val results = vaultSvc.queryBy<ContractState>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `unconsumed states recorded between two time intervals`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(10)
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
// DOCSTART VaultQueryExample6
val start = TEST_TX_TIME
val end = TEST_TX_TIME.plus(30, ChronoUnit.DAYS)
val recordedBetweenExpression = LogicalExpression(
QueryCriteria.TimeInstantType.RECORDED, Operator.BETWEEN, arrayOf(start, end))
val criteria = VaultQueryCriteria(timeCondition = recordedBetweenExpression)
val results = vaultSvc.queryBy<ContractState>(criteria)
// DOCEND VaultQueryExample6
assertThat(results.states).hasSize(3)
}
}
@Test
fun `states consumed after time`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(10)
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
val asOfDateTime = TEST_TX_TIME
val consumedAfterExpression = LogicalExpression(
QueryCriteria.TimeInstantType.CONSUMED, Operator.GREATER_THAN, arrayOf(asOfDateTime))
val criteria = VaultQueryCriteria(status = Vault.StateStatus.CONSUMED,
timeCondition = consumedAfterExpression)
val results = vaultSvc.queryBy<ContractState>(criteria)
assertThat(results.states).hasSize(3)
}
}
// pagination: first page
@Test
fun `all states with paging specification - first page`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 100, 100, Random(0L))
// DOCSTART VaultQueryExample7
val pagingSpec = PageSpecification(DEFAULT_PAGE_NUM, 10)
val criteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val results = vaultSvc.queryBy<ContractState>(criteria, paging = pagingSpec)
// DOCEND VaultQueryExample7
assertThat(results.states).hasSize(10)
}
}
// pagination: last page
@Test
fun `all states with paging specification - last`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 100, 100, Random(0L))
// Last page implies we need to perform a row count for the Query first,
// and then re-query for a given offset defined by (count - pageSize)
val pagingSpec = PageSpecification(-1, 10)
val criteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val results = vaultSvc.queryBy<ContractState>(criteria, paging = pagingSpec)
assertThat(results.states).hasSize(10) // should retrieve states 90..99
}
}
@Test
fun `unconsumed fungible assets`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
// services.fillWithSomeTestCommodity()
services.fillWithSomeTestLinearStates(10)
val criteria = VaultQueryCriteria(contractStateTypes = setOf(FungibleAsset::class.java)) // default is UNCONSUMED
val results = vaultSvc.queryBy<FungibleAsset<*>>(criteria)
assertThat(results.states).hasSize(4)
}
}
@Test
fun `consumed fungible assets`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
// services.consumeCash(2)
// services.fillWithSomeTestCommodity()
// services.consumeCommodity()
services.fillWithSomeTestLinearStates(10)
// services.consumeLinearStates(8)
val criteria = VaultQueryCriteria(status = Vault.StateStatus.CONSUMED,
contractStateTypes = setOf(FungibleAsset::class.java))
val results = vaultSvc.queryBy<FungibleAsset<*>>(criteria)
assertThat(results.states).hasSize(3)
}
}
@Test
fun `unconsumed cash fungible assets`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(10)
val criteria = VaultQueryCriteria(contractStateTypes = setOf(Cash.State::class.java)) // default is UNCONSUMED
val results = vaultSvc.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(3)
}
}
@Test
fun `consumed cash fungible assets`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
// services.consumeCash(2)
services.fillWithSomeTestLinearStates(10)
// services.consumeLinearStates(8)
val criteria = VaultQueryCriteria(status = Vault.StateStatus.CONSUMED)
val results = vaultSvc.queryBy<Cash.State>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `unconsumed linear heads`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(10)
val criteria = VaultQueryCriteria(contractStateTypes = setOf(LinearState::class.java)) // default is UNCONSUMED
val results = vaultSvc.queryBy<LinearState>(criteria)
assertThat(results.states).hasSize(10)
}
}
@Test
fun `consumed linear heads`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestLinearStates(10)
// services.consumeLinearStates(8)
val criteria = VaultQueryCriteria(status = Vault.StateStatus.CONSUMED,
contractStateTypes = setOf(LinearState::class.java))
val results = vaultSvc.queryBy<LinearState>(criteria)
assertThat(results.states).hasSize(2)
}
}
/** LinearState tests */
@Test
fun `unconsumed linear heads for linearId`() {
database.transaction {
val issuedStates = services.fillWithSomeTestLinearStates(10)
// DOCSTART VaultQueryExample8
val linearIds = issuedStates.states.map { it.state.data.linearId }.toList()
val criteria = LinearStateQueryCriteria(linearId = listOf(linearIds.first(), linearIds.last()))
val results = vaultSvc.queryBy<LinearState>(criteria)
// DOCEND VaultQueryExample8
assertThat(results.states).hasSize(2)
}
}
@Test
fun `latest unconsumed linear heads for linearId`() {
database.transaction {
val issuedStates = services.fillWithSomeTestLinearStates(2, UniqueIdentifier("TEST")) // create 2 states with same UID
services.fillWithSomeTestLinearStates(8)
val linearIds = issuedStates.states.map { it.state.data.linearId }.toList()
val criteria = LinearStateQueryCriteria(linearId = listOf(linearIds.first()),
latestOnly = true)
val results = vaultSvc.queryBy<LinearState>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `return chain of linear state for a given id`() {
database.transaction {
val id = UniqueIdentifier("TEST")
services.fillWithSomeTestLinearStates(1, UniqueIdentifier("TEST"))
// services.processLinearState(id) // consume current and produce new state reference
// services.processLinearState(id) // consume current and produce new state reference
// services.processLinearState(id) // consume current and produce new state reference
// should now have 1 UNCONSUMED & 3 CONSUMED state refs for Linear State with UniqueIdentifier("TEST")
// DOCSTART VaultQueryExample9
val linearStateCriteria = LinearStateQueryCriteria(linearId = listOf(id))
val vaultCriteria = VaultQueryCriteria(status = Vault.StateStatus.ALL)
val sorting = Sort(setOf(Sort.SortColumn(VaultSchema.VaultLinearState::uuid.name, Sort.Direction.DESC)))
val results = vaultSvc.queryBy<LinearState>(linearStateCriteria.and(vaultCriteria), sorting)
// DOCEND VaultQueryExample9
assertThat(results.states).hasSize(4)
}
}
@Test
fun `DEPRECATED return linear states for a given id`() {
database.transaction {
val linearUid = UniqueIdentifier("TEST")
services.fillWithSomeTestLinearStates(1, UniqueIdentifier("TEST"))
// services.processLinearState(id) // consume current and produce new state reference
// services.processLinearState(id) // consume current and produce new state reference
// services.processLinearState(id) // consume current and produce new state reference
// should now have 1 UNCONSUMED & 3 CONSUMED state refs for Linear State with UniqueIdentifier("TEST")
// DOCSTART VaultDeprecatedQueryExample1
val states = vaultSvc.linearHeadsOfType<LinearState>().filter { it.key == linearUid }
// DOCEND VaultDeprecatedQueryExample1
assertThat(states).hasSize(4)
}
}
@Test
fun `DEPRECATED return consumed linear states for a given id`() {
database.transaction {
val linearUid = UniqueIdentifier("TEST")
services.fillWithSomeTestLinearStates(1, UniqueIdentifier("TEST"))
// services.processLinearState(id) // consume current and produce new state reference
// services.processLinearState(id) // consume current and produce new state reference
// services.processLinearState(id) // consume current and produce new state reference
// should now have 1 UNCONSUMED & 3 CONSUMED state refs for Linear State with UniqueIdentifier("TEST")
// DOCSTART VaultDeprecatedQueryExample2
val states = vaultSvc.states(setOf(LinearState::class.java),
EnumSet.of(Vault.StateStatus.CONSUMED)).filter { it.state.data.linearId == linearUid }
// DOCEND VaultDeprecatedQueryExample2
assertThat(states).hasSize(3)
}
}
@Test
fun `latest unconsumed linear heads for state refs`() {
database.transaction {
val issuedStates = services.fillWithSomeTestLinearStates(2, UniqueIdentifier("TEST")) // create 2 states with same UID
services.fillWithSomeTestLinearStates(8)
val stateRefs = issuedStates.states.map { it.ref }.toList()
val vaultCriteria = VaultQueryCriteria(stateRefs = listOf(stateRefs.first(), stateRefs.last()))
val linearStateCriteria = LinearStateQueryCriteria(latestOnly = true)
val results = vaultSvc.queryBy<LinearState>(vaultCriteria.and(linearStateCriteria))
assertThat(results.states).hasSize(2)
}
}
@Test
fun `unconsumed deals`() {
database.transaction {
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
val criteria = LinearStateQueryCriteria()
val results = vaultSvc.queryBy<DealState>(criteria)
assertThat(results.states).hasSize(3)
}
}
@Test
fun `unconsumed deals for ref`() {
database.transaction {
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
// DOCSTART VaultQueryExample10
val criteria = LinearStateQueryCriteria(dealRef = listOf("456", "789"))
val results = vaultSvc.queryBy<DealState>(criteria)
// DOCEND VaultQueryExample10
assertThat(results.states).hasSize(2)
}
}
@Test
fun `latest unconsumed deals for ref`() {
database.transaction {
services.fillWithSomeTestLinearStates(2, UniqueIdentifier("TEST"))
services.fillWithSomeTestDeals(listOf("456"), 3) // create 3 revisions with same ID
services.fillWithSomeTestDeals(listOf("123", "789"))
val criteria = LinearStateQueryCriteria(dealRef = listOf("456"), latestOnly = true)
val results = vaultSvc.queryBy<DealState>(criteria)
assertThat(results.states).hasSize(1)
}
}
@Test
fun `latest unconsumed deals with party`() {
database.transaction {
services.fillWithSomeTestLinearStates(2, UniqueIdentifier("TEST"))
services.fillWithSomeTestDeals(listOf("456"), 3) // specify party
services.fillWithSomeTestDeals(listOf("123", "789"))
// DOCSTART VaultQueryExample11
val criteria = LinearStateQueryCriteria(dealPartyName = listOf(MEGA_CORP.name, MINI_CORP.name))
val results = vaultSvc.queryBy<DealState>(criteria)
// DOCEND VaultQueryExample11
assertThat(results.states).hasSize(1)
}
}
/** FungibleAsset tests */
@Test
fun `unconsumed fungible assets of token type`() {
database.transaction {
services.fillWithSomeTestLinearStates(10)
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestCash(100.SWISS_FRANCS, DUMMY_NOTARY, 3, 3, Random(0L))
val criteria = FungibleAssetQueryCriteria(tokenType = listOf(Currency::class.java))
val results = vaultSvc.queryBy<FungibleAsset<*>>(criteria)
assertThat(results.states).hasSize(9)
}
}
@Test
fun `unconsumed fungible assets for single currency`() {
database.transaction {
services.fillWithSomeTestLinearStates(10)
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestCash(100.SWISS_FRANCS, DUMMY_NOTARY, 3, 3, Random(0L))
// DOCSTART VaultQueryExample12
val criteria = FungibleAssetQueryCriteria(tokenValue = listOf(USD.currencyCode))
val results = vaultSvc.queryBy<FungibleAsset<*>>(criteria)
// DOCEND VaultQueryExample12
assertThat(results.states).hasSize(3)
}
}
@Test
fun `unconsumed fungible assets for single currency and quantity greater than`() {
database.transaction {
services.fillWithSomeTestLinearStates(10)
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 1, 1, Random(0L))
services.fillWithSomeTestCash(50.POUNDS, DUMMY_NOTARY, 1, 1, Random(0L))
services.fillWithSomeTestCash(100.SWISS_FRANCS, DUMMY_NOTARY, 3, 3, Random(0L))
// DOCSTART VaultQueryExample13
val criteria = FungibleAssetQueryCriteria(tokenValue = listOf(GBP.currencyCode),
quantity = LogicalExpression(this, Operator.GREATER_THAN, 50))
val results = vaultSvc.queryBy<Cash.State>(criteria)
// DOCEND VaultQueryExample13
assertThat(results.states).hasSize(1)
}
}
@Test
fun `unconsumed fungible assets for several currencies`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 3, 3, Random(0L))
services.fillWithSomeTestCash(100.SWISS_FRANCS, DUMMY_NOTARY, 3, 3, Random(0L))
val criteria = FungibleAssetQueryCriteria(tokenValue = listOf(CHF.currencyCode, GBP.currencyCode))
val results = vaultSvc.queryBy<FungibleAsset<*>>(criteria)
assertThat(results.states).hasSize(3)
}
}
@Test
fun `unconsumed fungible assets for issuer party`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (DUMMY_CASH_ISSUER))
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(1)), issuerKey = BOC_KEY)
// DOCSTART VaultQueryExample14
val criteria = FungibleAssetQueryCriteria(issuerPartyName = listOf(BOC.name))
val results = vaultSvc.queryBy<FungibleAsset<*>>(criteria)
// DOCEND VaultQueryExample14
assertThat(results.states).hasSize(1)
}
}
@Test
fun `unconsumed fungible assets for specific issuer party and refs`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (DUMMY_CASH_ISSUER))
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(1)), issuerKey = BOC_KEY, ref = OpaqueBytes.of(1))
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(2)), issuerKey = BOC_KEY, ref = OpaqueBytes.of(2))
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(3)), issuerKey = BOC_KEY, ref = OpaqueBytes.of(3))
val criteria = FungibleAssetQueryCriteria(issuerPartyName = listOf(BOC.name),
issuerRef = listOf(BOC.ref(1).reference, BOC.ref(2).reference))
val results = vaultSvc.queryBy<FungibleAsset<*>>(criteria)
assertThat(results.states).hasSize(2)
}
}
@Test
fun `unconsumed fungible assets with exit keys`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (DUMMY_CASH_ISSUER))
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(1)), issuerKey = BOC_KEY)
// DOCSTART VaultQueryExample15
val criteria = FungibleAssetQueryCriteria(exitKeyIdentity = listOf(DUMMY_CASH_ISSUER.party.toString()))
val results = vaultSvc.queryBy<FungibleAsset<*>>(criteria)
// DOCEND VaultQueryExample15
assertThat(results.states).hasSize(1)
}
}
@Test
fun `unconsumed fungible assets by owner`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 2, 2, Random(0L), issuedBy = (DUMMY_CASH_ISSUER))
// issue some cash to BOB
// issue some cash to ALICE
val criteria = FungibleAssetQueryCriteria(ownerIdentity = listOf(BOB.name, ALICE.name))
val results = vaultSvc.queryBy<FungibleAsset<*>>(criteria)
assertThat(results.states).hasSize(1)
}
}
/** Vault Custom Query tests */
// specifying Query on Commercial Paper contract state attributes
@Test
fun `commercial paper custom query`() {
database.transaction {
// MegaCorp™ issues $10,000 of commercial paper, to mature in 30 days, owned by itself.
val faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER
val issuance = MEGA_CORP.ref(1)
val commercialPaper =
CommercialPaper().generateIssue(issuance, faceValue, TEST_TX_TIME + 30.days, DUMMY_NOTARY).apply {
setTime(TEST_TX_TIME, 30.seconds)
signWith(MEGA_CORP_KEY)
signWith(DUMMY_NOTARY_KEY)
}.toSignedTransaction()
services.recordTransactions(commercialPaper)
val ccyIndex = LogicalExpression(PersistentCommercialPaperState::currency, Operator.EQUAL, USD.currencyCode)
val maturityIndex = LogicalExpression(PersistentCommercialPaperState::maturity, Operator.GREATER_THAN_OR_EQUAL, TEST_TX_TIME + 30.days)
val faceValueIndex = LogicalExpression(PersistentCommercialPaperState::faceValue, Operator.GREATER_THAN_OR_EQUAL, 10000)
val criteria = VaultCustomQueryCriteria(maturityIndex.and(faceValueIndex).or(ccyIndex))
val result = vaultSvc.queryBy<CommercialPaper.State>(criteria)
assertThat(result.states).hasSize(1)
assertThat(result.statesMetadata).hasSize(1)
}
}
/** Chaining together different Query Criteria tests**/
// specifying Query on Cash contract state attributes
@Test
fun `all cash states with amount of currency greater or equal than`() {
database.transaction {
services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 1, 1, Random(0L))
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L))
services.fillWithSomeTestCash(10.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L))
services.fillWithSomeTestCash(1.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L))
// DOCSTART VaultQueryExample16
val generalCriteria = VaultQueryCriteria(Vault.StateStatus.ALL)
val currencyIndex = LogicalExpression(PersistentCashState::currency, Operator.EQUAL, USD.currencyCode)
val quantityIndex = LogicalExpression(PersistentCashState::pennies, Operator.GREATER_THAN_OR_EQUAL, 10)
val customCriteria = VaultCustomQueryCriteria(currencyIndex.and(quantityIndex))
val criteria = generalCriteria.and(customCriteria)
val results = vaultSvc.queryBy<Cash.State>(criteria)
// DOCEND VaultQueryExample16
assertThat(results.states).hasSize(2)
}
}
// specifying Query on Linear state attributes
@Test
fun `consumed linear heads for linearId between two timestamps`() {
database.transaction {
val issuedStates = services.fillWithSomeTestLinearStates(10)
val externalIds = issuedStates.states.map { it.state.data.linearId }.map { it.externalId }[0]
val uuids = issuedStates.states.map { it.state.data.linearId }.map { it.id }[1]
val start = TEST_TX_TIME
val end = TEST_TX_TIME.plus(30, ChronoUnit.DAYS)
val recordedBetweenExpression = LogicalExpression(TimeInstantType.RECORDED, Operator.BETWEEN, arrayOf(start, end))
val basicCriteria = VaultQueryCriteria(timeCondition = recordedBetweenExpression)
val linearIdsExpression = LogicalExpression(VaultLinearStateEntity::externalId, Operator.IN, externalIds)
val linearIdCondition = LogicalExpression(VaultLinearStateEntity::uuid, Operator.EQUAL, uuids)
val customIndexCriteria = VaultCustomQueryCriteria(linearIdsExpression.or(linearIdCondition))
val criteria = basicCriteria.and(customIndexCriteria)
val results = vaultSvc.queryBy<LinearState>(criteria)
assertThat(results.states).hasSize(2)
}
}
/**
* USE CASE demonstrations (outside of mainline Corda)
*
* 1) Template / Tutorial CorDapp service using Vault API Custom Query to access attributes of IOU State
* 2) Template / Tutorial Flow using a JDBC session to execute a custom query
* 3) Template / Tutorial CorDapp service query extension executing Named Queries via JPA
* 4) Advanced pagination queries using Spring Data (and/or Hibernate/JPQL)
*/
}