mirror of
https://github.com/corda/corda.git
synced 2024-12-19 04:57:58 +00:00
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:
parent
c062d48e6e
commit
8c3b9ac589
@ -15,6 +15,9 @@ import net.corda.core.node.NodeInfo
|
|||||||
import net.corda.core.node.services.NetworkMapCache
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||||
import net.corda.core.node.services.Vault
|
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.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
import org.bouncycastle.asn1.x500.X500Name
|
||||||
@ -65,10 +68,64 @@ interface CordaRPCOps : RPCOps {
|
|||||||
@RPCReturnsObservables
|
@RPCReturnsObservables
|
||||||
fun stateMachinesAndUpdates(): Pair<List<StateMachineInfo>, Observable<StateMachineUpdate>>
|
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.
|
* Returns a pair of head states in the vault and an observable of future updates to the vault.
|
||||||
*/
|
*/
|
||||||
@RPCReturnsObservables
|
@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>>
|
fun vaultAndUpdates(): Pair<List<StateAndRef<ContractState>>, Observable<Vault.Update>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -5,6 +5,9 @@ import com.google.common.util.concurrent.ListenableFuture
|
|||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.*
|
import net.corda.core.crypto.*
|
||||||
import net.corda.core.flows.FlowException
|
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.CordaSerializable
|
||||||
import net.corda.core.serialization.OpaqueBytes
|
import net.corda.core.serialization.OpaqueBytes
|
||||||
import net.corda.core.toFuture
|
import net.corda.core.toFuture
|
||||||
@ -16,6 +19,7 @@ import java.io.InputStream
|
|||||||
import java.security.KeyPair
|
import java.security.KeyPair
|
||||||
import java.security.PrivateKey
|
import java.security.PrivateKey
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,8 +92,38 @@ class Vault<out T : ContractState>(val states: Iterable<StateAndRef<T>>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum class StateStatus {
|
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
|
* 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.
|
* 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>>
|
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
|
* 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<*>?>
|
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].
|
* 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)
|
* 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>>
|
fun <T : ContractState> states(clazzes: Set<Class<T>>, statuses: EnumSet<Vault.StateStatus>, includeSoftLockedStates: Boolean = true): Iterable<StateAndRef<T>>
|
||||||
// DOCEND VaultStatesQuery
|
// 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>>
|
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>> =
|
inline fun <reified T : ContractState> VaultService.unconsumedStates(includeSoftLockedStates: Boolean = true): Iterable<StateAndRef<T>> =
|
||||||
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED), includeSoftLockedStates)
|
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>> =
|
inline fun <reified T : ContractState> VaultService.consumedStates(): Iterable<StateAndRef<T>> =
|
||||||
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.CONSUMED))
|
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. */
|
/** 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() =
|
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() =
|
||||||
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
|
states(setOf(T::class.java), EnumSet.of(Vault.StateStatus.UNCONSUMED))
|
||||||
.associateBy { it.state.data.linearId }.mapValues { it.value }
|
.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 {
|
inline fun <reified T : DealState> VaultService.dealsWith(party: AbstractParty) = linearHeadsOfType<T>().values.filter {
|
||||||
it.state.data.parties.any { it == party }
|
it.state.data.parties.any { it == party }
|
||||||
}
|
}
|
||||||
|
@ -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)
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
* 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.
|
* 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
|
* 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.
|
* 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
|
* the vault database schemas are directly accessible via JDBC for customer joins and queries
|
||||||
|
|
||||||
|
339
docs/source/vault-query.rst
Normal file
339
docs/source/vault-query.rst
Normal 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>()
|
||||||
|
|
@ -10,6 +10,7 @@ description 'Corda finance modules'
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
|
compile project(':node-schemas')
|
||||||
|
|
||||||
testCompile project(':test-utils')
|
testCompile project(':test-utils')
|
||||||
testCompile project(path: ':core', configuration: 'testArtifacts')
|
testCompile project(path: ':core', configuration: 'testArtifacts')
|
||||||
|
@ -81,13 +81,14 @@ class CommercialPaper : Contract {
|
|||||||
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
|
override fun withFaceValue(newFaceValue: Amount<Issued<Currency>>): ICommercialPaperState = copy(faceValue = newFaceValue)
|
||||||
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
|
override fun withMaturityDate(newMaturityDate: Instant): ICommercialPaperState = copy(maturityDate = newMaturityDate)
|
||||||
|
|
||||||
|
// DOCSTART VaultIndexedQueryCriteria
|
||||||
/** Object Relational Mapping support. */
|
/** Object Relational Mapping support. */
|
||||||
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(CommercialPaperSchemaV1)
|
override fun supportedSchemas(): Iterable<MappedSchema> = listOf(CommercialPaperSchemaV1)
|
||||||
|
|
||||||
/** Object Relational Mapping support. */
|
/** Object Relational Mapping support. */
|
||||||
override fun generateMappedObject(schema: MappedSchema): PersistentState {
|
override fun generateMappedObject(schema: MappedSchema): PersistentState {
|
||||||
return when (schema) {
|
return when (schema) {
|
||||||
is CommercialPaperSchemaV1 -> CommercialPaperSchemaV1.PersistentCommericalPaperState(
|
is CommercialPaperSchemaV1 -> CommercialPaperSchemaV1.PersistentCommercialPaperState(
|
||||||
issuanceParty = this.issuance.party.owningKey.toBase58String(),
|
issuanceParty = this.issuance.party.owningKey.toBase58String(),
|
||||||
issuanceRef = this.issuance.reference.bytes,
|
issuanceRef = this.issuance.reference.bytes,
|
||||||
owner = this.owner.toBase58String(),
|
owner = this.owner.toBase58String(),
|
||||||
@ -100,6 +101,7 @@ class CommercialPaper : Contract {
|
|||||||
else -> throw IllegalArgumentException("Unrecognised schema $schema")
|
else -> throw IllegalArgumentException("Unrecognised schema $schema")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// DOCEND VaultIndexedQueryCriteria
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Clauses {
|
interface Clauses {
|
||||||
|
@ -5,10 +5,8 @@ package net.corda.contracts.testing
|
|||||||
import net.corda.contracts.asset.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
import net.corda.contracts.asset.DUMMY_CASH_ISSUER_KEY
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.contracts.Issued
|
import net.corda.core.crypto.CompositeKey
|
||||||
import net.corda.core.contracts.PartyAndReference
|
|
||||||
import net.corda.core.contracts.TransactionType
|
|
||||||
import net.corda.core.crypto.Party
|
import net.corda.core.crypto.Party
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
@ -20,12 +18,16 @@ import java.security.KeyPair
|
|||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
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 freshKey = keyManagementService.freshKey()
|
||||||
|
|
||||||
val transactions: List<SignedTransaction> = dealIds.map {
|
val transactions: List<SignedTransaction> = dealIds.map {
|
||||||
// Issue a deal state
|
// Issue a deal state
|
||||||
val dummyIssue = TransactionType.General.Builder(notary = DUMMY_NOTARY).apply {
|
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(freshKey)
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
signWith(DUMMY_NOTARY_KEY)
|
||||||
}
|
}
|
||||||
@ -33,19 +35,40 @@ fun ServiceHub.fillWithSomeTestDeals(dealIds: List<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
recordTransactions(transactions)
|
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()
|
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 {
|
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(freshKey)
|
||||||
signWith(DUMMY_NOTARY_KEY)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,6 +4,7 @@ import net.corda.core.schemas.MappedSchema
|
|||||||
import net.corda.core.schemas.PersistentState
|
import net.corda.core.schemas.PersistentState
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.Index
|
||||||
import javax.persistence.Table
|
import javax.persistence.Table
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -17,7 +18,9 @@ object CashSchema
|
|||||||
*/
|
*/
|
||||||
object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) {
|
object CashSchemaV1 : MappedSchema(schemaFamily = CashSchema.javaClass, version = 1, mappedTypes = listOf(PersistentCashState::class.java)) {
|
||||||
@Entity
|
@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(
|
class PersistentCashState(
|
||||||
@Column(name = "owner_key")
|
@Column(name = "owner_key")
|
||||||
var owner: String,
|
var owner: String,
|
||||||
|
@ -5,6 +5,7 @@ import net.corda.core.schemas.PersistentState
|
|||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import javax.persistence.Column
|
import javax.persistence.Column
|
||||||
import javax.persistence.Entity
|
import javax.persistence.Entity
|
||||||
|
import javax.persistence.Index
|
||||||
import javax.persistence.Table
|
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
|
* 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.
|
* 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
|
@Entity
|
||||||
@Table(name = "cp_states")
|
@Table(name = "cp_states",
|
||||||
class PersistentCommericalPaperState(
|
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")
|
@Column(name = "issuance_key")
|
||||||
var issuanceParty: String,
|
var issuanceParty: String,
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ dependencies {
|
|||||||
compile project(':core')
|
compile project(':core')
|
||||||
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
testCompile "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
|
||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
|
testCompile project(':test-utils')
|
||||||
|
|
||||||
// Requery: SQL based query & persistence for Kotlin
|
// Requery: SQL based query & persistence for Kotlin
|
||||||
kapt "io.requery:requery-processor:$requery_version"
|
kapt "io.requery:requery-processor:$requery_version"
|
||||||
|
@ -4,6 +4,7 @@ import io.requery.*
|
|||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import net.corda.core.schemas.requery.Requery
|
import net.corda.core.schemas.requery.Requery
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
object VaultSchema {
|
object VaultSchema {
|
||||||
|
|
||||||
@ -73,4 +74,20 @@ object VaultSchema {
|
|||||||
@get:Column(name = "lock_timestamp", nullable = true)
|
@get:Column(name = "lock_timestamp", nullable = true)
|
||||||
var lockUpdateTime: Instant?
|
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
|
||||||
|
}
|
||||||
}
|
}
|
@ -29,7 +29,6 @@ import org.junit.Before
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import sun.misc.MessageUtils.where
|
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
@ -15,6 +15,9 @@ import net.corda.core.node.NodeInfo
|
|||||||
import net.corda.core.node.services.NetworkMapCache
|
import net.corda.core.node.services.NetworkMapCache
|
||||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||||
import net.corda.core.node.services.Vault
|
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.serialization.CordaSerializable
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
import net.corda.node.services.api.ServiceHubInternal
|
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>> {
|
override fun verifiedTransactions(): Pair<List<SignedTransaction>, Observable<SignedTransaction>> {
|
||||||
return database.transaction {
|
return database.transaction {
|
||||||
services.storageService.validatedTransactions.track()
|
services.storageService.validatedTransactions.track()
|
||||||
|
@ -15,18 +15,15 @@ import net.corda.contracts.clause.AbstractConserveAmount
|
|||||||
import net.corda.core.ThreadBox
|
import net.corda.core.ThreadBox
|
||||||
import net.corda.core.bufferUntilSubscribed
|
import net.corda.core.bufferUntilSubscribed
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.crypto.AbstractParty
|
import net.corda.core.crypto.*
|
||||||
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.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.services.StatesNotAvailableException
|
import net.corda.core.node.services.StatesNotAvailableException
|
||||||
import net.corda.core.node.services.Vault
|
import net.corda.core.node.services.Vault
|
||||||
import net.corda.core.node.services.VaultService
|
import net.corda.core.node.services.VaultService
|
||||||
import net.corda.core.node.services.unconsumedStates
|
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.serialization.*
|
||||||
import net.corda.core.tee
|
import net.corda.core.tee
|
||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
@ -194,6 +191,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
|
|||||||
.map { it ->
|
.map { it ->
|
||||||
val stateRef = StateRef(SecureHash.parse(it.txId), it.index)
|
val stateRef = StateRef(SecureHash.parse(it.txId), it.index)
|
||||||
val state = it.contractState.deserialize<TransactionState<T>>(storageKryo())
|
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)
|
StateAndRef(state, stateRef)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -221,6 +219,26 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
|
|||||||
return stateAndRefs.associateBy({ it.ref }, { it.state })
|
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>) {
|
override fun notifyAll(txns: Iterable<WireTransaction>) {
|
||||||
val ourKeys = services.keyManagementService.keys.keys
|
val ourKeys = services.keyManagementService.keys.keys
|
||||||
val netDelta = txns.fold(Vault.NoUpdate) { netDelta, txn -> netDelta + makeUpdate(txn, ourKeys) }
|
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>> {
|
private fun stateRefArgs(stateRefs: Iterable<StateRef>): List<List<Any>> {
|
||||||
return stateRefs.map { listOf("'${it.txhash}'", it.index) }
|
return stateRefs.map { listOf("'${it.txhash}'", it.index) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -35,7 +35,7 @@ import kotlin.test.assertNull
|
|||||||
|
|
||||||
class NodeVaultServiceTest {
|
class NodeVaultServiceTest {
|
||||||
lateinit var services: MockServices
|
lateinit var services: MockServices
|
||||||
val vault: VaultService get() = services.vaultService
|
val vaultSvc: VaultService get() = services.vaultService
|
||||||
lateinit var dataSource: Closeable
|
lateinit var dataSource: Closeable
|
||||||
lateinit var database: Database
|
lateinit var database: Database
|
||||||
|
|
||||||
@ -73,11 +73,11 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
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)
|
assertThat(w1).hasSize(3)
|
||||||
|
|
||||||
val originalStorage = services.storageService
|
val originalStorage = services.storageService
|
||||||
val originalVault = services.vaultService
|
val originalVault = vaultSvc
|
||||||
val services2 = object : MockServices() {
|
val services2 = object : MockServices() {
|
||||||
override val vaultService: VaultService get() = originalVault
|
override val vaultService: VaultService get() = originalVault
|
||||||
|
|
||||||
@ -103,11 +103,11 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
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)
|
assertThat(w1).hasSize(3)
|
||||||
|
|
||||||
val stateRefs = listOf(w1[1].ref, w1[2].ref)
|
val stateRefs = listOf(w1[1].ref, w1[2].ref)
|
||||||
val states = services.vaultService.statesForRefs(stateRefs)
|
val states = vaultSvc.statesForRefs(stateRefs)
|
||||||
assertThat(states).hasSize(2)
|
assertThat(states).hasSize(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -118,32 +118,32 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
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)
|
assertThat(unconsumedStates).hasSize(3)
|
||||||
|
|
||||||
val stateRefsToSoftLock = setOf(unconsumedStates[1].ref, unconsumedStates[2].ref)
|
val stateRefsToSoftLock = setOf(unconsumedStates[1].ref, unconsumedStates[2].ref)
|
||||||
|
|
||||||
// soft lock two of the three states
|
// soft lock two of the three states
|
||||||
val softLockId = UUID.randomUUID()
|
val softLockId = UUID.randomUUID()
|
||||||
services.vaultService.softLockReserve(softLockId, stateRefsToSoftLock)
|
vaultSvc.softLockReserve(softLockId, stateRefsToSoftLock)
|
||||||
|
|
||||||
// all softlocked states
|
// all softlocked states
|
||||||
assertThat(services.vaultService.softLockedStates<Cash.State>()).hasSize(2)
|
assertThat(vaultSvc.softLockedStates<Cash.State>()).hasSize(2)
|
||||||
// my softlocked states
|
// my softlocked states
|
||||||
assertThat(services.vaultService.softLockedStates<Cash.State>(softLockId)).hasSize(2)
|
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId)).hasSize(2)
|
||||||
|
|
||||||
// excluding softlocked states
|
// 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)
|
assertThat(unlockedStates1).hasSize(1)
|
||||||
|
|
||||||
// soft lock release one of the states explicitly
|
// soft lock release one of the states explicitly
|
||||||
services.vaultService.softLockRelease(softLockId, setOf(unconsumedStates[1].ref))
|
vaultSvc.softLockRelease(softLockId, setOf(unconsumedStates[1].ref))
|
||||||
val unlockedStates2 = services.vaultService.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
|
val unlockedStates2 = vaultSvc.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
|
||||||
assertThat(unlockedStates2).hasSize(2)
|
assertThat(unlockedStates2).hasSize(2)
|
||||||
|
|
||||||
// soft lock release the rest by id
|
// soft lock release the rest by id
|
||||||
services.vaultService.softLockRelease(softLockId)
|
vaultSvc.softLockRelease(softLockId)
|
||||||
val unlockedStates = services.vaultService.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
|
val unlockedStates = vaultSvc.unconsumedStates<Cash.State>(includeSoftLockedStates = false).toList()
|
||||||
assertThat(unlockedStates).hasSize(3)
|
assertThat(unlockedStates).hasSize(3)
|
||||||
|
|
||||||
// should be back to original states
|
// should be back to original states
|
||||||
@ -162,7 +162,7 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
val vaultStates =
|
val vaultStates =
|
||||||
database.transaction {
|
database.transaction {
|
||||||
assertNull(vault.cashBalances[USD])
|
assertNull(vaultSvc.cashBalances[USD])
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
||||||
}
|
}
|
||||||
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
|
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
|
||||||
@ -172,8 +172,8 @@ class NodeVaultServiceTest {
|
|||||||
backgroundExecutor.submit {
|
backgroundExecutor.submit {
|
||||||
try {
|
try {
|
||||||
database.transaction {
|
database.transaction {
|
||||||
vault.softLockReserve(softLockId1, stateRefsToSoftLock)
|
vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock)
|
||||||
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
|
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
|
||||||
}
|
}
|
||||||
println("SOFT LOCK STATES #1 succeeded")
|
println("SOFT LOCK STATES #1 succeeded")
|
||||||
} catch(e: Throwable) {
|
} catch(e: Throwable) {
|
||||||
@ -188,8 +188,8 @@ class NodeVaultServiceTest {
|
|||||||
try {
|
try {
|
||||||
Thread.sleep(100) // let 1st thread soft lock them 1st
|
Thread.sleep(100) // let 1st thread soft lock them 1st
|
||||||
database.transaction {
|
database.transaction {
|
||||||
vault.softLockReserve(softLockId2, stateRefsToSoftLock)
|
vaultSvc.softLockReserve(softLockId2, stateRefsToSoftLock)
|
||||||
assertThat(vault.softLockedStates<Cash.State>(softLockId2)).hasSize(3)
|
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId2)).hasSize(3)
|
||||||
}
|
}
|
||||||
println("SOFT LOCK STATES #2 succeeded")
|
println("SOFT LOCK STATES #2 succeeded")
|
||||||
} catch(e: Throwable) {
|
} catch(e: Throwable) {
|
||||||
@ -201,10 +201,10 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
countDown.await()
|
countDown.await()
|
||||||
database.transaction {
|
database.transaction {
|
||||||
val lockStatesId1 = vault.softLockedStates<Cash.State>(softLockId1)
|
val lockStatesId1 = vaultSvc.softLockedStates<Cash.State>(softLockId1)
|
||||||
println("SOFT LOCK #1 final states: $lockStatesId1")
|
println("SOFT LOCK #1 final states: $lockStatesId1")
|
||||||
assertThat(lockStatesId1.size).isIn(0, 3)
|
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")
|
println("SOFT LOCK #2 final states: $lockStatesId2")
|
||||||
assertThat(lockStatesId2.size).isIn(0, 3)
|
assertThat(lockStatesId2.size).isIn(0, 3)
|
||||||
}
|
}
|
||||||
@ -218,7 +218,7 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
val vaultStates =
|
val vaultStates =
|
||||||
database.transaction {
|
database.transaction {
|
||||||
assertNull(vault.cashBalances[USD])
|
assertNull(vaultSvc.cashBalances[USD])
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
||||||
}
|
}
|
||||||
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
|
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
|
||||||
@ -226,14 +226,14 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
// lock 1st state with LockId1
|
// lock 1st state with LockId1
|
||||||
database.transaction {
|
database.transaction {
|
||||||
vault.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first()))
|
vaultSvc.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first()))
|
||||||
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(1)
|
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt to lock all 3 states with LockId2
|
// attempt to lock all 3 states with LockId2
|
||||||
database.transaction {
|
database.transaction {
|
||||||
assertThatExceptionOfType(StatesNotAvailableException::class.java).isThrownBy(
|
assertThatExceptionOfType(StatesNotAvailableException::class.java).isThrownBy(
|
||||||
{ vault.softLockReserve(softLockId2, stateRefsToSoftLock) }
|
{ vaultSvc.softLockReserve(softLockId2, stateRefsToSoftLock) }
|
||||||
).withMessageContaining("only 2 rows available").withNoCause()
|
).withMessageContaining("only 2 rows available").withNoCause()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,7 +245,7 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
val vaultStates =
|
val vaultStates =
|
||||||
database.transaction {
|
database.transaction {
|
||||||
assertNull(vault.cashBalances[USD])
|
assertNull(vaultSvc.cashBalances[USD])
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
||||||
}
|
}
|
||||||
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
|
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
|
||||||
@ -253,14 +253,14 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
// lock states with LockId1
|
// lock states with LockId1
|
||||||
database.transaction {
|
database.transaction {
|
||||||
vault.softLockReserve(softLockId1, stateRefsToSoftLock)
|
vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock)
|
||||||
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
|
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt to relock same states with LockId1
|
// attempt to relock same states with LockId1
|
||||||
database.transaction {
|
database.transaction {
|
||||||
vault.softLockReserve(softLockId1, stateRefsToSoftLock)
|
vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock)
|
||||||
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
|
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,7 +271,7 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
val vaultStates =
|
val vaultStates =
|
||||||
database.transaction {
|
database.transaction {
|
||||||
assertNull(vault.cashBalances[USD])
|
assertNull(vaultSvc.cashBalances[USD])
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
||||||
}
|
}
|
||||||
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
|
val stateRefsToSoftLock = vaultStates.states.map { it.ref }.toSet()
|
||||||
@ -279,14 +279,14 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
// lock states with LockId1
|
// lock states with LockId1
|
||||||
database.transaction {
|
database.transaction {
|
||||||
vault.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first()))
|
vaultSvc.softLockReserve(softLockId1, setOf(stateRefsToSoftLock.first()))
|
||||||
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(1)
|
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt to lock all states with LockId1 (including previously already locked one)
|
// attempt to lock all states with LockId1 (including previously already locked one)
|
||||||
database.transaction {
|
database.transaction {
|
||||||
vault.softLockReserve(softLockId1, stateRefsToSoftLock)
|
vaultSvc.softLockReserve(softLockId1, stateRefsToSoftLock)
|
||||||
assertThat(vault.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
|
assertThat(vaultSvc.softLockedStates<Cash.State>(softLockId1)).hasSize(3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -296,14 +296,14 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L))
|
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)
|
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)
|
spendableStatesUSD.forEach(::println)
|
||||||
assertThat(spendableStatesUSD).hasSize(1)
|
assertThat(spendableStatesUSD).hasSize(1)
|
||||||
assertThat(spendableStatesUSD[0].state.data.amount.quantity).isEqualTo(100L * 100)
|
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 = (DUMMY_CASH_ISSUER))
|
||||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(1)), issuerKey = BOC_KEY)
|
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()
|
onlyFromIssuerParties = setOf(DUMMY_CASH_ISSUER.party, BOC)).toList()
|
||||||
spendableStatesUSD.forEach(::println)
|
spendableStatesUSD.forEach(::println)
|
||||||
assertThat(spendableStatesUSD).hasSize(2)
|
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(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))
|
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)
|
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()
|
onlyFromIssuerParties = setOf(BOC), withIssuerRefs = setOf(OpaqueBytes.of(1), OpaqueBytes.of(2))).toList()
|
||||||
assertThat(spendableStatesUSD).hasSize(2)
|
assertThat(spendableStatesUSD).hasSize(2)
|
||||||
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.party).isEqualTo(BOC)
|
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))
|
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)
|
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)
|
spendableStatesUSD.forEach(::println)
|
||||||
assertThat(spendableStatesUSD).hasSize(1)
|
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))
|
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)
|
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)
|
spendableStatesUSD.forEach(::println)
|
||||||
assertThat(spendableStatesUSD).hasSize(1)
|
assertThat(spendableStatesUSD).hasSize(1)
|
||||||
assertThat(spendableStatesUSD[0].state.data.amount.quantity).isGreaterThanOrEqualTo(100L)
|
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.POUNDS, DUMMY_NOTARY, 10, 10, Random(0L))
|
||||||
services.fillWithSomeTestCash(100.SWISS_FRANCS, 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)
|
assertThat(allStates).hasSize(30)
|
||||||
|
|
||||||
for (i in 1..5) {
|
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)
|
spendableStatesUSD.forEach(::println)
|
||||||
}
|
}
|
||||||
// note only 3 spend attempts succeed with a total of 8 states
|
// 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.recordTransactions(listOf(usefulTX))
|
||||||
|
|
||||||
services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 1")
|
vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 1")
|
||||||
services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 2")
|
vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 2")
|
||||||
services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 3")
|
vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 3")
|
||||||
assertEquals(3, services.vaultService.getTransactionNotes(usefulTX.id).count())
|
assertEquals(3, vaultSvc.getTransactionNotes(usefulTX.id).count())
|
||||||
|
|
||||||
// Issue more Money (GBP)
|
// Issue more Money (GBP)
|
||||||
val anotherTX = TransactionType.General.Builder(null).apply {
|
val anotherTX = TransactionType.General.Builder(null).apply {
|
||||||
@ -424,8 +424,8 @@ class NodeVaultServiceTest {
|
|||||||
|
|
||||||
services.recordTransactions(listOf(anotherTX))
|
services.recordTransactions(listOf(anotherTX))
|
||||||
|
|
||||||
services.vaultService.addNoteToTransaction(anotherTX.id, "GPB Sample Note 1")
|
vaultSvc.addNoteToTransaction(anotherTX.id, "GPB Sample Note 1")
|
||||||
assertEquals(1, services.vaultService.getTransactionNotes(anotherTX.id).count())
|
assertEquals(1, vaultSvc.getTransactionNotes(anotherTX.id).count())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
*/
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user