Vault Query API design (#522)

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

* Minor fix following rebase.

* Spit out Vault Query tests into separate source file.

* WIP

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

* Added Deprecating annotations.
Added QueryCriteria for set of contractStateTypes

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

* Added Java Junit tests and QueryCriteria builder support.

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

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

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

* Enable infix notation.

* Fixed typo.

* Clarified future work to enforce DB level permissioning.

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

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

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

* Exposed new Vault Query API functions via RPC.

* Fixed compiler error in java test.

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

* Major updates following PR discussion and recommendations.

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

* Added Generic Index schema mapping and query support.

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

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

* Removed all directly exposed API dependencies on requery.

* Updated documentation.

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

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

* RST documentation updates.

* Updates to address RP latest set of review comments.

* Updates to address MH latest set of review comments.

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

* Aesthetic updates requested by MH

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

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

* Fixed compiler warnings (except deprecation warnings)

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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