Clean up of ServiceHubInternal, including how it's created in AbstractNode

This commit is contained in:
Shams Asari 2017-07-03 14:29:18 +01:00
parent 56402c744a
commit 46e23b7716
25 changed files with 221 additions and 276 deletions

View File

@ -1,7 +1,7 @@
package net.corda.core.contracts package net.corda.core.contracts
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.node.services.ReadOnlyTransactionStorage import net.corda.core.node.services.TransactionStorage
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.WireTransaction import net.corda.core.transactions.WireTransaction
import java.util.* import java.util.*
@ -18,7 +18,7 @@ import java.util.concurrent.Callable
* @param transactions map of transaction id to [SignedTransaction]. * @param transactions map of transaction id to [SignedTransaction].
* @param startPoints transactions to use as starting points for the search. * @param startPoints transactions to use as starting points for the search.
*/ */
class TransactionGraphSearch(val transactions: ReadOnlyTransactionStorage, class TransactionGraphSearch(val transactions: TransactionStorage,
val startPoints: List<WireTransaction>) : Callable<List<WireTransaction>> { val startPoints: List<WireTransaction>) : Callable<List<WireTransaction>> {
class Query( class Query(
val withCommandOfType: Class<out CommandData>? = null, val withCommandOfType: Class<out CommandData>? = null,

View File

@ -1,5 +1,6 @@
package net.corda.core.node package net.corda.core.node
import com.google.common.collect.Lists
import net.corda.core.contracts.* import net.corda.core.contracts.*
import net.corda.core.crypto.DigitalSignature import net.corda.core.crypto.DigitalSignature
import net.corda.core.node.services.* import net.corda.core.node.services.*
@ -17,8 +18,10 @@ import java.time.Clock
*/ */
interface ServicesForResolution { interface ServicesForResolution {
val identityService: IdentityService val identityService: IdentityService
/** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */ /** Provides access to storage of arbitrary JAR files (which may contain only data, no code). */
val attachments: AttachmentStorage val attachments: AttachmentStorage
/** /**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]. * Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
* *
@ -40,12 +43,13 @@ interface ServiceHub : ServicesForResolution {
val vaultService: VaultService val vaultService: VaultService
val vaultQueryService: VaultQueryService val vaultQueryService: VaultQueryService
val keyManagementService: KeyManagementService val keyManagementService: KeyManagementService
/** /**
* A map of hash->tx where tx has been signature/contract validated and the states are known to be correct. * 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 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. * the transaction data to other nodes that need it.
*/ */
val validatedTransactions: ReadOnlyTransactionStorage val validatedTransactions: TransactionStorage
val networkMapCache: NetworkMapCache val networkMapCache: NetworkMapCache
val transactionVerifierService: TransactionVerifierService val transactionVerifierService: TransactionVerifierService
@ -60,41 +64,41 @@ interface ServiceHub : ServicesForResolution {
fun <T : SerializeAsToken> cordaService(type: Class<T>): T fun <T : SerializeAsToken> cordaService(type: Class<T>): T
/** /**
* Given a [SignedTransaction], writes it to the local storage for validated transactions and then * Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for
* sends them to the vault for further processing. Expects to be run within a database transaction. * further processing. This is expected to be run within a database transaction.
* *
* @param txs The transactions to record. * @param txs The transactions to record.
*/ */
// TODO: Make this take a single tx.
fun recordTransactions(txs: Iterable<SignedTransaction>) fun recordTransactions(txs: Iterable<SignedTransaction>)
/** /**
* Given some [SignedTransaction]s, writes them to the local storage for validated transactions and then * Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for
* sends them to the vault for further processing. * further processing. This is expected to be run within a database transaction.
*
* @param txs The transactions to record.
*/ */
fun recordTransactions(vararg txs: SignedTransaction) = recordTransactions(txs.toList()) fun recordTransactions(first: SignedTransaction, vararg remaining: SignedTransaction) {
recordTransactions(Lists.asList(first, remaining))
}
/** /**
* Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState]. * Given a [StateRef] loads the referenced transaction and looks up the specified output [ContractState].
* *
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction. * @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
*/ */
@Throws(TransactionResolutionException::class) @Throws(TransactionResolutionException::class)
override fun loadState(stateRef: StateRef): TransactionState<*> { override fun loadState(stateRef: StateRef): TransactionState<*> {
val definingTx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash) val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return definingTx.tx.outputs[stateRef.index] return stx.tx.outputs[stateRef.index]
} }
/** /**
* Will check [logicType] and [args] against a whitelist and if acceptable then construct and initiate the protocol. * Converts the given [StateRef] into a [StateAndRef] object.
* *
* @throws IllegalProtocolLogicException or IllegalArgumentException if there are problems with the [logicType] or [args]. * @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
*/ */
fun <T : ContractState> toStateAndRef(ref: StateRef): StateAndRef<T> { @Throws(TransactionResolutionException::class)
val definingTx = validatedTransactions.getTransaction(ref.txhash) ?: throw TransactionResolutionException(ref.txhash) fun <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> {
return definingTx.tx.outRef<T>(ref.index) val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
return stx.tx.outRef<T>(stateRef.index)
} }
/** /**
@ -102,7 +106,7 @@ interface ServiceHub : ServicesForResolution {
* Node's primary signing identity. * Node's primary signing identity.
* Typical use is during signing in flows and for unit test signing. * Typical use is during signing in flows and for unit test signing.
* When this [PublicKey] is passed into the signing methods below, or on the KeyManagementService * When this [PublicKey] is passed into the signing methods below, or on the KeyManagementService
* the matching [PrivateKey] will be looked up internally and used to sign. * the matching [java.security.PrivateKey] will be looked up internally and used to sign.
* If the key is actually a CompositeKey, the first leaf key hosted on this node * If the key is actually a CompositeKey, the first leaf key hosted on this node
* will be used to create the signature. * will be used to create the signature.
*/ */
@ -114,8 +118,8 @@ interface ServiceHub : ServicesForResolution {
* otherwise an IllegalArgumentException will be thrown. * otherwise an IllegalArgumentException will be thrown.
* Typical use is during signing in flows and for unit test signing. * Typical use is during signing in flows and for unit test signing.
* When this [PublicKey] is passed into the signing methods below, or on the KeyManagementService * When this [PublicKey] is passed into the signing methods below, or on the KeyManagementService
* the matching [PrivateKey] will be looked up internally and used to sign. * the matching [java.security.PrivateKey] will be looked up internally and used to sign.
* If the key is actually a [CompositeKey], the first leaf key hosted on this node * If the key is actually a [net.corda.core.crypto.CompositeKey], the first leaf key hosted on this node
* will be used to create the signature. * will be used to create the signature.
*/ */
val notaryIdentityKey: PublicKey get() = this.myInfo.notaryIdentity.owningKey val notaryIdentityKey: PublicKey get() = this.myInfo.notaryIdentity.owningKey
@ -125,7 +129,7 @@ interface ServiceHub : ServicesForResolution {
* using keys stored inside the node. * using keys stored inside the node.
* @param builder The [TransactionBuilder] to seal with the node's signature. * @param builder The [TransactionBuilder] to seal with the node's signature.
* Any existing signatures on the builder will be preserved. * Any existing signatures on the builder will be preserved.
* @param publicKey The [PublicKey] matched to the internal [PrivateKey] to use in signing this transaction. * @param publicKey The [PublicKey] matched to the internal [java.security.PrivateKey] to use in signing this transaction.
* If the passed in key is actually a CompositeKey the code searches for the first child key hosted within this node * If the passed in key is actually a CompositeKey the code searches for the first child key hosted within this node
* to sign with. * to sign with.
* @return Returns a SignedTransaction with the new node signature attached. * @return Returns a SignedTransaction with the new node signature attached.
@ -150,30 +154,30 @@ interface ServiceHub : ServicesForResolution {
* using a set of keys all held in this node. * using a set of keys all held in this node.
* @param builder The [TransactionBuilder] to seal with the node's signature. * @param builder The [TransactionBuilder] to seal with the node's signature.
* Any existing signatures on the builder will be preserved. * Any existing signatures on the builder will be preserved.
* @param signingPubKeys A list of [PublicKeys] used to lookup the matching [PrivateKey] and sign. * @param signingPubKeys A list of [PublicKey]s used to lookup the matching [java.security.PrivateKey] and sign.
* @throws IllegalArgumentException is thrown if any keys are unavailable locally. * @throws IllegalArgumentException is thrown if any keys are unavailable locally.
* @return Returns a [SignedTransaction] with the new node signature attached. * @return Returns a [SignedTransaction] with the new node signature attached.
*/ */
fun signInitialTransaction(builder: TransactionBuilder, signingPubKeys: Iterable<PublicKey>): SignedTransaction { fun signInitialTransaction(builder: TransactionBuilder, signingPubKeys: Iterable<PublicKey>): SignedTransaction {
var stx: SignedTransaction? = null val it = signingPubKeys.iterator()
for (pubKey in signingPubKeys) { var stx = signInitialTransaction(builder, it.next())
stx = if (stx == null) { while (it.hasNext()) {
signInitialTransaction(builder, pubKey) stx = addSignature(stx, it.next())
} else {
addSignature(stx, pubKey)
}
} }
return stx!! return stx
} }
/** /**
* Helper method to create an additional signature for an existing (partially) [SignedTransaction]. * Helper method to create an additional signature for an existing (partially) [SignedTransaction].
* @param signedTransaction The [SignedTransaction] to which the signature will apply. * @param signedTransaction The [SignedTransaction] to which the signature will apply.
* @param publicKey The [PublicKey] matching to a signing [PrivateKey] hosted in the node. * @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
* If the [PublicKey] is actually a [CompositeKey] the first leaf key found locally will be used for signing. * If the [PublicKey] is actually a [net.corda.core.crypto.CompositeKey] the first leaf key found locally will be used
* @return The [DigitalSignature.WithKey] generated by signing with the internally held [PrivateKey]. * for signing.
* @return The [DigitalSignature.WithKey] generated by signing with the internally held [java.security.PrivateKey].
*/ */
fun createSignature(signedTransaction: SignedTransaction, publicKey: PublicKey): DigitalSignature.WithKey = keyManagementService.sign(signedTransaction.id.bytes, publicKey) fun createSignature(signedTransaction: SignedTransaction, publicKey: PublicKey): DigitalSignature.WithKey {
return keyManagementService.sign(signedTransaction.id.bytes, publicKey)
}
/** /**
* Helper method to create an additional signature for an existing (partially) SignedTransaction * Helper method to create an additional signature for an existing (partially) SignedTransaction
@ -181,16 +185,21 @@ interface ServiceHub : ServicesForResolution {
* @param signedTransaction The SignedTransaction to which the signature will apply. * @param signedTransaction The SignedTransaction to which the signature will apply.
* @return The DigitalSignature.WithKey generated by signing with the internally held identity PrivateKey. * @return The DigitalSignature.WithKey generated by signing with the internally held identity PrivateKey.
*/ */
fun createSignature(signedTransaction: SignedTransaction): DigitalSignature.WithKey = createSignature(signedTransaction, legalIdentityKey) fun createSignature(signedTransaction: SignedTransaction): DigitalSignature.WithKey {
return createSignature(signedTransaction, legalIdentityKey)
}
/** /**
* Helper method to append an additional signature to an existing (partially) [SignedTransaction]. * Helper method to append an additional signature to an existing (partially) [SignedTransaction].
* @param signedTransaction The [SignedTransaction] to which the signature will be added. * @param signedTransaction The [SignedTransaction] to which the signature will be added.
* @param publicKey The [PublicKey] matching to a signing [PrivateKey] hosted in the node. * @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
* If the [PublicKey] is actually a [CompositeKey] the first leaf key found locally will be used for signing. * If the [PublicKey] is actually a [net.corda.core.crypto.CompositeKey] the first leaf key found locally will be used
* for signing.
* @return A new [SignedTransaction] with the addition of the new signature. * @return A new [SignedTransaction] with the addition of the new signature.
*/ */
fun addSignature(signedTransaction: SignedTransaction, publicKey: PublicKey): SignedTransaction = signedTransaction + createSignature(signedTransaction, publicKey) fun addSignature(signedTransaction: SignedTransaction, publicKey: PublicKey): SignedTransaction {
return signedTransaction + createSignature(signedTransaction, publicKey)
}
/** /**
* Helper method to ap-pend an additional signature for an existing (partially) [SignedTransaction] * Helper method to ap-pend an additional signature for an existing (partially) [SignedTransaction]

View File

@ -2,21 +2,14 @@ package net.corda.core.node.services
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.nio.file.Path import java.nio.file.FileAlreadyExistsException
/** /**
* An attachment store records potentially large binary objects, identified by their hash. * An attachment store records potentially large binary objects, identified by their hash.
*/ */
interface AttachmentStorage { interface AttachmentStorage {
/**
* If true, newly inserted attachments will be unzipped to a subdirectory of the [storePath]. This is intended for
* human browsing convenience: the attachment itself will still be the file (that is, edits to the extracted directory
* will not have any effect).
*/
var automaticallyExtractAttachments: Boolean
var storePath: Path
/** /**
* Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open * Returns a handle to a locally stored attachment, or null if it's not known. The handle can be used to open
* a stream for the data, which will be a zip/jar file. * a stream for the data, which will be a zip/jar file.
@ -27,13 +20,14 @@ interface AttachmentStorage {
* Inserts the given attachment into the store, does *not* close the input stream. This can be an intensive * Inserts the given attachment into the store, does *not* close the input stream. This can be an intensive
* operation due to the need to copy the bytes to disk and hash them along the way. * operation due to the need to copy the bytes to disk and hash them along the way.
* *
* Note that you should not pass a [JarInputStream] into this method and it will throw if you do, because access * Note that you should not pass a [java.util.jar.JarInputStream] into this method and it will throw if you do, because
* to the raw byte stream is required. * access to the raw byte stream is required.
* *
* @throws FileAlreadyExistsException if the given byte stream has already been inserted. * @throws FileAlreadyExistsException if the given byte stream has already been inserted.
* @throws IllegalArgumentException if the given byte stream is empty or a [JarInputStream]. * @throws IllegalArgumentException if the given byte stream is empty or a [java.util.jar.JarInputStream].
* @throws IOException if something went wrong. * @throws IOException if something went wrong.
*/ */
@Throws(FileAlreadyExistsException::class, IOException::class)
fun importAttachment(jar: InputStream): SecureHash fun importAttachment(jar: InputStream): SecureHash
} }

View File

@ -8,7 +8,7 @@ import rx.Observable
/** /**
* Thread-safe storage of transactions. * Thread-safe storage of transactions.
*/ */
interface ReadOnlyTransactionStorage { interface TransactionStorage {
/** /**
* Return the transaction with the given [id], or null if no such transaction exists. * Return the transaction with the given [id], or null if no such transaction exists.
*/ */
@ -24,18 +24,4 @@ interface ReadOnlyTransactionStorage {
* Returns all currently stored transactions and further fresh ones. * Returns all currently stored transactions and further fresh ones.
*/ */
fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> fun track(): DataFeed<List<SignedTransaction>, SignedTransaction>
} }
/**
* Thread-safe storage of transactions.
*/
interface TransactionStorage : ReadOnlyTransactionStorage {
/**
* Add a new transaction to the store. If the store already has a transaction with the same id it will be
* overwritten.
* @param transaction The transaction to be recorded.
* @return true if the transaction was recorded successfully, false if it was already recorded.
*/
// TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry.
fun addTransaction(transaction: SignedTransaction): Boolean
}

View File

@ -74,16 +74,15 @@ class FinalityFlow(val transactions: Iterable<SignedTransaction>,
@Suspendable @Suspendable
private fun notariseAndRecord(stxnsAndParties: List<Pair<SignedTransaction, Set<Party>>>): List<Pair<SignedTransaction, Set<Party>>> { private fun notariseAndRecord(stxnsAndParties: List<Pair<SignedTransaction, Set<Party>>>): List<Pair<SignedTransaction, Set<Party>>> {
return stxnsAndParties.map { pair -> return stxnsAndParties.map { (stx, parties) ->
val stx = pair.first
val notarised = if (needsNotarySignature(stx)) { val notarised = if (needsNotarySignature(stx)) {
val notarySignatures = subFlow(NotaryFlow.Client(stx)) val notarySignatures = subFlow(NotaryFlow.Client(stx))
stx + notarySignatures stx + notarySignatures
} else { } else {
stx stx
} }
serviceHub.recordTransactions(listOf(notarised)) serviceHub.recordTransactions(notarised)
Pair(notarised, pair.second) Pair(notarised, parties)
} }
} }
@ -101,8 +100,7 @@ class FinalityFlow(val transactions: Iterable<SignedTransaction>,
} }
private fun lookupParties(ltxns: List<Pair<SignedTransaction, LedgerTransaction>>): List<Pair<SignedTransaction, Set<Party>>> { private fun lookupParties(ltxns: List<Pair<SignedTransaction, LedgerTransaction>>): List<Pair<SignedTransaction, Set<Party>>> {
return ltxns.map { pair -> return ltxns.map { (stx, ltx) ->
val (stx, ltx) = pair
// Calculate who is meant to see the results based on the participants involved. // Calculate who is meant to see the results based on the participants involved.
val keys = ltx.outputs.flatMap { it.data.participants } + ltx.inputs.flatMap { it.state.data.participants } val keys = ltx.outputs.flatMap { it.data.participants } + ltx.inputs.flatMap { it.state.data.participants }
// TODO: Is it safe to drop participants we don't know how to contact? Does not knowing how to contact them count as a reason to fail? // TODO: Is it safe to drop participants we don't know how to contact? Does not knowing how to contact them count as a reason to fail?

View File

@ -178,14 +178,14 @@ class ContractUpgradeFlowTest {
mockNet.runNetwork() mockNet.runNetwork()
val stx = result.getOrThrow().stx val stx = result.getOrThrow().stx
val stateAndRef = stx.tx.outRef<Cash.State>(0) val stateAndRef = stx.tx.outRef<Cash.State>(0)
val baseState = a.database.transaction { a.vault.unconsumedStates<ContractState>().single() } val baseState = a.database.transaction { a.services.vaultService.unconsumedStates<ContractState>().single() }
assertTrue(baseState.state.data is Cash.State, "Contract state is old version.") assertTrue(baseState.state.data is Cash.State, "Contract state is old version.")
// Starts contract upgrade flow. // Starts contract upgrade flow.
val upgradeResult = a.services.startFlow(ContractUpgradeFlow(stateAndRef, CashV2::class.java)).resultFuture val upgradeResult = a.services.startFlow(ContractUpgradeFlow(stateAndRef, CashV2::class.java)).resultFuture
mockNet.runNetwork() mockNet.runNetwork()
upgradeResult.getOrThrow() upgradeResult.getOrThrow()
// Get contract state from the vault. // Get contract state from the vault.
val firstState = a.database.transaction { a.vault.unconsumedStates<ContractState>().single() } val firstState = a.database.transaction { a.services.vaultService.unconsumedStates<ContractState>().single() }
assertTrue(firstState.state.data is CashV2.State, "Contract state is upgraded to the new version.") assertTrue(firstState.state.data is CashV2.State, "Contract state is upgraded to the new version.")
assertEquals(Amount(1000000, USD).`issued by`(a.info.legalIdentity.ref(1)), (firstState.state.data as CashV2.State).amount, "Upgraded cash contain the correct amount.") assertEquals(Amount(1000000, USD).`issued by`(a.info.legalIdentity.ref(1)), (firstState.state.data as CashV2.State).amount, "Upgraded cash contain the correct amount.")
assertEquals<Collection<AbstractParty>>(listOf(a.info.legalIdentity), (firstState.state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.") assertEquals<Collection<AbstractParty>>(listOf(a.info.legalIdentity), (firstState.state.data as CashV2.State).owners, "Upgraded cash belongs to the right owner.")

View File

@ -33,8 +33,8 @@ class CashPaymentFlowTests {
bankOfCorda = bankOfCordaNode.info.legalIdentity bankOfCorda = bankOfCordaNode.info.legalIdentity
notaryNode.registerInitiatedFlow(TxKeyFlow.Provider::class.java) notaryNode.registerInitiatedFlow(TxKeyFlow.Provider::class.java)
notaryNode.identity.registerIdentity(bankOfCordaNode.info.legalIdentityAndCert) notaryNode.services.identityService.registerIdentity(bankOfCordaNode.info.legalIdentityAndCert)
bankOfCordaNode.identity.registerIdentity(notaryNode.info.legalIdentityAndCert) bankOfCordaNode.services.identityService.registerIdentity(notaryNode.info.legalIdentityAndCert)
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref, val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref,
bankOfCorda, bankOfCorda,
notary)).resultFuture notary)).resultFuture

View File

@ -5,8 +5,8 @@ import net.corda.core.contracts.DummyContract
import net.corda.core.contracts.StateAndRef import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionType import net.corda.core.contracts.TransactionType
import net.corda.core.identity.Party
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.identity.Party
import net.corda.core.map import net.corda.core.map
import net.corda.core.utilities.DUMMY_BANK_A import net.corda.core.utilities.DUMMY_BANK_A
import net.corda.flows.NotaryError import net.corda.flows.NotaryError
@ -26,28 +26,28 @@ class RaftNotaryServiceTests : NodeBasedTest() {
@Test @Test
fun `detect double spend`() { fun `detect double spend`() {
val (masterNode, alice) = Futures.allAsList( val (bankA) = Futures.allAsList(
startNotaryCluster(notaryName, 3).map { it.first() }, startNode(DUMMY_BANK_A.name),
startNode(DUMMY_BANK_A.name) startNotaryCluster(notaryName, 3).map { it.first() }
).getOrThrow() ).getOrThrow()
val notaryParty = alice.netMapCache.getNotary(notaryName)!! val notaryParty = bankA.services.networkMapCache.getNotary(notaryName)!!
val inputState = issueState(alice, notaryParty) val inputState = issueState(bankA, notaryParty)
val firstTxBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState) val firstTxBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState)
val firstSpendTx = alice.services.signInitialTransaction(firstTxBuilder) val firstSpendTx = bankA.services.signInitialTransaction(firstTxBuilder)
val firstSpend = alice.services.startFlow(NotaryFlow.Client(firstSpendTx)) val firstSpend = bankA.services.startFlow(NotaryFlow.Client(firstSpendTx))
firstSpend.resultFuture.getOrThrow() firstSpend.resultFuture.getOrThrow()
val secondSpendBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState).run { val secondSpendBuilder = TransactionType.General.Builder(notaryParty).withItems(inputState).run {
val dummyState = DummyContract.SingleOwnerState(0, alice.info.legalIdentity) val dummyState = DummyContract.SingleOwnerState(0, bankA.info.legalIdentity)
addOutputState(dummyState) addOutputState(dummyState)
this this
} }
val secondSpendTx = alice.services.signInitialTransaction(secondSpendBuilder) val secondSpendTx = bankA.services.signInitialTransaction(secondSpendBuilder)
val secondSpend = alice.services.startFlow(NotaryFlow.Client(secondSpendTx)) val secondSpend = bankA.services.startFlow(NotaryFlow.Client(secondSpendTx))
val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() } val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() }
val error = ex.error as NotaryError.Conflict val error = ex.error as NotaryError.Conflict
@ -58,7 +58,7 @@ class RaftNotaryServiceTests : NodeBasedTest() {
return node.database.transaction { return node.database.transaction {
val builder = DummyContract.generateInitial(Random().nextInt(), notary, node.info.legalIdentity.ref(0)) val builder = DummyContract.generateInitial(Random().nextInt(), notary, node.info.legalIdentity.ref(0))
val stx = node.services.signInitialTransaction(builder) val stx = node.services.signInitialTransaction(builder)
node.services.recordTransactions(listOf(stx)) node.services.recordTransactions(stx)
StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0)) StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0))
} }
} }

View File

@ -20,7 +20,6 @@ import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.* import net.corda.core.node.*
import net.corda.core.node.services.* import net.corda.core.node.services.*
import net.corda.core.node.services.NetworkMapCache.MapChange import net.corda.core.node.services.NetworkMapCache.MapChange
import net.corda.core.node.services.NotaryService
import net.corda.core.serialization.SerializeAsToken import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
@ -42,6 +41,7 @@ import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.messaging.sendRequest import net.corda.node.services.messaging.sendRequest
import net.corda.node.services.network.InMemoryNetworkMapCache import net.corda.node.services.network.InMemoryNetworkMapCache
import net.corda.node.services.network.NetworkMapService import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.network.NetworkMapService.RegistrationRequest
import net.corda.node.services.network.NetworkMapService.RegistrationResponse import net.corda.node.services.network.NetworkMapService.RegistrationResponse
import net.corda.node.services.network.NodeRegistration import net.corda.node.services.network.NodeRegistration
import net.corda.node.services.network.PersistentNetworkMapService import net.corda.node.services.network.PersistentNetworkMapService
@ -122,78 +122,18 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>() private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
protected val partyKeys = mutableSetOf<KeyPair>() protected val partyKeys = mutableSetOf<KeyPair>()
val services = object : ServiceHubInternal { val services: ServiceHubInternal get() = _services
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 vaultService: VaultService get() = vault
override val vaultQueryService: VaultQueryService get() = vaultQuery
override val keyManagementService: KeyManagementService get() = keyManagement
override val identityService: IdentityService get() = identity
override val schedulerService: SchedulerService get() = scheduler
override val clock: Clock get() = platformClock
override val myInfo: NodeInfo get() = info
override val schemaService: SchemaService get() = schemas
override val transactionVerifierService: TransactionVerifierService get() = txVerifierService
override val auditService: AuditService get() = this@AbstractNode.auditService
override val database: Database get() = this@AbstractNode.database
override val configuration: NodeConfiguration get() = this@AbstractNode.configuration
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
return cordappServices.getInstance(type) ?: throw IllegalArgumentException("Corda service ${type.name} does not exist")
}
override val rpcFlows: List<Class<out FlowLogic<*>>> get() = this@AbstractNode.rpcFlows
// Internal only
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
override fun <T> startFlow(logic: FlowLogic<T>, flowInitiator: FlowInitiator): FlowStateMachineImpl<T> {
return serverThread.fetchFrom { smm.add(logic, flowInitiator) }
}
override fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? {
return flowFactories[initiatingFlowClass]
}
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
database.transaction {
super.recordTransactions(txs)
}
}
}
open fun findMyLocation(): WorldMapLocation? {
return configuration.myLegalName.locationOrNull?.let { CityDatabase[it] }
}
private lateinit var _services: ServiceHubInternalImpl
lateinit var info: NodeInfo lateinit var info: NodeInfo
lateinit var checkpointStorage: CheckpointStorage lateinit var checkpointStorage: CheckpointStorage
lateinit var smm: StateMachineManager lateinit var smm: StateMachineManager
lateinit var attachments: NodeAttachmentService 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
var inNodeNetworkMapService: NetworkMapService? = null var inNodeNetworkMapService: NetworkMapService? = null
lateinit var txVerifierService: TransactionVerifierService
lateinit var identity: IdentityService
lateinit var network: MessagingService lateinit var network: MessagingService
lateinit var netMapCache: NetworkMapCacheInternal
lateinit var scheduler: NodeSchedulerService
lateinit var schemas: SchemaService
lateinit var auditService: AuditService
protected val runOnStop = ArrayList<() -> Any?>() protected val runOnStop = ArrayList<() -> Any?>()
lateinit var database: Database lateinit var database: Database
protected var dbCloser: (() -> Any?)? = null protected var dbCloser: (() -> Any?)? = null
private lateinit var rpcFlows: List<Class<out FlowLogic<*>>>
var isPreviousCheckpointsPresent = false var isPreviousCheckpointsPresent = false
private set private set
@ -215,6 +155,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
/** The implementation of the [CordaRPCOps] interface used by this node. */ /** The implementation of the [CordaRPCOps] interface used by this node. */
open val rpcOps: CordaRPCOps by lazy { CordaRPCOpsImpl(services, smm, database) } // Lazy to avoid init ordering issue with the SMM. open val rpcOps: CordaRPCOps by lazy { CordaRPCOpsImpl(services, smm, database) } // Lazy to avoid init ordering issue with the SMM.
open fun findMyLocation(): WorldMapLocation? {
return configuration.myLegalName.locationOrNull?.let { CityDatabase[it] }
}
open fun start(): AbstractNode { open fun start(): AbstractNode {
require(!started) { "Node has already been started" } require(!started) { "Node has already been started" }
@ -233,8 +177,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
// Do all of this in a database transaction so anything that might need a connection has one. // Do all of this in a database transaction so anything that might need a connection has one.
initialiseDatabasePersistence { initialiseDatabasePersistence {
val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword) val tokenizableServices = makeServices()
val tokenizableServices = makeServices(keyStoreWrapper)
smm = StateMachineManager(services, smm = StateMachineManager(services,
checkpointStorage, checkpointStorage,
@ -266,9 +209,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
if (scanResult != null) { if (scanResult != null) {
installCordaServices(scanResult) installCordaServices(scanResult)
registerInitiatedFlows(scanResult) registerInitiatedFlows(scanResult)
rpcFlows = findRPCFlows(scanResult) findRPCFlows(scanResult)
} else {
rpcFlows = emptyList()
} }
// TODO: Investigate having class path scanning find this flow // TODO: Investigate having class path scanning find this flow
@ -283,7 +224,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
smm.start() smm.start()
// Shut down the SMM so no Fibers are scheduled. // Shut down the SMM so no Fibers are scheduled.
runOnStop += { smm.stop(acceptableLiveFiberCountOnStop()) } runOnStop += { smm.stop(acceptableLiveFiberCountOnStop()) }
scheduler.start() _services.schedulerService.start()
} }
started = true started = true
return this return this
@ -301,7 +242,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
} }
} }
return scanResult.getClassesWithAnnotation(SerializeAsToken::class, CordaService::class) scanResult.getClassesWithAnnotation(SerializeAsToken::class, CordaService::class)
.filter { .filter {
val serviceType = getServiceType(it) val serviceType = getServiceType(it)
if (serviceType != null && info.serviceIdentities(serviceType).isEmpty()) { if (serviceType != null && info.serviceIdentities(serviceType).isEmpty()) {
@ -434,19 +375,21 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
return observable return observable
} }
private fun findRPCFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> { private fun findRPCFlows(scanResult: ScanResult) {
fun Class<out FlowLogic<*>>.isUserInvokable(): Boolean { fun Class<out FlowLogic<*>>.isUserInvokable(): Boolean {
return isPublic(modifiers) && !isLocalClass && !isAnonymousClass && (!isMemberClass || isStatic(modifiers)) return isPublic(modifiers) && !isLocalClass && !isAnonymousClass && (!isMemberClass || isStatic(modifiers))
} }
return scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class).filter { it.isUserInvokable() } + _services.rpcFlows += scanResult
// Add any core flows here .getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class)
listOf( .filter { it.isUserInvokable() } +
ContractUpgradeFlow::class.java, // Add any core flows here
// TODO Remove all Cash flows from default list once they are split into separate CorDapp. listOf(
CashIssueFlow::class.java, ContractUpgradeFlow::class.java,
CashExitFlow::class.java, // TODO Remove all Cash flows from default list once they are split into separate CorDapp.
CashPaymentFlow::class.java) CashIssueFlow::class.java,
CashExitFlow::class.java,
CashPaymentFlow::class.java)
} }
/** /**
@ -476,36 +419,20 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
* Builds node internal, advertised, and plugin services. * Builds node internal, advertised, and plugin services.
* Returns a list of tokenizable services to be added to the serialisation context. * Returns a list of tokenizable services to be added to the serialisation context.
*/ */
private fun makeServices(keyStoreWrapper: KeyStoreWrapper): MutableList<Any> { private fun makeServices(): MutableList<Any> {
val keyStore = keyStoreWrapper.keyStore
attachments = createAttachmentStorage()
transactions = createTransactionStorage()
transactionMappings = DBTransactionMappingStorage()
checkpointStorage = DBCheckpointStorage() checkpointStorage = DBCheckpointStorage()
netMapCache = InMemoryNetworkMapCache(services) _services = ServiceHubInternalImpl()
attachments = createAttachmentStorage()
network = makeMessagingService() network = makeMessagingService()
schemas = makeSchemaService()
vault = makeVaultService(configuration.dataSourceProperties)
vaultQuery = makeVaultQueryService(schemas)
txVerifierService = makeTransactionVerifierService()
auditService = DummyAuditService()
info = makeInfo() info = makeInfo()
identity = makeIdentityService(keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)!! as X509Certificate,
keyStoreWrapper.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA),
info.legalIdentityAndCert)
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
// the identity key. But the infrastructure to make that easy isn't here yet.
keyManagement = makeKeyManagementService(identity)
scheduler = NodeSchedulerService(services, database, unfinishedSchedules = busyNodeLatch)
val tokenizableServices = mutableListOf(attachments, network, vault, vaultQuery, keyManagement, identity, platformClock, scheduler) val tokenizableServices = mutableListOf(attachments, network, services.vaultService, services.vaultQueryService,
services.keyManagementService, services.identityService, platformClock, services.schedulerService)
makeAdvertisedServices(tokenizableServices) makeAdvertisedServices(tokenizableServices)
return tokenizableServices return tokenizableServices
} }
protected open fun createTransactionStorage(): TransactionStorage = DBTransactionStorage() protected open fun makeTransactionStorage(): WritableTransactionStorage = DBTransactionStorage()
private fun scanCordapps(): ScanResult? { private fun scanCordapps(): ScanResult? {
val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package") val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package")
@ -560,14 +487,15 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
} }
private fun initUploaders() { private fun initUploaders() {
uploaders = listOf(attachments) + cordappServices.values.filterIsInstance(AcceptsFileUpload::class.java) _services.uploaders += attachments
cordappServices.values.filterIsInstanceTo(_services.uploaders, AcceptsFileUpload::class.java)
} }
private fun makeVaultObservers() { private fun makeVaultObservers() {
VaultSoftLockManager(vault, smm) VaultSoftLockManager(services.vaultService, smm)
CashBalanceAsMetricsObserver(services, database) CashBalanceAsMetricsObserver(services, database)
ScheduledActivityObserver(services) ScheduledActivityObserver(services)
HibernateObserver(vault.rawUpdates, HibernateConfiguration(schemas)) HibernateObserver(services.vaultService.rawUpdates, HibernateConfiguration(services.schemaService))
} }
private fun makeInfo(): NodeInfo { private fun makeInfo(): NodeInfo {
@ -684,7 +612,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
val reg = NodeRegistration(info, instant.toEpochMilli(), ADD, expires) val reg = NodeRegistration(info, instant.toEpochMilli(), ADD, expires)
val legalIdentityKey = obtainLegalIdentityKey() val legalIdentityKey = obtainLegalIdentityKey()
val request = NetworkMapService.RegistrationRequest(reg.toWire(keyManagement, legalIdentityKey.public), network.myAddress) val request = RegistrationRequest(reg.toWire(services.keyManagementService, legalIdentityKey.public), network.myAddress)
return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapAddress) return network.sendRequest(NetworkMapService.REGISTER_TOPIC, request, networkMapAddress)
} }
@ -735,7 +663,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
.toTypedArray() .toTypedArray()
val service = InMemoryIdentityService(setOf(info.legalIdentityAndCert), trustRoot = trustRoot, caCertificates = *caCertificates) val service = InMemoryIdentityService(setOf(info.legalIdentityAndCert), trustRoot = trustRoot, caCertificates = *caCertificates)
services.networkMapCache.partyNodes.forEach { service.registerIdentity(it.legalIdentityAndCert) } services.networkMapCache.partyNodes.forEach { service.registerIdentity(it.legalIdentityAndCert) }
netMapCache.changed.subscribe { mapChange -> services.networkMapCache.changed.subscribe { mapChange ->
// TODO how should we handle network map removal // TODO how should we handle network map removal
if (mapChange is MapChange.Added) { if (mapChange is MapChange.Added) {
service.registerIdentity(mapChange.node.legalIdentityAndCert) service.registerIdentity(mapChange.node.legalIdentityAndCert)
@ -744,13 +672,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
return service return service
} }
// TODO: sort out ordering of open & protected modifiers of functions in this class.
protected open fun makeVaultService(dataSourceProperties: Properties): VaultService = NodeVaultService(services, dataSourceProperties)
protected open fun makeVaultQueryService(schemas: SchemaService): VaultQueryService = HibernateVaultQueryImpl(HibernateConfiguration(schemas), vault.updatesPublisher)
protected open fun makeSchemaService(): SchemaService = NodeSchemaService(pluginRegistries.flatMap { it.requiredSchemas }.toSet())
protected abstract fun makeTransactionVerifierService(): TransactionVerifierService protected abstract fun makeTransactionVerifierService(): TransactionVerifierService
open fun stop() { open fun stop() {
@ -844,6 +765,60 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
val attachmentsDir = (configuration.baseDirectory / "attachments").createDirectories() val attachmentsDir = (configuration.baseDirectory / "attachments").createDirectories()
return NodeAttachmentService(attachmentsDir, configuration.dataSourceProperties, services.monitoringService.metrics) return NodeAttachmentService(attachmentsDir, configuration.dataSourceProperties, services.monitoringService.metrics)
} }
private inner class ServiceHubInternalImpl : ServiceHubInternal, SingletonSerializeAsToken() {
override val rpcFlows = ArrayList<Class<out FlowLogic<*>>>()
override val uploaders = ArrayList<FileUploader>()
override val stateMachineRecordedTransactionMapping = DBTransactionMappingStorage()
override val auditService = DummyAuditService()
override val monitoringService = MonitoringService(MetricRegistry())
override val validatedTransactions = makeTransactionStorage()
override val transactionVerifierService by lazy { makeTransactionVerifierService() }
override val networkMapCache by lazy { InMemoryNetworkMapCache(this) }
override val vaultService by lazy { NodeVaultService(this, configuration.dataSourceProperties) }
override val vaultQueryService by lazy {
HibernateVaultQueryImpl(HibernateConfiguration(schemaService), vaultService.updatesPublisher)
}
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
// the identity key. But the infrastructure to make that easy isn't here yet.
override val keyManagementService by lazy { makeKeyManagementService(identityService) }
override val schedulerService by lazy { NodeSchedulerService(this, unfinishedSchedules = busyNodeLatch) }
override val identityService by lazy {
val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
makeIdentityService(
keyStoreWrapper.keyStore.getCertificate(X509Utilities.CORDA_ROOT_CA)!! as X509Certificate,
keyStoreWrapper.certificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA),
info.legalIdentityAndCert)
}
override val attachments: AttachmentStorage get() = this@AbstractNode.attachments
override val networkService: MessagingService get() = network
override val clock: Clock get() = platformClock
override val myInfo: NodeInfo get() = info
override val schemaService by lazy { NodeSchemaService(pluginRegistries.flatMap { it.requiredSchemas }.toSet()) }
override val database: Database get() = this@AbstractNode.database
override val configuration: NodeConfiguration get() = this@AbstractNode.configuration
override fun <T : SerializeAsToken> cordaService(type: Class<T>): T {
require(type.isAnnotationPresent(CordaService::class.java)) { "${type.name} is not a Corda service" }
return cordappServices.getInstance(type) ?: throw IllegalArgumentException("Corda service ${type.name} does not exist")
}
override fun <T> startFlow(logic: FlowLogic<T>, flowInitiator: FlowInitiator): FlowStateMachineImpl<T> {
return serverThread.fetchFrom { smm.add(logic, flowInitiator) }
}
override fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? {
return flowFactories[initiatingFlowClass]
}
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
database.transaction {
super.recordTransactions(txs)
}
}
}
} }
private class KeyStoreWrapper(val keyStore: KeyStore, val storePath: Path, private val storePassword: String) { private class KeyStoreWrapper(val keyStore: KeyStore, val storePath: Path, private val storePassword: String) {

View File

@ -72,7 +72,7 @@ interface ServiceHubInternal : PluginServiceHub {
* The signatures aren't technically needed after that point, but we keep them around so that we can relay * 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. * the transaction data to other nodes that need it.
*/ */
override val validatedTransactions: TransactionStorage override val validatedTransactions: WritableTransactionStorage
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage
val monitoringService: MonitoringService val monitoringService: MonitoringService
val schemaService: SchemaService val schemaService: SchemaService
@ -89,8 +89,9 @@ interface ServiceHubInternal : PluginServiceHub {
val uploaders: List<FileUploader> val uploaders: List<FileUploader>
override fun recordTransactions(txs: Iterable<SignedTransaction>) { override fun recordTransactions(txs: Iterable<SignedTransaction>) {
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) } val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) }
require(recordedTransactions.isNotEmpty()) { "No transactions passed in for recording" }
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
if (stateMachineRunId != null) { if (stateMachineRunId != null) {
recordedTransactions.forEach { recordedTransactions.forEach {
stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id) stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id)
@ -135,6 +136,20 @@ interface ServiceHubInternal : PluginServiceHub {
fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>?
} }
/**
* Thread-safe storage of transactions.
*/
interface WritableTransactionStorage : TransactionStorage {
/**
* Add a new transaction to the store. If the store already has a transaction with the same id it will be
* overwritten.
* @param transaction The transaction to be recorded.
* @return true if the transaction was recorded successfully, false if it was already recorded.
*/
// TODO: Throw an exception if trying to add a transaction with fewer signatures than an existing entry.
fun addTransaction(transaction: SignedTransaction): Boolean
}
/** /**
* This is the interface to storage storing state machine -> recorded tx mappings. Any time a transaction is recorded * 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. * during a flow run [addMapping] should be called.

View File

@ -17,7 +17,6 @@ import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
import net.corda.node.utilities.* import net.corda.node.utilities.*
import org.apache.activemq.artemis.utils.ReusableLatch import org.apache.activemq.artemis.utils.ReusableLatch
import org.jetbrains.exposed.sql.Database
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 java.time.Instant import java.time.Instant
@ -43,7 +42,6 @@ import javax.annotation.concurrent.ThreadSafe
*/ */
@ThreadSafe @ThreadSafe
class NodeSchedulerService(private val services: ServiceHubInternal, class NodeSchedulerService(private val services: ServiceHubInternal,
private val database: Database,
private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(), private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(),
private val unfinishedSchedules: ReusableLatch = ReusableLatch()) private val unfinishedSchedules: ReusableLatch = ReusableLatch())
: SchedulerService, SingletonSerializeAsToken() { : SchedulerService, SingletonSerializeAsToken() {
@ -159,7 +157,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal,
} }
private fun onTimeReached(scheduledState: ScheduledStateRef) { private fun onTimeReached(scheduledState: ScheduledStateRef) {
database.transaction { services.database.transaction {
val scheduledFlow = getScheduledFlow(scheduledState) val scheduledFlow = getScheduledFlow(scheduledState)
if (scheduledFlow != null) { if (scheduledFlow != null) {
// TODO Because the flow is executed asynchronously, there is a small window between this tx we're in // TODO Because the flow is executed asynchronously, there is a small window between this tx we're in

View File

@ -4,9 +4,9 @@ import com.google.common.annotations.VisibleForTesting
import net.corda.core.bufferUntilSubscribed import net.corda.core.bufferUntilSubscribed
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.messaging.DataFeed import net.corda.core.messaging.DataFeed
import net.corda.core.node.services.TransactionStorage
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.utilities.* import net.corda.node.utilities.*
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.exposedLogger import org.jetbrains.exposed.sql.exposedLogger
@ -15,7 +15,7 @@ import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.util.Collections.synchronizedMap import java.util.Collections.synchronizedMap
class DBTransactionStorage : TransactionStorage, SingletonSerializeAsToken() { class DBTransactionStorage : WritableTransactionStorage, SingletonSerializeAsToken() {
private object Table : JDBCHashedTable("${NODE_DATABASE_PREFIX}transactions") { private object Table : JDBCHashedTable("${NODE_DATABASE_PREFIX}transactions") {
val txId = secureHash("tx_id") val txId = secureHash("tx_id")
val transaction = blob("transaction") val transaction = blob("transaction")

View File

@ -8,10 +8,7 @@ import com.google.common.hash.HashingInputStream
import com.google.common.io.CountingInputStream import com.google.common.io.CountingInputStream
import net.corda.core.contracts.AbstractAttachment import net.corda.core.contracts.AbstractAttachment
import net.corda.core.contracts.Attachment import net.corda.core.contracts.Attachment
import net.corda.core.createDirectory
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.div
import net.corda.core.extractZipFile
import net.corda.core.isDirectory import net.corda.core.isDirectory
import net.corda.core.node.services.AttachmentStorage import net.corda.core.node.services.AttachmentStorage
import net.corda.core.serialization.* import net.corda.core.serialization.*
@ -35,7 +32,7 @@ import javax.annotation.concurrent.ThreadSafe
* Stores attachments in H2 database. * Stores attachments in H2 database.
*/ */
@ThreadSafe @ThreadSafe
class NodeAttachmentService(override var storePath: Path, dataSourceProperties: Properties, metrics: MetricRegistry) class NodeAttachmentService(val storePath: Path, dataSourceProperties: Properties, metrics: MetricRegistry)
: AttachmentStorage, AcceptsFileUpload, SingletonSerializeAsToken() { : AttachmentStorage, AcceptsFileUpload, SingletonSerializeAsToken() {
companion object { companion object {
private val log = loggerFor<NodeAttachmentService>() private val log = loggerFor<NodeAttachmentService>()
@ -48,7 +45,6 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
var checkAttachmentsOnLoad = true var checkAttachmentsOnLoad = true
private val attachmentCount = metrics.counter("Attachments") private val attachmentCount = metrics.counter("Attachments")
@Volatile override var automaticallyExtractAttachments = false
init { init {
require(storePath.isDirectory()) { "$storePath must be a directory" } require(storePath.isDirectory()) { "$storePath must be a directory" }
@ -183,19 +179,6 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
log.info("Stored new attachment $id") log.info("Stored new attachment $id")
if (automaticallyExtractAttachments) {
val extractTo = storePath / "$id.jar"
try {
extractTo.createDirectory()
extractZipFile(ByteArrayInputStream(bytes), extractTo)
} catch(e: FileAlreadyExistsException) {
log.trace("Did not extract attachment jar to directory because it already exists")
} catch(e: Exception) {
log.error("Failed to extract attachment jar $id, ", e)
// TODO: Delete the extractTo directory here.
}
}
return id return id
} }

View File

@ -22,7 +22,6 @@ import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.messaging.StateMachineTransactionMapping import net.corda.core.messaging.StateMachineTransactionMapping
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.ServiceInfo 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.node.services.Vault
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
@ -32,6 +31,7 @@ import net.corda.core.utilities.*
import net.corda.flows.TwoPartyTradeFlow.Buyer import net.corda.flows.TwoPartyTradeFlow.Buyer
import net.corda.flows.TwoPartyTradeFlow.Seller import net.corda.flows.TwoPartyTradeFlow.Seller
import net.corda.node.internal.AbstractNode import net.corda.node.internal.AbstractNode
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.persistence.DBTransactionStorage import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.checkpoints import net.corda.node.services.persistence.checkpoints
@ -153,7 +153,7 @@ class TwoPartyTradeFlowTests {
val cashLockId = UUID.randomUUID() val cashLockId = UUID.randomUUID()
bobNode.database.transaction { bobNode.database.transaction {
// lock the cash states with an arbitrary lockId (to prevent the Buyer flow from claiming the states) // lock the cash states with an arbitrary lockId (to prevent the Buyer flow from claiming the states)
bobNode.vault.softLockReserve(cashLockId, cashStates.states.map { it.ref }.toSet()) bobNode.services.vaultService.softLockReserve(cashLockId, cashStates.states.map { it.ref }.toSet())
} }
val (bobStateMachine, aliceResult) = runBuyerAndSeller(notaryNode, aliceNode, bobNode, val (bobStateMachine, aliceResult) = runBuyerAndSeller(notaryNode, aliceNode, bobNode,
@ -284,8 +284,8 @@ class TwoPartyTradeFlowTests {
entropyRoot: BigInteger): MockNetwork.MockNode { entropyRoot: BigInteger): MockNetwork.MockNode {
return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) { return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) {
// That constructs a recording tx storage // That constructs a recording tx storage
override fun createTransactionStorage(): TransactionStorage { override fun makeTransactionStorage(): WritableTransactionStorage {
return RecordingTransactionStorage(database, super.createTransactionStorage()) return RecordingTransactionStorage(database, super.makeTransactionStorage())
} }
} }
} }
@ -311,7 +311,7 @@ class TwoPartyTradeFlowTests {
attachment(ByteArrayInputStream(stream.toByteArray())) attachment(ByteArrayInputStream(stream.toByteArray()))
} }
val extraKey = bobNode.keyManagement.keys.single() val extraKey = bobNode.services.keyManagementService.keys.single()
val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(extraKey), val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(extraKey),
DUMMY_CASH_ISSUER.party, DUMMY_CASH_ISSUER.party,
notaryNode.info.notaryIdentity).second notaryNode.info.notaryIdentity).second
@ -410,7 +410,7 @@ class TwoPartyTradeFlowTests {
attachment(ByteArrayInputStream(stream.toByteArray())) attachment(ByteArrayInputStream(stream.toByteArray()))
} }
val bobsKey = bobNode.keyManagement.keys.single() val bobsKey = bobNode.services.keyManagementService.keys.single()
val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(bobsKey), val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(bobsKey),
DUMMY_CASH_ISSUER.party, DUMMY_CASH_ISSUER.party,
notaryNode.info.notaryIdentity).second notaryNode.info.notaryIdentity).second
@ -675,7 +675,7 @@ class TwoPartyTradeFlowTests {
} }
class RecordingTransactionStorage(val database: Database, val delegate: TransactionStorage) : TransactionStorage { class RecordingTransactionStorage(val database: Database, val delegate: WritableTransactionStorage) : WritableTransactionStorage {
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> { override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {
return database.transaction { return database.transaction {
delegate.track() delegate.track()

View File

@ -18,19 +18,20 @@ import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.testing.MOCK_IDENTITY_SERVICE import net.corda.testing.MOCK_IDENTITY_SERVICE
import net.corda.testing.node.MockAttachmentStorage import net.corda.testing.node.MockAttachmentStorage
import net.corda.testing.node.MockNetworkMapCache import net.corda.testing.node.MockNetworkMapCache
import org.jetbrains.exposed.sql.Database
import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage
import net.corda.testing.node.MockTransactionStorage import net.corda.testing.node.MockTransactionStorage
import org.jetbrains.exposed.sql.Database
import java.time.Clock import java.time.Clock
open class MockServiceHubInternal( open class MockServiceHubInternal(
override val database: Database,
val customVault: VaultService? = null, val customVault: VaultService? = null,
val customVaultQuery: VaultQueryService? = null, val customVaultQuery: VaultQueryService? = null,
val keyManagement: KeyManagementService? = null, val keyManagement: KeyManagementService? = null,
val network: MessagingService? = null, val network: MessagingService? = null,
val identity: IdentityService? = MOCK_IDENTITY_SERVICE, val identity: IdentityService? = MOCK_IDENTITY_SERVICE,
override val attachments: AttachmentStorage = MockAttachmentStorage(), override val attachments: AttachmentStorage = MockAttachmentStorage(),
override val validatedTransactions: TransactionStorage = MockTransactionStorage(), override val validatedTransactions: WritableTransactionStorage = MockTransactionStorage(),
override val uploaders: List<FileUploader> = listOf<FileUploader>(), override val uploaders: List<FileUploader> = listOf<FileUploader>(),
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage(), override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage(),
val mapCache: NetworkMapCacheInternal? = null, val mapCache: NetworkMapCacheInternal? = null,
@ -59,8 +60,6 @@ open class MockServiceHubInternal(
get() = overrideClock ?: throw UnsupportedOperationException() get() = overrideClock ?: throw UnsupportedOperationException()
override val myInfo: NodeInfo override val myInfo: NodeInfo
get() = throw UnsupportedOperationException() get() = throw UnsupportedOperationException()
override val database: Database
get() = throw UnsupportedOperationException()
override val configuration: NodeConfiguration override val configuration: NodeConfiguration
get() = throw UnsupportedOperationException() get() = throw UnsupportedOperationException()
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry()) override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())

View File

@ -135,7 +135,7 @@ class NotaryChangeTests {
addOutputState(stateB, notary, encumbrance = 1) // Encumbered by stateC addOutputState(stateB, notary, encumbrance = 1) // Encumbered by stateC
} }
val stx = node.services.signInitialTransaction(tx) val stx = node.services.signInitialTransaction(tx)
node.services.recordTransactions(listOf(stx)) node.services.recordTransactions(stx)
return tx.toWireTransaction() return tx.toWireTransaction()
} }
@ -152,7 +152,7 @@ fun issueState(node: AbstractNode, notaryNode: AbstractNode): StateAndRef<*> {
val tx = DummyContract.generateInitial(Random().nextInt(), notaryNode.info.notaryIdentity, node.info.legalIdentity.ref(0)) val tx = DummyContract.generateInitial(Random().nextInt(), notaryNode.info.notaryIdentity, node.info.legalIdentity.ref(0))
val signedByNode = node.services.signInitialTransaction(tx) val signedByNode = node.services.signInitialTransaction(tx)
val stx = notaryNode.services.addSignature(signedByNode, notaryNode.services.notaryIdentityKey) val stx = notaryNode.services.addSignature(signedByNode, notaryNode.services.notaryIdentityKey)
node.services.recordTransactions(listOf(stx)) node.services.recordTransactions(stx)
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0)) return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
} }
@ -163,8 +163,8 @@ fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode, notaryNode: A
val signedByA = nodeA.services.signInitialTransaction(tx) val signedByA = nodeA.services.signInitialTransaction(tx)
val signedByAB = nodeB.services.addSignature(signedByA) val signedByAB = nodeB.services.addSignature(signedByA)
val stx = notaryNode.services.addSignature(signedByAB, notaryNode.services.notaryIdentityKey) val stx = notaryNode.services.addSignature(signedByAB, notaryNode.services.notaryIdentityKey)
nodeA.services.recordTransactions(listOf(stx)) nodeA.services.recordTransactions(stx)
nodeB.services.recordTransactions(listOf(stx)) nodeB.services.recordTransactions(stx)
val stateAndRef = StateAndRef(state, StateRef(stx.id, 0)) val stateAndRef = StateAndRef(state, StateRef(stx.id, 0))
return stateAndRef return stateAndRef
} }
@ -173,6 +173,6 @@ fun issueInvalidState(node: AbstractNode, notary: Party): StateAndRef<*> {
val tx = DummyContract.generateInitial(Random().nextInt(), notary, node.info.legalIdentity.ref(0)) val tx = DummyContract.generateInitial(Random().nextInt(), notary, node.info.legalIdentity.ref(0))
tx.addTimeWindow(Instant.now(), 30.seconds) tx.addTimeWindow(Instant.now(), 30.seconds)
val stx = node.services.signInitialTransaction(tx) val stx = node.services.signInitialTransaction(tx)
node.services.recordTransactions(listOf(stx)) node.services.recordTransactions(stx)
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0)) return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
} }

View File

@ -87,11 +87,11 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
InMemoryMessagingNetwork.PeerHandle(0, nullIdentity), InMemoryMessagingNetwork.PeerHandle(0, nullIdentity),
AffinityExecutor.ServiceAffinityExecutor("test", 1), AffinityExecutor.ServiceAffinityExecutor("test", 1),
database) database)
services = object : MockServiceHubInternal(overrideClock = testClock, keyManagement = kms, network = mockMessagingService), TestReference { services = object : MockServiceHubInternal(database, overrideClock = testClock, keyManagement = kms, network = mockMessagingService), TestReference {
override val vaultService: VaultService = NodeVaultService(this, dataSourceProps) override val vaultService: VaultService = NodeVaultService(this, dataSourceProps)
override val testReference = this@NodeSchedulerServiceTest override val testReference = this@NodeSchedulerServiceTest
} }
scheduler = NodeSchedulerService(services, database, schedulerGatedExecutor) scheduler = NodeSchedulerService(services, schedulerGatedExecutor)
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1) smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)
val mockSMM = StateMachineManager(services, DBCheckpointStorage(), smmExecutor, database) val mockSMM = StateMachineManager(services, DBCheckpointStorage(), smmExecutor, database)
mockSMM.changes.subscribe { change -> mockSMM.changes.subscribe { change ->

View File

@ -38,13 +38,13 @@ class InMemoryNetworkMapCacheTest {
mockNet.runNetwork() mockNet.runNetwork()
// Node A currently knows only about itself, so this returns node A // Node A currently knows only about itself, so this returns node A
assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeA.info) assertEquals(nodeA.services.networkMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeA.info)
nodeA.database.transaction { nodeA.database.transaction {
nodeA.netMapCache.addNode(nodeB.info) nodeA.services.networkMapCache.addNode(nodeB.info)
} }
// The details of node B write over those for node A // The details of node B write over those for node A
assertEquals(nodeA.netMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeB.info) assertEquals(nodeA.services.networkMapCache.getNodeByLegalIdentityKey(nodeA.info.legalIdentity.owningKey), nodeB.info)
} }
@Test @Test

View File

@ -131,7 +131,7 @@ class NotaryServiceTests {
val tx = DummyContract.generateInitial(Random().nextInt(), notaryNode.info.notaryIdentity, node.info.legalIdentity.ref(0)) val tx = DummyContract.generateInitial(Random().nextInt(), notaryNode.info.notaryIdentity, node.info.legalIdentity.ref(0))
val signedByNode = node.services.signInitialTransaction(tx) val signedByNode = node.services.signInitialTransaction(tx)
val stx = notaryNode.services.addSignature(signedByNode, notaryNode.services.notaryIdentityKey) val stx = notaryNode.services.addSignature(signedByNode, notaryNode.services.notaryIdentityKey)
node.services.recordTransactions(listOf(stx)) node.services.recordTransactions(stx)
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0)) return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
} }
} }

