Removed the StorageService and puts its components directly into the service hub

This commit is contained in:
Shams Asari 2017-06-29 11:13:40 +01:00
parent ecc96b29b0
commit a08f701dc5
45 changed files with 240 additions and 316 deletions

View File

@ -12,12 +12,12 @@ import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.StateMachineRunId
import net.corda.core.getOrThrow
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.messaging.startFlow
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.transactions.SignedTransaction

View File

@ -7,9 +7,9 @@ import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineInfo
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.seconds
import net.corda.core.transactions.SignedTransaction

View File

@ -79,8 +79,7 @@ interface ContractState {
* so that they receive the updated state, and don't end up in a situation where they can no longer use a state
* they possess, since someone consumed that state during the notary change process.
*
* The participants list should normally be derived from the contents of the state. E.g. for [Cash] the participants
* list should just contain the owner.
* The participants list should normally be derived from the contents of the state.
*/
val participants: List<AbstractParty>
}
@ -126,7 +125,7 @@ infix fun <T : ContractState> T.withNotary(newNotary: Party) = TransactionState(
* Definition for an issued product, which can be cash, a cash-like thing, assets, or generally anything else that's
* quantifiable with integer quantities.
*
* @param P the type of product underlying the definition, for example [Currency].
* @param P the type of product underlying the definition, for example [java.util.Currency].
*/
@CordaSerializable
data class Issued<out P : Any>(val issuer: PartyAndReference, val product: P) {
@ -159,8 +158,8 @@ interface Scheduled {
}
/**
* Represents a contract state (unconsumed output) of type [LinearState] and a point in time that a lifecycle event is expected to take place
* for that contract state.
* Represents a contract state (unconsumed output) of type [LinearState] and a point in time that a lifecycle event is
* expected to take place for that contract state.
*
* This is effectively the input to a scheduler, which wakes up at that point in time and asks the contract state what
* lifecycle processing needs to take place. e.g. a fixing or a late payment etc.
@ -168,10 +167,11 @@ interface Scheduled {
data class ScheduledStateRef(val ref: StateRef, override val scheduledAt: Instant) : Scheduled
/**
* This class represents the lifecycle activity that a contract state of type [LinearState] would like to perform at a given point in time.
* e.g. run a fixing flow.
* This class represents the lifecycle activity that a contract state of type [LinearState] would like to perform at a
* given point in time. e.g. run a fixing flow.
*
* Note the use of [FlowLogicRef] to represent a safe way to transport a [FlowLogic] out of the contract sandbox.
* Note the use of [FlowLogicRef] to represent a safe way to transport a [net.corda.core.flows.FlowLogic] out of the
* contract sandbox.
*
* Currently we support only flow based activities as we expect there to be a transaction generated off the back of
* the activity, otherwise we have to start tracking secondary state on the platform of which scheduled activities
@ -383,9 +383,9 @@ class TimeWindow private constructor(
// DOCSTART 5
/**
* Implemented by a program that implements business logic on the shared ledger. All participants run this code for
* every [LedgerTransaction] they see on the network, for every input and output state. All contracts must accept the
* transaction for it to be accepted: failure of any aborts the entire thing. The time is taken from a trusted
* time-window attached to the transaction itself i.e. it is NOT necessarily the current time.
* every [net.corda.core.transactions.LedgerTransaction] they see on the network, for every input and output state. All
* contracts must accept the transaction for it to be accepted: failure of any aborts the entire thing. The time is taken
* from a trusted time-window attached to the transaction itself i.e. it is NOT necessarily the current time.
*
* TODO: Contract serialization is likely to change, so the annotation is likely temporary.
*/
@ -461,9 +461,8 @@ interface Attachment : NamedByHash {
abstract class AbstractAttachment(dataLoader: () -> ByteArray) : Attachment {
companion object {
fun SerializeAsTokenContext.attachmentDataLoader(id: SecureHash): () -> ByteArray {
val storage = serviceHub.storageService.attachments
return {
val a = storage.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
val a = serviceHub.attachments.openAttachment(id) ?: throw MissingAttachmentsException(listOf(id))
(a as? AbstractAttachment)?.attachmentData ?: a.open().use { it.readBytes() }
}
}

View File

@ -13,7 +13,6 @@ import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.Party
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
@ -48,6 +47,9 @@ sealed class StateMachineUpdate {
data class Removed(override val id: StateMachineRunId, val result: ErrorOr<*>) : StateMachineUpdate()
}
@CordaSerializable
data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRunId, val transactionId: SecureHash)
/**
* RPC operations that the node exposes to clients using the Java client library. These can be called from
* client apps and are implemented by the node in the [net.corda.node.internal.CordaRPCOpsImpl] class.

View File

@ -17,8 +17,8 @@ import java.time.Clock
*/
interface ServicesForResolution {
val identityService: IdentityService
val storageService: AttachmentsStorageService
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage
/**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
*
@ -40,7 +40,13 @@ interface ServiceHub : ServicesForResolution {
val vaultService: VaultService
val vaultQueryService: VaultQueryService
val keyManagementService: KeyManagementService
override val storageService: StorageService
/**
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
* The signatures aren't technically needed after that point, but we keep them around so that we can relay
* the transaction data to other nodes that need it.
*/
val validatedTransactions: ReadOnlyTransactionStorage
val networkMapCache: NetworkMapCache
val transactionVerifierService: TransactionVerifierService
val clock: Clock
@ -77,7 +83,7 @@ interface ServiceHub : ServicesForResolution {
*/
@Throws(TransactionResolutionException::class)
override fun loadState(stateRef: StateRef): TransactionState<*> {
val definingTx = storageService.validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
val definingTx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return definingTx.tx.outputs[stateRef.index]
}
@ -87,7 +93,7 @@ interface ServiceHub : ServicesForResolution {
* @throws IllegalProtocolLogicException or IllegalArgumentException if there are problems with the [logicType] or [args].
*/
fun <T : ContractState> toStateAndRef(ref: StateRef): StateAndRef<T> {
val definingTx = storageService.validatedTransactions.getTransaction(ref.txhash) ?: throw TransactionResolutionException(ref.txhash)
val definingTx = validatedTransactions.getTransaction(ref.txhash) ?: throw TransactionResolutionException(ref.txhash)
return definingTx.tx.outRef<T>(ref.index)
}

View File

@ -22,12 +22,10 @@ import net.corda.core.transactions.LedgerTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.transactions.WireTransaction
import net.corda.flows.AnonymisedIdentity
import org.bouncycastle.cert.X509CertificateHolder
import rx.Observable
import rx.subjects.PublishSubject
import java.io.InputStream
import java.security.PublicKey
import java.security.cert.CertPath
import java.security.cert.X509Certificate
import java.time.Instant
import java.util.*
@ -528,44 +526,6 @@ interface FileUploader {
fun accepts(type: String): Boolean
}
interface AttachmentsStorageService {
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage
val attachmentsClassLoaderEnabled: Boolean
}
/**
* A sketch of an interface to a simple key/value storage system. Intended for persistence of simple blobs like
* transactions, serialised flow state machines and so on. Again, this isn't intended to imply lack of SQL or
* anything like that, this interface is only big enough to support the prototyping work.
*/
interface StorageService : AttachmentsStorageService {
/**
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
* The signatures aren't technically needed after that point, but we keep them around so that we can relay
* the transaction data to other nodes that need it.
*/
val validatedTransactions: ReadOnlyTransactionStorage
@Suppress("DEPRECATION")
@Deprecated("This service will be removed in a future milestone")
val uploaders: List<FileUploader>
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage
}
/**
* Storage service, with extensions to allow validated transactions to be added to. For use only within [ServiceHub].
*/
interface TxWritableStorageService : StorageService {
/**
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
* The signatures aren't technically needed after that point, but we keep them around so that we can relay
* the transaction data to other nodes that need it.
*/
override val validatedTransactions: TransactionStorage
}
/**
* Provides verification service. The implementation may be a simple in-memory verify() call or perhaps an IPC/RPC.
*/

View File

@ -1,19 +0,0 @@
package net.corda.core.node.services
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.DataFeed
import net.corda.core.serialization.CordaSerializable
import rx.Observable
@CordaSerializable
data class StateMachineTransactionMapping(val stateMachineRunId: StateMachineRunId, val transactionId: SecureHash)
/**
* This is the interface to storage storing state machine -> recorded tx mappings. Any time a transaction is recorded
* during a flow run [addMapping] should be called.
*/
interface StateMachineRecordedTransactionMappingStorage {
fun addMapping(stateMachineRunId: StateMachineRunId, transactionId: SecureHash)
fun track(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping>
}

View File

@ -317,6 +317,9 @@ class MissingAttachmentsException(val ids: List<SecureHash>) : Exception()
/** A serialisation engine that knows how to deserialise code inside a sandbox */
@ThreadSafe
object WireTransactionSerializer : Serializer<WireTransaction>() {
@VisibleForTesting
internal val attachmentsClassLoaderEnabled = "attachments.class.loader.enabled"
override fun write(kryo: Kryo, output: Output, obj: WireTransaction) {
kryo.writeClassAndObject(output, obj.inputs)
kryo.writeClassAndObject(output, obj.attachments)
@ -329,12 +332,12 @@ object WireTransactionSerializer : Serializer<WireTransaction>() {
}
private fun attachmentsClassLoader(kryo: Kryo, attachmentHashes: List<SecureHash>): ClassLoader? {
kryo.context[attachmentsClassLoaderEnabled] as? Boolean ?: false || return null
val serializationContext = kryo.serializationContext() ?: return null // Some tests don't set one.
serializationContext.serviceHub.storageService.attachmentsClassLoaderEnabled || return null
val missing = ArrayList<SecureHash>()
val attachments = ArrayList<Attachment>()
attachmentHashes.forEach { id ->
serializationContext.serviceHub.storageService.attachments.openAttachment(id)?.let { attachments += it } ?: run { missing += id }
serializationContext.serviceHub.attachments.openAttachment(id)?.let { attachments += it } ?: run { missing += id }
}
missing.isNotEmpty() && throw MissingAttachmentsException(missing)
return AttachmentsClassLoader(attachments)
@ -635,7 +638,7 @@ object X500NameSerializer : Serializer<X500Name>() {
*/
@ThreadSafe
object CertPathSerializer : Serializer<CertPath>() {
val factory = CertificateFactory.getInstance("X.509")
val factory: CertificateFactory = CertificateFactory.getInstance("X.509")
override fun read(kryo: Kryo, input: Input, type: Class<CertPath>): CertPath {
return factory.generateCertPath(input)
}
@ -646,7 +649,7 @@ object CertPathSerializer : Serializer<CertPath>() {
}
/**
* For serialising an [CX509CertificateHolder] in an X.500 standard format.
* For serialising an [X509CertificateHolder] in an X.500 standard format.
*/
@ThreadSafe
object X509CertificateSerializer : Serializer<X509CertificateHolder>() {

View File

@ -73,7 +73,7 @@ class WireTransaction(
fun toLedgerTransaction(services: ServicesForResolution): LedgerTransaction {
return toLedgerTransaction(
resolveIdentity = { services.identityService.partyFromKey(it) },
resolveAttachment = { services.storageService.attachments.openAttachment(it) },
resolveAttachment = { services.attachments.openAttachment(it) },
resolveStateRef = { services.loadState(it) }
)
}

View File

@ -18,13 +18,13 @@ import net.corda.core.serialization.SerializeAsTokenContext
class FetchAttachmentsFlow(requests: Set<SecureHash>,
otherSide: Party) : FetchDataFlow<Attachment, ByteArray>(requests, otherSide) {
override fun load(txid: SecureHash): Attachment? = serviceHub.storageService.attachments.openAttachment(txid)
override fun load(txid: SecureHash): Attachment? = serviceHub.attachments.openAttachment(txid)
override fun convert(wire: ByteArray): Attachment = FetchedAttachment({ wire })
override fun maybeWriteToDisk(downloaded: List<Attachment>) {
for (attachment in downloaded) {
serviceHub.storageService.attachments.importAttachment(attachment.open())
serviceHub.attachments.importAttachment(attachment.open())
}
}

View File

@ -17,7 +17,5 @@ import net.corda.core.transactions.SignedTransaction
class FetchTransactionsFlow(requests: Set<SecureHash>, otherSide: Party) :
FetchDataFlow<SignedTransaction, SignedTransaction>(requests, otherSide) {
override fun load(txid: SecureHash): SignedTransaction? {
return serviceHub.storageService.validatedTransactions.getTransaction(txid)
}
override fun load(txid: SecureHash): SignedTransaction? = serviceHub.validatedTransactions.getTransaction(txid)
}

View File

@ -4,10 +4,8 @@ import net.corda.core.contracts.*
import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import java.security.PublicKey
/**
* A flow to be used for changing a state's Notary. This is required since all input states to a transaction
@ -55,7 +53,7 @@ class NotaryChangeFlow<out T : ContractState>(
private fun resolveEncumbrances(tx: TransactionBuilder): Iterable<AbstractParty> {
val stateRef = originalState.ref
val txId = stateRef.txhash
val issuingTx = serviceHub.storageService.validatedTransactions.getTransaction(txId)
val issuingTx = serviceHub.validatedTransactions.getTransaction(txId)
?: throw StateReplacementException("Transaction $txId not found")
val outputs = issuingTx.tx.outputs

View File

@ -193,7 +193,7 @@ class ResolveTransactionsFlow(private val txHashes: Set<SecureHash>,
private fun fetchMissingAttachments(downloads: List<WireTransaction>) {
// TODO: This could be done in parallel with other fetches for extra speed.
val missingAttachments = downloads.flatMap { wtx ->
wtx.attachments.filter { serviceHub.storageService.attachments.openAttachment(it) == null }
wtx.attachments.filter { serviceHub.attachments.openAttachment(it) == null }
}
if (missingAttachments.isNotEmpty())
subFlow(FetchAttachmentsFlow(missingAttachments.toSet(), otherSide))

View File

@ -64,8 +64,8 @@ class ContractUpgradeFlowTest {
a.services.startFlow(FinalityFlow(stx, setOf(a.info.legalIdentity, b.info.legalIdentity)))
mockNet.runNetwork()
val atx = a.database.transaction { a.services.storageService.validatedTransactions.getTransaction(stx.id) }
val btx = b.database.transaction { b.services.storageService.validatedTransactions.getTransaction(stx.id) }
val atx = a.database.transaction { a.services.validatedTransactions.getTransaction(stx.id) }
val btx = b.database.transaction { b.services.validatedTransactions.getTransaction(stx.id) }
requireNotNull(atx)
requireNotNull(btx)
@ -85,13 +85,13 @@ class ContractUpgradeFlowTest {
fun check(node: MockNetwork.MockNode) {
val nodeStx = node.database.transaction {
node.services.storageService.validatedTransactions.getTransaction(result.ref.txhash)
node.services.validatedTransactions.getTransaction(result.ref.txhash)
}
requireNotNull(nodeStx)
// Verify inputs.
val input = node.database.transaction {
node.services.storageService.validatedTransactions.getTransaction(nodeStx!!.tx.inputs.single().txhash)
node.services.validatedTransactions.getTransaction(nodeStx!!.tx.inputs.single().txhash)
}
requireNotNull(input)
assertTrue(input!!.tx.outputs.single().data is DummyContract.State)
@ -132,8 +132,8 @@ class ContractUpgradeFlowTest {
mockNet.runNetwork()
handle.returnValue.getOrThrow()
val atx = a.database.transaction { a.services.storageService.validatedTransactions.getTransaction(stx.id) }
val btx = b.database.transaction { b.services.storageService.validatedTransactions.getTransaction(stx.id) }
val atx = a.database.transaction { a.services.validatedTransactions.getTransaction(stx.id) }
val btx = b.database.transaction { b.services.validatedTransactions.getTransaction(stx.id) }
requireNotNull(atx)
requireNotNull(btx)
@ -156,11 +156,11 @@ class ContractUpgradeFlowTest {
val result = resultFuture.getOrThrow()
// Check results.
listOf(a, b).forEach {
val signedTX = a.database.transaction { a.services.storageService.validatedTransactions.getTransaction(result.ref.txhash) }
val signedTX = a.database.transaction { a.services.validatedTransactions.getTransaction(result.ref.txhash) }
requireNotNull(signedTX)
// Verify inputs.
val input = a.database.transaction { a.services.storageService.validatedTransactions.getTransaction(signedTX!!.tx.inputs.single().txhash) }
val input = a.database.transaction { a.services.validatedTransactions.getTransaction(signedTX!!.tx.inputs.single().txhash) }
requireNotNull(input)
assertTrue(input!!.tx.outputs.single().data is DummyContract.State)

View File

@ -58,8 +58,8 @@ class ResolveTransactionsFlowTest {
val results = future.getOrThrow()
assertEquals(listOf(stx1.id, stx2.id), results.map { it.id })
b.database.transaction {
assertEquals(stx1, b.storage.validatedTransactions.getTransaction(stx1.id))
assertEquals(stx2, b.storage.validatedTransactions.getTransaction(stx2.id))
assertEquals(stx1, b.services.validatedTransactions.getTransaction(stx1.id))
assertEquals(stx2, b.services.validatedTransactions.getTransaction(stx2.id))
}
}
// DOCEND 1
@ -81,9 +81,9 @@ class ResolveTransactionsFlowTest {
mockNet.runNetwork()
future.getOrThrow()
b.database.transaction {
assertEquals(stx1, b.storage.validatedTransactions.getTransaction(stx1.id))
assertEquals(stx1, b.services.validatedTransactions.getTransaction(stx1.id))
// But stx2 wasn't inserted, just stx1.
assertNull(b.storage.validatedTransactions.getTransaction(stx2.id))
assertNull(b.services.validatedTransactions.getTransaction(stx2.id))
}
}
@ -148,7 +148,7 @@ class ResolveTransactionsFlowTest {
}
// TODO: this operation should not require an explicit transaction
val id = a.database.transaction {
a.services.storageService.attachments.importAttachment(makeJar())
a.services.attachments.importAttachment(makeJar())
}
val stx2 = makeTransactions(withAttachment = id).second
val p = ResolveTransactionsFlow(stx2, a.info.legalIdentity)
@ -158,7 +158,7 @@ class ResolveTransactionsFlowTest {
// TODO: this operation should not require an explicit transaction
b.database.transaction {
assertNotNull(b.services.storageService.attachments.openAttachment(id))
assertNotNull(b.services.attachments.openAttachment(id))
}
}

View File

@ -8,7 +8,6 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.StorageService
import net.corda.core.serialization.*
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.DUMMY_NOTARY
@ -22,7 +21,6 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.net.URL
import java.net.URLClassLoader
import java.security.PublicKey
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import kotlin.test.assertEquals
@ -42,11 +40,9 @@ class AttachmentClassLoaderTests {
val ISOLATED_CONTRACTS_JAR_PATH: URL = AttachmentClassLoaderTests::class.java.getResource("isolated.jar")
private fun <T> Kryo.withAttachmentStorage(attachmentStorage: AttachmentStorage, block: () -> T) = run {
context.put(WireTransactionSerializer.attachmentsClassLoaderEnabled, true)
val serviceHub = mock<ServiceHub>()
val storageService = mock<StorageService>()
whenever(serviceHub.storageService).thenReturn(storageService)
whenever(storageService.attachmentsClassLoaderEnabled).thenReturn(true)
whenever(storageService.attachments).thenReturn(attachmentStorage)
whenever(serviceHub.attachments).thenReturn(attachmentStorage)
withSerializationContext(SerializeAsTokenContext(serviceHub) {}, block)
}
}

View File

@ -42,9 +42,12 @@ private fun createAttachmentData(content: String) = ByteArrayOutputStream().appl
private fun Attachment.extractContent() = ByteArrayOutputStream().apply { extractFile("content", this) }.toString(UTF_8.name())
private fun MockNetwork.MockNode.attachments() = services.storageService.attachments as NodeAttachmentService
private fun MockNetwork.MockNode.saveAttachment(content: String) = database.transaction { attachments().importAttachment(createAttachmentData(content).inputStream()) }
private fun MockNetwork.MockNode.hackAttachment(attachmentId: SecureHash, content: String) = database.transaction { attachments().updateAttachment(attachmentId, createAttachmentData(content)) }
private fun MockNetwork.MockNode.saveAttachment(content: String) = database.transaction {
attachments.importAttachment(createAttachmentData(content).inputStream())
}
private fun MockNetwork.MockNode.hackAttachment(attachmentId: SecureHash, content: String) = database.transaction {
attachments.updateAttachment(attachmentId, createAttachmentData(content))
}
/**
* @see NodeAttachmentService.importAttachment
@ -122,7 +125,7 @@ class AttachmentSerializationTest {
private class OpenAttachmentLogic(server: MockNetwork.MockNode, private val attachmentId: SecureHash) : ClientLogic(server) {
@Suspendable
override fun getAttachmentContent(): String {
val localAttachment = serviceHub.storageService.attachments.openAttachment(attachmentId)!!
val localAttachment = serviceHub.attachments.openAttachment(attachmentId)!!
communicate()
return localAttachment.extractContent()
}
@ -153,7 +156,7 @@ class AttachmentSerializationTest {
override fun create(config: NodeConfiguration, network: MockNetwork, networkMapAddr: SingleMessageRecipient?, advertisedServices: Set<ServiceInfo>, id: Int, overrideServices: Map<ServiceInfo, KeyPair>?, entropyRoot: BigInteger): MockNetwork.MockNode {
return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) {
override fun startMessagingService(rpcOps: RPCOps) {
attachments().checkAttachmentsOnLoad = checkAttachmentsOnLoad
attachments.checkAttachmentsOnLoad = checkAttachmentsOnLoad
super.startMessagingService(rpcOps)
}
}
@ -180,7 +183,7 @@ class AttachmentSerializationTest {
@Test
fun `only the hash of a regular attachment should be saved in checkpoint`() {
val attachmentId = client.saveAttachment("genuine")
client.attachments().checkAttachmentsOnLoad = false // Cached by AttachmentImpl.
client.attachments.checkAttachmentsOnLoad = false // Cached by AttachmentImpl.
launchFlow(OpenAttachmentLogic(server, attachmentId), 1)
client.hackAttachment(attachmentId, "hacked")
assertEquals("hacked", rebootClientAndGetAttachmentContent(false)) // Pass in false to allow non-genuine data to be loaded.

View File

@ -7,10 +7,12 @@ various services the node provides. The services offered by the ``ServiceHub`` a
* Provides information on other nodes on the network (e.g. notaries…)
* ``ServiceHub.identityService``
* Allows you to resolve anonymous identities to well-known identities if you have the required certificates
* ``ServiceHub.attachments``
* Gives you access to the node's attachments
* ``ServiceHub.validatedTransactions``
* Gives you access to the transactions stored in the node
* ``ServiceHub.vaultService``
* Stores the nodes current and historic states
* ``ServiceHub.storageService``
* Stores additional information such as transactions and attachments
* ``ServiceHub.keyManagementService``
* Manages signing transactions and generating fresh public keys
* ``ServiceHub.myInfo``

View File

@ -6,6 +6,7 @@ from the previous milestone release.
UNRELEASED
----------
* Changes in ``NodeInfo``:
* ``PhysicalLocation`` was renamed to ``WorldMapLocation`` to emphasise that it doesn't need to map to a truly physical
@ -13,8 +14,12 @@ UNRELEASED
* Slots for multiple IP addresses and ``legalIdentitiesAndCert``s were introduced. Addresses are no longer of type
``SingleMessageRecipient``, but of ``HostAndPort``.
* ``ServiceHub.storageService`` has been removed. ``attachments`` and ``validatedTransactions`` are now direct members of
``ServiceHub``.
Milestone 13
----------
------------
Special thank you to `Frederic Dalibard <https://github.com/FredericDalibard>`_, for his contribution which adds
support for more currencies to the DemoBench and Explorer tools.

View File

@ -220,7 +220,7 @@ class CommercialPaperTestsGeneric {
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
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 })
@ -240,7 +240,7 @@ class CommercialPaperTestsGeneric {
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
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 })
@ -251,8 +251,8 @@ class CommercialPaperTestsGeneric {
}
// Propagate the cash transactions to each side.
aliceServices.recordTransactions(bigCorpVault.states.map { bigCorpServices.storageService.validatedTransactions.getTransaction(it.ref.txhash)!! })
bigCorpServices.recordTransactions(alicesVault.states.map { aliceServices.storageService.validatedTransactions.getTransaction(it.ref.txhash)!! })
aliceServices.recordTransactions(bigCorpVault.states.map { bigCorpServices.validatedTransactions.getTransaction(it.ref.txhash)!! })
bigCorpServices.recordTransactions(alicesVault.states.map { aliceServices.validatedTransactions.getTransaction(it.ref.txhash)!! })
// BigCorp™ issues $10,000 of commercial paper, to mature in 30 days, owned initially by itself.
val faceValue = 10000.DOLLARS `issued by` DUMMY_CASH_ISSUER

View File

@ -63,7 +63,7 @@ class CashTests {
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
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 })

View File

@ -45,7 +45,10 @@ import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.network.NetworkMapService.RegistrationResponse
import net.corda.node.services.network.NodeRegistration
import net.corda.node.services.network.PersistentNetworkMapService
import net.corda.node.services.persistence.*
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.schema.HibernateObserver
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.FlowStateMachineImpl
@ -70,7 +73,6 @@ import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Modifier.*
import java.net.JarURLConnection
import java.net.URI
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
import java.nio.file.Paths
import java.security.KeyPair
@ -120,10 +122,14 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
protected val partyKeys = mutableSetOf<KeyPair>()
val services = object : ServiceHubInternal() {
val services = object : ServiceHubInternal {
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
override val uploaders: List<FileUploader> get() = this@AbstractNode.uploaders
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage
get() = this@AbstractNode.transactionMappings
override val validatedTransactions: TransactionStorage get() = this@AbstractNode.transactions
override val networkService: MessagingService get() = network
override val networkMapCache: NetworkMapCacheInternal get() = netMapCache
override val storageService: TxWritableStorageService get() = storage
override val vaultService: VaultService get() = vault
override val vaultQueryService: VaultQueryService get() = vaultQuery
override val keyManagementService: KeyManagementService get() = keyManagement
@ -157,7 +163,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
database.transaction {
recordTransactionsInternal(storage, txs)
super.recordTransactions(txs)
}
}
}
@ -167,9 +173,12 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
}
lateinit var info: NodeInfo
lateinit var storage: TxWritableStorageService
lateinit var checkpointStorage: CheckpointStorage
lateinit var smm: StateMachineManager
lateinit var attachments: NodeAttachmentService
lateinit var transactions: TransactionStorage
lateinit var transactionMappings: StateMachineRecordedTransactionMappingStorage
lateinit var uploaders: List<FileUploader>
lateinit var vault: VaultService
lateinit var vaultQuery: VaultQueryService
lateinit var keyManagement: KeyManagementService
@ -469,9 +478,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
*/
private fun makeServices(keyStoreWrapper: KeyStoreWrapper): MutableList<Any> {
val keyStore = keyStoreWrapper.keyStore
val storageServices = initialiseStorageService(configuration.baseDirectory)
storage = storageServices.first
checkpointStorage = storageServices.second
attachments = createAttachmentStorage()
transactions = createTransactionStorage()
transactionMappings = DBTransactionMappingStorage()
checkpointStorage = DBCheckpointStorage()
netMapCache = InMemoryNetworkMapCache(services)
network = makeMessagingService()
schemas = makeSchemaService()
@ -490,11 +500,13 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
keyManagement = makeKeyManagementService(identity)
scheduler = NodeSchedulerService(services, database, unfinishedSchedules = busyNodeLatch)
val tokenizableServices = mutableListOf(storage, network, vault, vaultQuery, keyManagement, identity, platformClock, scheduler)
val tokenizableServices = mutableListOf(attachments, network, vault, vaultQuery, keyManagement, identity, platformClock, scheduler)
makeAdvertisedServices(tokenizableServices)
return tokenizableServices
}
protected open fun createTransactionStorage(): TransactionStorage = DBTransactionStorage()
private fun scanCordapps(): ScanResult? {
val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package")
val paths = if (scanPackage != null) {
@ -548,9 +560,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
}
private fun initUploaders() {
val uploaders: List<FileUploader> = listOf(storage.attachments as NodeAttachmentService) +
cordappServices.values.filterIsInstance(AcceptsFileUpload::class.java)
(storage as StorageServiceImpl).initUploaders(uploaders)
uploaders = listOf(attachments) + cordappServices.values.filterIsInstance(AcceptsFileUpload::class.java)
}
private fun makeVaultObservers() {
@ -625,7 +635,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
* Run any tasks that are needed to ensure the node is in a correct state before running start().
*/
open fun setup(): AbstractNode {
createNodeDir()
configuration.baseDirectory.createDirectories()
return this
}
@ -761,22 +771,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
protected abstract fun startMessagingService(rpcOps: RPCOps)
protected open fun initialiseStorageService(dir: Path): Pair<TxWritableStorageService, CheckpointStorage> {
val attachments = makeAttachmentStorage(dir)
val checkpointStorage = DBCheckpointStorage()
val transactionStorage = DBTransactionStorage()
val stateMachineTransactionMappingStorage = DBTransactionMappingStorage()
return Pair(
constructStorageService(attachments, transactionStorage, stateMachineTransactionMappingStorage),
checkpointStorage
)
}
protected open fun constructStorageService(attachments: AttachmentStorage,
transactionStorage: TransactionStorage,
stateMachineRecordedTransactionMappingStorage: StateMachineRecordedTransactionMappingStorage) =
StorageServiceImpl(attachments, transactionStorage, stateMachineRecordedTransactionMappingStorage)
protected fun obtainLegalIdentity(): PartyAndCertificate = identityKeyPair.first
protected fun obtainLegalIdentityKey(): KeyPair = identityKeyPair.second
private val identityKeyPair by lazy { obtainKeyPair("identity", configuration.myLegalName) }
@ -846,18 +840,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
protected open fun generateKeyPair() = cryptoGenerateKeyPair()
protected fun makeAttachmentStorage(dir: Path): AttachmentStorage {
val attachmentsDir = dir / "attachments"
try {
attachmentsDir.createDirectory()
} catch (e: FileAlreadyExistsException) {
}
private fun createAttachmentStorage(): NodeAttachmentService {
val attachmentsDir = (configuration.baseDirectory / "attachments").createDirectories()
return NodeAttachmentService(attachmentsDir, configuration.dataSourceProperties, services.monitoringService.metrics)
}
protected fun createNodeDir() {
configuration.baseDirectory.createDirectories()
}
}
private class KeyStoreWrapper(val keyStore: KeyStore, val storePath: Path, private val storePassword: String) {

View File

@ -12,7 +12,6 @@ import net.corda.core.identity.Party
import net.corda.core.messaging.*
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.node.services.Vault
import net.corda.core.node.services.vault.PageSpecification
import net.corda.core.node.services.vault.QueryCriteria
@ -76,7 +75,7 @@ class CordaRPCOpsImpl(
override fun verifiedTransactionsFeed(): DataFeed<List<SignedTransaction>, SignedTransaction> {
return database.transaction {
services.storageService.validatedTransactions.track()
services.validatedTransactions.track()
}
}
@ -92,7 +91,7 @@ class CordaRPCOpsImpl(
override fun stateMachineRecordedTransactionMappingFeed(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping> {
return database.transaction {
services.storageService.stateMachineRecordedTransactionMapping.track()
services.stateMachineRecordedTransactionMapping.track()
}
}
@ -143,21 +142,21 @@ class CordaRPCOpsImpl(
override fun attachmentExists(id: SecureHash): Boolean {
// TODO: this operation should not require an explicit transaction
return database.transaction {
services.storageService.attachments.openAttachment(id) != null
services.attachments.openAttachment(id) != null
}
}
override fun openAttachment(id: SecureHash): InputStream {
// TODO: this operation should not require an explicit transaction
return database.transaction {
services.storageService.attachments.openAttachment(id)!!.open()
services.attachments.openAttachment(id)!!.open()
}
}
override fun uploadAttachment(jar: InputStream): SecureHash {
// TODO: this operation should not require an explicit transaction
return database.transaction {
services.storageService.attachments.importAttachment(jar)
services.attachments.importAttachment(jar)
}
}
@ -166,7 +165,7 @@ class CordaRPCOpsImpl(
override fun currentNodeTime(): Instant = Instant.now(services.clock)
@Suppress("OverridingDeprecatedMember", "DEPRECATION")
override fun uploadFile(dataType: String, name: String?, file: InputStream): String {
val acceptor = services.storageService.uploaders.firstOrNull { it.accepts(dataType) }
val acceptor = services.uploaders.firstOrNull { it.accepts(dataType) }
return database.transaction {
acceptor?.upload(file) ?: throw RuntimeException("Cannot find file upload acceptor for $dataType")
}

View File

@ -27,14 +27,14 @@ import net.corda.flows.*
*/
class FetchTransactionsHandler(otherParty: Party) : FetchDataHandler<SignedTransaction>(otherParty) {
override fun getData(id: SecureHash): SignedTransaction? {
return serviceHub.storageService.validatedTransactions.getTransaction(id)
return serviceHub.validatedTransactions.getTransaction(id)
}
}
// TODO: Use Artemis message streaming support here, called "large messages". This avoids the need to buffer.
class FetchAttachmentsHandler(otherParty: Party) : FetchDataHandler<ByteArray>(otherParty) {
override fun getData(id: SecureHash): ByteArray? {
return serviceHub.storageService.attachments.openAttachment(id)?.open()?.readBytes()
return serviceHub.attachments.openAttachment(id)?.open()?.readBytes()
}
}

View File

@ -1,16 +1,20 @@
package net.corda.node.services.api
import com.google.common.annotations.VisibleForTesting
import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.FlowStateMachine
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.node.NodeInfo
import net.corda.core.node.PluginServiceHub
import net.corda.core.node.services.FileUploader
import net.corda.core.node.services.NetworkMapCache
import net.corda.core.node.services.TxWritableStorageService
import net.corda.core.node.services.TransactionStorage
import net.corda.core.serialization.CordaSerializable
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.loggerFor
@ -58,34 +62,38 @@ sealed class NetworkCacheError : Exception() {
class DeregistrationFailed : NetworkCacheError()
}
abstract class ServiceHubInternal : PluginServiceHub {
interface ServiceHubInternal : PluginServiceHub {
companion object {
private val log = loggerFor<ServiceHubInternal>()
}
abstract val monitoringService: MonitoringService
abstract val schemaService: SchemaService
abstract override val networkMapCache: NetworkMapCacheInternal
abstract val schedulerService: SchedulerService
abstract val auditService: AuditService
abstract val rpcFlows: List<Class<out FlowLogic<*>>>
abstract val networkService: MessagingService
abstract val database: Database
abstract val configuration: NodeConfiguration
/**
* Given a list of [SignedTransaction]s, writes them to the given storage for validated transactions and then
* sends them to the vault for further processing. This is intended for implementations to call from
* [recordTransactions].
*
* @param txs The transactions to record.
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct.
* The signatures aren't technically needed after that point, but we keep them around so that we can relay
* the transaction data to other nodes that need it.
*/
internal fun recordTransactionsInternal(writableStorageService: TxWritableStorageService, txs: Iterable<SignedTransaction>) {
override val validatedTransactions: TransactionStorage
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage
val monitoringService: MonitoringService
val schemaService: SchemaService
override val networkMapCache: NetworkMapCacheInternal
val schedulerService: SchedulerService
val auditService: AuditService
val rpcFlows: List<Class<out FlowLogic<*>>>
val networkService: MessagingService
val database: Database
val configuration: NodeConfiguration
@Suppress("DEPRECATION")
@Deprecated("This service will be removed in a future milestone")
val uploaders: List<FileUploader>
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
val recordedTransactions = txs.filter { writableStorageService.validatedTransactions.addTransaction(it) }
val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) }
if (stateMachineRunId != null) {
recordedTransactions.forEach {
storageService.stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id)
stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id)
}
} else {
log.warn("Transactions recorded from outside of a state machine")
@ -104,7 +112,7 @@ abstract class ServiceHubInternal : PluginServiceHub {
* Starts an already constructed flow. Note that you must be on the server thread to call this method.
* @param flowInitiator indicates who started the flow, see: [FlowInitiator].
*/
abstract fun <T> startFlow(logic: FlowLogic<T>, flowInitiator: FlowInitiator): FlowStateMachineImpl<T>
fun <T> startFlow(logic: FlowLogic<T>, flowInitiator: FlowInitiator): FlowStateMachineImpl<T>
/**
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the flow.
@ -124,5 +132,14 @@ abstract class ServiceHubInternal : PluginServiceHub {
return startFlow(logic, flowInitiator)
}
abstract fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>?
}
fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>?
}
/**
* This is the interface to storage storing state machine -> recorded tx mappings. Any time a transaction is recorded
* during a flow run [addMapping] should be called.
*/
interface StateMachineRecordedTransactionMappingStorage {
fun addMapping(stateMachineRunId: StateMachineRunId, transactionId: SecureHash)
fun track(): DataFeed<List<StateMachineTransactionMapping>, StateMachineTransactionMapping>
}

View File

@ -5,12 +5,11 @@ import net.corda.core.bufferUntilSubscribed
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.DataFeed
import net.corda.core.node.services.StateMachineRecordedTransactionMappingStorage
import net.corda.core.node.services.StateMachineTransactionMapping
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
import net.corda.node.utilities.*
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.statements.InsertStatement
import rx.Observable
import rx.subjects.PublishSubject
import javax.annotation.concurrent.ThreadSafe

View File

@ -5,6 +5,7 @@ import net.corda.core.bufferUntilSubscribed
import net.corda.core.crypto.SecureHash
import net.corda.core.messaging.DataFeed
import net.corda.core.node.services.TransactionStorage
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.node.utilities.*
import org.jetbrains.exposed.sql.ResultRow
@ -14,7 +15,7 @@ import rx.Observable
import rx.subjects.PublishSubject
import java.util.Collections.synchronizedMap
class DBTransactionStorage : TransactionStorage {
class DBTransactionStorage : TransactionStorage, SingletonSerializeAsToken() {
private object Table : JDBCHashedTable("${NODE_DATABASE_PREFIX}transactions") {
val txId = secureHash("tx_id")
val transaction = blob("transaction")
@ -59,7 +60,7 @@ class DBTransactionStorage : TransactionStorage {
}
}
val updatesPublisher = PublishSubject.create<SignedTransaction>().toSerialized()
private val updatesPublisher = PublishSubject.create<SignedTransaction>().toSerialized()
override val updates: Observable<SignedTransaction> = updatesPublisher.wrapWithDatabaseTransaction()
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {

View File

@ -5,9 +5,8 @@ import net.corda.core.bufferUntilSubscribed
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.DataFeed
import net.corda.core.node.services.StateMachineRecordedTransactionMappingStorage
import net.corda.core.node.services.StateMachineTransactionMapping
import rx.Observable
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
import rx.subjects.PublishSubject
import java.util.*
import javax.annotation.concurrent.ThreadSafe

View File

@ -14,10 +14,7 @@ import net.corda.core.div
import net.corda.core.extractZipFile
import net.corda.core.isDirectory
import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SerializationToken
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SerializeAsTokenContext
import net.corda.core.serialization.*
import net.corda.core.utilities.loggerFor
import net.corda.node.services.api.AcceptsFileUpload
import net.corda.node.services.database.RequeryConfiguration
@ -38,8 +35,11 @@ import javax.annotation.concurrent.ThreadSafe
* Stores attachments in H2 database.
*/
@ThreadSafe
class NodeAttachmentService(override var storePath: Path, dataSourceProperties: Properties, metrics: MetricRegistry) : AttachmentStorage, AcceptsFileUpload {
private val log = loggerFor<NodeAttachmentService>()
class NodeAttachmentService(override var storePath: Path, dataSourceProperties: Properties, metrics: MetricRegistry)
: AttachmentStorage, AcceptsFileUpload, SingletonSerializeAsToken() {
companion object {
private val log = loggerFor<NodeAttachmentService>()
}
val configuration = RequeryConfiguration(dataSourceProperties)
val session = configuration.sessionForModel(Models.PERSISTENCE)

View File

@ -1,18 +0,0 @@
package net.corda.node.services.persistence
import net.corda.core.node.services.*
import net.corda.core.serialization.SingletonSerializeAsToken
open class StorageServiceImpl(override val attachments: AttachmentStorage,
override val validatedTransactions: TransactionStorage,
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage)
: SingletonSerializeAsToken(), TxWritableStorageService {
override val attachmentsClassLoaderEnabled = false
lateinit override var uploaders: List<FileUploader>
fun initUploaders(uploadersList: List<FileUploader>) {
@Suppress("DEPRECATION")
uploaders = uploadersList
}
}

View File

@ -205,7 +205,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
override fun waitForLedgerCommit(hash: SecureHash, sessionFlow: FlowLogic<*>): SignedTransaction {
logger.debug { "waitForLedgerCommit($hash) ..." }
suspend(WaitForLedgerCommit(hash, sessionFlow.stateMachine as FlowStateMachineImpl<*>))
val stx = serviceHub.storageService.validatedTransactions.getTransaction(hash)
val stx = serviceHub.validatedTransactions.getTransaction(hash)
if (stx != null) {
logger.debug { "Transaction $hash committed to ledger" }
return stx

View File

@ -187,7 +187,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
private fun listenToLedgerTransactions() {
// Observe the stream of committed, validated transactions and resume fibers that are waiting for them.
serviceHub.storageService.validatedTransactions.updates.subscribe { stx ->
serviceHub.validatedTransactions.updates.subscribe { stx ->
val hash = stx.id
val fibers: Set<FlowStateMachineImpl<*>> = mutex.locked { fibersWaitingForLedgerCommit.removeAll(hash) }
if (fibers.isNotEmpty()) {
@ -268,7 +268,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
if (waitingForResponse != null) {
if (waitingForResponse is WaitForLedgerCommit) {
val stx = database.transaction {
serviceHub.storageService.validatedTransactions.getTransaction(waitingForResponse.hash)
serviceHub.validatedTransactions.getTransaction(waitingForResponse.hash)
}
if (stx != null) {
fiber.logger.info("Resuming fiber as tx ${waitingForResponse.hash} has committed")
@ -548,7 +548,7 @@ class StateMachineManager(val serviceHub: ServiceHubInternal,
private fun processWaitForCommitRequest(ioRequest: WaitForLedgerCommit) {
// Is it already committed?
val stx = database.transaction {
serviceHub.storageService.validatedTransactions.getTransaction(ioRequest.hash)
serviceHub.validatedTransactions.getTransaction(ioRequest.hash)
}
if (stx != null) {
resumeFiber(ioRequest.fiber)

View File

@ -72,10 +72,10 @@ public class VaultQueryJavaTests {
@Override
public void recordTransactions(@NotNull Iterable<SignedTransaction> txs) {
for (SignedTransaction stx : txs) {
getStorageService().getValidatedTransactions().addTransaction(stx);
getValidatedTransactions().addTransaction(stx);
}
Stream<WireTransaction> wtxn = StreamSupport.stream(txs.spliterator(), false).map(txn -> txn.getTx());
Stream<WireTransaction> wtxn = StreamSupport.stream(txs.spliterator(), false).map(SignedTransaction::getTx);
getVaultService().notifyAll(wtxn.collect(Collectors.toList()));
}
};

View File

@ -11,11 +11,10 @@ import net.corda.flows.FetchDataFlow
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.database.RequeryConfiguration
import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.persistence.schemas.requery.AttachmentEntity
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.node.MockNetwork
import net.corda.node.utilities.transaction
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.makeTestDataSourceProperties
import org.jetbrains.exposed.sql.Database
import org.junit.Before
@ -61,7 +60,7 @@ class AttachmentTests {
// Insert an attachment into node zero's store directly.
val id = n0.database.transaction {
n0.storage.attachments.importAttachment(ByteArrayInputStream(fakeAttachment()))
n0.attachments.importAttachment(ByteArrayInputStream(fakeAttachment()))
}
// Get node one to run a flow to fetch it and insert it.
@ -72,7 +71,7 @@ class AttachmentTests {
// Verify it was inserted into node one's store.
val attachment = n1.database.transaction {
n1.storage.attachments.openAttachment(id)!!
n1.attachments.openAttachment(id)!!
}
assertEquals(id, attachment.open().readBytes().sha256())
@ -108,7 +107,7 @@ class AttachmentTests {
return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) {
override fun start(): MockNetwork.MockNode {
super.start()
(storage.attachments as NodeAttachmentService).checkAttachmentsOnLoad = false
attachments.checkAttachmentsOnLoad = false
return this
}
}
@ -119,7 +118,7 @@ class AttachmentTests {
val attachment = fakeAttachment()
// Insert an attachment into node zero's store directly.
val id = n0.database.transaction {
n0.storage.attachments.importAttachment(ByteArrayInputStream(attachment))
n0.attachments.importAttachment(ByteArrayInputStream(attachment))
}
// Corrupt its store.
@ -130,7 +129,7 @@ class AttachmentTests {
corruptAttachment.attId = id
corruptAttachment.content = attachment
n0.database.transaction {
(n0.storage.attachments as NodeAttachmentService).session.update(corruptAttachment)
n0.attachments.session.update(corruptAttachment)
}

View File

@ -9,15 +9,21 @@ import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sign
import net.corda.core.flows.*
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.AnonymousParty
import net.corda.core.identity.Party
import net.corda.core.internal.FlowStateMachine
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.*
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.TransactionStorage
import net.corda.core.node.services.Vault
import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
@ -28,7 +34,6 @@ import net.corda.flows.TwoPartyTradeFlow.Seller
import net.corda.node.internal.AbstractNode
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.StorageServiceImpl
import net.corda.node.services.persistence.checkpoints
import net.corda.node.utilities.transaction
import net.corda.testing.*
@ -211,7 +216,7 @@ class TwoPartyTradeFlowTests {
assertThat(bobNode.checkpointStorage.checkpoints()).hasSize(1)
}
val storage = bobNode.storage.validatedTransactions
val storage = bobNode.services.validatedTransactions
val bobTransactionsBeforeCrash = bobNode.database.transaction {
(storage as DBTransactionStorage).transactions
}
@ -252,7 +257,9 @@ class TwoPartyTradeFlowTests {
}
bobNode.database.transaction {
val restoredBobTransactions = bobTransactionsBeforeCrash.filter { bobNode.storage.validatedTransactions.getTransaction(it.id) != null }
val restoredBobTransactions = bobTransactionsBeforeCrash.filter {
bobNode.services.validatedTransactions.getTransaction(it.id) != null
}
assertThat(restoredBobTransactions).containsAll(bobTransactionsBeforeCrash)
}
@ -276,13 +283,9 @@ class TwoPartyTradeFlowTests {
overrideServices: Map<ServiceInfo, KeyPair>?,
entropyRoot: BigInteger): MockNetwork.MockNode {
return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) {
// That constructs the storage service object in a customised way ...
override fun constructStorageService(
attachments: AttachmentStorage,
transactionStorage: TransactionStorage,
stateMachineRecordedTransactionMappingStorage: StateMachineRecordedTransactionMappingStorage
): StorageServiceImpl {
return StorageServiceImpl(attachments, RecordingTransactionStorage(database, transactionStorage), stateMachineRecordedTransactionMappingStorage)
// That constructs a recording tx storage
override fun createTransactionStorage(): TransactionStorage {
return RecordingTransactionStorage(database, super.createTransactionStorage())
}
}
}
@ -326,7 +329,7 @@ class TwoPartyTradeFlowTests {
mockNet.runNetwork()
run {
val records = (bobNode.storage.validatedTransactions as RecordingTransactionStorage).records
val records = (bobNode.services.validatedTransactions as RecordingTransactionStorage).records
// Check Bobs's database accesses as Bob's cash transactions are downloaded by Alice.
records.expectEvents(isStrict = false) {
sequence(
@ -344,7 +347,7 @@ class TwoPartyTradeFlowTests {
// Bob has downloaded the attachment.
bobNode.database.transaction {
bobNode.storage.attachments.openAttachment(attachmentID)!!.openAsJAR().use {
bobNode.services.attachments.openAttachment(attachmentID)!!.openAsJAR().use {
it.nextJarEntry
val contents = it.reader().readText()
assertTrue(contents.contains("Our commercial paper is top notch stuff"))
@ -354,7 +357,7 @@ class TwoPartyTradeFlowTests {
// And from Alice's perspective ...
run {
val records = (aliceNode.storage.validatedTransactions as RecordingTransactionStorage).records
val records = (aliceNode.services.validatedTransactions as RecordingTransactionStorage).records
records.expectEvents(isStrict = false) {
sequence(
// Seller Alice sends her seller info to Bob, who wants to check the asset for sale.
@ -422,8 +425,10 @@ class TwoPartyTradeFlowTests {
mockNet.runNetwork() // Clear network map registration messages
val aliceTxStream = aliceNode.storage.validatedTransactions.track().second
val aliceTxMappings = with(aliceNode) { database.transaction { storage.stateMachineRecordedTransactionMapping.track().second } }
val aliceTxStream = aliceNode.services.validatedTransactions.track().updates
val aliceTxMappings = with(aliceNode) {
database.transaction { services.stateMachineRecordedTransactionMapping.track().updates }
}
val aliceSmId = runBuyerAndSeller(notaryNode, aliceNode, bobNode,
"alice's paper".outputStateAndRef()).sellerId
@ -443,7 +448,7 @@ class TwoPartyTradeFlowTests {
)
aliceTxStream.expectEvents { aliceTxExpectations }
val aliceMappingExpectations = sequence(
expect { (stateMachineRunId, transactionId) ->
expect<StateMachineTransactionMapping> { (stateMachineRunId, transactionId) ->
require(stateMachineRunId == aliceSmId)
require(transactionId == bobsFakeCash[0].id)
},
@ -451,9 +456,9 @@ class TwoPartyTradeFlowTests {
require(stateMachineRunId == aliceSmId)
require(transactionId == bobsFakeCash[2].id)
},
expect { mapping: StateMachineTransactionMapping ->
require(mapping.stateMachineRunId == aliceSmId)
require(mapping.transactionId == bobsFakeCash[1].id)
expect { (stateMachineRunId, transactionId) ->
require(stateMachineRunId == aliceSmId)
require(transactionId == bobsFakeCash[1].id)
}
)
aliceTxMappings.expectEvents { aliceMappingExpectations }
@ -589,7 +594,7 @@ class TwoPartyTradeFlowTests {
}
return node.database.transaction {
node.services.recordTransactions(signed)
val validatedTransactions = node.services.storageService.validatedTransactions
val validatedTransactions = node.services.validatedTransactions
if (validatedTransactions is RecordingTransactionStorage) {
validatedTransactions.records.clear()
}

View File

@ -6,7 +6,6 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.*
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.transactions.SignedTransaction
import net.corda.node.internal.InitiatedFlowFactory
import net.corda.node.serialization.NodeClock
import net.corda.node.services.api.*
@ -17,9 +16,11 @@ import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.testing.MOCK_IDENTITY_SERVICE
import net.corda.testing.node.MockAttachmentStorage
import net.corda.testing.node.MockNetworkMapCache
import net.corda.testing.node.MockStorageService
import org.jetbrains.exposed.sql.Database
import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage
import net.corda.testing.node.MockTransactionStorage
import java.time.Clock
open class MockServiceHubInternal(
@ -28,13 +29,16 @@ open class MockServiceHubInternal(
val keyManagement: KeyManagementService? = null,
val network: MessagingService? = null,
val identity: IdentityService? = MOCK_IDENTITY_SERVICE,
val storage: TxWritableStorageService? = MockStorageService(),
override val attachments: AttachmentStorage = MockAttachmentStorage(),
override val validatedTransactions: TransactionStorage = MockTransactionStorage(),
override val uploaders: List<FileUploader> = listOf<FileUploader>(),
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage(),
val mapCache: NetworkMapCacheInternal? = null,
val scheduler: SchedulerService? = null,
val overrideClock: Clock? = NodeClock(),
val schemas: SchemaService? = NodeSchemaService(),
val customTransactionVerifierService: TransactionVerifierService? = InMemoryTransactionVerifierService(2)
) : ServiceHubInternal() {
) : ServiceHubInternal {
override val vaultQueryService: VaultQueryService
get() = customVaultQuery ?: throw UnsupportedOperationException()
override val transactionVerifierService: TransactionVerifierService
@ -49,8 +53,6 @@ open class MockServiceHubInternal(
get() = network ?: throw UnsupportedOperationException()
override val networkMapCache: NetworkMapCacheInternal
get() = mapCache ?: MockNetworkMapCache(this)
override val storageService: StorageService
get() = storage ?: throw UnsupportedOperationException()
override val schedulerService: SchedulerService
get() = scheduler ?: throw UnsupportedOperationException()
override val clock: Clock
@ -67,14 +69,9 @@ open class MockServiceHubInternal(
override val schemaService: SchemaService
get() = schemas ?: throw UnsupportedOperationException()
override val auditService: AuditService = DummyAuditService()
// We isolate the storage service with writable TXes so that it can't be accessed except via recordTransactions()
private val txStorageService: TxWritableStorageService
get() = storage ?: throw UnsupportedOperationException()
lateinit var smm: StateMachineManager
override fun recordTransactions(txs: Iterable<SignedTransaction>) = recordTransactionsInternal(txStorageService, txs)
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T = throw UnsupportedOperationException()
override fun <T> startFlow(logic: FlowLogic<T>, flowInitiator: FlowInitiator): FlowStateMachineImpl<T> {

View File

@ -99,7 +99,7 @@ class NotaryChangeTests {
val newState = future.resultFuture.getOrThrow()
assertEquals(newState.state.notary, newNotary)
val notaryChangeTx = clientNodeA.services.storageService.validatedTransactions.getTransaction(newState.ref.txhash)!!.tx
val notaryChangeTx = clientNodeA.services.validatedTransactions.getTransaction(newState.ref.txhash)!!.tx
// Check that all encumbrances have been propagated to the outputs
val originalOutputs = issueTx.outputs.map { it.data }

View File

@ -85,7 +85,7 @@ class HibernateConfigurationTest {
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
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 })

View File

@ -6,7 +6,6 @@ import net.corda.contracts.testing.fillWithSomeTestCash
import net.corda.core.contracts.*
import net.corda.core.identity.AnonymousParty
import net.corda.core.node.services.StatesNotAvailableException
import net.corda.core.node.services.TxWritableStorageService
import net.corda.core.node.services.VaultService
import net.corda.core.node.services.unconsumedStates
import net.corda.core.serialization.OpaqueBytes
@ -53,7 +52,7 @@ class NodeVaultServiceTest {
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
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 })
@ -77,17 +76,12 @@ class NodeVaultServiceTest {
val w1 = vaultSvc.unconsumedStates<Cash.State>()
assertThat(w1).hasSize(3)
val originalStorage = services.storageService
val originalVault = vaultSvc
val services2 = object : MockServices() {
override val vaultService: VaultService get() = originalVault
// We need to be able to find the same transactions as before, too.
override val storageService: TxWritableStorageService get() = originalStorage
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
validatedTransactions.addTransaction(stx)
vaultService.notify(stx.tx)
}
}

View File

@ -75,7 +75,7 @@ class VaultQueryTests {
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
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 })
@ -93,9 +93,9 @@ class VaultQueryTests {
/**
* Helper method for generating a Persistent H2 test database
*/
@Ignore //@Test
@Ignore
@Test
fun createPersistentTestDb() {
val dataSourceAndDatabase = configureDatabase(makePersistentDataSourceProperties())
val dataSource = dataSourceAndDatabase.first
val database = dataSourceAndDatabase.second

View File

@ -3,7 +3,9 @@ package net.corda.node.services.vault
import net.corda.contracts.DummyDealContract
import net.corda.contracts.asset.Cash
import net.corda.contracts.asset.DUMMY_CASH_ISSUER
import net.corda.contracts.testing.*
import net.corda.contracts.testing.fillWithSomeTestCash
import net.corda.contracts.testing.fillWithSomeTestDeals
import net.corda.contracts.testing.fillWithSomeTestLinearStates
import net.corda.core.contracts.*
import net.corda.core.identity.AnonymousParty
import net.corda.core.node.services.VaultService
@ -54,7 +56,7 @@ class VaultWithCashTest {
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
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 })

View File

@ -55,14 +55,14 @@ class BuyerFlow(val otherParty: Party) : FlowLogic<Unit>() {
private fun logIssuanceAttachment(tradeTX: SignedTransaction) {
// Find the original CP issuance.
val search = TransactionGraphSearch(serviceHub.storageService.validatedTransactions, listOf(tradeTX.tx))
val search = TransactionGraphSearch(serviceHub.validatedTransactions, listOf(tradeTX.tx))
search.query = TransactionGraphSearch.Query(withCommandOfType = CommercialPaper.Commands.Issue::class.java,
followInputsOfType = CommercialPaper.State::class.java)
val cpIssuance = search.call().single()
// Buyer will fetch the attachment from the seller automatically when it resolves the transaction.
// For demo purposes just extract attachment jars when saved to disk, so the user can explore them.
val attachmentsPath = (serviceHub.storageService.attachments).let {
val attachmentsPath = (serviceHub.attachments).let {
it.automaticallyExtractAttachments = true
it.storePath
}

View File

@ -79,7 +79,7 @@ class SellerFlow(val otherParty: Party,
// TODO: Consider moving these two steps below into generateIssue.
// Attach the prospectus.
tx.addAttachment(serviceHub.storageService.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
tx.addAttachment(serviceHub.attachments.openAttachment(PROSPECTUS_HASH)!!.id)
// Requesting a time-window to be set, all CP must have a validation window.
tx.addTimeWindow(Instant.now(), 30.seconds)

View File

@ -211,8 +211,9 @@ data class TestLedgerDSLInterpreter private constructor(
}
}
internal fun resolveAttachment(attachmentId: SecureHash): Attachment =
services.storageService.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
internal fun resolveAttachment(attachmentId: SecureHash): Attachment {
return services.attachments.openAttachment(attachmentId) ?: throw AttachmentResolutionException(attachmentId)
}
private fun <R> interpretTransactionDsl(
transactionBuilder: TransactionBuilder,
@ -276,7 +277,7 @@ data class TestLedgerDSLInterpreter private constructor(
dsl(LedgerDSL(copy()))
override fun attachment(attachment: InputStream): SecureHash {
return services.storageService.attachments.importAttachment(attachment)
return services.attachments.importAttachment(attachment)
}
override fun verifies(): EnforceVerifyOrFail {

View File

@ -6,7 +6,6 @@ import net.corda.core.crypto.*
import net.corda.core.flows.StateMachineRunId
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.*
@ -16,6 +15,7 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.DUMMY_CA
import net.corda.core.utilities.getTestPartyAndCertificate
import net.corda.flows.AnonymisedIdentity
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
import net.corda.node.services.database.HibernateConfiguration
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.keys.freshCertificate
@ -28,7 +28,6 @@ import net.corda.node.services.vault.NodeVaultService
import net.corda.testing.MEGA_CORP
import net.corda.testing.MOCK_IDENTITIES
import net.corda.testing.MOCK_VERSION_INFO
import org.bouncycastle.cert.X509CertificateHolder
import org.bouncycastle.operator.ContentSigner
import rx.Observable
import rx.subjects.PublishSubject
@ -41,11 +40,9 @@ import java.nio.file.Paths
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import java.security.cert.CertPath
import java.time.Clock
import java.util.*
import java.util.jar.JarInputStream
import javax.annotation.concurrent.ThreadSafe
// TODO: We need a single, rationalised unit testing environment that is usable for everything. Fix this!
// That means it probably shouldn't be in the 'core' module, which lacks enough code to create a realistic test env.
@ -61,14 +58,16 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
txs.forEach {
storageService.stateMachineRecordedTransactionMapping.addMapping(StateMachineRunId.createRandom(), it.id)
stateMachineRecordedTransactionMapping.addMapping(StateMachineRunId.createRandom(), it.id)
}
for (stx in txs) {
storageService.validatedTransactions.addTransaction(stx)
validatedTransactions.addTransaction(stx)
}
}
override val storageService: TxWritableStorageService = MockStorageService()
override val attachments: AttachmentStorage = MockAttachmentStorage()
override val validatedTransactions: TransactionStorage = MockTransactionStorage()
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage()
override final val identityService: IdentityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate)
override val keyManagementService: KeyManagementService = MockKeyManagementService(identityService, *keys)
@ -185,15 +184,6 @@ open class MockTransactionStorage : TransactionStorage {
override fun getTransaction(id: SecureHash): SignedTransaction? = txns[id]
}
@ThreadSafe
class MockStorageService(override val attachments: AttachmentStorage = MockAttachmentStorage(),
override val validatedTransactions: TransactionStorage = MockTransactionStorage(),
override val uploaders: List<FileUploader> = listOf<FileUploader>(),
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage())
: SingletonSerializeAsToken(), TxWritableStorageService {
override val attachmentsClassLoaderEnabled = false
}
/**
* Make properties appropriate for creating a DataSource for unit tests.
*