mirror of
https://github.com/corda/corda.git
synced 2025-05-18 08:23:25 +00:00
Vault Query Sort by StateRef (or constituents: txId, index) (#990)
* Provide sorting by state reference (and individual constituents of: txId, index) * Fixed formatting. * Updated import following rebase from master. * Updated import following rebase from master.
This commit is contained in:
parent
f718acb939
commit
c81ef7eb93
@ -150,7 +150,13 @@ data class Sort(val columns: Collection<SortColumn>) {
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
interface Attribute
|
interface Attribute
|
||||||
|
|
||||||
enum class VaultStateAttribute(val columnName: String) : Attribute {
|
enum class CommonStateAttribute(val attributeParent: String, val attributeChild: String?) : Attribute {
|
||||||
|
STATE_REF("stateRef", null),
|
||||||
|
STATE_REF_TXN_ID("stateRef", "txId"),
|
||||||
|
STATE_REF_INDEX("stateRef", "index")
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class VaultStateAttribute(val attributeName: String) : Attribute {
|
||||||
/** Vault States */
|
/** Vault States */
|
||||||
NOTARY_NAME("notaryName"),
|
NOTARY_NAME("notaryName"),
|
||||||
CONTRACT_TYPE("contractStateClassName"),
|
CONTRACT_TYPE("contractStateClassName"),
|
||||||
@ -160,14 +166,14 @@ data class Sort(val columns: Collection<SortColumn>) {
|
|||||||
LOCK_ID("lockId")
|
LOCK_ID("lockId")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class LinearStateAttribute(val columnName: String) : Attribute {
|
enum class LinearStateAttribute(val attributeName: String) : Attribute {
|
||||||
/** Vault Linear States */
|
/** Vault Linear States */
|
||||||
UUID("uuid"),
|
UUID("uuid"),
|
||||||
EXTERNAL_ID("externalId"),
|
EXTERNAL_ID("externalId"),
|
||||||
DEAL_REFERENCE("dealReference")
|
DEAL_REFERENCE("dealReference")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class FungibleStateAttribute(val columnName: String) : Attribute {
|
enum class FungibleStateAttribute(val attributeName: String) : Attribute {
|
||||||
/** Vault Fungible States */
|
/** Vault Fungible States */
|
||||||
QUANTITY("quantity"),
|
QUANTITY("quantity"),
|
||||||
ISSUER_REF("issuerRef")
|
ISSUER_REF("issuerRef")
|
||||||
|
@ -28,6 +28,9 @@ UNRELEASED
|
|||||||
|
|
||||||
* ``random63BitValue()`` has moved to ``CryptoUtils``
|
* ``random63BitValue()`` has moved to ``CryptoUtils``
|
||||||
|
|
||||||
|
* Added additional common Sort attributes (see ``Sort.CommandStateAttribute``) for use in Vault Query criteria
|
||||||
|
to include STATE_REF, STATE_REF_TXN_ID, STATE_REF_INDEX
|
||||||
|
|
||||||
Milestone 13
|
Milestone 13
|
||||||
------------
|
------------
|
||||||
|
|
||||||
|
@ -334,7 +334,7 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
|||||||
val leftPredicates = parse(left)
|
val leftPredicates = parse(left)
|
||||||
val rightPredicates = parse(right)
|
val rightPredicates = parse(right)
|
||||||
|
|
||||||
val andPredicate = criteriaBuilder.and(criteriaBuilder.and(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray()))
|
val andPredicate = criteriaBuilder.and(*leftPredicates.toTypedArray(), *rightPredicates.toTypedArray())
|
||||||
predicateSet.add(andPredicate)
|
predicateSet.add(andPredicate)
|
||||||
|
|
||||||
return predicateSet
|
return predicateSet
|
||||||
@ -378,11 +378,11 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
|||||||
var orderCriteria = mutableListOf<Order>()
|
var orderCriteria = mutableListOf<Order>()
|
||||||
|
|
||||||
sorting.columns.map { (sortAttribute, direction) ->
|
sorting.columns.map { (sortAttribute, direction) ->
|
||||||
val (entityStateClass, entityStateColumnName) =
|
val (entityStateClass, entityStateAttributeParent, entityStateAttributeChild) =
|
||||||
when(sortAttribute) {
|
when(sortAttribute) {
|
||||||
is SortAttribute.Standard -> parse(sortAttribute.attribute)
|
is SortAttribute.Standard -> parse(sortAttribute.attribute)
|
||||||
is SortAttribute.Custom -> Pair(sortAttribute.entityStateClass, sortAttribute.entityStateColumnName)
|
is SortAttribute.Custom -> Triple(sortAttribute.entityStateClass, sortAttribute.entityStateColumnName, null)
|
||||||
}
|
}
|
||||||
val sortEntityRoot =
|
val sortEntityRoot =
|
||||||
rootEntities.getOrElse(entityStateClass) {
|
rootEntities.getOrElse(entityStateClass) {
|
||||||
// scenario where sorting on attributes not parsed as criteria
|
// scenario where sorting on attributes not parsed as criteria
|
||||||
@ -394,10 +394,16 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
|||||||
}
|
}
|
||||||
when (direction) {
|
when (direction) {
|
||||||
Sort.Direction.ASC -> {
|
Sort.Direction.ASC -> {
|
||||||
orderCriteria.add(criteriaBuilder.asc(sortEntityRoot.get<String>(entityStateColumnName)))
|
if (entityStateAttributeChild != null)
|
||||||
|
orderCriteria.add(criteriaBuilder.asc(sortEntityRoot.get<String>(entityStateAttributeParent).get<String>(entityStateAttributeChild)))
|
||||||
|
else
|
||||||
|
orderCriteria.add(criteriaBuilder.asc(sortEntityRoot.get<String>(entityStateAttributeParent)))
|
||||||
}
|
}
|
||||||
Sort.Direction.DESC ->
|
Sort.Direction.DESC ->
|
||||||
orderCriteria.add(criteriaBuilder.desc(sortEntityRoot.get<String>(entityStateColumnName)))
|
if (entityStateAttributeChild != null)
|
||||||
|
orderCriteria.add(criteriaBuilder.desc(sortEntityRoot.get<String>(entityStateAttributeParent).get<String>(entityStateAttributeChild)))
|
||||||
|
else
|
||||||
|
orderCriteria.add(criteriaBuilder.desc(sortEntityRoot.get<String>(entityStateAttributeParent)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (orderCriteria.isNotEmpty()) {
|
if (orderCriteria.isNotEmpty()) {
|
||||||
@ -406,20 +412,23 @@ class HibernateQueryCriteriaParser(val contractType: Class<out ContractState>,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parse(sortAttribute: Sort.Attribute): Pair<Class<out PersistentState>, String> {
|
private fun parse(sortAttribute: Sort.Attribute): Triple<Class<out PersistentState>, String, String?> {
|
||||||
val entityClassAndColumnName : Pair<Class<out PersistentState>, String> =
|
val entityClassAndColumnName : Triple<Class<out PersistentState>, String, String?> =
|
||||||
when(sortAttribute) {
|
when(sortAttribute) {
|
||||||
is Sort.VaultStateAttribute -> {
|
is Sort.CommonStateAttribute -> {
|
||||||
Pair(VaultSchemaV1.VaultStates::class.java, sortAttribute.columnName)
|
Triple(VaultSchemaV1.VaultStates::class.java, sortAttribute.attributeParent, sortAttribute.attributeChild)
|
||||||
|
}
|
||||||
|
is Sort.VaultStateAttribute -> {
|
||||||
|
Triple(VaultSchemaV1.VaultStates::class.java, sortAttribute.attributeName, null)
|
||||||
|
}
|
||||||
|
is Sort.LinearStateAttribute -> {
|
||||||
|
Triple(VaultSchemaV1.VaultLinearStates::class.java, sortAttribute.attributeName, null)
|
||||||
|
}
|
||||||
|
is Sort.FungibleStateAttribute -> {
|
||||||
|
Triple(VaultSchemaV1.VaultFungibleStates::class.java, sortAttribute.attributeName, null)
|
||||||
|
}
|
||||||
|
else -> throw VaultQueryException("Invalid sort attribute: $sortAttribute")
|
||||||
}
|
}
|
||||||
is Sort.LinearStateAttribute -> {
|
|
||||||
Pair(VaultSchemaV1.VaultLinearStates::class.java, sortAttribute.columnName)
|
|
||||||
}
|
|
||||||
is Sort.FungibleStateAttribute -> {
|
|
||||||
Pair(VaultSchemaV1.VaultFungibleStates::class.java, sortAttribute.columnName)
|
|
||||||
}
|
|
||||||
else -> throw VaultQueryException("Invalid sort attribute: $sortAttribute")
|
|
||||||
}
|
|
||||||
return entityClassAndColumnName
|
return entityClassAndColumnName
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -55,6 +55,7 @@ import static net.corda.node.utilities.DatabaseSupportKt.transaction;
|
|||||||
import static net.corda.testing.CoreTestUtils.getMEGA_CORP;
|
import static net.corda.testing.CoreTestUtils.getMEGA_CORP;
|
||||||
import static net.corda.testing.CoreTestUtils.getMEGA_CORP_KEY;
|
import static net.corda.testing.CoreTestUtils.getMEGA_CORP_KEY;
|
||||||
import static net.corda.testing.node.MockServicesKt.makeTestDataSourceProperties;
|
import static net.corda.testing.node.MockServicesKt.makeTestDataSourceProperties;
|
||||||
|
import static net.corda.core.utilities.ByteArrays.toHexString;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
public class VaultQueryJavaTests {
|
public class VaultQueryJavaTests {
|
||||||
@ -134,6 +135,31 @@ public class VaultQueryJavaTests {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void unconsumedStatesForStateRefsSortedByTxnId() {
|
||||||
|
transaction(database, tx -> {
|
||||||
|
|
||||||
|
VaultFiller.fillWithSomeTestLinearStates(services, 8);
|
||||||
|
Vault<LinearState> issuedStates = VaultFiller.fillWithSomeTestLinearStates(services, 2);
|
||||||
|
|
||||||
|
Stream<StateRef> stateRefsStream = StreamSupport.stream(issuedStates.getStates().spliterator(), false).map(StateAndRef::getRef);
|
||||||
|
List<StateRef> stateRefs = stateRefsStream.collect(Collectors.toList());
|
||||||
|
|
||||||
|
SortAttribute.Standard sortAttribute = new SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID);
|
||||||
|
Sort sorting = new Sort(Arrays.asList(new Sort.SortColumn(sortAttribute, Sort.Direction.ASC)));
|
||||||
|
VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.UNCONSUMED, null, stateRefs);
|
||||||
|
Vault.Page<DummyLinearContract.State> results = vaultQuerySvc.queryBy(DummyLinearContract.State.class, criteria, sorting);
|
||||||
|
|
||||||
|
assertThat(results.getStates()).hasSize(2);
|
||||||
|
|
||||||
|
stateRefs.sort(Comparator.comparing(stateRef -> toHexString(stateRef.getTxhash().getBytes())));
|
||||||
|
assertThat(results.getStates().get(0).getRef()).isEqualTo(stateRefs.get(0));
|
||||||
|
assertThat(results.getStates().get(1).getRef()).isEqualTo(stateRefs.get(1));
|
||||||
|
|
||||||
|
return tx;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void consumedCashStates() {
|
public void consumedCashStates() {
|
||||||
transaction(database, tx -> {
|
transaction(database, tx -> {
|
||||||
|
@ -202,6 +202,67 @@ class HibernateConfigurationTest {
|
|||||||
queryResultsAsc.map { println(it.recordedTime) }
|
queryResultsAsc.map { println(it.recordedTime) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `with sorting by state ref desc and asc`() {
|
||||||
|
// generate additional state ref indexes
|
||||||
|
database.transaction {
|
||||||
|
services.consumeCash(1.DOLLARS)
|
||||||
|
services.consumeCash(2.DOLLARS)
|
||||||
|
services.consumeCash(3.DOLLARS)
|
||||||
|
services.consumeCash(4.DOLLARS)
|
||||||
|
services.consumeCash(5.DOLLARS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// structure query
|
||||||
|
val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultStates::class.java)
|
||||||
|
val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
|
||||||
|
|
||||||
|
val sortByStateRef = vaultStates.get<PersistentStateRef>("stateRef")
|
||||||
|
|
||||||
|
// order by DESC
|
||||||
|
criteriaQuery.orderBy(criteriaBuilder.desc(sortByStateRef))
|
||||||
|
val queryResults = entityManager.createQuery(criteriaQuery).resultList
|
||||||
|
println("DESC by stateRef")
|
||||||
|
queryResults.map { println(it.stateRef) }
|
||||||
|
|
||||||
|
// order by ASC
|
||||||
|
criteriaQuery.orderBy(criteriaBuilder.asc(sortByStateRef))
|
||||||
|
val queryResultsAsc = entityManager.createQuery(criteriaQuery).resultList
|
||||||
|
println("ASC by stateRef")
|
||||||
|
queryResultsAsc.map { println(it.stateRef) }
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `with sorting by state ref index and txId desc and asc`() {
|
||||||
|
// generate additional state ref indexes
|
||||||
|
database.transaction {
|
||||||
|
services.consumeCash(1.DOLLARS)
|
||||||
|
services.consumeCash(2.DOLLARS)
|
||||||
|
services.consumeCash(3.DOLLARS)
|
||||||
|
services.consumeCash(4.DOLLARS)
|
||||||
|
services.consumeCash(5.DOLLARS)
|
||||||
|
}
|
||||||
|
|
||||||
|
// structure query
|
||||||
|
val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultStates::class.java)
|
||||||
|
val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
|
||||||
|
|
||||||
|
val sortByIndex = vaultStates.get<PersistentStateRef>("stateRef").get<String>("index")
|
||||||
|
val sortByTxId = vaultStates.get<PersistentStateRef>("stateRef").get<String>("txId")
|
||||||
|
|
||||||
|
// order by DESC
|
||||||
|
criteriaQuery.orderBy(criteriaBuilder.desc(sortByIndex), criteriaBuilder.desc(sortByTxId))
|
||||||
|
val queryResults = entityManager.createQuery(criteriaQuery).resultList
|
||||||
|
println("DESC by index txId")
|
||||||
|
queryResults.map { println(it.stateRef) }
|
||||||
|
|
||||||
|
// order by ASC
|
||||||
|
criteriaQuery.orderBy(criteriaBuilder.asc(sortByIndex), criteriaBuilder.asc(sortByTxId))
|
||||||
|
val queryResultsAsc = entityManager.createQuery(criteriaQuery).resultList
|
||||||
|
println("ASC by index txId")
|
||||||
|
queryResultsAsc.map { println(it.stateRef) }
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `with pagination`() {
|
fun `with pagination`() {
|
||||||
// add 100 additional cash entries
|
// add 100 additional cash entries
|
||||||
|
@ -18,6 +18,7 @@ import net.corda.core.schemas.testing.DummyLinearStateSchemaV1
|
|||||||
import net.corda.core.seconds
|
import net.corda.core.seconds
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
|
import net.corda.core.utilities.toHexString
|
||||||
import net.corda.node.services.database.HibernateConfiguration
|
import net.corda.node.services.database.HibernateConfiguration
|
||||||
import net.corda.node.services.schema.NodeSchemaService
|
import net.corda.node.services.schema.NodeSchemaService
|
||||||
import net.corda.node.services.vault.schemas.jpa.VaultSchemaV1
|
import net.corda.node.services.vault.schemas.jpa.VaultSchemaV1
|
||||||
@ -220,21 +221,80 @@ class VaultQueryTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `unconsumed states for state refs`() {
|
fun `unconsumed cash states sorted by state ref`() {
|
||||||
|
database.transaction {
|
||||||
|
|
||||||
|
var stateRefs : MutableList<StateRef> = mutableListOf()
|
||||||
|
|
||||||
|
val issuedStates = services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
|
||||||
|
val issuedStateRefs = issuedStates.states.map { it.ref }.toList()
|
||||||
|
stateRefs.addAll(issuedStateRefs)
|
||||||
|
|
||||||
|
val spentStates = services.consumeCash(25.DOLLARS)
|
||||||
|
var spentStateRefs = spentStates.states.map { it.ref }.toList()
|
||||||
|
stateRefs.addAll(spentStateRefs)
|
||||||
|
|
||||||
|
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
|
||||||
|
val criteria = VaultQueryCriteria()
|
||||||
|
val results = vaultQuerySvc.queryBy<Cash.State>(criteria, Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC))))
|
||||||
|
|
||||||
|
// default StateRef sort is by index then txnId:
|
||||||
|
// order by
|
||||||
|
// vaultschem1_.output_index,
|
||||||
|
// vaultschem1_.transaction_id asc
|
||||||
|
assertThat(results.states).hasSize(8) // -3 CONSUMED + 1 NEW UNCONSUMED (change)
|
||||||
|
|
||||||
|
val sortedStateRefs = stateRefs.sortedBy { it.index }
|
||||||
|
|
||||||
|
assertThat(results.states.first().ref.index).isEqualTo(sortedStateRefs.first().index) // 0
|
||||||
|
assertThat(results.states.last().ref.index).isEqualTo(sortedStateRefs.last().index) // 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unconsumed cash states sorted by state ref txnId and index`() {
|
||||||
|
database.transaction {
|
||||||
|
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
|
||||||
|
services.consumeCash(10.DOLLARS)
|
||||||
|
services.consumeCash(10.DOLLARS)
|
||||||
|
|
||||||
|
val sortAttributeTxnId = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID)
|
||||||
|
val sortAttributeIndex = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_INDEX)
|
||||||
|
val sortBy = Sort(setOf(Sort.SortColumn(sortAttributeTxnId, Sort.Direction.ASC),
|
||||||
|
Sort.SortColumn(sortAttributeIndex, Sort.Direction.ASC)))
|
||||||
|
val criteria = VaultQueryCriteria()
|
||||||
|
val results = vaultQuerySvc.queryBy<Cash.State>(criteria, sortBy)
|
||||||
|
|
||||||
|
results.statesMetadata.forEach {
|
||||||
|
println(" ${it.ref}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicit sort order asc by txnId and then index:
|
||||||
|
// order by
|
||||||
|
// vaultschem1_.transaction_id asc,
|
||||||
|
// vaultschem1_.output_index asc
|
||||||
|
assertThat(results.states).hasSize(9) // -2 CONSUMED + 1 NEW UNCONSUMED (change)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unconsumed states for state refs`() {
|
||||||
database.transaction {
|
database.transaction {
|
||||||
services.fillWithSomeTestLinearStates(8)
|
services.fillWithSomeTestLinearStates(8)
|
||||||
val issuedStates = services.fillWithSomeTestLinearStates(2)
|
val issuedStates = services.fillWithSomeTestLinearStates(2)
|
||||||
val stateRefs = issuedStates.states.map { it.ref }.toList()
|
val stateRefs = issuedStates.states.map { it.ref }.toList()
|
||||||
|
|
||||||
// DOCSTART VaultQueryExample2
|
// DOCSTART VaultQueryExample2
|
||||||
|
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID)
|
||||||
val criteria = VaultQueryCriteria(stateRefs = listOf(stateRefs.first(), stateRefs.last()))
|
val criteria = VaultQueryCriteria(stateRefs = listOf(stateRefs.first(), stateRefs.last()))
|
||||||
val results = vaultQuerySvc.queryBy<DummyLinearContract.State>(criteria)
|
val results = vaultQuerySvc.queryBy<DummyLinearContract.State>(criteria, Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC))))
|
||||||
// DOCEND VaultQueryExample2
|
// DOCEND VaultQueryExample2
|
||||||
|
|
||||||
assertThat(results.states).hasSize(2)
|
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)
|
val sortedStateRefs = stateRefs.sortedBy { it.txhash.bytes.toHexString() }
|
||||||
|
assertThat(results.states.first().ref).isEqualTo(sortedStateRefs.first())
|
||||||
|
assertThat(results.states.last().ref).isEqualTo(sortedStateRefs.last())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,7 +223,7 @@ fun ServiceHub.evolveLinearStates(linearStates: List<StateAndRef<LinearState>>)
|
|||||||
fun ServiceHub.evolveLinearState(linearState: StateAndRef<LinearState>) : StateAndRef<LinearState> = consumeAndProduce(linearState)
|
fun ServiceHub.evolveLinearState(linearState: StateAndRef<LinearState>) : StateAndRef<LinearState> = consumeAndProduce(linearState)
|
||||||
|
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE) {
|
fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE): Vault<Cash.State> {
|
||||||
// A tx that spends our money.
|
// A tx that spends our money.
|
||||||
val spendTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
val spendTX = TransactionType.General.Builder(DUMMY_NOTARY).apply {
|
||||||
vaultService.generateSpend(this, amount, to)
|
vaultService.generateSpend(this, amount, to)
|
||||||
@ -231,4 +231,10 @@ fun ServiceHub.consumeCash(amount: Amount<Currency>, to: Party = CHARLIE) {
|
|||||||
}.toSignedTransaction(checkSufficientSignatures = false)
|
}.toSignedTransaction(checkSufficientSignatures = false)
|
||||||
|
|
||||||
recordTransactions(spendTX)
|
recordTransactions(spendTX)
|
||||||
|
|
||||||
|
// Get all the StateRefs of all the generated transactions.
|
||||||
|
val states = spendTX.tx.outputs.indices.map { i -> spendTX.tx.outRef<Cash.State>(i) }
|
||||||
|
|
||||||
|
return Vault(states)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user