View File

@ -82,7 +82,7 @@ class ValidatingNotaryServiceTests {
val tx = DummyContract.generateInitial(Random().nextInt(), notaryNode.info.notaryIdentity, node.info.legalIdentity.ref(0)) val tx = DummyContract.generateInitial(Random().nextInt(), notaryNode.info.notaryIdentity, node.info.legalIdentity.ref(0))
val signedByNode = node.services.signInitialTransaction(tx) val signedByNode = node.services.signInitialTransaction(tx)
val stx = notaryNode.services.addSignature(signedByNode, notaryNode.services.notaryIdentityKey) val stx = notaryNode.services.addSignature(signedByNode, notaryNode.services.notaryIdentityKey)
node.services.recordTransactions(listOf(stx)) node.services.recordTransactions(stx)
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0)) return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
} }
} }

View File

@ -405,7 +405,7 @@ class NodeVaultServiceTest {
} }
val usefulTX = megaCorpServices.signInitialTransaction(usefulBuilder) val usefulTX = megaCorpServices.signInitialTransaction(usefulBuilder)
services.recordTransactions(listOf(usefulTX)) services.recordTransactions(usefulTX)
vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 1") vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 1")
vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 2") vaultSvc.addNoteToTransaction(usefulTX.id, "USD Sample Note 2")
@ -418,7 +418,7 @@ class NodeVaultServiceTest {
} }
val anotherTX = megaCorpServices.signInitialTransaction(anotherBuilder) val anotherTX = megaCorpServices.signInitialTransaction(anotherBuilder)
services.recordTransactions(listOf(anotherTX)) services.recordTransactions(anotherTX)
vaultSvc.addNoteToTransaction(anotherTX.id, "GPB Sample Note 1") vaultSvc.addNoteToTransaction(anotherTX.id, "GPB Sample Note 1")
assertEquals(1, vaultSvc.getTransactionNotes(anotherTX.id).count()) assertEquals(1, vaultSvc.getTransactionNotes(anotherTX.id).count())

