mirror of
https://github.com/corda/corda.git
synced 2025-01-03 03:36:48 +00:00
Additional method on VaultService to add notes to a transaction
Additional method on VaultService to retrieve notes for a transaction
This commit is contained in:
parent
bc525aabdf
commit
369214a747
@ -4,6 +4,7 @@ import com.google.common.util.concurrent.ListenableFuture
|
|||||||
import com.google.common.util.concurrent.SettableFuture
|
import com.google.common.util.concurrent.SettableFuture
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.transactions.TransactionBuilder
|
import com.r3corda.core.transactions.TransactionBuilder
|
||||||
import com.r3corda.core.transactions.WireTransaction
|
import com.r3corda.core.transactions.WireTransaction
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -35,7 +36,8 @@ val DEFAULT_SESSION_ID = 0L
|
|||||||
* Active means they haven't been consumed yet (or we don't know about it).
|
* Active means they haven't been consumed yet (or we don't know about it).
|
||||||
* Relevant means they contain at least one of our pubkeys.
|
* Relevant means they contain at least one of our pubkeys.
|
||||||
*/
|
*/
|
||||||
class Vault(val states: Iterable<StateAndRef<ContractState>>) {
|
class Vault(val states: Iterable<StateAndRef<ContractState>>,
|
||||||
|
val transactionNotes: Map<SecureHash, Set<String>> = emptyMap()) {
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
inline fun <reified T : ContractState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
|
inline fun <reified T : ContractState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
|
||||||
|
|
||||||
@ -164,6 +166,13 @@ interface VaultService {
|
|||||||
return future
|
return future
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a note to an existing [LedgerTransaction] given by its unique [SecureHash] id
|
||||||
|
*/
|
||||||
|
fun addNoteToTransaction(txnId: SecureHash, noteText: String)
|
||||||
|
|
||||||
|
fun getTransactionNotes(txnId: SecureHash): Iterable<String>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fungible Asset operations
|
* Fungible Asset operations
|
||||||
**/
|
**/
|
||||||
|
@ -5,6 +5,7 @@ import com.r3corda.contracts.asset.Cash
|
|||||||
import com.r3corda.core.ThreadBox
|
import com.r3corda.core.ThreadBox
|
||||||
import com.r3corda.core.bufferUntilSubscribed
|
import com.r3corda.core.bufferUntilSubscribed
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.core.node.ServiceHub
|
import com.r3corda.core.node.ServiceHub
|
||||||
import com.r3corda.core.node.services.Vault
|
import com.r3corda.core.node.services.Vault
|
||||||
@ -14,10 +15,8 @@ import com.r3corda.core.transactions.TransactionBuilder
|
|||||||
import com.r3corda.core.transactions.WireTransaction
|
import com.r3corda.core.transactions.WireTransaction
|
||||||
import com.r3corda.core.utilities.loggerFor
|
import com.r3corda.core.utilities.loggerFor
|
||||||
import com.r3corda.core.utilities.trace
|
import com.r3corda.core.utilities.trace
|
||||||
import com.r3corda.node.utilities.AbstractJDBCHashSet
|
import com.r3corda.node.utilities.*
|
||||||
import com.r3corda.node.utilities.JDBCHashedTable
|
import kotlinx.support.jdk8.collections.putIfAbsent
|
||||||
import com.r3corda.node.utilities.NODE_DATABASE_PREFIX
|
|
||||||
import com.r3corda.node.utilities.stateRef
|
|
||||||
import org.jetbrains.exposed.sql.ResultRow
|
import org.jetbrains.exposed.sql.ResultRow
|
||||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -46,6 +45,11 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
|
|||||||
val stateRef = stateRef("transaction_id", "output_index")
|
val stateRef = stateRef("transaction_id", "output_index")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object TransactionNotesTable : JDBCHashedTable("${NODE_DATABASE_PREFIX}vault_txn_notes") {
|
||||||
|
val txnId = secureHash("txnId")
|
||||||
|
val notes = blob("notes")
|
||||||
|
}
|
||||||
|
|
||||||
private val mutex = ThreadBox(object {
|
private val mutex = ThreadBox(object {
|
||||||
val unconsumedStates = object : AbstractJDBCHashSet<StateRef, StatesSetTable>(StatesSetTable) {
|
val unconsumedStates = object : AbstractJDBCHashSet<StateRef, StatesSetTable>(StatesSetTable) {
|
||||||
override fun elementFromRow(row: ResultRow): StateRef = StateRef(row[table.stateRef.txId], row[table.stateRef.index])
|
override fun elementFromRow(row: ResultRow): StateRef = StateRef(row[table.stateRef.txId], row[table.stateRef.index])
|
||||||
@ -56,6 +60,28 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val transactionNotes = object : AbstractJDBCHashMap<SecureHash, Set<String>, TransactionNotesTable>(TransactionNotesTable, loadOnInit = false) {
|
||||||
|
override fun keyFromRow(row: ResultRow): SecureHash {
|
||||||
|
return row[table.txnId]
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun valueFromRow(row: ResultRow): Set<String> {
|
||||||
|
return deserializeFromBlob(row[table.notes])
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addKeyToInsert(insert: InsertStatement, entry: Map.Entry<SecureHash, Set<String>>, finalizables: MutableList<() -> Unit>) {
|
||||||
|
insert[table.txnId] = entry.key
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addValueToInsert(insert: InsertStatement, entry: Map.Entry<SecureHash, Set<String>>, finalizables: MutableList<() -> Unit>) {
|
||||||
|
insert[table.notes] = serializeToBlob(entry.value, finalizables)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun allTransactionNotes(): Map<SecureHash,Set<String>> {
|
||||||
|
return transactionNotes
|
||||||
|
}
|
||||||
|
|
||||||
val _updatesPublisher = PublishSubject.create<Vault.Update>()
|
val _updatesPublisher = PublishSubject.create<Vault.Update>()
|
||||||
|
|
||||||
fun allUnconsumedStates(): Iterable<StateAndRef<ContractState>> {
|
fun allUnconsumedStates(): Iterable<StateAndRef<ContractState>> {
|
||||||
@ -79,14 +105,14 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
override val currentVault: Vault get() = mutex.locked { Vault(allUnconsumedStates()) }
|
override val currentVault: Vault get() = mutex.locked { Vault(allUnconsumedStates(), allTransactionNotes()) }
|
||||||
|
|
||||||
override val updates: Observable<Vault.Update>
|
override val updates: Observable<Vault.Update>
|
||||||
get() = mutex.locked { _updatesPublisher }
|
get() = mutex.locked { _updatesPublisher }
|
||||||
|
|
||||||
override fun track(): Pair<Vault, Observable<Vault.Update>> {
|
override fun track(): Pair<Vault, Observable<Vault.Update>> {
|
||||||
return mutex.locked {
|
return mutex.locked {
|
||||||
Pair(Vault(allUnconsumedStates()), _updatesPublisher.bufferUntilSubscribed())
|
Pair(Vault(allUnconsumedStates(), allTransactionNotes()), _updatesPublisher.bufferUntilSubscribed())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +136,22 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
|
|||||||
return currentVault
|
return currentVault
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun addNoteToTransaction(txnId: SecureHash, noteText: String) {
|
||||||
|
mutex.locked {
|
||||||
|
val notes = transactionNotes.getOrPut(key = txnId, defaultValue = {
|
||||||
|
setOf(noteText)
|
||||||
|
})
|
||||||
|
transactionNotes.put(txnId, notes.plus(noteText))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTransactionNotes(txnId: SecureHash): Iterable<String> {
|
||||||
|
mutex.locked {
|
||||||
|
return transactionNotes.get(txnId)!!.asIterable()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a transaction that moves an amount of currency to the given pubkey.
|
* Generate a transaction that moves an amount of currency to the given pubkey.
|
||||||
*
|
*
|
||||||
|
@ -1,15 +1,22 @@
|
|||||||
package com.r3corda.node.services
|
package com.r3corda.node.services
|
||||||
|
|
||||||
|
import com.r3corda.contracts.asset.Cash
|
||||||
import com.r3corda.contracts.testing.fillWithSomeTestCash
|
import com.r3corda.contracts.testing.fillWithSomeTestCash
|
||||||
import com.r3corda.core.contracts.DOLLARS
|
import com.r3corda.core.contracts.DOLLARS
|
||||||
|
import com.r3corda.core.contracts.POUNDS
|
||||||
|
import com.r3corda.core.contracts.TransactionType
|
||||||
|
import com.r3corda.core.contracts.`issued by`
|
||||||
import com.r3corda.core.node.services.TxWritableStorageService
|
import com.r3corda.core.node.services.TxWritableStorageService
|
||||||
import com.r3corda.core.node.services.VaultService
|
import com.r3corda.core.node.services.VaultService
|
||||||
import com.r3corda.core.transactions.SignedTransaction
|
import com.r3corda.core.transactions.SignedTransaction
|
||||||
import com.r3corda.core.utilities.DUMMY_NOTARY
|
import com.r3corda.core.utilities.DUMMY_NOTARY
|
||||||
import com.r3corda.core.utilities.LogHelper
|
import com.r3corda.core.utilities.LogHelper
|
||||||
|
import com.r3corda.node.services.schema.NodeSchemaService
|
||||||
import com.r3corda.node.services.vault.NodeVaultService
|
import com.r3corda.node.services.vault.NodeVaultService
|
||||||
import com.r3corda.node.utilities.configureDatabase
|
import com.r3corda.node.utilities.configureDatabase
|
||||||
import com.r3corda.node.utilities.databaseTransaction
|
import com.r3corda.node.utilities.databaseTransaction
|
||||||
|
import com.r3corda.testing.MEGA_CORP
|
||||||
|
import com.r3corda.testing.MEGA_CORP_KEY
|
||||||
import com.r3corda.testing.node.MockServices
|
import com.r3corda.testing.node.MockServices
|
||||||
import com.r3corda.testing.node.makeTestDataSourceProperties
|
import com.r3corda.testing.node.makeTestDataSourceProperties
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
@ -19,6 +26,7 @@ import org.junit.Before
|
|||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
class NodeVaultServiceTest {
|
class NodeVaultServiceTest {
|
||||||
lateinit var dataSource: Closeable
|
lateinit var dataSource: Closeable
|
||||||
@ -75,4 +83,50 @@ class NodeVaultServiceTest {
|
|||||||
assertThat(w2.states).hasSize(3)
|
assertThat(w2.states).hasSize(3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun addNoteToTransaction() {
|
||||||
|
|
||||||
|
databaseTransaction(database) {
|
||||||
|
val services = object : MockServices() {
|
||||||
|
override val vaultService: VaultService = NodeVaultService(this)
|
||||||
|
|
||||||
|
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||||
|
for (stx in txs) {
|
||||||
|
storageService.validatedTransactions.addTransaction(stx)
|
||||||
|
}
|
||||||
|
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
|
||||||
|
vaultService.notifyAll(txs.map { it.tx })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val freshKey = services.legalIdentityKey
|
||||||
|
|
||||||
|
// Issue a txn to Send us some Money
|
||||||
|
val usefulTX = TransactionType.General.Builder(null).apply {
|
||||||
|
Cash().generateIssue(this, 100.DOLLARS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
|
||||||
|
signWith(MEGA_CORP_KEY)
|
||||||
|
}.toSignedTransaction()
|
||||||
|
|
||||||
|
services.recordTransactions(listOf(usefulTX))
|
||||||
|
|
||||||
|
services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 1")
|
||||||
|
services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 2")
|
||||||
|
services.vaultService.addNoteToTransaction(usefulTX.id, "USD Sample Note 3")
|
||||||
|
assertEquals(1, services.vaultService.currentVault.transactionNotes.toList().size)
|
||||||
|
assertEquals(3, services.vaultService.getTransactionNotes(usefulTX.id).count())
|
||||||
|
|
||||||
|
// Issue more Money (GBP)
|
||||||
|
val anotherTX = TransactionType.General.Builder(null).apply {
|
||||||
|
Cash().generateIssue(this, 200.POUNDS `issued by` MEGA_CORP.ref(1), freshKey.public, DUMMY_NOTARY)
|
||||||
|
signWith(MEGA_CORP_KEY)
|
||||||
|
}.toSignedTransaction()
|
||||||
|
|
||||||
|
services.recordTransactions(listOf(anotherTX))
|
||||||
|
|
||||||
|
services.vaultService.addNoteToTransaction(anotherTX.id, "GPB Sample Note 1")
|
||||||
|
assertEquals(2, services.vaultService.currentVault.transactionNotes.toList().size)
|
||||||
|
assertEquals(1, services.vaultService.getTransactionNotes(anotherTX.id).count())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user