Requery removal (#1276)

* Removed Requery object relational mapping usage (and associated schemas including node-schemas module)

* Fixed issues with NodeAttachmentService tests.
Cannot use JPA custom converters with Primary Key fields.
Hibernate entities require explicit call to flush() to persist to disk.

* Removed redundant requery converters (equivalents not even required in Hibernate).

* Removed remaining gradle requery dependency definitions.

* Fixed broken tests.

* Fixes for failing NodeVaultService tests:
- Dynamic SQL updates (in soft locking code)
- Explicit request by session to participate in transaction (causing "TransactionRequiredException" Executing an update/delete query)
- Explicit flush() required to persist to disk

* Updated changelog.
Fixed compiler warning.

* Fixed WHERE clause AND/OR condition.
Enforced immediate data visibility through transaction commit.

* Final fixes to address failing tests.

* Deferred all hibernate session/txn management to DatabaseTransactionManager.

* Fixed transaction boundaries in failing Cash tests.

* Fixes to address failing tests (transaction boundaries, merge detached object, config clean-up).

* Final adjustment to transaction boundaries in JUnit tests.

* Refactored AttachmentSchemaV1 into NodeAttachmentService itself and referenced from NodeServicesV1.

* Refactored HSQL UPDATE statements to use CriteriaUpdate API.

* Updated all criteria API getters to reference attribute names by type.

* Remove redundant VaultSchema entity name (required when previously using HSQL UPDATE syntax)

* Fix compiler warnings.

* Minor changes following rebase from master.

* Fixed suppress warning type.
This commit is contained in:
josecoll
2017-08-21 10:42:59 +01:00
committed by GitHub
parent 56a84882a7
commit a2ede0fc73
44 changed files with 764 additions and 2023 deletions

View File

@ -412,7 +412,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
private fun makeServices(): MutableList<Any> {
checkpointStorage = DBCheckpointStorage()
_services = ServiceHubInternalImpl()
attachments = NodeAttachmentService(configuration.dataSourceProperties, services.monitoringService.metrics, configuration.database)
attachments = NodeAttachmentService(services.monitoringService.metrics)
val legalIdentity = obtainIdentity("identity", configuration.myLegalName)
network = makeMessagingService(legalIdentity)
info = makeInfo(legalIdentity)
@ -749,6 +749,9 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
private inner class ServiceHubInternalImpl : ServiceHubInternal, SingletonSerializeAsToken() {
private val hibernateConfig by lazy { HibernateConfiguration(schemaService, configuration.database ?: Properties(), { identityService }) }
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
override val auditService = DummyAuditService()
@ -756,9 +759,9 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
override val validatedTransactions = makeTransactionStorage()
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
override val networkMapCache by lazy { InMemoryNetworkMapCache(this) }
override val vaultService by lazy { NodeVaultService(this, configuration.dataSourceProperties, configuration.database) }
override val vaultService by lazy { NodeVaultService(this) }
override val vaultQueryService by lazy {
HibernateVaultQueryImpl(HibernateConfiguration(schemaService, configuration.database ?: Properties(), { identityService }), vaultService)
HibernateVaultQueryImpl(hibernateConfig, vaultService)
}
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with

View File

@ -7,6 +7,7 @@ import net.corda.core.schemas.converters.AbstractPartyToX500NameAsStringConverte
import net.corda.core.utilities.loggerFor
import net.corda.node.services.api.SchemaService
import net.corda.node.utilities.DatabaseTransactionManager
import net.corda.node.utilities.parserTransactionIsolationLevel
import org.hibernate.SessionFactory
import org.hibernate.boot.MetadataSources
import org.hibernate.boot.model.naming.Identifier
@ -28,6 +29,8 @@ class HibernateConfiguration(val schemaService: SchemaService, val databasePrope
// TODO: make this a guava cache or similar to limit ability for this to grow forever.
val sessionFactories = ConcurrentHashMap<MappedSchema, SessionFactory>()
private val transactionIsolationLevel = parserTransactionIsolationLevel(databaseProperties.getProperty("transactionIsolationLevel") ?:"")
init {
schemaService.schemaOptions.map { it.key }.forEach { mappedSchema ->
sessionFactories.computeIfAbsent(mappedSchema, { makeSessionFactoryForSchema(mappedSchema) })
@ -60,6 +63,7 @@ class HibernateConfiguration(val schemaService: SchemaService, val databasePrope
val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", HibernateConfiguration.NodeDatabaseConnectionProvider::class.java.name)
.setProperty("hibernate.hbm2ddl.auto", if (databaseProperties.getProperty("initDatabase","true") == "true") "update" else "validate")
.setProperty("hibernate.format_sql", "true")
.setProperty("hibernate.connection.isolation", transactionIsolationLevel.toString())
schemas.forEach { schema ->
// TODO: require mechanism to set schemaOptions (databaseSchema, tablePrefix) which are not global to session

View File

@ -1,146 +0,0 @@
package net.corda.node.services.database
import io.requery.EntityCache
import io.requery.TransactionIsolation
import io.requery.TransactionListener
import io.requery.cache.WeakEntityCache
import io.requery.meta.EntityModel
import io.requery.sql.*
import io.requery.sql.platform.H2
import io.requery.util.function.Function
import io.requery.util.function.Supplier
import net.corda.core.schemas.requery.converters.InstantConverter
import net.corda.core.schemas.requery.converters.SecureHashConverter
import net.corda.core.schemas.requery.converters.StateRefConverter
import net.corda.core.schemas.requery.converters.VaultStateStatusConverter
import net.corda.node.utilities.DatabaseTransactionManager
import java.sql.Connection
import java.util.*
import java.util.concurrent.Executor
import javax.sql.DataSource
/**
* Requery KotlinConfiguration wrapper class to enable us to pass in an existing database connection and
* associated transaction context.
*/
class KotlinConfigurationTransactionWrapper(private val model: EntityModel,
dataSource: DataSource,
private val mapping: Mapping? = null,
private val platform: Platform? = null,
private val cache: EntityCache = WeakEntityCache(),
private val useDefaultLogging: Boolean = false,
private val statementCacheSize: Int = 0,
private val batchUpdateSize: Int = 64,
private val quoteTableNames: Boolean = false,
private val quoteColumnNames: Boolean = false,
private val tableTransformer: Function<String, String>? = null,
private val columnTransformer: Function<String, String>? = null,
private val transactionMode: TransactionMode = TransactionMode.NONE,
private val transactionIsolation: TransactionIsolation? = null,
private val statementListeners: Set<StatementListener> = LinkedHashSet(),
private val entityStateListeners: Set<EntityStateListener<Any>> = LinkedHashSet(),
private val transactionListeners: Set<Supplier<TransactionListener>> = LinkedHashSet(),
private val writeExecutor: Executor? = null) : Configuration {
private val connectionProvider = CordaDataSourceConnectionProvider(dataSource)
override fun getBatchUpdateSize(): Int {
return batchUpdateSize
}
override fun getConnectionProvider(): ConnectionProvider? {
return connectionProvider
}
override fun getCache(): EntityCache? {
return cache
}
override fun getEntityStateListeners(): Set<EntityStateListener<Any>> {
return entityStateListeners
}
override fun getMapping(): Mapping? {
// TODO: database platform provider to become configurable and parameterised into this configuration
val customMapping = GenericMapping(H2())
// register our custom converters
val instantConverter = InstantConverter()
customMapping.addConverter(instantConverter, instantConverter.mappedType)
val vaultStateStatusConverter = VaultStateStatusConverter()
customMapping.addConverter(vaultStateStatusConverter, vaultStateStatusConverter.mappedType)
customMapping.addConverter(StateRefConverter(), StateRefConverter::getMappedType.javaClass)
customMapping.addConverter(SecureHashConverter(), SecureHashConverter::getMappedType.javaClass)
return customMapping
}
override fun getModel(): EntityModel {
return model
}
override fun getPlatform(): Platform? {
return platform
}
override fun getQuoteTableNames(): Boolean {
return quoteTableNames
}
override fun getQuoteColumnNames(): Boolean {
return quoteColumnNames
}
override fun getTableTransformer(): Function<String, String>? {
return tableTransformer
}
override fun getColumnTransformer(): Function<String, String>? {
return columnTransformer
}
override fun getStatementCacheSize(): Int {
return statementCacheSize
}
override fun getStatementListeners(): Set<StatementListener>? {
return statementListeners
}
override fun getTransactionMode(): TransactionMode? {
return transactionMode
}
override fun getTransactionIsolation(): TransactionIsolation? {
return transactionIsolation
}
override fun getTransactionListenerFactories(): Set<Supplier<TransactionListener>>? {
return transactionListeners
}
override fun getUseDefaultLogging(): Boolean {
return useDefaultLogging
}
override fun getWriteExecutor(): Executor? {
return writeExecutor
}
class CordaDataSourceConnectionProvider(val dataSource: DataSource) : ConnectionProvider {
override fun getConnection(): Connection = CordaConnection(DatabaseTransactionManager.current().connection)
}
class CordaConnection(val connection: Connection) : Connection by connection {
override fun close() {
// TODO: address requery auto-closing the connection in SchemaModifier upon table creation
// https://github.com/requery/requery/issues/424
}
override fun setAutoCommit(autoCommit: Boolean) {
// TODO: address requery bug in ConnectionTransaction commit()
// https://github.com/requery/requery/issues/423
connection.autoCommit = false
}
}
}

View File

@ -1,65 +0,0 @@
package net.corda.node.services.database
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import io.requery.Persistable
import io.requery.TransactionIsolation
import io.requery.meta.EntityModel
import io.requery.sql.KotlinEntityDataStore
import io.requery.sql.SchemaModifier
import io.requery.sql.TableCreationMode
import net.corda.core.utilities.loggerFor
import net.corda.node.utilities.DatabaseTransactionManager
import java.sql.Connection
import java.util.*
import java.util.concurrent.ConcurrentHashMap
class RequeryConfiguration(val properties: Properties, val useDefaultLogging: Boolean = false, val databaseProperties: Properties) {
companion object {
val logger = loggerFor<RequeryConfiguration>()
}
// TODO:
// 1. schemaService schemaOptions needs to be applied: eg. default schema, table prefix
// 2. set other generic database configuration options: show_sql, format_sql
// 3. Configure Requery Database platform specific features (see http://requery.github.io/javadoc/io/requery/sql/Platform.html)
// 4. Configure Cache Manager and Cache Provider and set in Requery Configuration (see http://requery.github.io/javadoc/io/requery/EntityCache.html)
// 5. Consider database schema deployment/upgrade strategies to replace dynamic table creation.
// Note: Annotations are pre-processed using (kapt) so no need to register dynamically
val config = HikariConfig(properties)
val dataSource = HikariDataSource(config)
// TODO: make this a guava cache or similar to limit ability for this to grow forever.
private val sessionFactories = ConcurrentHashMap<EntityModel, KotlinEntityDataStore<Persistable>>()
fun sessionForModel(model: EntityModel): KotlinEntityDataStore<Persistable> {
return sessionFactories.computeIfAbsent(model, { makeSessionFactoryForModel(it) })
}
fun makeSessionFactoryForModel(model: EntityModel): KotlinEntityDataStore<Persistable> {
val configuration = KotlinConfigurationTransactionWrapper(model, dataSource, useDefaultLogging = this.useDefaultLogging)
val tables = SchemaModifier(configuration)
if (databaseProperties.getProperty("initDatabase","true") == "true" ) {
val mode = TableCreationMode.CREATE_NOT_EXISTS
tables.createTables(mode)
}
return KotlinEntityDataStore(configuration)
}
// TODO: remove once Requery supports QUERY WITH COMPOSITE_KEY IN
fun jdbcSession(): Connection = DatabaseTransactionManager.current().connection
}
fun parserTransactionIsolationLevel(property: String?) : TransactionIsolation =
when (property) {
"none" -> TransactionIsolation.NONE
"readUncommitted" -> TransactionIsolation.READ_UNCOMMITTED
"readCommitted" -> TransactionIsolation.READ_COMMITTED
"repeatableRead" -> TransactionIsolation.REPEATABLE_READ
"serializable" -> TransactionIsolation.SERIALIZABLE
else -> {
TransactionIsolation.REPEATABLE_READ
}
}

View File

@ -12,41 +12,50 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.*
import net.corda.core.utilities.loggerFor
import net.corda.node.services.database.RequeryConfiguration
import net.corda.node.services.persistence.schemas.requery.AttachmentEntity
import net.corda.node.services.persistence.schemas.requery.Models
import java.io.ByteArrayInputStream
import java.io.FilterInputStream
import java.io.IOException
import java.io.InputStream
import net.corda.node.utilities.DatabaseTransactionManager
import net.corda.node.utilities.NODE_DATABASE_PREFIX
import java.io.*
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Paths
import java.util.*
import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe
import javax.persistence.*
/**
* Stores attachments in H2 database.
* Stores attachments using Hibernate to database.
*/
@ThreadSafe
class NodeAttachmentService(dataSourceProperties: Properties, metrics: MetricRegistry, databaseProperties: Properties?)
: AttachmentStorage, SingletonSerializeAsToken() {
class NodeAttachmentService(metrics: MetricRegistry) : AttachmentStorage, SingletonSerializeAsToken() {
@Entity
@Table(name = "${NODE_DATABASE_PREFIX}attachments",
indexes = arrayOf(Index(name = "att_id_idx", columnList = "att_id")))
class DBAttachment(
@Id
@Column(name = "att_id", length = 65535)
var attId: String,
@Column(name = "content")
@Lob
var content: ByteArray
) : Serializable
companion object {
private val log = loggerFor<NodeAttachmentService>()
}
val configuration = RequeryConfiguration(dataSourceProperties, databaseProperties = databaseProperties ?: Properties())
val session = configuration.sessionForModel(Models.PERSISTENCE)
@VisibleForTesting
var checkAttachmentsOnLoad = true
private val attachmentCount = metrics.counter("Attachments")
init {
session.withTransaction {
attachmentCount.inc(session.count(AttachmentEntity::class).get().value().toLong())
}
val session = DatabaseTransactionManager.current().session
val criteriaBuilder = session.criteriaBuilder
val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java)))
val count = session.createQuery(criteriaQuery).singleResult
attachmentCount.inc(count)
}
@CordaSerializable
@ -128,16 +137,13 @@ class NodeAttachmentService(dataSourceProperties: Properties, metrics: MetricReg
}
override fun openAttachment(id: SecureHash): Attachment? = session.withTransaction {
try {
session.select(AttachmentEntity::class)
.where(AttachmentEntity.ATT_ID.eq(id))
.get()
.single()
} catch (e: NoSuchElementException) {
null
override fun openAttachment(id: SecureHash): Attachment? {
val attachment = DatabaseTransactionManager.current().session.get(NodeAttachmentService.DBAttachment::class.java, id.toString())
attachment?.let {
return AttachmentImpl(id, { attachment.content }, checkAttachmentsOnLoad)
}
}?.run { AttachmentImpl(id, { content }, checkAttachmentsOnLoad) }
return null
}
// TODO: PLT-147: The attachment should be randomised to prevent brute force guessing and thus privacy leaks.
override fun importAttachment(jar: InputStream): SecureHash {
@ -153,25 +159,21 @@ class NodeAttachmentService(dataSourceProperties: Properties, metrics: MetricReg
checkIsAValidJAR(ByteArrayInputStream(bytes))
val id = SecureHash.SHA256(hs.hash().asBytes())
val count = session.withTransaction {
session.count(AttachmentEntity::class)
.where(AttachmentEntity.ATT_ID.eq(id))
.get().value()
}
val session = DatabaseTransactionManager.current().session
val criteriaBuilder = session.criteriaBuilder
val criteriaQuery = criteriaBuilder.createQuery(Long::class.java)
val attachments = criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java)
criteriaQuery.select(criteriaBuilder.count(criteriaQuery.from(NodeAttachmentService.DBAttachment::class.java)))
criteriaQuery.where(criteriaBuilder.equal(attachments.get<String>(DBAttachment::attId.name), id.toString()))
val count = session.createQuery(criteriaQuery).singleResult
if (count > 0) {
throw FileAlreadyExistsException(id.toString())
}
session.withTransaction {
val attachment = AttachmentEntity()
attachment.attId = id
attachment.content = bytes
session.insert(attachment)
}
val attachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes)
session.save(attachment)
attachmentCount.inc()
log.info("Stored new attachment $id")
return id

View File

@ -14,6 +14,7 @@ import net.corda.node.services.keys.PersistentKeyManagementService
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionMappingStorage
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.vault.VaultSchemaV1
@ -36,7 +37,9 @@ class NodeSchemaService(customSchemas: Set<MappedSchema> = emptySet()) : SchemaS
DBTransactionMappingStorage.DBTransactionMapping::class.java,
PersistentKeyManagementService.PersistentKey::class.java,
PersistentUniquenessProvider.PersistentUniqueness::class.java,
NodeSchedulerService.PersistentScheduledState::class.java
PersistentUniquenessProvider.PersistentUniqueness::class.java,
NodeSchedulerService.PersistentScheduledState::class.java,
NodeAttachmentService.DBAttachment::class.java
))
// Required schemas are those used by internal Corda services
@ -46,7 +49,6 @@ class NodeSchemaService(customSchemas: Set<MappedSchema> = emptySet()) : SchemaS
Pair(VaultSchemaV1, SchemaService.SchemaOptions()),
Pair(NodeServicesV1, SchemaService.SchemaOptions()))
override val schemaOptions: Map<MappedSchema, SchemaService.SchemaOptions> = requiredSchemas.plus(customSchemas.map {
mappedSchema -> Pair(mappedSchema, SchemaService.SchemaOptions())
})

View File

@ -191,7 +191,7 @@ object BFTSMaRt {
}
override fun getStateManager() = stateManagerOverride
// TODO: Use Requery with proper DB schema instead of JDBCHashMap.
// TODO: Use proper DB schema instead of JDBCHashMap.
// Must be initialised before ServiceReplica is started
private val commitLog = services.database.transaction { JDBCHashMap<StateRef, UniquenessProvider.ConsumingTx>(tableName) }
private val replica = run {

View File

@ -2,15 +2,11 @@ package net.corda.node.services.vault
import co.paralleluniverse.fibers.Suspendable
import co.paralleluniverse.strands.Strand
import net.corda.core.internal.VisibleForTesting
import io.requery.PersistenceException
import io.requery.kotlin.eq
import io.requery.query.RowExpression
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.internal.ThreadBox
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.tee
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.StatesNotAvailableException
@ -21,6 +17,7 @@ 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.schemas.PersistentStateRef
import net.corda.core.serialization.SerializationDefaults.STORAGE_CONTEXT
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize
@ -28,22 +25,15 @@ import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.NotaryChangeWireTransaction
import net.corda.core.transactions.WireTransaction
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.core.utilities.*
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.services.vault.schemas.requery.Models
import net.corda.node.services.vault.schemas.requery.VaultSchema
import net.corda.node.services.vault.schemas.requery.VaultStatesEntity
import net.corda.node.services.vault.schemas.requery.VaultTxnNoteEntity
import net.corda.node.utilities.DatabaseTransactionManager
import net.corda.node.utilities.bufferUntilDatabaseCommit
import net.corda.node.utilities.wrapWithDatabaseTransaction
import rx.Observable
import rx.subjects.PublishSubject
import java.security.PublicKey
import java.time.Instant
import java.util.*
import javax.persistence.criteria.Predicate
@ -54,23 +44,15 @@ import javax.persistence.criteria.Predicate
* This class needs database transactions to be in-flight during method calls and init, and will throw exceptions if
* this is not the case.
*
* TODO: move query / filter criteria into the database query.
* TODO: keep an audit trail with time stamps of previously unconsumed states "as of" a particular point in time.
* TODO: have transaction storage do some caching.
*/
class NodeVaultService(private val services: ServiceHub, dataSourceProperties: Properties, databaseProperties: Properties?) : SingletonSerializeAsToken(), VaultService {
class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsToken(), VaultService {
private companion object {
val log = loggerFor<NodeVaultService>()
// Define composite primary key used in Requery Expression
val stateRefCompositeColumn: RowExpression = RowExpression.of(listOf(VaultStatesEntity.TX_ID, VaultStatesEntity.INDEX))
}
val configuration = RequeryConfiguration(dataSourceProperties, databaseProperties = databaseProperties ?: Properties())
val session = configuration.sessionForModel(Models.VAULT)
private val transactionIsolationLevel = parserTransactionIsolationLevel(databaseProperties?.getProperty("transactionIsolationLevel") ?:"")
private class InnerState {
val _updatesPublisher = PublishSubject.create<Vault.Update<ContractState>>()!!
val _rawUpdatesPublisher = PublishSubject.create<Vault.Update<ContractState>>()!!
@ -89,35 +71,29 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
val consumedStateRefs = update.consumed.map { it.ref }
log.trace { "Removing $consumedStateRefs consumed contract states and adding $producedStateRefs produced contract states to the database." }
session.withTransaction(transactionIsolationLevel) {
producedStateRefsMap.forEach { it ->
val state = VaultStatesEntity().apply {
txId = it.key.txhash.toString()
index = it.key.index
stateStatus = Vault.StateStatus.UNCONSUMED
contractStateClassName = it.value.state.data.javaClass.name
contractState = it.value.state.serialize(context = STORAGE_CONTEXT).bytes
notaryName = it.value.state.notary.name.toString()
recordedTime = services.clock.instant()
}
insert(state)
}
// TODO: awaiting support of UPDATE WHERE <Composite key> IN in Requery DSL
consumedStateRefs.forEach { stateRef ->
val queryKey = io.requery.proxy.CompositeKey(mapOf(VaultStatesEntity.TX_ID to stateRef.txhash.toString(),
VaultStatesEntity.INDEX to stateRef.index))
val state = findByKey(VaultStatesEntity::class, queryKey)
state?.run {
stateStatus = Vault.StateStatus.CONSUMED
consumedTime = services.clock.instant()
// remove lock (if held)
if (lockId != null) {
lockId = null
lockUpdateTime = services.clock.instant()
log.trace("Releasing soft lock on consumed state: $stateRef")
}
update(state)
val session = DatabaseTransactionManager.current().session
producedStateRefsMap.forEach { stateAndRef ->
val state = VaultSchemaV1.VaultStates(
notary = stateAndRef.value.state.notary,
contractStateClassName = stateAndRef.value.state.data.javaClass.name,
contractState = stateAndRef.value.state.serialize(context = STORAGE_CONTEXT).bytes,
stateStatus = Vault.StateStatus.UNCONSUMED,
recordedTime = services.clock.instant())
state.stateRef = PersistentStateRef(stateAndRef.key)
session.save(state)
}
consumedStateRefs.forEach { stateRef ->
val state = session.get<VaultSchemaV1.VaultStates>(VaultSchemaV1.VaultStates::class.java, PersistentStateRef(stateRef))
state?.run {
stateStatus = Vault.StateStatus.CONSUMED
consumedTime = services.clock.instant()
// remove lock (if held)
if (lockId != null) {
lockId = null
lockUpdateTime = services.clock.instant()
log.trace("Releasing soft lock on consumed state: $stateRef")
}
session.save(state)
}
}
}
@ -220,19 +196,25 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
processAndNotify(netDelta)
}
// TODO: replace this method in favour of a VaultQuery query
private fun loadStates(refs: Collection<StateRef>): HashSet<StateAndRef<ContractState>> {
val states = HashSet<StateAndRef<ContractState>>()
if (refs.isNotEmpty()) {
session.withTransaction(transactionIsolationLevel) {
val result = select(VaultStatesEntity::class).
where(stateRefCompositeColumn.`in`(stateRefArgs(refs))).
and(VaultSchema.VaultStates::stateStatus eq Vault.StateStatus.UNCONSUMED)
result.get().forEach {
val txHash = SecureHash.parse(it.txId)
val index = it.index
val state = it.contractState.deserialize<TransactionState<ContractState>>(context = STORAGE_CONTEXT)
states.add(StateAndRef(state, StateRef(txHash, index)))
}
val session = DatabaseTransactionManager.current().session
val criteriaBuilder = session.criteriaBuilder
val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultStates::class.java)
val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultStates::class.java)
val statusPredicate = criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED)
val persistentStateRefs = refs.map { PersistentStateRef(it.txhash.bytes.toHexString(), it.index) }
val compositeKey = vaultStates.get<PersistentStateRef>(VaultSchemaV1.VaultStates::stateRef.name)
val stateRefsPredicate = criteriaBuilder.and(compositeKey.`in`(persistentStateRefs))
criteriaQuery.where(statusPredicate, stateRefsPredicate)
val results = session.createQuery(criteriaQuery).resultList
results.asSequence().forEach {
val txHash = SecureHash.parse(it.stateRef?.txId!!)
val index = it.stateRef?.index!!
val state = it.contractState.deserialize<TransactionState<ContractState>>(context = STORAGE_CONTEXT)
states.add(StateAndRef(state, StateRef(txHash, index)))
}
}
return states
@ -251,85 +233,105 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
}
override fun addNoteToTransaction(txnId: SecureHash, noteText: String) {
session.withTransaction(transactionIsolationLevel) {
val txnNoteEntity = VaultTxnNoteEntity()
txnNoteEntity.txId = txnId.toString()
txnNoteEntity.note = noteText
insert(txnNoteEntity)
}
val txnNoteEntity = VaultSchemaV1.VaultTxnNote(txnId.toString(), noteText)
DatabaseTransactionManager.current().session.save(txnNoteEntity)
}
override fun getTransactionNotes(txnId: SecureHash): Iterable<String> {
return session.withTransaction(transactionIsolationLevel) {
(select(VaultSchema.VaultTxnNote::class) where (VaultSchema.VaultTxnNote::txId eq txnId.toString())).get().asIterable().map { it.note }
}
val session = DatabaseTransactionManager.current().session
val criteriaBuilder = session.criteriaBuilder
val criteriaQuery = criteriaBuilder.createQuery(VaultSchemaV1.VaultTxnNote::class.java)
val vaultStates = criteriaQuery.from(VaultSchemaV1.VaultTxnNote::class.java)
val txIdPredicate = criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>(VaultSchemaV1.VaultTxnNote::txId.name), txnId.toString())
criteriaQuery.where(txIdPredicate)
val results = session.createQuery(criteriaQuery).resultList
return results.asIterable().map { it.note }
}
@Throws(StatesNotAvailableException::class)
override fun softLockReserve(lockId: UUID, stateRefs: NonEmptySet<StateRef>) {
val softLockTimestamp = services.clock.instant()
val stateRefArgs = stateRefArgs(stateRefs)
try {
session.withTransaction(transactionIsolationLevel) {
val updatedRows = update(VaultStatesEntity::class)
.set(VaultStatesEntity.LOCK_ID, lockId.toString())
.set(VaultStatesEntity.LOCK_UPDATE_TIME, softLockTimestamp)
.where(VaultStatesEntity.STATE_STATUS eq Vault.StateStatus.UNCONSUMED)
.and((VaultStatesEntity.LOCK_ID eq lockId.toString()) or (VaultStatesEntity.LOCK_ID.isNull()))
.and(stateRefCompositeColumn.`in`(stateRefArgs)).get().value()
if (updatedRows > 0 && updatedRows == stateRefs.size) {
log.trace("Reserving soft lock states for $lockId: $stateRefs")
FlowStateMachineImpl.currentStateMachine()?.hasSoftLockedStates = true
} else {
// revert partial soft locks
val revertUpdatedRows = update(VaultStatesEntity::class)
.set(VaultStatesEntity.LOCK_ID, null)
.where(VaultStatesEntity.LOCK_UPDATE_TIME eq softLockTimestamp)
.and(VaultStatesEntity.LOCK_ID eq lockId.toString())
.and(stateRefCompositeColumn.`in`(stateRefArgs)).get().value()
if (revertUpdatedRows > 0) {
log.trace("Reverting $revertUpdatedRows partially soft locked states for $lockId")
}
throw StatesNotAvailableException("Attempted to reserve $stateRefs for $lockId but only $updatedRows rows available")
val session = DatabaseTransactionManager.current().session
val criteriaBuilder = session.criteriaBuilder
val criteriaUpdate = criteriaBuilder.createCriteriaUpdate(VaultSchemaV1.VaultStates::class.java)
val vaultStates = criteriaUpdate.from(VaultSchemaV1.VaultStates::class.java)
val stateStatusPredication = criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED)
val lockIdPredicate = criteriaBuilder.or(vaultStates.get<String>(VaultSchemaV1.VaultStates::lockId.name).isNull,
criteriaBuilder.equal(vaultStates.get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString()))
val persistentStateRefs = stateRefs.map { PersistentStateRef(it.txhash.bytes.toHexString(), it.index) }
val compositeKey = vaultStates.get<PersistentStateRef>(VaultSchemaV1.VaultStates::stateRef.name)
val stateRefsPredicate = criteriaBuilder.and(compositeKey.`in`(persistentStateRefs))
criteriaUpdate.set(vaultStates.get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
criteriaUpdate.set(vaultStates.get<Instant>(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
criteriaUpdate.where(stateStatusPredication, lockIdPredicate, stateRefsPredicate)
val updatedRows = session.createQuery(criteriaUpdate).executeUpdate()
if (updatedRows > 0 && updatedRows == stateRefs.size) {
log.trace("Reserving soft lock states for $lockId: $stateRefs")
FlowStateMachineImpl.currentStateMachine()?.hasSoftLockedStates = true
} else {
// revert partial soft locks
val criteriaRevertUpdate = criteriaBuilder.createCriteriaUpdate(VaultSchemaV1.VaultStates::class.java)
val vaultStatesRevert = criteriaRevertUpdate.from(VaultSchemaV1.VaultStates::class.java)
val lockIdPredicateRevert = criteriaBuilder.equal(vaultStatesRevert.get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
val lockUpdateTime = criteriaBuilder.equal(vaultStatesRevert.get<Instant>(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
val persistentStateRefsRevert = stateRefs.map { PersistentStateRef(it.txhash.bytes.toHexString(), it.index) }
val compositeKeyRevert = vaultStatesRevert.get<PersistentStateRef>(VaultSchemaV1.VaultStates::stateRef.name)
val stateRefsPredicateRevert = criteriaBuilder.and(compositeKeyRevert.`in`(persistentStateRefsRevert))
criteriaRevertUpdate.set(vaultStatesRevert.get<String>(VaultSchemaV1.VaultStates::lockId.name), criteriaBuilder.nullLiteral(String::class.java))
criteriaRevertUpdate.where(lockUpdateTime, lockIdPredicateRevert, stateRefsPredicateRevert)
val revertUpdatedRows = session.createQuery(criteriaRevertUpdate).executeUpdate()
if (revertUpdatedRows > 0) {
log.trace("Reverting $revertUpdatedRows partially soft locked states for $lockId")
}
throw StatesNotAvailableException("Attempted to reserve $stateRefs for $lockId but only $updatedRows rows available")
}
} catch (e: PersistenceException) {
} catch (e: Exception) {
log.error("""soft lock update error attempting to reserve states for $lockId and $stateRefs")
$e.
""")
if (e.cause is StatesNotAvailableException) throw (e.cause as StatesNotAvailableException)
throw e
}
}
override fun softLockRelease(lockId: UUID, stateRefs: NonEmptySet<StateRef>?) {
val softLockTimestamp = services.clock.instant()
val session = DatabaseTransactionManager.current().session
val criteriaBuilder = session.criteriaBuilder
if (stateRefs == null) {
session.withTransaction(transactionIsolationLevel) {
val update = update(VaultStatesEntity::class)
.set(VaultStatesEntity.LOCK_ID, null)
.set(VaultStatesEntity.LOCK_UPDATE_TIME, services.clock.instant())
.where(VaultStatesEntity.STATE_STATUS eq Vault.StateStatus.UNCONSUMED)
.and(VaultStatesEntity.LOCK_ID eq lockId.toString()).get()
if (update.value() > 0) {
log.trace("Releasing ${update.value()} soft locked states for $lockId")
}
val criteriaUpdate = criteriaBuilder.createCriteriaUpdate(VaultSchemaV1.VaultStates::class.java)
val vaultStates = criteriaUpdate.from(VaultSchemaV1.VaultStates::class.java)
val stateStatusPredication = criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED)
val lockIdPredicate = criteriaBuilder.equal(vaultStates.get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
criteriaUpdate.set<String>(vaultStates.get<String>(VaultSchemaV1.VaultStates::lockId.name), criteriaBuilder.nullLiteral(String::class.java))
criteriaUpdate.set(vaultStates.get<Instant>(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
criteriaUpdate.where(stateStatusPredication, lockIdPredicate)
val update = session.createQuery(criteriaUpdate).executeUpdate()
if (update > 0) {
log.trace("Releasing $update soft locked states for $lockId")
}
} else {
try {
session.withTransaction(transactionIsolationLevel) {
val updatedRows = update(VaultStatesEntity::class)
.set(VaultStatesEntity.LOCK_ID, null)
.set(VaultStatesEntity.LOCK_UPDATE_TIME, services.clock.instant())
.where(VaultStatesEntity.STATE_STATUS eq Vault.StateStatus.UNCONSUMED)
.and(VaultStatesEntity.LOCK_ID eq lockId.toString())
.and(stateRefCompositeColumn.`in`(stateRefArgs(stateRefs))).get().value()
if (updatedRows > 0) {
log.trace("Releasing $updatedRows soft locked states for $lockId and stateRefs $stateRefs")
}
val criteriaUpdate = criteriaBuilder.createCriteriaUpdate(VaultSchemaV1.VaultStates::class.java)
val vaultStates = criteriaUpdate.from(VaultSchemaV1.VaultStates::class.java)
val stateStatusPredication = criteriaBuilder.equal(vaultStates.get<Vault.StateStatus>(VaultSchemaV1.VaultStates::stateStatus.name), Vault.StateStatus.UNCONSUMED)
val lockIdPredicate = criteriaBuilder.equal(vaultStates.get<String>(VaultSchemaV1.VaultStates::lockId.name), lockId.toString())
val persistentStateRefs = stateRefs.map { PersistentStateRef(it.txhash.bytes.toHexString(), it.index) }
val compositeKey = vaultStates.get<PersistentStateRef>(VaultSchemaV1.VaultStates::stateRef.name)
val stateRefsPredicate = criteriaBuilder.and(compositeKey.`in`(persistentStateRefs))
criteriaUpdate.set<String>(vaultStates.get<String>(VaultSchemaV1.VaultStates::lockId.name), criteriaBuilder.nullLiteral(String::class.java))
criteriaUpdate.set(vaultStates.get<Instant>(VaultSchemaV1.VaultStates::lockUpdateTime.name), softLockTimestamp)
criteriaUpdate.where(stateStatusPredication, lockIdPredicate, stateRefsPredicate)
val updatedRows = session.createQuery(criteriaUpdate).executeUpdate()
if (updatedRows > 0) {
log.trace("Releasing $updatedRows soft locked states for $lockId and stateRefs $stateRefs")
}
} catch (e: PersistenceException) {
} catch (e: Exception) {
log.error("""soft lock update error attempting to release states for $lockId and $stateRefs")
$e.
""")
throw e
}
}
}
@ -462,11 +464,4 @@ class NodeVaultService(private val services: ServiceHub, dataSourceProperties: P
is LinearState -> state.isRelevant(ourKeys)
else -> ourKeys.intersect(state.participants.map { it.owningKey }).isNotEmpty()
}
/**
* Helper method to generate a string formatted list of Composite Keys for Requery Expression clause
*/
private fun stateRefArgs(stateRefs: Iterable<StateRef>): List<List<Any>> {
return stateRefs.map { listOf("'${it.txhash}'", it.index) }
}
}

View File

@ -8,6 +8,9 @@ import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.OpaqueBytes
import org.hibernate.annotations.Generated
import org.hibernate.annotations.GenerationTime
import java.io.Serializable
import java.time.Instant
import java.util.*
import javax.persistence.*
@ -22,7 +25,7 @@ object VaultSchema
*/
@CordaSerializable
object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, version = 1,
mappedTypes = listOf(VaultStates::class.java, VaultLinearStates::class.java, VaultFungibleStates::class.java)) {
mappedTypes = listOf(VaultStates::class.java, VaultLinearStates::class.java, VaultFungibleStates::class.java, VaultTxnNote::class.java)) {
@Entity
@Table(name = "vault_states",
indexes = arrayOf(Index(name = "state_status_idx", columnList = "state_status")))
@ -50,16 +53,16 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
/** refers to timestamp recorded upon entering CONSUMED state */
@Column(name = "consumed_timestamp", nullable = true)
var consumedTime: Instant?,
var consumedTime: Instant? = null,
/** used to denote a state has been soft locked (to prevent double spend)
* will contain a temporary unique [UUID] obtained from a flow session */
@Column(name = "lock_id", nullable = true)
var lockId: String,
var lockId: String? = null,
/** refers to the last time a lock was taken (reserved) or updated (released, re-reserved) */
@Column(name = "lock_timestamp", nullable = true)
var lockUpdateTime: Instant?
var lockUpdateTime: Instant? = null
) : PersistentState()
@Entity
@ -133,4 +136,23 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
issuerRef = _issuerRef.bytes,
participants = _participants.toMutableSet())
}
@Entity
@Table(name = "vault_transaction_notes",
indexes = arrayOf(Index(name = "seq_no_index", columnList = "seq_no"),
Index(name = "transaction_id_index", columnList = "transaction_id")))
class VaultTxnNote(
@Id
@GeneratedValue
@Column(name = "seq_no")
var seqNo: Int,
@Column(name = "transaction_id", length = 64)
var txId: String,
@Column(name = "note")
var note: String
) : Serializable {
constructor(txId: String, note: String) : this(0, txId, note)
}
}

View File

@ -1,7 +1,7 @@
package net.corda.node.services.vault;
import com.google.common.collect.ImmutableSet;
import kotlin.Pair;
import kotlin.*;
import net.corda.contracts.DealState;
import net.corda.contracts.asset.Cash;
import net.corda.contracts.asset.CashUtilities;
@ -19,7 +19,7 @@ import net.corda.core.node.services.vault.QueryCriteria.VaultCustomQueryCriteria
import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
import net.corda.core.schemas.MappedSchema;
import net.corda.core.utilities.OpaqueBytes;
import net.corda.node.utilities.CordaPersistence;
import net.corda.node.utilities.*;
import net.corda.schemas.CashSchemaV1;
import net.corda.testing.TestConstants;
import net.corda.testing.TestDependencyInjectionBase;
@ -66,6 +66,7 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
Set<MappedSchema> requiredSchemas = new HashSet<>();
requiredSchemas.add(CashSchemaV1.INSTANCE);
IdentityService identitySvc = makeTestIdentityService();
@SuppressWarnings("unchecked")
Pair<CordaPersistence, MockServices> databaseAndServices = makeTestDatabaseAndMockServices(requiredSchemas, keys, () -> identitySvc);
issuerServices = new MockServices(getDUMMY_CASH_ISSUER_KEY(), getBOC_KEY());
database = databaseAndServices.getFirst();
@ -89,9 +90,10 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@Test
public void unconsumedLinearStates() throws VaultQueryException {
database.transaction(tx -> {
VaultFiller.fillWithSomeTestLinearStates(services, 3);
return tx;
});
database.transaction(tx -> {
// DOCSTART VaultJavaQueryExample0
Vault.Page<LinearState> results = vaultQuerySvc.queryBy(LinearState.class);
// DOCEND VaultJavaQueryExample0
@ -104,11 +106,12 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@Test
public void unconsumedStatesForStateRefsSortedByTxnId() {
Vault<LinearState> issuedStates =
database.transaction(tx -> {
VaultFiller.fillWithSomeTestLinearStates(services, 8);
return VaultFiller.fillWithSomeTestLinearStates(services, 2);
});
database.transaction(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());
@ -129,13 +132,11 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@Test
public void consumedCashStates() {
Amount<Currency> amount = new Amount<>(100, Currency.getInstance("USD"));
database.transaction(tx -> {
Amount<Currency> amount = new Amount<>(100, Currency.getInstance("USD"));
VaultFiller.fillWithSomeTestCash(services,
new Amount<Currency>(100, Currency.getInstance("USD")),
issuerServices,
issuerServices,
TestConstants.getDUMMY_NOTARY(),
3,
3,
@ -143,9 +144,13 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
new OpaqueBytes("1".getBytes()),
null,
CashUtilities.getDUMMY_CASH_ISSUER());
return tx;
});
database.transaction(tx -> {
VaultFiller.consumeCash(services, amount, getDUMMY_NOTARY());
return tx;
});
database.transaction(tx -> {
// DOCSTART VaultJavaQueryExample1
VaultQueryCriteria criteria = new VaultQueryCriteria(Vault.StateStatus.CONSUMED);
Vault.Page<Cash.State> results = vaultQuerySvc.queryBy(Cash.State.class, criteria);
@ -159,19 +164,24 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@Test
public void consumedDealStatesPagedSorted() throws VaultQueryException {
List<String> dealIds = Arrays.asList("123", "456", "789");
@SuppressWarnings("unchecked")
Triple<StateAndRef<LinearState>, UniqueIdentifier, Vault<DealState>> ids =
database.transaction((DatabaseTransaction tx) -> {
Vault<LinearState> states = VaultFiller.fillWithSomeTestLinearStates(services, 10, null);
StateAndRef<LinearState> linearState = states.getStates().iterator().next();
UniqueIdentifier uid = linearState.component1().getData().getLinearId();
Vault<DealState> dealStates = VaultFiller.fillWithSomeTestDeals(services, dealIds);
return new Triple(linearState,uid,dealStates);
});
database.transaction(tx -> {
Vault<LinearState> states = VaultFiller.fillWithSomeTestLinearStates(services, 10, null);
StateAndRef<LinearState> linearState = states.getStates().iterator().next();
UniqueIdentifier uid = linearState.component1().getData().getLinearId();
List<String> dealIds = Arrays.asList("123", "456", "789");
Vault<DealState> dealStates = VaultFiller.fillWithSomeTestDeals(services, dealIds);
// consume states
VaultFiller.consumeDeals(services, (List<? extends StateAndRef<? extends DealState>>) dealStates.getStates(), getDUMMY_NOTARY());
VaultFiller.consumeLinearStates(services, Collections.singletonList(linearState), getDUMMY_NOTARY());
VaultFiller.consumeDeals(services, (List<? extends StateAndRef<? extends DealState>>) ids.getThird().getStates(), getDUMMY_NOTARY());
VaultFiller.consumeLinearStates(services, Collections.singletonList(ids.getFirst()), getDUMMY_NOTARY());
return tx;
});
database.transaction(tx -> {
// DOCSTART VaultJavaQueryExample2
Vault.StateStatus status = Vault.StateStatus.CONSUMED;
@SuppressWarnings("unchecked")
@ -179,7 +189,7 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
QueryCriteria vaultCriteria = new VaultQueryCriteria(status, contractStateTypes);
List<UUID> linearIds = Collections.singletonList(uid.getId());
List<UUID> linearIds = Collections.singletonList(ids.getSecond().getId());
QueryCriteria linearCriteriaAll = new LinearStateQueryCriteria(null, linearIds);
QueryCriteria dealCriteriaAll = new LinearStateQueryCriteria(null, null, dealIds);
@ -212,7 +222,9 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
VaultFiller.fillWithSomeTestCash(services, dollars100, issuerServices, TestConstants.getDUMMY_NOTARY(), 1, 1, new Random(0L), new OpaqueBytes("1".getBytes()), null, getDUMMY_CASH_ISSUER());
VaultFiller.fillWithSomeTestCash(services, dollars10, issuerServices, TestConstants.getDUMMY_NOTARY(), 1, 1, new Random(0L), new OpaqueBytes("1".getBytes()), null, getDUMMY_CASH_ISSUER());
VaultFiller.fillWithSomeTestCash(services, dollars1, issuerServices, TestConstants.getDUMMY_NOTARY(), 1, 1, new Random(0L), new OpaqueBytes("1".getBytes()), null, getDUMMY_CASH_ISSUER());
return tx;
});
database.transaction(tx -> {
try {
// DOCSTART VaultJavaQueryExample3
QueryCriteria generalCriteria = new VaultQueryCriteria(Vault.StateStatus.ALL);
@ -256,7 +268,9 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
new OpaqueBytes("1".getBytes()),
null,
getDUMMY_CASH_ISSUER());
return tx;
});
database.transaction(tx -> {
// DOCSTART VaultJavaQueryExample4
@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(Collections.singletonList(Cash.State.class));
@ -276,14 +290,16 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@Test
public void trackDealStatesPagedSorted() {
List<String> dealIds = Arrays.asList("123", "456", "789");
UniqueIdentifier uid =
database.transaction(tx -> {
Vault<LinearState> states = VaultFiller.fillWithSomeTestLinearStates(services, 10, null);
UniqueIdentifier _uid = states.getStates().iterator().next().component1().getData().getLinearId();
VaultFiller.fillWithSomeTestDeals(services, dealIds);
return _uid;
});
database.transaction(tx -> {
Vault<LinearState> states = VaultFiller.fillWithSomeTestLinearStates(services, 10, null);
UniqueIdentifier uid = states.getStates().iterator().next().component1().getData().getLinearId();
List<String> dealIds = Arrays.asList("123", "456", "789");
VaultFiller.fillWithSomeTestDeals(services, dealIds);
// DOCSTART VaultJavaQueryExample5
@SuppressWarnings("unchecked")
Set<Class<ContractState>> contractStateTypes = new HashSet(Arrays.asList(DealState.class, LinearState.class));
@ -331,6 +347,9 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
VaultFiller.fillWithSomeTestCash(services, pounds, issuerServices, TestConstants.getDUMMY_NOTARY(), 4, 4, new Random(0L), new OpaqueBytes("1".getBytes()), null, getDUMMY_CASH_ISSUER());
VaultFiller.fillWithSomeTestCash(services, swissfrancs, issuerServices, TestConstants.getDUMMY_NOTARY(), 5, 5, new Random(0L), new OpaqueBytes("1".getBytes()), null, getDUMMY_CASH_ISSUER());
return tx;
});
database.transaction(tx -> {
try {
// DOCSTART VaultJavaQueryExample21
Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies");
@ -376,6 +395,9 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
VaultFiller.fillWithSomeTestCash(services, pounds, issuerServices, TestConstants.getDUMMY_NOTARY(), 4, 4, new Random(0L), new OpaqueBytes("1".getBytes()), null, getDUMMY_CASH_ISSUER());
VaultFiller.fillWithSomeTestCash(services, swissfrancs, issuerServices, TestConstants.getDUMMY_NOTARY(), 5, 5, new Random(0L), new OpaqueBytes("1".getBytes()), null, getDUMMY_CASH_ISSUER());
return tx;
});
database.transaction(tx -> {
try {
// DOCSTART VaultJavaQueryExample22
Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies");
@ -434,7 +456,6 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
@SuppressWarnings("unchecked")
public void aggregateFunctionsSumByIssuerAndCurrencyAndSortByAggregateSum() {
database.transaction(tx -> {
Amount<Currency> dollars100 = new Amount<>(100, Currency.getInstance("USD"));
Amount<Currency> dollars200 = new Amount<>(200, Currency.getInstance("USD"));
Amount<Currency> pounds300 = new Amount<>(300, Currency.getInstance("GBP"));
@ -445,6 +466,9 @@ public class VaultQueryJavaTests extends TestDependencyInjectionBase {
VaultFiller.fillWithSomeTestCash(services, pounds300, issuerServices, TestConstants.getDUMMY_NOTARY(), 3, 3, new Random(0L), new OpaqueBytes("1".getBytes()), null, getDUMMY_CASH_ISSUER());
VaultFiller.fillWithSomeTestCash(services, pounds400, issuerServices, TestConstants.getDUMMY_NOTARY(), 4, 4, new Random(0L), new OpaqueBytes("1".getBytes()), null, getBOC().ref(new OpaqueBytes("1".getBytes())));
return tx;
});
database.transaction(tx -> {
try {
// DOCSTART VaultJavaQueryExample23
Field pennies = CashSchemaV1.PersistentCashState.class.getDeclaredField("pennies");

View File

@ -74,7 +74,7 @@ class HibernateConfigurationTest : TestDependencyInjectionBase() {
database.transaction {
hibernateConfig = HibernateConfiguration(NodeSchemaService(customSchemas), makeTestDatabaseProperties(), ::makeTestIdentityService)
services = object : MockServices(BOB_KEY, BOC_KEY, DUMMY_NOTARY_KEY) {
override val vaultService: VaultService = makeVaultService(dataSourceProps, hibernateConfig)
override val vaultService: VaultService = makeVaultService(hibernateConfig)
override fun recordTransactions(notifyVault: Boolean, txs: Iterable<SignedTransaction>) {
for (stx in txs) {

View File

@ -1,213 +0,0 @@
package net.corda.node.services.database
import io.requery.Persistable
import io.requery.kotlin.eq
import io.requery.sql.KotlinEntityDataStore
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
import net.corda.core.identity.AnonymousParty
import net.corda.core.node.services.Vault
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.vault.schemas.requery.Models
import net.corda.node.services.vault.schemas.requery.VaultCashBalancesEntity
import net.corda.node.services.vault.schemas.requery.VaultSchema
import net.corda.node.services.vault.schemas.requery.VaultStatesEntity
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.configureDatabase
import net.corda.testing.*
import net.corda.testing.contracts.DummyContract
import net.corda.testing.dummyCommand
import net.corda.testing.node.makeTestDataSourceProperties
import net.corda.testing.node.makeTestDatabaseProperties
import net.corda.testing.node.makeTestIdentityService
import org.assertj.core.api.Assertions
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import java.time.Instant
import java.util.*
class RequeryConfigurationTest : TestDependencyInjectionBase() {
lateinit var database: CordaPersistence
lateinit var transactionStorage: DBTransactionStorage
lateinit var requerySession: KotlinEntityDataStore<Persistable>
@Before
fun setUp() {
val dataSourceProperties = makeTestDataSourceProperties()
database = configureDatabase(dataSourceProperties, makeTestDatabaseProperties(), identitySvc = ::makeTestIdentityService)
newTransactionStorage()
newRequeryStorage(dataSourceProperties)
}
@After
fun cleanUp() {
database.close()
}
@Test
fun `transaction inserts in same DB transaction scope across two persistence engines`() {
val txn = newTransaction()
database.transaction {
transactionStorage.addTransaction(txn)
requerySession.withTransaction {
insert(createVaultStateEntity(txn))
}
}
database.transaction {
Assertions.assertThat(transactionStorage.transactions).containsOnly(txn)
requerySession.withTransaction {
val result = select(VaultSchema.VaultStates::class) where (VaultSchema.VaultStates::txId eq txn.tx.inputs[0].txhash.toString())
Assertions.assertThat(result.get().first().txId).isEqualTo(txn.tx.inputs[0].txhash.toString())
}
}
}
@Test
fun `transaction operations in same DB transaction scope across two persistence engines`() {
val txn = newTransaction()
database.transaction {
transactionStorage.addTransaction(txn)
requerySession.withTransaction {
upsert(createCashBalance())
select(VaultSchema.VaultCashBalances::class).get().first()
insert(createVaultStateEntity(txn))
}
}
database.transaction {
Assertions.assertThat(transactionStorage.transactions).containsOnly(txn)
requerySession.withTransaction {
val cashQuery = select(VaultSchema.VaultCashBalances::class) where (VaultSchema.VaultCashBalances::currency eq "GBP")
assertEquals(12345, cashQuery.get().first().amount)
val stateQuery = select(VaultSchema.VaultStates::class) where (VaultSchema.VaultStates::txId eq txn.tx.inputs[0].txhash.toString())
Assertions.assertThat(stateQuery.get().first().txId).isEqualTo(txn.tx.inputs[0].txhash.toString())
}
}
}
@Test
fun `transaction rollback in same DB transaction scope across two persistence engines`() {
val txn = newTransaction()
database.transaction {
transactionStorage.addTransaction(txn)
requerySession.withTransaction {
insert(createVaultStateEntity(txn))
}
rollback()
}
database.transaction {
Assertions.assertThat(transactionStorage.transactions).isEmpty()
requerySession.withTransaction {
val result = select(VaultSchema.VaultStates::class) where (VaultSchema.VaultStates::txId eq txn.tx.inputs[0].txhash.toString())
Assertions.assertThat(result.get().count() == 0)
}
}
}
@Test
fun `bounded iteration`() {
// insert 100 entities
database.transaction {
requerySession.withTransaction {
(1..100)
.map { newTransaction(it) }
.forEach { insert(createVaultStateEntity(it)) }
}
}
// query entities 41..45
database.transaction {
requerySession.withTransaction {
// Note: cannot specify a limit explicitly when using iterator skip & take
val query = select(VaultSchema.VaultStates::class)
val count = query.get().count()
Assertions.assertThat(count).isEqualTo(100)
val result = query.get().iterator(40, 5)
Assertions.assertThat(result.asSequence().count()).isEqualTo(5)
}
}
}
@Test
fun `test calling an arbitrary JDBC native query`() {
val txn = newTransaction()
database.transaction {
transactionStorage.addTransaction(txn)
requerySession.withTransaction {
insert(createVaultStateEntity(txn))
}
}
val dataSourceProperties = makeTestDataSourceProperties()
val nativeQuery = "SELECT v.transaction_id, v.output_index FROM vault_states v WHERE v.state_status = 0"
database.transaction {
val configuration = RequeryConfiguration(dataSourceProperties, true, makeTestDatabaseProperties())
val jdbcSession = configuration.jdbcSession()
val prepStatement = jdbcSession.prepareStatement(nativeQuery)
val rs = prepStatement.executeQuery()
assertTrue(rs.next())
assertEquals(rs.getString(1), txn.tx.inputs[0].txhash.toString())
assertEquals(rs.getInt(2), txn.tx.inputs[0].index)
}
}
private fun createVaultStateEntity(txn: SignedTransaction): VaultStatesEntity {
val txnState = txn.tx.inputs[0]
val state = VaultStatesEntity().apply {
txId = txnState.txhash.toString()
index = txnState.index
stateStatus = Vault.StateStatus.UNCONSUMED
contractStateClassName = DummyContract.SingleOwnerState::class.java.name
contractState = DummyContract.SingleOwnerState(owner = AnonymousParty(MEGA_CORP_PUBKEY)).serialize().bytes
notaryName = txn.tx.notary!!.name.toString()
recordedTime = Instant.now()
}
return state
}
private fun createCashBalance(): VaultCashBalancesEntity {
val cashBalanceEntity = VaultCashBalancesEntity()
cashBalanceEntity.currency = "GBP"
cashBalanceEntity.amount = 12345
return cashBalanceEntity
}
private fun newTransactionStorage() {
database.transaction {
transactionStorage = DBTransactionStorage()
}
}
private fun newRequeryStorage(dataSourceProperties: Properties) {
database.transaction {
val configuration = RequeryConfiguration(dataSourceProperties, true, makeTestDatabaseProperties())
requerySession = configuration.sessionForModel(Models.VAULT)
}
}
private fun newTransaction(index: Int = 0): SignedTransaction {
val wtx = WireTransaction(
inputs = listOf(StateRef(SecureHash.randomSHA256(), index)),
attachments = emptyList(),
outputs = emptyList(),
commands = listOf(dummyCommand()),
notary = DUMMY_NOTARY,
timeWindow = null
)
return SignedTransaction(wtx, listOf(TransactionSignature(ByteArray(1), ALICE_PUBKEY, SignatureMetadata(1, Crypto.findSignatureScheme(ALICE_PUBKEY).schemeNumberID))))
}
}

View File

@ -11,8 +11,10 @@ import net.corda.core.node.services.VaultService
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.TransactionBuilder
import net.corda.node.services.MockServiceHubInternal
import net.corda.node.services.database.HibernateConfiguration
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.vault.NodeVaultService
@ -71,7 +73,8 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
smmHasRemovedAllFlows = CountDownLatch(1)
calls = 0
val dataSourceProps = makeTestDataSourceProperties()
database = configureDatabase(dataSourceProps, makeTestDatabaseProperties(), identitySvc = ::makeTestIdentityService)
val databaseProperties = makeTestDatabaseProperties()
database = configureDatabase(dataSourceProps, databaseProperties, identitySvc = ::makeTestIdentityService)
val identityService = InMemoryIdentityService(trustRoot = DUMMY_CA.certificate)
val kms = MockKeyManagementService(identityService, ALICE_KEY)
@ -88,7 +91,7 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
overrideClock = testClock,
keyManagement = kms,
network = mockMessagingService), TestReference {
override val vaultService: VaultService = NodeVaultService(this, dataSourceProps, makeTestDatabaseProperties())
override val vaultService: VaultService = NodeVaultService(this)
override val testReference = this@NodeSchedulerServiceTest
}
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)

View File

@ -59,7 +59,7 @@ class DBTransactionStorageTests : TestDependencyInjectionBase() {
services = object : MockServices(BOB_KEY) {
override val vaultService: VaultService get() {
val vaultService = NodeVaultService(this, dataSourceProps, makeTestDatabaseProperties())
val vaultService = NodeVaultService(this)
hibernatePersister = HibernateObserver(vaultService.rawUpdates, hibernateConfig)
return vaultService
}

View File

@ -7,14 +7,13 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.internal.read
import net.corda.core.internal.readAll
import net.corda.testing.LogHelper
import net.corda.core.internal.write
import net.corda.core.internal.writeLines
import net.corda.node.services.database.RequeryConfiguration
import net.corda.node.services.persistence.schemas.requery.AttachmentEntity
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.CordaPersistence
import net.corda.node.utilities.DatabaseTransactionManager
import net.corda.node.utilities.configureDatabase
import net.corda.testing.LogHelper
import net.corda.testing.node.makeTestDataSourceProperties
import net.corda.testing.node.makeTestDatabaseProperties
import net.corda.testing.node.makeTestIdentityService
@ -25,7 +24,6 @@ import java.nio.charset.Charset
import java.nio.file.FileAlreadyExistsException
import java.nio.file.FileSystem
import java.nio.file.Path
import java.util.*
import java.util.jar.JarEntry
import java.util.jar.JarOutputStream
import kotlin.test.assertEquals
@ -36,17 +34,13 @@ class NodeAttachmentStorageTest {
// Use an in memory file system for testing attachment storage.
lateinit var fs: FileSystem
lateinit var database: CordaPersistence
lateinit var dataSourceProperties: Properties
lateinit var configuration: RequeryConfiguration
@Before
fun setUp() {
LogHelper.setLevel(PersistentUniquenessProvider::class)
dataSourceProperties = makeTestDataSourceProperties()
val dataSourceProperties = makeTestDataSourceProperties()
database = configureDatabase(dataSourceProperties, makeTestDatabaseProperties(), identitySvc = ::makeTestIdentityService)
configuration = RequeryConfiguration(dataSourceProperties, databaseProperties = makeTestDatabaseProperties())
fs = Jimfs.newFileSystem(Configuration.unix())
}
@ -61,7 +55,7 @@ class NodeAttachmentStorageTest {
val expectedHash = testJar.readAll().sha256()
database.transaction {
val storage = NodeAttachmentService(dataSourceProperties, MetricRegistry(), makeTestDatabaseProperties())
val storage = NodeAttachmentService(MetricRegistry())
val id = testJar.read { storage.importAttachment(it) }
assertEquals(expectedHash, id)
@ -87,7 +81,7 @@ class NodeAttachmentStorageTest {
fun `duplicates not allowed`() {
val testJar = makeTestJar()
database.transaction {
val storage = NodeAttachmentService(dataSourceProperties, MetricRegistry(), makeTestDatabaseProperties())
val storage = NodeAttachmentService(MetricRegistry())
testJar.read {
storage.importAttachment(it)
}
@ -102,19 +96,21 @@ class NodeAttachmentStorageTest {
@Test
fun `corrupt entry throws exception`() {
val testJar = makeTestJar()
val id =
database.transaction {
val storage = NodeAttachmentService(dataSourceProperties, MetricRegistry(), makeTestDatabaseProperties())
val storage = NodeAttachmentService(MetricRegistry())
val id = testJar.read { storage.importAttachment(it) }
// Corrupt the file in the store.
val bytes = testJar.readAll()
val corruptBytes = "arggghhhh".toByteArray()
System.arraycopy(corruptBytes, 0, bytes, 0, corruptBytes.size)
val corruptAttachment = AttachmentEntity()
corruptAttachment.attId = id
corruptAttachment.content = bytes
storage.session.update(corruptAttachment)
val corruptAttachment = NodeAttachmentService.DBAttachment(attId = id.toString(), content = bytes)
DatabaseTransactionManager.current().session.merge(corruptAttachment)
id
}
database.transaction {
val storage = NodeAttachmentService(MetricRegistry())
val e = assertFailsWith<NodeAttachmentService.HashMismatchException> {
storage.openAttachment(id)!!.open().use { it.readBytes() }
}
@ -131,7 +127,7 @@ class NodeAttachmentStorageTest {
@Test
fun `non jar rejected`() {
database.transaction {
val storage = NodeAttachmentService(dataSourceProperties, MetricRegistry(), makeTestDatabaseProperties())
val storage = NodeAttachmentService(MetricRegistry())
val path = fs.getPath("notajar")
path.writeLines(listOf("Hey", "there!"))
path.read {

View File

@ -84,9 +84,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Test
fun `states not local to instance`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L))
}
database.transaction {
val w1 = vaultQuery.queryBy<Cash.State>().states
assertThat(w1).hasSize(3)
@ -112,9 +112,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Test
fun `states for refs`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L))
}
database.transaction {
val w1 = vaultQuery.queryBy<Cash.State>().states
assertThat(w1).hasSize(3)
@ -126,8 +126,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Test
fun `states soft locking reserve and release`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L))
}
database.transaction {
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
assertThat(unconsumedStates).hasSize(3)
@ -309,8 +310,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Test
fun `unconsumedStatesForSpending exact amount`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L))
}
database.transaction {
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
assertThat(unconsumedStates).hasSize(1)
@ -327,10 +329,10 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Test
fun `unconsumedStatesForSpending from two issuer parties`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (DUMMY_CASH_ISSUER))
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(1)))
}
database.transaction {
val spendableStatesUSD = vaultSvc.unconsumedCashStatesForSpending(200.DOLLARS,
onlyFromIssuerParties = setOf(DUMMY_CASH_ISSUER.party, BOC))
spendableStatesUSD.forEach(::println)
@ -344,12 +346,12 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Test
fun `unconsumedStatesForSpending from specific issuer party and refs`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (DUMMY_CASH_ISSUER))
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(1)), ref = OpaqueBytes.of(1))
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(2)), ref = OpaqueBytes.of(2))
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L), issuedBy = (BOC.ref(3)), ref = OpaqueBytes.of(3))
}
database.transaction {
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
assertThat(unconsumedStates).hasSize(4)
@ -366,9 +368,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Test
fun `unconsumedStatesForSpending insufficient amount`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L))
}
database.transaction {
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
assertThat(unconsumedStates).hasSize(1)
@ -383,9 +385,9 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Test
fun `unconsumedStatesForSpending small amount`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L))
}
database.transaction {
val unconsumedStates = vaultQuery.queryBy<Cash.State>().states
assertThat(unconsumedStates).hasSize(2)
@ -401,11 +403,11 @@ class NodeVaultServiceTest : TestDependencyInjectionBase() {
@Test
fun `states soft locking query granularity`() {
database.transaction {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L))
services.fillWithSomeTestCash(100.POUNDS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L))
services.fillWithSomeTestCash(100.SWISS_FRANCS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L))
}
database.transaction {
var unlockedStates = 30
val allStates = vaultQuery.queryBy<Cash.State>().states
assertThat(allStates).hasSize(unlockedStates)

View File

@ -61,7 +61,8 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
database.transaction {
// Fix the PRNG so that we get the same splits every time.
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L), issuedBy = DUMMY_CASH_ISSUER)
}
database.transaction {
val w = vaultQuery.queryBy<Cash.State>().states
assertEquals(3, w.size)
@ -77,25 +78,31 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
@Test
fun `issue and spend total correctly and irrelevant ignored`() {
val megaCorpServices = MockServices(MEGA_CORP_KEY)
val freshKey = services.keyManagementService.freshKey()
val usefulTX =
database.transaction {
// A tx that sends us money.
val usefulBuilder = TransactionBuilder(null)
Cash().generateIssue(usefulBuilder, 100.DOLLARS `issued by` MEGA_CORP.ref(1), AnonymousParty(freshKey), DUMMY_NOTARY)
megaCorpServices.signInitialTransaction(usefulBuilder)
}
database.transaction {
// A tx that sends us money.
val freshKey = services.keyManagementService.freshKey()
val usefulBuilder = TransactionBuilder(null)
Cash().generateIssue(usefulBuilder, 100.DOLLARS `issued by` MEGA_CORP.ref(1), AnonymousParty(freshKey), DUMMY_NOTARY)
val usefulTX = megaCorpServices.signInitialTransaction(usefulBuilder)
assertEquals(0.DOLLARS, services.getCashBalance(USD))
services.recordTransactions(usefulTX)
// A tx that spends our money.
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY)
Cash.generateSpend(services, spendTXBuilder, 80.DOLLARS, BOB)
val spendPTX = services.signInitialTransaction(spendTXBuilder, freshKey)
val spendTX = notaryServices.addSignature(spendPTX)
}
val spendTX =
database.transaction {
// A tx that spends our money.
val spendTXBuilder = TransactionBuilder(DUMMY_NOTARY)
Cash.generateSpend(services, spendTXBuilder, 80.DOLLARS, BOB)
val spendPTX = services.signInitialTransaction(spendTXBuilder, freshKey)
notaryServices.addSignature(spendPTX)
}
database.transaction {
assertEquals(100.DOLLARS, services.getCashBalance(USD))
}
database.transaction {
// A tx that doesn't send us anything.
val irrelevantBuilder = TransactionBuilder(DUMMY_NOTARY)
Cash().generateIssue(irrelevantBuilder, 100.DOLLARS `issued by` MEGA_CORP.ref(1), BOB, DUMMY_NOTARY)
@ -104,12 +111,15 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
val irrelevantTX = notaryServices.addSignature(irrelevantPTX)
services.recordTransactions(irrelevantTX)
}
database.transaction {
assertEquals(100.DOLLARS, services.getCashBalance(USD))
}
database.transaction {
services.recordTransactions(spendTX)
}
database.transaction {
assertEquals(20.DOLLARS, services.getCashBalance(USD))
// TODO: Flesh out these tests as needed.
}
}
@ -123,7 +133,8 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 10, 10, Random(0L), ownedBy = AnonymousParty(freshKey),
issuedBy = MEGA_CORP.ref(1))
println("Cash balance: ${services.getCashBalance(USD)}")
}
database.transaction {
assertThat(vaultQuery.queryBy<Cash.State>().states).hasSize(10)
assertThat(vaultQuery.queryBy<Cash.State>(criteriaLocked).states).hasSize(0)
}
@ -234,22 +245,23 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
@Test
fun `sequencing LinearStates works`() {
database.transaction {
val freshKey = services.keyManagementService.freshKey()
val freshIdentity = AnonymousParty(freshKey)
val freshKey = services.keyManagementService.freshKey()
val freshIdentity = AnonymousParty(freshKey)
val linearId = UniqueIdentifier()
val linearId = UniqueIdentifier()
// Issue a linear state
val dummyIssue =
database.transaction { // Issue a linear state
val dummyIssueBuilder = TransactionBuilder(notary = DUMMY_NOTARY)
.addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity)))
.addCommand(dummyCommand(notaryServices.legalIdentityKey))
.addOutputState(DummyLinearContract.State(linearId = linearId, participants = listOf(freshIdentity))).addCommand(dummyCommand(notaryServices.legalIdentityKey))
val dummyIssuePtx = notaryServices.signInitialTransaction(dummyIssueBuilder)
val dummyIssue = services.addSignature(dummyIssuePtx)
dummyIssue.toLedgerTransaction(services).verify()
dummyIssue.toLedgerTransaction(services).verify()
services.recordTransactions(dummyIssue)
services.recordTransactions(dummyIssue)
dummyIssue
}
database.transaction {
assertThat(vaultQuery.queryBy<DummyLinearContract.State>().states).hasSize(1)
// Move the same state
@ -263,6 +275,8 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
dummyIssue.toLedgerTransaction(services).verify()
services.recordTransactions(dummyMove)
}
database.transaction {
assertThat(vaultQuery.queryBy<DummyLinearContract.State>().states).hasSize(1)
}
}
@ -275,10 +289,15 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
services.fillWithSomeTestCash(100.DOLLARS, issuerServices, DUMMY_NOTARY, 3, 3, Random(0L), ownedBy = AnonymousParty(freshKey))
services.fillWithSomeTestCash(100.SWISS_FRANCS, issuerServices, DUMMY_NOTARY, 2, 2, Random(0L))
services.fillWithSomeTestCash(100.POUNDS, issuerServices, DUMMY_NOTARY, 1, 1, Random(0L))
}
database.transaction {
val cash = vaultQuery.queryBy<Cash.State>().states
cash.forEach { println(it.state.data.amount) }
}
database.transaction {
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
}
database.transaction {
val deals = vaultQuery.queryBy<DummyDealContract.State>().states
deals.forEach { println(it.state.data.linearId.externalId!!) }
}
@ -290,7 +309,8 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
val spendPTX = notaryServices.signInitialTransaction(spendTXBuilder)
val spendTX = services.addSignature(spendPTX, freshKey)
services.recordTransactions(spendTX)
}
database.transaction {
val consumedStates = vaultQuery.queryBy<ContractState>(VaultQueryCriteria(status = Vault.StateStatus.CONSUMED)).states
assertEquals(3, consumedStates.count())
@ -300,17 +320,21 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
}
@Test
fun `consuming multiple contract state types in same transaction`() {
fun `consuming multiple contract state types`() {
val freshKey = services.keyManagementService.freshKey()
val freshIdentity = AnonymousParty(freshKey)
database.transaction {
services.fillWithSomeTestDeals(listOf("123", "456", "789"))
val deals = vaultQuery.queryBy<DummyDealContract.State>().states
deals.forEach { println(it.state.data.linearId.externalId!!) }
}
val deals =
database.transaction {
vaultQuery.queryBy<DummyDealContract.State>().states
}
database.transaction {
services.fillWithSomeTestLinearStates(3)
}
database.transaction {
val linearStates = vaultQuery.queryBy<DummyLinearContract.State>().states
linearStates.forEach { println(it.state.data.linearId) }
@ -324,10 +348,10 @@ class VaultWithCashTest : TestDependencyInjectionBase() {
}
val dummyMove = notaryServices.signInitialTransaction(dummyMoveBuilder)
dummyMove.toLedgerTransaction(services).verify()
services.recordTransactions(dummyMove)
}
database.transaction {
val consumedStates = vaultQuery.queryBy<ContractState>(VaultQueryCriteria(status = Vault.StateStatus.CONSUMED)).states
assertEquals(2, consumedStates.count())