View File

@ -167,7 +167,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
val serviceProviders: List<SimulatedNode> = listOf(notary, ratesOracle, networkMap) val serviceProviders: List<SimulatedNode> = listOf(notary, ratesOracle, networkMap)
val banks: List<SimulatedNode> = bankFactory.createAll() val banks: List<SimulatedNode> = bankFactory.createAll()
val clocks = (serviceProviders + regulators + banks).map { it.services.clock as TestClock } val clocks = (serviceProviders + regulators + banks).map { it.platformClock as TestClock }
// These are used from the network visualiser tool. // These are used from the network visualiser tool.
private val _allFlowSteps = PublishSubject.create<Pair<SimulatedNode, ProgressTracker.Change>>() private val _allFlowSteps = PublishSubject.create<Pair<SimulatedNode, ProgressTracker.Change>>()

View File

@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.contracts.CommercialPaper import net.corda.contracts.CommercialPaper
import net.corda.core.contracts.Amount import net.corda.core.contracts.Amount
import net.corda.core.contracts.TransactionGraphSearch import net.corda.core.contracts.TransactionGraphSearch
import net.corda.core.div
import net.corda.core.flows.FlowLogic import net.corda.core.flows.FlowLogic
import net.corda.core.flows.InitiatedBy import net.corda.core.flows.InitiatedBy
import net.corda.core.identity.Party import net.corda.core.identity.Party
@ -39,7 +38,7 @@ class BuyerFlow(val otherParty: Party) : FlowLogic<Unit>() {
// This invokes the trading flow and out pops our finished transaction. // This invokes the trading flow and out pops our finished transaction.
val tradeTX: SignedTransaction = subFlow(buyer) val tradeTX: SignedTransaction = subFlow(buyer)
// TODO: This should be moved into the flow itself. // TODO: This should be moved into the flow itself.
serviceHub.recordTransactions(listOf(tradeTX)) serviceHub.recordTransactions(tradeTX)
println("Purchase complete - we are a happy customer! Final transaction is: " + println("Purchase complete - we are a happy customer! Final transaction is: " +
"\n\n${Emoji.renderIfSupported(tradeTX.tx)}") "\n\n${Emoji.renderIfSupported(tradeTX.tx)}")
@ -61,18 +60,11 @@ class BuyerFlow(val otherParty: Party) : FlowLogic<Unit>() {
val cpIssuance = search.call().single() val cpIssuance = search.call().single()
// Buyer will fetch the attachment from the seller automatically when it resolves the transaction. // 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.attachments).let {
it.automaticallyExtractAttachments = true
it.storePath
}
cpIssuance.attachments.first().let { cpIssuance.attachments.first().let {
val p = attachmentsPath / "$it.jar"
println(""" println("""
The issuance of the commercial paper came with an attachment. You can find it expanded in this directory: The issuance of the commercial paper came with an attachment. You can find it in the attachments directory: $it.jar
$p
${Emoji.renderIfSupported(cpIssuance)}""") ${Emoji.renderIfSupported(cpIssuance)}""")
} }

View File

@ -15,9 +15,11 @@ import net.corda.core.messaging.MessageRecipients
import net.corda.core.messaging.RPCOps import net.corda.core.messaging.RPCOps
import net.corda.core.messaging.SingleMessageRecipient import net.corda.core.messaging.SingleMessageRecipient
import net.corda.core.node.CordaPluginRegistry import net.corda.core.node.CordaPluginRegistry
import net.corda.core.node.WorldMapLocation
import net.corda.core.node.ServiceEntry import net.corda.core.node.ServiceEntry
import net.corda.core.node.services.* import net.corda.core.node.WorldMapLocation
import net.corda.core.node.services.IdentityService
import net.corda.core.node.services.KeyManagementService
import net.corda.core.node.services.ServiceInfo
import net.corda.core.utilities.DUMMY_NOTARY_KEY import net.corda.core.utilities.DUMMY_NOTARY_KEY
import net.corda.core.utilities.getTestPartyAndCertificate import net.corda.core.utilities.getTestPartyAndCertificate
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
@ -32,7 +34,6 @@ import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.InMemoryTransactionVerifierService import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.node.services.transactions.ValidatingNotaryService import net.corda.node.services.transactions.ValidatingNotaryService
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.testing.MOCK_VERSION_INFO import net.corda.testing.MOCK_VERSION_INFO
@ -181,8 +182,6 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
trustRoot = trustRoot, caCertificates = *caCertificates) trustRoot = trustRoot, caCertificates = *caCertificates)
} }
override fun makeVaultService(dataSourceProperties: Properties): VaultService = NodeVaultService(services, dataSourceProperties)
override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService { override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService {
return E2ETestKeyManagementService(identityService, partyKeys + (overrideServices?.values ?: emptySet())) return E2ETestKeyManagementService(identityService, partyKeys + (overrideServices?.values ?: emptySet()))
} }

