mirror of
https://github.com/corda/corda.git
synced 2025-06-17 06:38:21 +00:00
Make a generic generate spend. Tests pass.
Fixup after rebase Fixup after rebase Make node have only test compile dependency against finance module. Use original query code. Fixup after rebase Update docs Edit docs Add to changelog Follow recommendations from PR Follow recommendations from PR Make a re-usable helper function to create MockServices with database for tests Tweak a few comments Don't include tryLockFungibleStateForSpending in soft lock docs. Respond to PR comments Fix whitespace error Fix compile error Fixup after rebase
This commit is contained in:
@ -8,13 +8,13 @@ import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.VaultQueryException
|
||||
import net.corda.core.node.services.vault.*
|
||||
import net.corda.core.node.services.vault.QueryCriteria.CommonQueryCriteria
|
||||
import net.corda.core.schemas.CommonSchemaV1
|
||||
import net.corda.core.schemas.PersistentState
|
||||
import net.corda.core.schemas.PersistentStateRef
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toHexString
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.toHexString
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.core.schemas.CommonSchemaV1
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import java.util.*
|
||||
import javax.persistence.Tuple
|
||||
|
@ -8,28 +8,32 @@ import io.requery.kotlin.eq
|
||||
import io.requery.kotlin.notNull
|
||||
import io.requery.query.RowExpression
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.OnLedgerAsset
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.containsAny
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.ThreadBox
|
||||
import net.corda.core.internal.tee
|
||||
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.vault.IQueryCriteriaParser
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.node.services.vault.Sort
|
||||
import net.corda.core.node.services.vault.SortAttribute
|
||||
import net.corda.core.schemas.PersistentState
|
||||
import net.corda.core.serialization.SerializationDefaults.STORAGE_CONTEXT
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.transactions.CoreTransaction
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.toNonEmptySet
|
||||
import net.corda.core.utilities.trace
|
||||
import net.corda.node.services.database.RequeryConfiguration
|
||||
import net.corda.node.services.database.parserTransactionIsolationLevel
|
||||
import net.corda.node.services.statemachine.FlowStateMachineImpl
|
||||
@ -42,10 +46,8 @@ import net.corda.node.utilities.wrapWithDatabaseTransaction
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.security.PublicKey
|
||||
import java.sql.SQLException
|
||||
import java.util.*
|
||||
import java.util.concurrent.locks.ReentrantLock
|
||||
import kotlin.concurrent.withLock
|
||||
import javax.persistence.criteria.Predicate
|
||||
|
||||
/**
|
||||
* Currently, the node vault service is a very simple RDBMS backed implementation. It will change significantly when
|
||||
@ -79,6 +81,7 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
|
||||
// For use during publishing only.
|
||||
val updatesPublisher: rx.Observer<Vault.Update<ContractState>> get() = _updatesPublisher.bufferUntilDatabaseCommit().tee(_rawUpdatesPublisher)
|
||||
}
|
||||
|
||||
private val mutex = ThreadBox(InnerState())
|
||||
|
||||
private fun recordUpdate(update: Vault.Update<ContractState>): Vault.Update<ContractState> {
|
||||
@ -334,123 +337,110 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
|
||||
}
|
||||
}
|
||||
|
||||
// coin selection retry loop counter, sleep (msecs) and lock for selecting states
|
||||
val MAX_RETRIES = 5
|
||||
val RETRY_SLEEP = 100
|
||||
val spendLock: ReentrantLock = ReentrantLock()
|
||||
// TODO We shouldn't need to rewrite the query if we could modify the defaults.
|
||||
private class QueryEditor<out T : ContractState>(val services: ServiceHub,
|
||||
val lockId: UUID,
|
||||
val contractType: Class<out T>) : IQueryCriteriaParser {
|
||||
var alreadyHasVaultQuery: Boolean = false
|
||||
var modifiedCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(contractStateTypes = setOf(contractType),
|
||||
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)),
|
||||
status = Vault.StateStatus.UNCONSUMED)
|
||||
|
||||
@Suspendable
|
||||
override fun <T : ContractState> unconsumedStatesForSpending(amount: Amount<Currency>, onlyFromIssuerParties: Set<AbstractParty>?, notary: Party?, lockId: UUID, withIssuerRefs: Set<OpaqueBytes>?): List<StateAndRef<T>> {
|
||||
|
||||
val issuerKeysStr = onlyFromIssuerParties?.fold("") { left, right -> left + "('${right.owningKey.toBase58String()}')," }?.dropLast(1)
|
||||
val issuerRefsStr = withIssuerRefs?.fold("") { left, right -> left + "('${right.bytes.toHexString()}')," }?.dropLast(1)
|
||||
|
||||
val stateAndRefs = mutableListOf<StateAndRef<T>>()
|
||||
|
||||
// TODO: Need to provide a database provider independent means of performing this function.
|
||||
// We are using an H2 specific means of selecting a minimum set of rows that match a request amount of coins:
|
||||
// 1) There is no standard SQL mechanism of calculating a cumulative total on a field and restricting row selection on the
|
||||
// running total of such an accumulator
|
||||
// 2) H2 uses session variables to perform this accumulator function:
|
||||
// http://www.h2database.com/html/functions.html#set
|
||||
// 3) H2 does not support JOIN's in FOR UPDATE (hence we are forced to execute 2 queries)
|
||||
|
||||
for (retryCount in 1..MAX_RETRIES) {
|
||||
|
||||
spendLock.withLock {
|
||||
val statement = configuration.jdbcSession().createStatement()
|
||||
try {
|
||||
statement.execute("CALL SET(@t, 0);")
|
||||
|
||||
// we select spendable states irrespective of lock but prioritised by unlocked ones (Eg. null)
|
||||
// the softLockReserve update will detect whether we try to lock states locked by others
|
||||
val selectJoin = """
|
||||
SELECT vs.transaction_id, vs.output_index, vs.contract_state, ccs.pennies, SET(@t, ifnull(@t,0)+ccs.pennies) total_pennies, vs.lock_id
|
||||
FROM vault_states AS vs, contract_cash_states AS ccs
|
||||
WHERE vs.transaction_id = ccs.transaction_id AND vs.output_index = ccs.output_index
|
||||
AND vs.state_status = 0
|
||||
AND ccs.ccy_code = '${amount.token}' and @t < ${amount.quantity}
|
||||
AND (vs.lock_id = '$lockId' OR vs.lock_id is null)
|
||||
""" +
|
||||
(if (notary != null)
|
||||
" AND vs.notary_key = '${notary.owningKey.toBase58String()}'" else "") +
|
||||
(if (issuerKeysStr != null)
|
||||
" AND ccs.issuer_key IN ($issuerKeysStr)" else "") +
|
||||
(if (issuerRefsStr != null)
|
||||
" AND ccs.issuer_ref IN ($issuerRefsStr)" else "")
|
||||
|
||||
// Retrieve spendable state refs
|
||||
val rs = statement.executeQuery(selectJoin)
|
||||
stateAndRefs.clear()
|
||||
log.debug(selectJoin)
|
||||
var totalPennies = 0L
|
||||
while (rs.next()) {
|
||||
val txHash = SecureHash.parse(rs.getString(1))
|
||||
val index = rs.getInt(2)
|
||||
val stateRef = StateRef(txHash, index)
|
||||
val state = rs.getBytes(3).deserialize<TransactionState<T>>(context = STORAGE_CONTEXT)
|
||||
val pennies = rs.getLong(4)
|
||||
totalPennies = rs.getLong(5)
|
||||
val rowLockId = rs.getString(6)
|
||||
stateAndRefs.add(StateAndRef(state, stateRef))
|
||||
log.trace { "ROW: $rowLockId ($lockId): $stateRef : $pennies ($totalPennies)" }
|
||||
}
|
||||
|
||||
if (stateAndRefs.isNotEmpty() && totalPennies >= amount.quantity) {
|
||||
// we should have a minimum number of states to satisfy our selection `amount` criteria
|
||||
log.trace("Coin selection for $amount retrieved ${stateAndRefs.count()} states totalling $totalPennies pennies: $stateAndRefs")
|
||||
|
||||
// update database
|
||||
softLockReserve(lockId, (stateAndRefs.map { it.ref }).toNonEmptySet())
|
||||
return stateAndRefs
|
||||
}
|
||||
log.trace("Coin selection requested $amount but retrieved $totalPennies pennies with state refs: ${stateAndRefs.map { it.ref }}")
|
||||
// retry as more states may become available
|
||||
} catch (e: SQLException) {
|
||||
log.error("""Failed retrieving unconsumed states for: amount [$amount], onlyFromIssuerParties [$onlyFromIssuerParties], notary [$notary], lockId [$lockId]
|
||||
$e.
|
||||
""")
|
||||
} catch (e: StatesNotAvailableException) {
|
||||
stateAndRefs.clear()
|
||||
log.warn(e.message)
|
||||
// retry only if there are locked states that may become available again (or consumed with change)
|
||||
} finally {
|
||||
statement.close()
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("Coin selection failed on attempt $retryCount")
|
||||
// TODO: revisit the back off strategy for contended spending.
|
||||
if (retryCount != MAX_RETRIES) {
|
||||
FlowStateMachineImpl.sleep(RETRY_SLEEP * retryCount.toLong())
|
||||
}
|
||||
override fun parseCriteria(criteria: QueryCriteria.CommonQueryCriteria): Collection<Predicate> {
|
||||
modifiedCriteria = criteria
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
log.warn("Insufficient spendable states identified for $amount")
|
||||
return stateAndRefs
|
||||
override fun parseCriteria(criteria: QueryCriteria.FungibleAssetQueryCriteria): Collection<Predicate> {
|
||||
modifiedCriteria = criteria
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun parseCriteria(criteria: QueryCriteria.LinearStateQueryCriteria): Collection<Predicate> {
|
||||
modifiedCriteria = criteria
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun <L : PersistentState> parseCriteria(criteria: QueryCriteria.VaultCustomQueryCriteria<L>): Collection<Predicate> {
|
||||
modifiedCriteria = criteria
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun parseCriteria(criteria: QueryCriteria.VaultQueryCriteria): Collection<Predicate> {
|
||||
modifiedCriteria = criteria.copy(contractStateTypes = setOf(contractType),
|
||||
softLockingCondition = QueryCriteria.SoftLockingCondition(QueryCriteria.SoftLockingType.UNLOCKED_AND_SPECIFIED, listOf(lockId)),
|
||||
status = Vault.StateStatus.UNCONSUMED)
|
||||
alreadyHasVaultQuery = true
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun parseOr(left: QueryCriteria, right: QueryCriteria): Collection<Predicate> {
|
||||
parse(left)
|
||||
val modifiedLeft = modifiedCriteria
|
||||
parse(right)
|
||||
val modifiedRight = modifiedCriteria
|
||||
modifiedCriteria = modifiedLeft.or(modifiedRight)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun parseAnd(left: QueryCriteria, right: QueryCriteria): Collection<Predicate> {
|
||||
parse(left)
|
||||
val modifiedLeft = modifiedCriteria
|
||||
parse(right)
|
||||
val modifiedRight = modifiedCriteria
|
||||
modifiedCriteria = modifiedLeft.and(modifiedRight)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun parse(criteria: QueryCriteria, sorting: Sort?): Collection<Predicate> {
|
||||
val basicQuery = modifiedCriteria
|
||||
criteria.visit(this)
|
||||
modifiedCriteria = if (alreadyHasVaultQuery) modifiedCriteria else criteria.and(basicQuery)
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
fun queryForEligibleStates(criteria: QueryCriteria): Vault.Page<T> {
|
||||
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
|
||||
val sorter = Sort(setOf(Sort.SortColumn(sortAttribute, Sort.Direction.ASC)))
|
||||
parse(criteria, sorter)
|
||||
|
||||
return services.vaultQueryService.queryBy(contractType, modifiedCriteria, sorter)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a transaction that moves an amount of currency to the given pubkey.
|
||||
*
|
||||
* @param onlyFromParties if non-null, the asset states will be filtered to only include those issued by the set
|
||||
* of given parties. This can be useful if the party you're trying to pay has expectations
|
||||
* about which type of asset claims they are willing to accept.
|
||||
*/
|
||||
|
||||
@Suspendable
|
||||
override fun generateSpend(tx: TransactionBuilder,
|
||||
amount: Amount<Currency>,
|
||||
to: AbstractParty,
|
||||
onlyFromParties: Set<AbstractParty>?): Pair<TransactionBuilder, List<PublicKey>> {
|
||||
// Retrieve unspent and unlocked cash states that meet our spending criteria.
|
||||
val acceptableCoins = unconsumedStatesForSpending<Cash.State>(amount, onlyFromParties, tx.notary, tx.lockId)
|
||||
return OnLedgerAsset.generateSpend(tx, amount, to, acceptableCoins,
|
||||
{ state, quantity, owner -> deriveState(state, quantity, owner) },
|
||||
{ Cash().generateMoveCommand() })
|
||||
}
|
||||
@Throws(StatesNotAvailableException::class)
|
||||
override fun <T : FungibleAsset<U>, U : Any> tryLockFungibleStatesForSpending(lockId: UUID,
|
||||
eligibleStatesQuery: QueryCriteria,
|
||||
amount: Amount<U>,
|
||||
contractType: Class<out T>): List<StateAndRef<T>> {
|
||||
if (amount.quantity == 0L) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
private fun deriveState(txState: TransactionState<Cash.State>, amount: Amount<Issued<Currency>>, owner: AbstractParty)
|
||||
= txState.copy(data = txState.data.copy(amount = amount, owner = owner))
|
||||
// TODO This helper code re-writes the query to alter the defaults on things such as soft locks
|
||||
// and then runs the query. Ideally we would not need to do this.
|
||||
val results = QueryEditor(services, lockId, contractType).queryForEligibleStates(eligibleStatesQuery)
|
||||
|
||||
var claimedAmount = 0L
|
||||
val claimedStates = mutableListOf<StateAndRef<T>>()
|
||||
for (state in results.states) {
|
||||
val issuedAssetToken = state.state.data.amount.token
|
||||
if (issuedAssetToken.product == amount.token) {
|
||||
claimedStates += state
|
||||
claimedAmount += state.state.data.amount.quantity
|
||||
if (claimedAmount > amount.quantity) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if (claimedStates.isEmpty() || claimedAmount < amount.quantity) {
|
||||
return emptyList()
|
||||
}
|
||||
softLockReserve(lockId, claimedStates.map { it.ref }.toNonEmptySet())
|
||||
return claimedStates
|
||||
}
|
||||
|
||||
// TODO : Persists this in DB.
|
||||
private val authorisedUpgrade = mutableMapOf<StateRef, Class<out UpgradedContract<*, *>>>()
|
||||
|
@ -1,42 +1,50 @@
|
||||
package net.corda.node.services.vault;
|
||||
|
||||
import com.google.common.collect.*;
|
||||
import net.corda.contracts.*;
|
||||
import net.corda.contracts.asset.*;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import kotlin.Pair;
|
||||
import net.corda.contracts.DealState;
|
||||
import net.corda.contracts.asset.Cash;
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.crypto.*;
|
||||
import net.corda.core.identity.*;
|
||||
import net.corda.core.messaging.*;
|
||||
import net.corda.core.node.services.*;
|
||||
import net.corda.core.crypto.EncodingUtils;
|
||||
import net.corda.core.identity.AbstractParty;
|
||||
import net.corda.core.messaging.DataFeed;
|
||||
import net.corda.core.node.services.Vault;
|
||||
import net.corda.core.node.services.VaultQueryException;
|
||||
import net.corda.core.node.services.VaultQueryService;
|
||||
import net.corda.core.node.services.VaultService;
|
||||
import net.corda.core.node.services.vault.*;
|
||||
import net.corda.core.node.services.vault.QueryCriteria.*;
|
||||
import net.corda.core.schemas.*;
|
||||
import net.corda.core.transactions.*;
|
||||
import net.corda.core.utilities.*;
|
||||
import net.corda.node.services.database.*;
|
||||
import net.corda.node.services.schema.*;
|
||||
import net.corda.node.utilities.*;
|
||||
import net.corda.schemas.*;
|
||||
import net.corda.testing.*;
|
||||
import net.corda.testing.contracts.*;
|
||||
import net.corda.testing.node.*;
|
||||
import net.corda.testing.schemas.*;
|
||||
import org.jetbrains.annotations.*;
|
||||
import org.junit.*;
|
||||
import net.corda.core.node.services.vault.QueryCriteria.LinearStateQueryCriteria;
|
||||
import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria;
|
||||
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
|
||||
import net.corda.core.utilities.OpaqueBytes;
|
||||
import net.corda.node.utilities.CordaPersistence;
|
||||
import net.corda.schemas.CashSchemaV1;
|
||||
import net.corda.testing.TestConstants;
|
||||
import net.corda.testing.TestDependencyInjectionBase;
|
||||
import net.corda.testing.contracts.DummyLinearContract;
|
||||
import net.corda.testing.contracts.VaultFiller;
|
||||
import net.corda.testing.node.MockServices;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import rx.Observable;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.security.KeyPair;
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import static net.corda.contracts.asset.CashKt.*;
|
||||
import static net.corda.core.node.services.vault.QueryCriteriaUtils.*;
|
||||
import static net.corda.core.utilities.ByteArrays.*;
|
||||
import static net.corda.node.utilities.CordaPersistenceKt.*;
|
||||
import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER;
|
||||
import static net.corda.contracts.asset.CashKt.getDUMMY_CASH_ISSUER_KEY;
|
||||
import static net.corda.core.node.services.vault.QueryCriteriaUtils.DEFAULT_PAGE_NUM;
|
||||
import static net.corda.core.node.services.vault.QueryCriteriaUtils.MAX_PAGE_SIZE;
|
||||
import static net.corda.core.utilities.ByteArrays.toHexString;
|
||||
import static net.corda.testing.CoreTestUtils.*;
|
||||
import static net.corda.testing.node.MockServicesKt.*;
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static net.corda.testing.node.MockServicesKt.makeTestDatabaseAndMockServices;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class VaultQueryJavaTests extends TestDependencyInjectionBase {
|
||||
|
||||
@ -47,38 +55,13 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
Properties dataSourceProps = makeTestDataSourceProperties(SecureHash.randomSHA256().toString());
|
||||
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties());
|
||||
database.transaction(statement -> {
|
||||
Set<MappedSchema> customSchemas = new HashSet<>(Collections.singletonList(DummyLinearStateSchemaV1.INSTANCE));
|
||||
HibernateConfiguration hibernateConfig = new HibernateConfiguration(new NodeSchemaService(customSchemas), makeTestDatabaseProperties());
|
||||
services = new MockServices(getMEGA_CORP_KEY()) {
|
||||
@NotNull
|
||||
@Override
|
||||
public VaultService getVaultService() {
|
||||
if (vaultSvc != null) return vaultSvc;
|
||||
return makeVaultService(dataSourceProps, hibernateConfig);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public VaultQueryService getVaultQueryService() {
|
||||
return new HibernateVaultQueryImpl(hibernateConfig, vaultSvc.getUpdatesPublisher());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void recordTransactions(@NotNull Iterable<SignedTransaction> txs) {
|
||||
for (SignedTransaction stx : txs) {
|
||||
getValidatedTransactions().addTransaction(stx);
|
||||
}
|
||||
Stream<WireTransaction> wtxn = StreamSupport.stream(txs.spliterator(), false).map(SignedTransaction::getTx);
|
||||
vaultSvc.notifyAll(wtxn.collect(Collectors.toList()));
|
||||
}
|
||||
};
|
||||
vaultSvc = services.getVaultService();
|
||||
vaultQuerySvc = services.getVaultQueryService();
|
||||
return services;
|
||||
});
|
||||
ArrayList<KeyPair> keys = new ArrayList<>();
|
||||
keys.add(getMEGA_CORP_KEY());
|
||||
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(Collections.EMPTY_SET, keys);
|
||||
database = databaseAndServices.getFirst();
|
||||
services = databaseAndServices.getSecond();
|
||||
vaultSvc = services.getVaultService();
|
||||
vaultQuerySvc = services.getVaultQueryService();
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -148,8 +148,8 @@ class TwoPartyTradeFlowTests {
|
||||
bobNode.disableDBCloseOnStop()
|
||||
|
||||
val cashStates = bobNode.database.transaction {
|
||||
bobNode.services.fillWithSomeTestCash(2000.DOLLARS, notaryNode.info.notaryIdentity, 3, 3)
|
||||
}
|
||||
bobNode.services.fillWithSomeTestCash(2000.DOLLARS, notaryNode.info.notaryIdentity, 3, 3)
|
||||
}
|
||||
|
||||
val alicesFakePaper = aliceNode.database.transaction {
|
||||
fillUpForSeller(false, cpIssuer, aliceNode.info.legalIdentity,
|
||||
@ -168,7 +168,7 @@ class TwoPartyTradeFlowTests {
|
||||
}
|
||||
|
||||
val (bobStateMachine, aliceResult) = runBuyerAndSeller(notaryNode, aliceNode, bobNode,
|
||||
"alice's paper".outputStateAndRef())
|
||||
"alice's paper".outputStateAndRef())
|
||||
|
||||
assertEquals(aliceResult.getOrThrow(), bobStateMachine.getOrThrow().resultFuture.getOrThrow())
|
||||
|
||||
@ -533,11 +533,11 @@ class TwoPartyTradeFlowTests {
|
||||
override fun call(): SignedTransaction {
|
||||
send(buyer, Pair(notary.notaryIdentity, price))
|
||||
return subFlow(Seller(
|
||||
buyer,
|
||||
notary,
|
||||
assetToSell,
|
||||
price,
|
||||
me.party))
|
||||
buyer,
|
||||
notary,
|
||||
assetToSell,
|
||||
price,
|
||||
me.party))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,10 +3,12 @@ package net.corda.node.services.database
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.asset.DummyFungibleContract
|
||||
import net.corda.contracts.asset.sumCash
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.VaultQueryService
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.schemas.CommonSchemaV1
|
||||
import net.corda.core.schemas.PersistentStateRef
|
||||
@ -14,6 +16,7 @@ import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.node.services.schema.HibernateObserver
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.services.vault.HibernateVaultQueryImpl
|
||||
import net.corda.node.services.vault.NodeVaultService
|
||||
import net.corda.node.services.vault.VaultSchemaV1
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
@ -38,6 +41,7 @@ import org.junit.After
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.persistence.EntityManager
|
||||
@ -126,7 +130,8 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
|
||||
|
||||
// execute query
|
||||
val queryResults = entityManager.createQuery(criteriaQuery).resultList
|
||||
assertThat(queryResults.size).isEqualTo(6)
|
||||
val coins = queryResults.map { it.contractState.deserialize<TransactionState<Cash.State>>().data }.sumCash()
|
||||
assertThat(coins.toDecimal() >= BigDecimal("50.00"))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,39 +1,40 @@
|
||||
package net.corda.node.services.vault
|
||||
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.contracts.asset.sumCash
|
||||
import net.corda.contracts.getCashBalance
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.generateKeyPair
|
||||
import net.corda.core.identity.AbstractParty
|
||||
import net.corda.core.identity.AnonymousParty
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.StatesNotAvailableException
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.VaultQueryService
|
||||
import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.node.services.vault.QueryCriteria.*
|
||||
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
|
||||
import net.corda.core.transactions.NotaryChangeWireTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toNonEmptySet
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.fillWithSomeTestCash
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.makeTestDatabaseAndMockServices
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatExceptionOfType
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import rx.observers.TestSubscriber
|
||||
import java.math.BigDecimal
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.Executors
|
||||
@ -50,23 +51,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
@Before
|
||||
fun setUp() {
|
||||
LogHelper.setLevel(NodeVaultService::class)
|
||||
val dataSourceProps = makeTestDataSourceProperties()
|
||||
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties())
|
||||
database.transaction {
|
||||
val hibernateConfig = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties())
|
||||
services = object : MockServices() {
|
||||
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
|
||||
|
||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||
for (stx in txs) {
|
||||
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 })
|
||||
}
|
||||
override val vaultQueryService : VaultQueryService = HibernateVaultQueryImpl(hibernateConfig, vaultService.updatesPublisher)
|
||||
}
|
||||
}
|
||||
val databaseAndServices = makeTestDatabaseAndMockServices()
|
||||
database = databaseAndServices.first
|
||||
services = databaseAndServices.second
|
||||
}
|
||||
|
||||
@After
|
||||
@ -75,6 +62,25 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
LogHelper.reset(NodeVaultService::class)
|
||||
}
|
||||
|
||||
@Suspendable
|
||||
private fun VaultService.unconsumedCashStatesForSpending(amount: Amount<Currency>,
|
||||
onlyFromIssuerParties: Set<AbstractParty>? = null,
|
||||
notary: Party? = null,
|
||||
lockId: UUID = UUID.randomUUID(),
|
||||
withIssuerRefs: Set<OpaqueBytes>? = null): List<StateAndRef<Cash.State>> {
|
||||
|
||||
val notaryName = if (notary != null) listOf(notary.name) else null
|
||||
var baseCriteria: QueryCriteria = QueryCriteria.VaultQueryCriteria(notaryName = notaryName)
|
||||
if (onlyFromIssuerParties != null || withIssuerRefs != null) {
|
||||
baseCriteria = baseCriteria.and(QueryCriteria.FungibleAssetQueryCriteria(
|
||||
issuerPartyName = onlyFromIssuerParties?.toList(),
|
||||
issuerRef = withIssuerRefs?.toList()))
|
||||
}
|
||||
|
||||
return tryLockFungibleStatesForSpending(lockId, baseCriteria, amount, Cash.State::class.java)
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
fun `states not local to instance`() {
|
||||
database.transaction {
|
||||
@ -308,7 +314,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
|
||||
assertThat(unconsumedStates).hasSize(1)
|
||||
|
||||
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(100.DOLLARS, lockId = UUID.randomUUID())
|
||||
val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(100.DOLLARS)
|
||||
spendableStatesUSD.forEach(::println)
|
||||
assertThat(spendableStatesUSD).hasSize(1)
|
||||
assertThat(spendableStatesUSD[0].state.data.amount.quantity).isEqualTo(100L * 100)
|
||||
@ -324,12 +330,13 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
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 = vaultSvc.unconsumedStatesForSpending<Cash.State>(200.DOLLARS, lockId = UUID.randomUUID(),
|
||||
onlyFromIssuerParties = setOf(DUMMY_CASH_ISSUER.party, BOC)).toList()
|
||||
val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(200.DOLLARS,
|
||||
onlyFromIssuerParties = setOf(DUMMY_CASH_ISSUER.party, BOC))
|
||||
spendableStatesUSD.forEach(::println)
|
||||
assertThat(spendableStatesUSD).hasSize(2)
|
||||
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer).isEqualTo(DUMMY_CASH_ISSUER)
|
||||
assertThat(spendableStatesUSD[1].state.data.amount.token.issuer).isEqualTo(BOC.ref(1))
|
||||
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer).isIn(DUMMY_CASH_ISSUER, BOC.ref(1))
|
||||
assertThat(spendableStatesUSD[1].state.data.amount.token.issuer).isIn(DUMMY_CASH_ISSUER, BOC.ref(1))
|
||||
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer).isNotEqualTo(spendableStatesUSD[1].state.data.amount.token.issuer)
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,12 +352,13 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
|
||||
assertThat(unconsumedStates).hasSize(4)
|
||||
|
||||
val spendableStatesUSD = vaultSvc.unconsumedStatesForSpending<Cash.State>(200.DOLLARS, lockId = UUID.randomUUID(),
|
||||
onlyFromIssuerParties = setOf(BOC), withIssuerRefs = setOf(OpaqueBytes.of(1), OpaqueBytes.of(2))).toList()
|
||||
val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(200.DOLLARS,
|
||||
onlyFromIssuerParties = setOf(BOC), withIssuerRefs = setOf(OpaqueBytes.of(1), OpaqueBytes.of(2)))
|
||||
assertThat(spendableStatesUSD).hasSize(2)
|
||||
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.party).isEqualTo(BOC)
|
||||
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.reference).isEqualTo(BOC.ref(1).reference)
|
||||
assertThat(spendableStatesUSD[1].state.data.amount.token.issuer.reference).isEqualTo(BOC.ref(2).reference)
|
||||
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.reference).isIn(BOC.ref(1).reference, BOC.ref(2).reference)
|
||||
assertThat(spendableStatesUSD[1].state.data.amount.token.issuer.reference).isIn(BOC.ref(1).reference, BOC.ref(2).reference)
|
||||
assertThat(spendableStatesUSD[0].state.data.amount.token.issuer.reference).isNotEqualTo(spendableStatesUSD[1].state.data.amount.token.issuer.reference)
|
||||
}
|
||||
}
|
||||
|
||||
@ -363,9 +371,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
|
||||
assertThat(unconsumedStates).hasSize(1)
|
||||
|
||||
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(110.DOLLARS, lockId = UUID.randomUUID())
|
||||
val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(110.DOLLARS)
|
||||
spendableStatesUSD.forEach(::println)
|
||||
assertThat(spendableStatesUSD).hasSize(1)
|
||||
assertThat(spendableStatesUSD).hasSize(0)
|
||||
val criteriaLocked = VaultQueryCriteria(softLockingCondition = SoftLockingCondition(SoftLockingType.LOCKED_ONLY))
|
||||
assertThat(vaultQuery.queryBy<Cash.State>(criteriaLocked).states).hasSize(0)
|
||||
}
|
||||
@ -380,7 +388,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
|
||||
assertThat(unconsumedStates).hasSize(2)
|
||||
|
||||
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(1.DOLLARS, lockId = UUID.randomUUID())
|
||||
val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(1.DOLLARS)
|
||||
spendableStatesUSD.forEach(::println)
|
||||
assertThat(spendableStatesUSD).hasSize(1)
|
||||
assertThat(spendableStatesUSD[0].state.data.amount.quantity).isGreaterThanOrEqualTo(100L)
|
||||
@ -397,16 +405,30 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
services.fillWithSomeTestCash(100.POUNDS, DUMMY_NOTARY, 10, 10, Random(0L))
|
||||
services.fillWithSomeTestCash(100.SWISS_FRANCS, DUMMY_NOTARY, 10, 10, Random(0L))
|
||||
|
||||
var unlockedStates = 30
|
||||
val allStates = vaultQuery.queryBy<Cash.State>().states
|
||||
assertThat(allStates).hasSize(30)
|
||||
assertThat(allStates).hasSize(unlockedStates)
|
||||
|
||||
var lockedCount = 0
|
||||
for (i in 1..5) {
|
||||
val spendableStatesUSD = (vaultSvc as NodeVaultService).unconsumedStatesForSpending<Cash.State>(20.DOLLARS, lockId = UUID.randomUUID())
|
||||
val lockId = UUID.randomUUID()
|
||||
val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(20.DOLLARS, lockId = lockId)
|
||||
spendableStatesUSD.forEach(::println)
|
||||
assertThat(spendableStatesUSD.size <= unlockedStates)
|
||||
unlockedStates -= spendableStatesUSD.size
|
||||
val criteriaLocked = VaultQueryCriteria(softLockingCondition = SoftLockingCondition(SoftLockingType.SPECIFIED, listOf(lockId)))
|
||||
val lockedStates = vaultQuery.queryBy<Cash.State>(criteriaLocked).states
|
||||
if (spendableStatesUSD.isNotEmpty()) {
|
||||
assertEquals(spendableStatesUSD.size, lockedStates.size)
|
||||
val lockedTotal = lockedStates.map { it.state.data }.sumCash()
|
||||
val foundAmount = spendableStatesUSD.map { it.state.data }.sumCash()
|
||||
assertThat(foundAmount.toDecimal() >= BigDecimal("20.00"))
|
||||
assertThat(lockedTotal == foundAmount)
|
||||
lockedCount += lockedStates.size
|
||||
}
|
||||
}
|
||||
// note only 3 spend attempts succeed with a total of 8 states
|
||||
val criteriaLocked = VaultQueryCriteria(softLockingCondition = SoftLockingCondition(SoftLockingType.LOCKED_ONLY))
|
||||
assertThat(vaultQuery.queryBy<Cash.State>(criteriaLocked).states).hasSize(8)
|
||||
assertThat(vaultQuery.queryBy<Cash.State>(criteriaLocked).states).hasSize(lockedCount)
|
||||
}
|
||||
}
|
||||
|
||||
@ -484,7 +506,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
|
||||
database.transaction {
|
||||
val moveTx = TransactionBuilder(services.myInfo.legalIdentity).apply {
|
||||
service.generateSpend(this, Amount(1000, GBP), thirdPartyIdentity)
|
||||
Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity)
|
||||
}.toWireTransaction()
|
||||
service.notify(moveTx)
|
||||
}
|
||||
@ -530,7 +552,7 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
|
||||
// Move cash
|
||||
val moveTx = database.transaction {
|
||||
TransactionBuilder(newNotary).apply {
|
||||
service.generateSpend(this, Amount(1000, GBP), thirdPartyIdentity)
|
||||
Cash.generateSpend(services, this, Amount(1000, GBP), thirdPartyIdentity)
|
||||
}.toWireTransaction()
|
||||
}
|
||||
|
||||
|
@ -1,23 +1,19 @@
|
||||
package net.corda.node.services.vault
|
||||
|
||||
import net.corda.contracts.*
|
||||
import net.corda.contracts.CommercialPaper
|
||||
import net.corda.contracts.Commodity
|
||||
import net.corda.contracts.DealState
|
||||
import net.corda.contracts.asset.Cash
|
||||
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.core.identity.Party
|
||||
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.utilities.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.NonEmptySet
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.toHexString
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.core.utilities.*
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.schemas.CashSchemaV1
|
||||
@ -27,7 +23,7 @@ import net.corda.schemas.SampleCashSchemaV3
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.makeTestDatabaseAndMockServices
|
||||
import net.corda.testing.node.makeTestDatabaseProperties
|
||||
import net.corda.testing.schemas.DummyLinearStateSchemaV1
|
||||
import org.assertj.core.api.Assertions
|
||||
@ -54,24 +50,9 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val dataSourceProps = makeTestDataSourceProperties()
|
||||
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties())
|
||||
database.transaction {
|
||||
val customSchemas = setOf(CommercialPaperSchemaV1, DummyLinearStateSchemaV1)
|
||||
val hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), makeTestDatabaseProperties())
|
||||
services = object : MockServices(MEGA_CORP_KEY) {
|
||||
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
|
||||
|
||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||
for (stx in txs) {
|
||||
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 })
|
||||
}
|
||||
override val vaultQueryService : VaultQueryService = HibernateVaultQueryImpl(hibernateConfig, vaultService.updatesPublisher)
|
||||
}
|
||||
}
|
||||
val databaseAndServices = makeTestDatabaseAndMockServices(keys = listOf(MEGA_CORP_KEY))
|
||||
database = databaseAndServices.first
|
||||
services = databaseAndServices.second
|
||||
}
|
||||
|
||||
@After
|
||||
@ -97,7 +78,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
_database.transaction {
|
||||
|
||||
// create new states
|
||||
services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 10, 10, Random(0L))
|
||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
|
||||
val linearStatesXYZ = services.fillWithSomeTestLinearStates(1, "XYZ")
|
||||
val linearStatesJKL = services.fillWithSomeTestLinearStates(2, "JKL")
|
||||
services.fillWithSomeTestLinearStates(3, "ABC")
|
||||
@ -239,15 +220,15 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
fun `unconsumed cash states sorted by state ref`() {
|
||||
database.transaction {
|
||||
|
||||
var stateRefs : MutableList<StateRef> = mutableListOf()
|
||||
val 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 consumedStateRefs = spentStates.consumed.map { it.ref }.toList()
|
||||
var producedStateRefs = spentStates.produced.map { it.ref }.toList()
|
||||
val consumedStateRefs = spentStates.consumed.map { it.ref }.toList()
|
||||
val producedStateRefs = spentStates.produced.map { it.ref }.toList()
|
||||
stateRefs.addAll(consumedStateRefs.plus(producedStateRefs))
|
||||
|
||||
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
|
||||
@ -271,8 +252,9 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
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 consumed = mutableSetOf<SecureHash>()
|
||||
services.consumeCash(10.DOLLARS).consumed.forEach { consumed += it.ref.txhash }
|
||||
services.consumeCash(10.DOLLARS).consumed.forEach { consumed += it.ref.txhash }
|
||||
|
||||
val sortAttributeTxnId = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_TXN_ID)
|
||||
val sortAttributeIndex = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF_INDEX)
|
||||
@ -283,13 +265,11 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
|
||||
results.statesMetadata.forEach {
|
||||
println(" ${it.ref}")
|
||||
assertThat(it.status).isEqualTo(Vault.StateStatus.UNCONSUMED)
|
||||
}
|
||||
|
||||
// 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)
|
||||
val sorted = results.states.sortedBy { it.ref.toString() }
|
||||
assertThat(results.states).isEqualTo(sorted)
|
||||
assertThat(results.states).allSatisfy { !consumed.contains(it.ref.txhash) }
|
||||
}
|
||||
}
|
||||
|
||||
@ -411,7 +391,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
}
|
||||
}
|
||||
|
||||
val CASH_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(20)) }
|
||||
val CASH_NOTARY_KEY: KeyPair by lazy { entropyToKeyPair(BigInteger.valueOf(21)) }
|
||||
val CASH_NOTARY: Party get() = Party(X500Name("CN=Cash Notary Service,O=R3,OU=corda,L=Zurich,C=CH"), CASH_NOTARY_KEY.public)
|
||||
|
||||
@Test
|
||||
@ -870,7 +850,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
fun `aggregate functions count by contract type and state status`() {
|
||||
database.transaction {
|
||||
// create new states
|
||||
services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 10, 10, Random(0L))
|
||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 10, 10, Random(0L))
|
||||
val linearStatesXYZ = services.fillWithSomeTestLinearStates(1, "XYZ")
|
||||
val linearStatesJKL = services.fillWithSomeTestLinearStates(2, "JKL")
|
||||
services.fillWithSomeTestLinearStates(3, "ABC")
|
||||
@ -896,14 +876,14 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
services.consumeLinearStates(linearStatesXYZ.states.toList())
|
||||
services.consumeLinearStates(linearStatesJKL.states.toList())
|
||||
services.consumeDeals(dealStates.states.filter { it.state.data.ref == "456" })
|
||||
services.consumeCash(50.DOLLARS)
|
||||
val cashUpdates = services.consumeCash(50.DOLLARS)
|
||||
|
||||
// UNCONSUMED states (default)
|
||||
|
||||
// count fungible assets
|
||||
val countCriteriaUnconsumed = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.UNCONSUMED)
|
||||
val fungibleStateCountUnconsumed = vaultQuerySvc.queryBy<FungibleAsset<*>>(countCriteriaUnconsumed).otherResults.single() as Long
|
||||
assertThat(fungibleStateCountUnconsumed).isEqualTo(5L)
|
||||
assertThat(fungibleStateCountUnconsumed.toInt()).isEqualTo(10 - cashUpdates.consumed.size + cashUpdates.produced.size)
|
||||
|
||||
// count linear states
|
||||
val linearStateCountUnconsumed = vaultQuerySvc.queryBy<LinearState>(countCriteriaUnconsumed).otherResults.single() as Long
|
||||
@ -918,7 +898,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
// count fungible assets
|
||||
val countCriteriaConsumed = QueryCriteria.VaultCustomQueryCriteria(count, Vault.StateStatus.CONSUMED)
|
||||
val fungibleStateCountConsumed = vaultQuerySvc.queryBy<FungibleAsset<*>>(countCriteriaConsumed).otherResults.single() as Long
|
||||
assertThat(fungibleStateCountConsumed).isEqualTo(6L)
|
||||
assertThat(fungibleStateCountConsumed.toInt()).isEqualTo(cashUpdates.consumed.size)
|
||||
|
||||
// count linear states
|
||||
val linearStateCountConsumed = vaultQuerySvc.queryBy<LinearState>(countCriteriaConsumed).otherResults.single() as Long
|
||||
@ -962,7 +942,7 @@ class VaultQueryTests : TestDependencyInjectionBase() {
|
||||
fun `states consumed after time`() {
|
||||
database.transaction {
|
||||
|
||||
services.fillWithSomeTestCash(100.DOLLARS, CASH_NOTARY, 3, 3, Random(0L))
|
||||
services.fillWithSomeTestCash(100.DOLLARS, DUMMY_NOTARY, 3, 3, Random(0L))
|
||||
services.fillWithSomeTestLinearStates(10)
|
||||
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
|
||||
|
||||
|
@ -11,17 +11,12 @@ import net.corda.core.node.services.VaultService
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.schema.NodeSchemaService
|
||||
import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.configureDatabase
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.contracts.*
|
||||
import net.corda.testing.node.MockServices
|
||||
import net.corda.testing.node.makeTestDataSourceProperties
|
||||
import net.corda.testing.node.makeTestDatabaseProperties
|
||||
import net.corda.testing.node.makeTestDatabaseAndMockServices
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.After
|
||||
@ -44,23 +39,9 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
|
||||
@Before
|
||||
fun setUp() {
|
||||
LogHelper.setLevel(VaultWithCashTest::class)
|
||||
val dataSourceProps = makeTestDataSourceProperties()
|
||||
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties())
|
||||
database.transaction {
|
||||
val hibernateConfig = HibernateConfiguration(NodeSchemaService(), makeTestDatabaseProperties())
|
||||
services = object : MockServices() {
|
||||
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
|
||||
|
||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||
for (stx in txs) {
|
||||
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 })
|
||||
}
|
||||
override val vaultQueryService : VaultQueryService = HibernateVaultQueryImpl(hibernateConfig, vaultService.updatesPublisher)
|
||||
}
|
||||
}
|
||||
val databaseAndServices = makeTestDatabaseAndMockServices()
|
||||
database = databaseAndServices.first
|
||||
services = databaseAndServices.second
|
||||
}
|
||||
|
||||
@After
|
||||
@ -103,7 +84,7 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
|
||||
|
||||
// A tx that spends our money.
|
||||
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY)
|
||||
vault.generateSpend(spendTXBuilder, 80.DOLLARS, BOB)
|
||||
Cash.generateSpend(services, spendTXBuilder, 80.DOLLARS, BOB)
|
||||
val spendPTX = services.signInitialTransaction(spendTXBuilder, freshKey)
|
||||
val spendTX = notaryServices.addSignature(spendPTX)
|
||||
|
||||
@ -151,7 +132,7 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
|
||||
database.transaction {
|
||||
try {
|
||||
val txn1Builder = TransactionBuilder(DUMMY_NOTARY)
|
||||
vault.generateSpend(txn1Builder, 60.DOLLARS, BOB)
|
||||
Cash.generateSpend(services, txn1Builder, 60.DOLLARS, BOB)
|
||||
val ptxn1 = notaryServices.signInitialTransaction(txn1Builder)
|
||||
val txn1 = services.addSignature(ptxn1, freshKey)
|
||||
println("txn1: ${txn1.id} spent ${((txn1.tx.outputs[0].data) as Cash.State).amount}")
|
||||
@ -187,7 +168,7 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
|
||||
database.transaction {
|
||||
try {
|
||||
val txn2Builder = TransactionBuilder(DUMMY_NOTARY)
|
||||
vault.generateSpend(txn2Builder, 80.DOLLARS, BOB)
|
||||
Cash.generateSpend(services, txn2Builder, 80.DOLLARS, BOB)
|
||||
val ptxn2 = notaryServices.signInitialTransaction(txn2Builder)
|
||||
val txn2 = services.addSignature(ptxn2, freshKey)
|
||||
println("txn2: ${txn2.id} spent ${((txn2.tx.outputs[0].data) as Cash.State).amount}")
|
||||
@ -299,7 +280,7 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
|
||||
database.transaction {
|
||||
// A tx that spends our money.
|
||||
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY)
|
||||
vault.generateSpend(spendTXBuilder, 80.DOLLARS, BOB)
|
||||
Cash.generateSpend(services, spendTXBuilder, 80.DOLLARS, BOB)
|
||||
val spendPTX = notaryServices.signInitialTransaction(spendTXBuilder)
|
||||
val spendTX = services.addSignature(spendPTX, freshKey)
|
||||
services.recordTransactions(spendTX)
|
||||
|
Reference in New Issue
Block a user