Additional method on VaultService to add notes to a transaction

This commit is contained in:
Jose Coll 2016-10-19 15:06:52 +01:00
parent 6c6d7f8546
commit 04920c9507
4 changed files with 92 additions and 7 deletions

View File

@ -4,6 +4,7 @@ import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.SettableFuture
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.Party
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.transactions.WireTransaction
import rx.Observable
import java.security.KeyPair
@ -33,7 +34,8 @@ val DEFAULT_SESSION_ID = 0L
* 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.
*/
class Vault(val states: Iterable<StateAndRef<ContractState>>) {
class Vault(val states: Iterable<StateAndRef<ContractState>>,
val transactionNotes: Map<SecureHash, Set<String>> = emptyMap()) {
@Suppress("UNCHECKED_CAST")
inline fun <reified T : ContractState> statesOfType() = states.filter { it.state.data is T } as List<StateAndRef<T>>
@ -147,6 +149,11 @@ interface VaultService {
}
return future
}
/**
* Add a note to an existing [LedgerTransaction] given by its unique [SecureHash] id
*/
fun addNoteToTransaction(txnId: SecureHash, noteText: String)
}
inline fun <reified T : LinearState> VaultService.linearHeadsOfType() = linearHeadsOfType_(T::class.java)

View File

@ -3,6 +3,7 @@ package com.r3corda.core.testing
import com.r3corda.core.ThreadBox
import com.r3corda.core.bufferUntilSubscribed
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.Vault
import com.r3corda.core.node.services.VaultService
@ -94,6 +95,10 @@ open class InMemoryVaultService(protected val services: ServiceHub) : SingletonS
return changedVault
}
override fun addNoteToTransaction(txnId: SecureHash, noteText: String) {
throw UnsupportedOperationException("not implemented") //To change body of created functions use File | Settings | File Templates.
}
private fun isRelevant(state: ContractState, ourKeys: Set<PublicKey>): Boolean {
return if (state is OwnableState) {
state.owner in ourKeys

View File

@ -4,6 +4,7 @@ import com.google.common.collect.Sets
import com.r3corda.core.ThreadBox
import com.r3corda.core.bufferUntilSubscribed
import com.r3corda.core.contracts.*
import com.r3corda.core.crypto.SecureHash
import com.r3corda.core.node.ServiceHub
import com.r3corda.core.node.services.Vault
import com.r3corda.core.node.services.VaultService
@ -11,10 +12,7 @@ import com.r3corda.core.serialization.SingletonSerializeAsToken
import com.r3corda.core.transactions.WireTransaction
import com.r3corda.core.utilities.loggerFor
import com.r3corda.core.utilities.trace
import com.r3corda.node.utilities.AbstractJDBCHashSet
import com.r3corda.node.utilities.JDBCHashedTable
import com.r3corda.node.utilities.NODE_DATABASE_PREFIX
import com.r3corda.node.utilities.stateRef
import com.r3corda.node.utilities.*
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.statements.InsertStatement
import rx.Observable
@ -42,6 +40,11 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
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 {
val unconsumedStates = object : AbstractJDBCHashSet<StateRef, StatesSetTable>(StatesSetTable) {
override fun elementFromRow(row: ResultRow): StateRef = StateRef(row[table.stateRef.txId], row[table.stateRef.index])
@ -52,6 +55,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>()
fun allUnconsumedStates(): Iterable<StateAndRef<ContractState>> {
@ -75,14 +100,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>
get() = mutex.locked { _updatesPublisher }
override fun track(): Pair<Vault, Observable<Vault.Update>> {
return mutex.locked {
Pair(Vault(allUnconsumedStates()), _updatesPublisher.bufferUntilSubscribed())
Pair(Vault(allUnconsumedStates(), allTransactionNotes()), _updatesPublisher.bufferUntilSubscribed())
}
}
@ -106,6 +131,15 @@ class NodeVaultService(private val services: ServiceHub) : SingletonSerializeAsT
return currentVault
}
override fun addNoteToTransaction(txnId: SecureHash, noteText: String) {
mutex.locked {
transactionNotes.getOrPut(key = txnId, defaultValue = {
setOf(noteText)
}).plus(noteText)
}
}
private fun makeUpdate(tx: WireTransaction, netDelta: Vault.Update, ourKeys: Set<PublicKey>): Vault.Update {
val ourNewStates = tx.outputs.
filter { isRelevant(it.data, ourKeys) }.

View File

@ -1,15 +1,21 @@
package com.r3corda.node.services
import com.r3corda.contracts.asset.Cash
import com.r3corda.contracts.testing.fillWithSomeTestCash
import com.r3corda.core.contracts.DOLLARS
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.VaultService
import com.r3corda.core.transactions.SignedTransaction
import com.r3corda.core.utilities.DUMMY_NOTARY
import com.r3corda.core.utilities.LogHelper
import com.r3corda.node.services.schema.NodeSchemaService
import com.r3corda.node.services.vault.NodeVaultService
import com.r3corda.node.utilities.configureDatabase
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.makeTestDataSourceProperties
import org.assertj.core.api.Assertions.assertThat
@ -19,6 +25,7 @@ import org.junit.Before
import org.junit.Test
import java.io.Closeable
import java.util.*
import kotlin.test.assertEquals
class NodeVaultServiceTest {
lateinit var dataSource: Closeable
@ -75,4 +82,36 @@ class NodeVaultServiceTest {
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, "Sample Note 1")
assertEquals(1, services.vaultService.currentVault.transactionNotes.toList().size)
}
}
}