View File

@ -16,6 +16,7 @@ import net.corda.core.utilities.DUMMY_CA
import net.corda.core.utilities.getTestPartyAndCertificate import net.corda.core.utilities.getTestPartyAndCertificate
import net.corda.flows.AnonymisedIdentity import net.corda.flows.AnonymisedIdentity
import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage import net.corda.node.services.api.StateMachineRecordedTransactionMappingStorage
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.database.HibernateConfiguration import net.corda.node.services.database.HibernateConfiguration
import net.corda.node.services.identity.InMemoryIdentityService import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.services.keys.freshCertificate import net.corda.node.services.keys.freshCertificate
@ -35,8 +36,6 @@ import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.nio.file.Path
import java.nio.file.Paths
import java.security.KeyPair import java.security.KeyPair
import java.security.PrivateKey import java.security.PrivateKey
import java.security.PublicKey import java.security.PublicKey
@ -66,7 +65,7 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
} }
override val attachments: AttachmentStorage = MockAttachmentStorage() override val attachments: AttachmentStorage = MockAttachmentStorage()
override val validatedTransactions: TransactionStorage = MockTransactionStorage() override val validatedTransactions: WritableTransactionStorage = MockTransactionStorage()
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage() val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage()
override final val identityService: IdentityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate) override final val identityService: IdentityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate)
override val keyManagementService: KeyManagementService = MockKeyManagementService(identityService, *keys) override val keyManagementService: KeyManagementService = MockKeyManagementService(identityService, *keys)
@ -124,10 +123,8 @@ class MockKeyManagementService(val identityService: IdentityService,
} }
} }
class MockAttachmentStorage : AttachmentStorage { class MockAttachmentStorage : AttachmentStorage, SingletonSerializeAsToken() {
val files = HashMap<SecureHash, ByteArray>() val files = HashMap<SecureHash, ByteArray>()
override var automaticallyExtractAttachments = false
override var storePath: Path = Paths.get("")
override fun openAttachment(id: SecureHash): Attachment? { override fun openAttachment(id: SecureHash): Attachment? {
val f = files[id] ?: return null val f = files[id] ?: return null
@ -159,7 +156,7 @@ class MockStateMachineRecordedTransactionMappingStorage(
val storage: StateMachineRecordedTransactionMappingStorage = InMemoryStateMachineRecordedTransactionMappingStorage() val storage: StateMachineRecordedTransactionMappingStorage = InMemoryStateMachineRecordedTransactionMappingStorage()
) : StateMachineRecordedTransactionMappingStorage by storage ) : StateMachineRecordedTransactionMappingStorage by storage
open class MockTransactionStorage : TransactionStorage { open class MockTransactionStorage : WritableTransactionStorage, SingletonSerializeAsToken() {
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> { override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {
return DataFeed(txns.values.toList(), _updatesPublisher) return DataFeed(txns.values.toList(), _updatesPublisher)
} }