mirror of
https://github.com/corda/corda.git
synced 2025-01-22 20:38:05 +00:00
Clean up of ServiceHubInternal, including how it's created in AbstractNode
This commit is contained in:
parent
56402c744a
commit
46e23b7716
@ -1,7 +1,7 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
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.WireTransaction
|
||||
import java.util.*
|
||||
@ -18,7 +18,7 @@ import java.util.concurrent.Callable
|
||||
* @param transactions map of transaction id to [SignedTransaction].
|
||||
* @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>> {
|
||||
class Query(
|
||||
val withCommandOfType: Class<out CommandData>? = null,
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.node
|
||||
|
||||
import com.google.common.collect.Lists
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.node.services.*
|
||||
@ -17,8 +18,10 @@ import java.time.Clock
|
||||
*/
|
||||
interface ServicesForResolution {
|
||||
val identityService: IdentityService
|
||||
|
||||
/** 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,12 +43,13 @@ interface ServiceHub : ServicesForResolution {
|
||||
val vaultService: VaultService
|
||||
val vaultQueryService: VaultQueryService
|
||||
val keyManagementService: KeyManagementService
|
||||
|
||||
/**
|
||||
* 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 validatedTransactions: TransactionStorage
|
||||
|
||||
val networkMapCache: NetworkMapCache
|
||||
val transactionVerifierService: TransactionVerifierService
|
||||
@ -60,41 +64,41 @@ interface ServiceHub : ServicesForResolution {
|
||||
fun <T : SerializeAsToken> cordaService(type: Class<T>): T
|
||||
|
||||
/**
|
||||
* Given a [SignedTransaction], writes it to the local storage for validated transactions and then
|
||||
* sends them to the vault for further processing. Expects to be run within a database transaction.
|
||||
* Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for
|
||||
* further processing. This is expected to be run within a database transaction.
|
||||
*
|
||||
* @param txs The transactions to record.
|
||||
*/
|
||||
// TODO: Make this take a single tx.
|
||||
fun recordTransactions(txs: Iterable<SignedTransaction>)
|
||||
|
||||
/**
|
||||
* Given some [SignedTransaction]s, writes them to the local storage for validated transactions and then
|
||||
* sends them to the vault for further processing.
|
||||
*
|
||||
* @param txs The transactions to record.
|
||||
* Stores the given [SignedTransaction]s in the local transaction storage and then sends them to the vault for
|
||||
* further processing. This is expected to be run within a database transaction.
|
||||
*/
|
||||
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].
|
||||
*
|
||||
* @throws TransactionResolutionException if the [StateRef] points to a non-existent transaction.
|
||||
* @throws TransactionResolutionException if [stateRef] points to a non-existent transaction.
|
||||
*/
|
||||
@Throws(TransactionResolutionException::class)
|
||||
override fun loadState(stateRef: StateRef): TransactionState<*> {
|
||||
val definingTx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||
return definingTx.tx.outputs[stateRef.index]
|
||||
val stx = validatedTransactions.getTransaction(stateRef.txhash) ?: throw TransactionResolutionException(stateRef.txhash)
|
||||
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> {
|
||||
val definingTx = validatedTransactions.getTransaction(ref.txhash) ?: throw TransactionResolutionException(ref.txhash)
|
||||
return definingTx.tx.outRef<T>(ref.index)
|
||||
@Throws(TransactionResolutionException::class)
|
||||
fun <T : ContractState> toStateAndRef(stateRef: StateRef): StateAndRef<T> {
|
||||
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.
|
||||
* 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
|
||||
* 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
|
||||
* will be used to create the signature.
|
||||
*/
|
||||
@ -114,8 +118,8 @@ interface ServiceHub : ServicesForResolution {
|
||||
* otherwise an IllegalArgumentException will be thrown.
|
||||
* 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
|
||||
* the matching [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
|
||||
* the matching [java.security.PrivateKey] will be looked up internally and used to sign.
|
||||
* 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.
|
||||
*/
|
||||
val notaryIdentityKey: PublicKey get() = this.myInfo.notaryIdentity.owningKey
|
||||
@ -125,7 +129,7 @@ interface ServiceHub : ServicesForResolution {
|
||||
* using keys stored inside the node.
|
||||
* @param builder The [TransactionBuilder] to seal with the node's signature.
|
||||
* 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
|
||||
* to sign with.
|
||||
* @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.
|
||||
* @param builder The [TransactionBuilder] to seal with the node's signature.
|
||||
* 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.
|
||||
* @return Returns a [SignedTransaction] with the new node signature attached.
|
||||
*/
|
||||
fun signInitialTransaction(builder: TransactionBuilder, signingPubKeys: Iterable<PublicKey>): SignedTransaction {
|
||||
var stx: SignedTransaction? = null
|
||||
for (pubKey in signingPubKeys) {
|
||||
stx = if (stx == null) {
|
||||
signInitialTransaction(builder, pubKey)
|
||||
} else {
|
||||
addSignature(stx, pubKey)
|
||||
}
|
||||
val it = signingPubKeys.iterator()
|
||||
var stx = signInitialTransaction(builder, it.next())
|
||||
while (it.hasNext()) {
|
||||
stx = addSignature(stx, it.next())
|
||||
}
|
||||
return stx!!
|
||||
return stx
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to create an additional signature for an existing (partially) [SignedTransaction].
|
||||
* @param signedTransaction The [SignedTransaction] to which the signature will apply.
|
||||
* @param publicKey The [PublicKey] matching to a signing [PrivateKey] hosted in the node.
|
||||
* If the [PublicKey] is actually a [CompositeKey] the first leaf key found locally will be used for signing.
|
||||
* @return The [DigitalSignature.WithKey] generated by signing with the internally held [PrivateKey].
|
||||
* @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
|
||||
* If the [PublicKey] is actually a [net.corda.core.crypto.CompositeKey] the first leaf key found locally will be used
|
||||
* 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
|
||||
@ -181,16 +185,21 @@ interface ServiceHub : ServicesForResolution {
|
||||
* @param signedTransaction The SignedTransaction to which the signature will apply.
|
||||
* @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].
|
||||
* @param signedTransaction The [SignedTransaction] to which the signature will be added.
|
||||
* @param publicKey The [PublicKey] matching to a signing [PrivateKey] hosted in the node.
|
||||
* If the [PublicKey] is actually a [CompositeKey] the first leaf key found locally will be used for signing.
|
||||
* @param publicKey The [PublicKey] matching to a signing [java.security.PrivateKey] hosted in the node.
|
||||
* 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.
|
||||
*/
|
||||
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]
|
||||
|
@ -2,21 +2,14 @@ package net.corda.core.node.services
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import java.io.IOException
|
||||
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.
|
||||
*/
|
||||
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
|
||||
* 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
|
||||
* 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
|
||||
* to the raw byte stream is required.
|
||||
* Note that you should not pass a [java.util.jar.JarInputStream] into this method and it will throw if you do, because
|
||||
* access to the raw byte stream is required.
|
||||
*
|
||||
* @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(FileAlreadyExistsException::class, IOException::class)
|
||||
fun importAttachment(jar: InputStream): SecureHash
|
||||
}
|
||||
|
||||
|
@ -8,7 +8,7 @@ import rx.Observable
|
||||
/**
|
||||
* Thread-safe storage of transactions.
|
||||
*/
|
||||
interface ReadOnlyTransactionStorage {
|
||||
interface TransactionStorage {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
}
|
||||
}
|
@ -74,16 +74,15 @@ class FinalityFlow(val transactions: Iterable<SignedTransaction>,
|
||||
|
||||
@Suspendable
|
||||
private fun notariseAndRecord(stxnsAndParties: List<Pair<SignedTransaction, Set<Party>>>): List<Pair<SignedTransaction, Set<Party>>> {
|
||||
return stxnsAndParties.map { pair ->
|
||||
val stx = pair.first
|
||||
return stxnsAndParties.map { (stx, parties) ->
|
||||
val notarised = if (needsNotarySignature(stx)) {
|
||||
val notarySignatures = subFlow(NotaryFlow.Client(stx))
|
||||
stx + notarySignatures
|
||||
} else {
|
||||
stx
|
||||
}
|
||||
serviceHub.recordTransactions(listOf(notarised))
|
||||
Pair(notarised, pair.second)
|
||||
serviceHub.recordTransactions(notarised)
|
||||
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>>> {
|
||||
return ltxns.map { pair ->
|
||||
val (stx, ltx) = pair
|
||||
return ltxns.map { (stx, ltx) ->
|
||||
// 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 }
|
||||
// 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?
|
||||
|
@ -178,14 +178,14 @@ class ContractUpgradeFlowTest {
|
||||
mockNet.runNetwork()
|
||||
val stx = result.getOrThrow().stx
|
||||
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.")
|
||||
// Starts contract upgrade flow.
|
||||
val upgradeResult = a.services.startFlow(ContractUpgradeFlow(stateAndRef, CashV2::class.java)).resultFuture
|
||||
mockNet.runNetwork()
|
||||
upgradeResult.getOrThrow()
|
||||
// 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.")
|
||||
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.")
|
||||
|
@ -33,8 +33,8 @@ class CashPaymentFlowTests {
|
||||
bankOfCorda = bankOfCordaNode.info.legalIdentity
|
||||
|
||||
notaryNode.registerInitiatedFlow(TxKeyFlow.Provider::class.java)
|
||||
notaryNode.identity.registerIdentity(bankOfCordaNode.info.legalIdentityAndCert)
|
||||
bankOfCordaNode.identity.registerIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
notaryNode.services.identityService.registerIdentity(bankOfCordaNode.info.legalIdentityAndCert)
|
||||
bankOfCordaNode.services.identityService.registerIdentity(notaryNode.info.legalIdentityAndCert)
|
||||
val future = bankOfCordaNode.services.startFlow(CashIssueFlow(initialBalance, ref,
|
||||
bankOfCorda,
|
||||
notary)).resultFuture
|
||||
|
@ -5,8 +5,8 @@ import net.corda.core.contracts.DummyContract
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionType
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.getOrThrow
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.map
|
||||
import net.corda.core.utilities.DUMMY_BANK_A
|
||||
import net.corda.flows.NotaryError
|
||||
@ -26,28 +26,28 @@ class RaftNotaryServiceTests : NodeBasedTest() {
|
||||
|
||||
@Test
|
||||
fun `detect double spend`() {
|
||||
val (masterNode, alice) = Futures.allAsList(
|
||||
startNotaryCluster(notaryName, 3).map { it.first() },
|
||||
startNode(DUMMY_BANK_A.name)
|
||||
val (bankA) = Futures.allAsList(
|
||||
startNode(DUMMY_BANK_A.name),
|
||||
startNotaryCluster(notaryName, 3).map { it.first() }
|
||||
).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 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()
|
||||
|
||||
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)
|
||||
this
|
||||
}
|
||||
val secondSpendTx = alice.services.signInitialTransaction(secondSpendBuilder)
|
||||
val secondSpend = alice.services.startFlow(NotaryFlow.Client(secondSpendTx))
|
||||
val secondSpendTx = bankA.services.signInitialTransaction(secondSpendBuilder)
|
||||
val secondSpend = bankA.services.startFlow(NotaryFlow.Client(secondSpendTx))
|
||||
|
||||
val ex = assertFailsWith(NotaryException::class) { secondSpend.resultFuture.getOrThrow() }
|
||||
val error = ex.error as NotaryError.Conflict
|
||||
@ -58,7 +58,7 @@ class RaftNotaryServiceTests : NodeBasedTest() {
|
||||
return node.database.transaction {
|
||||
val builder = DummyContract.generateInitial(Random().nextInt(), notary, node.info.legalIdentity.ref(0))
|
||||
val stx = node.services.signInitialTransaction(builder)
|
||||
node.services.recordTransactions(listOf(stx))
|
||||
node.services.recordTransactions(stx)
|
||||
StateAndRef(builder.outputStates().first(), StateRef(stx.id, 0))
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,6 @@ import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.services.*
|
||||
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.SingletonSerializeAsToken
|
||||
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.network.InMemoryNetworkMapCache
|
||||
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.NodeRegistration
|
||||
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<*>>()
|
||||
protected val partyKeys = mutableSetOf<KeyPair>()
|
||||
|
||||
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 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] }
|
||||
}
|
||||
val services: ServiceHubInternal get() = _services
|
||||
|
||||
private lateinit var _services: ServiceHubInternalImpl
|
||||
lateinit var info: NodeInfo
|
||||
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
|
||||
var inNodeNetworkMapService: NetworkMapService? = null
|
||||
lateinit var txVerifierService: TransactionVerifierService
|
||||
lateinit var identity: IdentityService
|
||||
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?>()
|
||||
lateinit var database: Database
|
||||
protected var dbCloser: (() -> Any?)? = null
|
||||
private lateinit var rpcFlows: List<Class<out FlowLogic<*>>>
|
||||
|
||||
var isPreviousCheckpointsPresent = false
|
||||
private set
|
||||
@ -215,6 +155,10 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
/** 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 fun findMyLocation(): WorldMapLocation? {
|
||||
return configuration.myLegalName.locationOrNull?.let { CityDatabase[it] }
|
||||
}
|
||||
|
||||
open fun start(): AbstractNode {
|
||||
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.
|
||||
initialiseDatabasePersistence {
|
||||
val keyStoreWrapper = KeyStoreWrapper(configuration.trustStoreFile, configuration.trustStorePassword)
|
||||
val tokenizableServices = makeServices(keyStoreWrapper)
|
||||
val tokenizableServices = makeServices()
|
||||
|
||||
smm = StateMachineManager(services,
|
||||
checkpointStorage,
|
||||
@ -266,9 +209,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
if (scanResult != null) {
|
||||
installCordaServices(scanResult)
|
||||
registerInitiatedFlows(scanResult)
|
||||
rpcFlows = findRPCFlows(scanResult)
|
||||
} else {
|
||||
rpcFlows = emptyList()
|
||||
findRPCFlows(scanResult)
|
||||
}
|
||||
|
||||
// TODO: Investigate having class path scanning find this flow
|
||||
@ -283,7 +224,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
smm.start()
|
||||
// Shut down the SMM so no Fibers are scheduled.
|
||||
runOnStop += { smm.stop(acceptableLiveFiberCountOnStop()) }
|
||||
scheduler.start()
|
||||
_services.schedulerService.start()
|
||||
}
|
||||
started = true
|
||||
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 {
|
||||
val serviceType = getServiceType(it)
|
||||
if (serviceType != null && info.serviceIdentities(serviceType).isEmpty()) {
|
||||
@ -434,19 +375,21 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
return observable
|
||||
}
|
||||
|
||||
private fun findRPCFlows(scanResult: ScanResult): List<Class<out FlowLogic<*>>> {
|
||||
private fun findRPCFlows(scanResult: ScanResult) {
|
||||
fun Class<out FlowLogic<*>>.isUserInvokable(): Boolean {
|
||||
return isPublic(modifiers) && !isLocalClass && !isAnonymousClass && (!isMemberClass || isStatic(modifiers))
|
||||
}
|
||||
|
||||
return scanResult.getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class).filter { it.isUserInvokable() } +
|
||||
// Add any core flows here
|
||||
listOf(
|
||||
ContractUpgradeFlow::class.java,
|
||||
// TODO Remove all Cash flows from default list once they are split into separate CorDapp.
|
||||
CashIssueFlow::class.java,
|
||||
CashExitFlow::class.java,
|
||||
CashPaymentFlow::class.java)
|
||||
_services.rpcFlows += scanResult
|
||||
.getClassesWithAnnotation(FlowLogic::class, StartableByRPC::class)
|
||||
.filter { it.isUserInvokable() } +
|
||||
// Add any core flows here
|
||||
listOf(
|
||||
ContractUpgradeFlow::class.java,
|
||||
// TODO Remove all Cash flows from default list once they are split into separate CorDapp.
|
||||
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.
|
||||
* Returns a list of tokenizable services to be added to the serialisation context.
|
||||
*/
|
||||
private fun makeServices(keyStoreWrapper: KeyStoreWrapper): MutableList<Any> {
|
||||
val keyStore = keyStoreWrapper.keyStore
|
||||
attachments = createAttachmentStorage()
|
||||
transactions = createTransactionStorage()
|
||||
transactionMappings = DBTransactionMappingStorage()
|
||||
private fun makeServices(): MutableList<Any> {
|
||||
checkpointStorage = DBCheckpointStorage()
|
||||
netMapCache = InMemoryNetworkMapCache(services)
|
||||
_services = ServiceHubInternalImpl()
|
||||
attachments = createAttachmentStorage()
|
||||
network = makeMessagingService()
|
||||
schemas = makeSchemaService()
|
||||
vault = makeVaultService(configuration.dataSourceProperties)
|
||||
vaultQuery = makeVaultQueryService(schemas)
|
||||
txVerifierService = makeTransactionVerifierService()
|
||||
auditService = DummyAuditService()
|
||||
|
||||
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)
|
||||
return tokenizableServices
|
||||
}
|
||||
|
||||
protected open fun createTransactionStorage(): TransactionStorage = DBTransactionStorage()
|
||||
protected open fun makeTransactionStorage(): WritableTransactionStorage = DBTransactionStorage()
|
||||
|
||||
private fun scanCordapps(): ScanResult? {
|
||||
val scanPackage = System.getProperty("net.corda.node.cordapp.scan.package")
|
||||
@ -560,14 +487,15 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
}
|
||||
|
||||
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() {
|
||||
VaultSoftLockManager(vault, smm)
|
||||
VaultSoftLockManager(services.vaultService, smm)
|
||||
CashBalanceAsMetricsObserver(services, database)
|
||||
ScheduledActivityObserver(services)
|
||||
HibernateObserver(vault.rawUpdates, HibernateConfiguration(schemas))
|
||||
HibernateObserver(services.vaultService.rawUpdates, HibernateConfiguration(services.schemaService))
|
||||
}
|
||||
|
||||
private fun makeInfo(): NodeInfo {
|
||||
@ -684,7 +612,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
val expires = instant + NetworkMapService.DEFAULT_EXPIRATION_PERIOD
|
||||
val reg = NodeRegistration(info, instant.toEpochMilli(), ADD, expires)
|
||||
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)
|
||||
}
|
||||
|
||||
@ -735,7 +663,7 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
.toTypedArray()
|
||||
val service = InMemoryIdentityService(setOf(info.legalIdentityAndCert), trustRoot = trustRoot, caCertificates = *caCertificates)
|
||||
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
|
||||
if (mapChange is MapChange.Added) {
|
||||
service.registerIdentity(mapChange.node.legalIdentityAndCert)
|
||||
@ -744,13 +672,6 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
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
|
||||
|
||||
open fun stop() {
|
||||
@ -844,6 +765,60 @@ abstract class AbstractNode(open val configuration: NodeConfiguration,
|
||||
val attachmentsDir = (configuration.baseDirectory / "attachments").createDirectories()
|
||||
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) {
|
||||
|
@ -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 transaction data to other nodes that need it.
|
||||
*/
|
||||
override val validatedTransactions: TransactionStorage
|
||||
override val validatedTransactions: WritableTransactionStorage
|
||||
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage
|
||||
val monitoringService: MonitoringService
|
||||
val schemaService: SchemaService
|
||||
@ -89,8 +89,9 @@ interface ServiceHubInternal : PluginServiceHub {
|
||||
val uploaders: List<FileUploader>
|
||||
|
||||
override fun recordTransactions(txs: Iterable<SignedTransaction>) {
|
||||
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
|
||||
val recordedTransactions = txs.filter { validatedTransactions.addTransaction(it) }
|
||||
require(recordedTransactions.isNotEmpty()) { "No transactions passed in for recording" }
|
||||
val stateMachineRunId = FlowStateMachineImpl.currentStateMachine()?.id
|
||||
if (stateMachineRunId != null) {
|
||||
recordedTransactions.forEach {
|
||||
stateMachineRecordedTransactionMapping.addMapping(stateMachineRunId, it.id)
|
||||
@ -135,6 +136,20 @@ interface ServiceHubInternal : PluginServiceHub {
|
||||
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
|
||||
* during a flow run [addMapping] should be called.
|
||||
|
@ -17,7 +17,6 @@ import net.corda.node.services.api.ServiceHubInternal
|
||||
import net.corda.node.services.statemachine.FlowLogicRefFactoryImpl
|
||||
import net.corda.node.utilities.*
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.statements.InsertStatement
|
||||
import java.time.Instant
|
||||
@ -43,7 +42,6 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
*/
|
||||
@ThreadSafe
|
||||
class NodeSchedulerService(private val services: ServiceHubInternal,
|
||||
private val database: Database,
|
||||
private val schedulerTimerExecutor: Executor = Executors.newSingleThreadExecutor(),
|
||||
private val unfinishedSchedules: ReusableLatch = ReusableLatch())
|
||||
: SchedulerService, SingletonSerializeAsToken() {
|
||||
@ -159,7 +157,7 @@ class NodeSchedulerService(private val services: ServiceHubInternal,
|
||||
}
|
||||
|
||||
private fun onTimeReached(scheduledState: ScheduledStateRef) {
|
||||
database.transaction {
|
||||
services.database.transaction {
|
||||
val scheduledFlow = getScheduledFlow(scheduledState)
|
||||
if (scheduledFlow != null) {
|
||||
// TODO Because the flow is executed asynchronously, there is a small window between this tx we're in
|
||||
|
@ -4,9 +4,9 @@ import com.google.common.annotations.VisibleForTesting
|
||||
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.services.api.WritableTransactionStorage
|
||||
import net.corda.node.utilities.*
|
||||
import org.jetbrains.exposed.sql.ResultRow
|
||||
import org.jetbrains.exposed.sql.exposedLogger
|
||||
@ -15,7 +15,7 @@ import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
import java.util.Collections.synchronizedMap
|
||||
|
||||
class DBTransactionStorage : TransactionStorage, SingletonSerializeAsToken() {
|
||||
class DBTransactionStorage : WritableTransactionStorage, SingletonSerializeAsToken() {
|
||||
private object Table : JDBCHashedTable("${NODE_DATABASE_PREFIX}transactions") {
|
||||
val txId = secureHash("tx_id")
|
||||
val transaction = blob("transaction")
|
||||
|
@ -8,10 +8,7 @@ import com.google.common.hash.HashingInputStream
|
||||
import com.google.common.io.CountingInputStream
|
||||
import net.corda.core.contracts.AbstractAttachment
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.createDirectory
|
||||
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.node.services.AttachmentStorage
|
||||
import net.corda.core.serialization.*
|
||||
@ -35,7 +32,7 @@ import javax.annotation.concurrent.ThreadSafe
|
||||
* Stores attachments in H2 database.
|
||||
*/
|
||||
@ThreadSafe
|
||||
class NodeAttachmentService(override var storePath: Path, dataSourceProperties: Properties, metrics: MetricRegistry)
|
||||
class NodeAttachmentService(val storePath: Path, dataSourceProperties: Properties, metrics: MetricRegistry)
|
||||
: AttachmentStorage, AcceptsFileUpload, SingletonSerializeAsToken() {
|
||||
companion object {
|
||||
private val log = loggerFor<NodeAttachmentService>()
|
||||
@ -48,7 +45,6 @@ class NodeAttachmentService(override var storePath: Path, dataSourceProperties:
|
||||
var checkAttachmentsOnLoad = true
|
||||
|
||||
private val attachmentCount = metrics.counter("Attachments")
|
||||
@Volatile override var automaticallyExtractAttachments = false
|
||||
|
||||
init {
|
||||
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")
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,6 @@ import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.messaging.StateMachineTransactionMapping
|
||||
import net.corda.core.node.NodeInfo
|
||||
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
|
||||
@ -32,6 +31,7 @@ import net.corda.core.utilities.*
|
||||
import net.corda.flows.TwoPartyTradeFlow.Buyer
|
||||
import net.corda.flows.TwoPartyTradeFlow.Seller
|
||||
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.persistence.DBTransactionStorage
|
||||
import net.corda.node.services.persistence.checkpoints
|
||||
@ -153,7 +153,7 @@ class TwoPartyTradeFlowTests {
|
||||
val cashLockId = UUID.randomUUID()
|
||||
bobNode.database.transaction {
|
||||
// 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,
|
||||
@ -284,8 +284,8 @@ class TwoPartyTradeFlowTests {
|
||||
entropyRoot: BigInteger): MockNetwork.MockNode {
|
||||
return object : MockNetwork.MockNode(config, network, networkMapAddr, advertisedServices, id, overrideServices, entropyRoot) {
|
||||
// That constructs a recording tx storage
|
||||
override fun createTransactionStorage(): TransactionStorage {
|
||||
return RecordingTransactionStorage(database, super.createTransactionStorage())
|
||||
override fun makeTransactionStorage(): WritableTransactionStorage {
|
||||
return RecordingTransactionStorage(database, super.makeTransactionStorage())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -311,7 +311,7 @@ class TwoPartyTradeFlowTests {
|
||||
attachment(ByteArrayInputStream(stream.toByteArray()))
|
||||
}
|
||||
|
||||
val extraKey = bobNode.keyManagement.keys.single()
|
||||
val extraKey = bobNode.services.keyManagementService.keys.single()
|
||||
val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(extraKey),
|
||||
DUMMY_CASH_ISSUER.party,
|
||||
notaryNode.info.notaryIdentity).second
|
||||
@ -410,7 +410,7 @@ class TwoPartyTradeFlowTests {
|
||||
attachment(ByteArrayInputStream(stream.toByteArray()))
|
||||
}
|
||||
|
||||
val bobsKey = bobNode.keyManagement.keys.single()
|
||||
val bobsKey = bobNode.services.keyManagementService.keys.single()
|
||||
val bobsFakeCash = fillUpForBuyer(false, AnonymousParty(bobsKey),
|
||||
DUMMY_CASH_ISSUER.party,
|
||||
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> {
|
||||
return database.transaction {
|
||||
delegate.track()
|
||||
|
@ -18,19 +18,20 @@ 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 org.jetbrains.exposed.sql.Database
|
||||
import net.corda.testing.node.MockStateMachineRecordedTransactionMappingStorage
|
||||
import net.corda.testing.node.MockTransactionStorage
|
||||
import org.jetbrains.exposed.sql.Database
|
||||
import java.time.Clock
|
||||
|
||||
open class MockServiceHubInternal(
|
||||
override val database: Database,
|
||||
val customVault: VaultService? = null,
|
||||
val customVaultQuery: VaultQueryService? = null,
|
||||
val keyManagement: KeyManagementService? = null,
|
||||
val network: MessagingService? = null,
|
||||
val identity: IdentityService? = MOCK_IDENTITY_SERVICE,
|
||||
override val attachments: AttachmentStorage = MockAttachmentStorage(),
|
||||
override val validatedTransactions: TransactionStorage = MockTransactionStorage(),
|
||||
override val validatedTransactions: WritableTransactionStorage = MockTransactionStorage(),
|
||||
override val uploaders: List<FileUploader> = listOf<FileUploader>(),
|
||||
override val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage(),
|
||||
val mapCache: NetworkMapCacheInternal? = null,
|
||||
@ -59,8 +60,6 @@ open class MockServiceHubInternal(
|
||||
get() = overrideClock ?: throw UnsupportedOperationException()
|
||||
override val myInfo: NodeInfo
|
||||
get() = throw UnsupportedOperationException()
|
||||
override val database: Database
|
||||
get() = throw UnsupportedOperationException()
|
||||
override val configuration: NodeConfiguration
|
||||
get() = throw UnsupportedOperationException()
|
||||
override val monitoringService: MonitoringService = MonitoringService(MetricRegistry())
|
||||
|
@ -135,7 +135,7 @@ class NotaryChangeTests {
|
||||
addOutputState(stateB, notary, encumbrance = 1) // Encumbered by stateC
|
||||
}
|
||||
val stx = node.services.signInitialTransaction(tx)
|
||||
node.services.recordTransactions(listOf(stx))
|
||||
node.services.recordTransactions(stx)
|
||||
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 signedByNode = node.services.signInitialTransaction(tx)
|
||||
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))
|
||||
}
|
||||
|
||||
@ -163,8 +163,8 @@ fun issueMultiPartyState(nodeA: AbstractNode, nodeB: AbstractNode, notaryNode: A
|
||||
val signedByA = nodeA.services.signInitialTransaction(tx)
|
||||
val signedByAB = nodeB.services.addSignature(signedByA)
|
||||
val stx = notaryNode.services.addSignature(signedByAB, notaryNode.services.notaryIdentityKey)
|
||||
nodeA.services.recordTransactions(listOf(stx))
|
||||
nodeB.services.recordTransactions(listOf(stx))
|
||||
nodeA.services.recordTransactions(stx)
|
||||
nodeB.services.recordTransactions(stx)
|
||||
val stateAndRef = StateAndRef(state, StateRef(stx.id, 0))
|
||||
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))
|
||||
tx.addTimeWindow(Instant.now(), 30.seconds)
|
||||
val stx = node.services.signInitialTransaction(tx)
|
||||
node.services.recordTransactions(listOf(stx))
|
||||
node.services.recordTransactions(stx)
|
||||
return StateAndRef(tx.outputStates().first(), StateRef(stx.id, 0))
|
||||
}
|
||||
|
@ -87,11 +87,11 @@ class NodeSchedulerServiceTest : SingletonSerializeAsToken() {
|
||||
InMemoryMessagingNetwork.PeerHandle(0, nullIdentity),
|
||||
AffinityExecutor.ServiceAffinityExecutor("test", 1),
|
||||
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 testReference = this@NodeSchedulerServiceTest
|
||||
}
|
||||
scheduler = NodeSchedulerService(services, database, schedulerGatedExecutor)
|
||||
scheduler = NodeSchedulerService(services, schedulerGatedExecutor)
|
||||
smmExecutor = AffinityExecutor.ServiceAffinityExecutor("test", 1)
|
||||
val mockSMM = StateMachineManager(services, DBCheckpointStorage(), smmExecutor, database)
|
||||
mockSMM.changes.subscribe { change ->
|
||||
|
@ -38,13 +38,13 @@ class InMemoryNetworkMapCacheTest {
|
||||
mockNet.runNetwork()
|
||||
|
||||
// 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.netMapCache.addNode(nodeB.info)
|
||||
nodeA.services.networkMapCache.addNode(nodeB.info)
|
||||
}
|
||||
// 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
|
||||
|
@ -131,7 +131,7 @@ class NotaryServiceTests {
|
||||
val tx = DummyContract.generateInitial(Random().nextInt(), notaryNode.info.notaryIdentity, node.info.legalIdentity.ref(0))
|
||||
val signedByNode = node.services.signInitialTransaction(tx)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ class ValidatingNotaryServiceTests {
|
||||
val tx = DummyContract.generateInitial(Random().nextInt(), notaryNode.info.notaryIdentity, node.info.legalIdentity.ref(0))
|
||||
val signedByNode = node.services.signInitialTransaction(tx)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
@ -405,7 +405,7 @@ class NodeVaultServiceTest {
|
||||
}
|
||||
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 2")
|
||||
@ -418,7 +418,7 @@ class NodeVaultServiceTest {
|
||||
}
|
||||
val anotherTX = megaCorpServices.signInitialTransaction(anotherBuilder)
|
||||
|
||||
services.recordTransactions(listOf(anotherTX))
|
||||
services.recordTransactions(anotherTX)
|
||||
|
||||
vaultSvc.addNoteToTransaction(anotherTX.id, "GPB Sample Note 1")
|
||||
assertEquals(1, vaultSvc.getTransactionNotes(anotherTX.id).count())
|
||||
|
@ -167,7 +167,7 @@ abstract class Simulation(val networkSendManuallyPumped: Boolean,
|
||||
val serviceProviders: List<SimulatedNode> = listOf(notary, ratesOracle, networkMap)
|
||||
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.
|
||||
private val _allFlowSteps = PublishSubject.create<Pair<SimulatedNode, ProgressTracker.Change>>()
|
||||
|
@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.contracts.CommercialPaper
|
||||
import net.corda.core.contracts.Amount
|
||||
import net.corda.core.contracts.TransactionGraphSearch
|
||||
import net.corda.core.div
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
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.
|
||||
val tradeTX: SignedTransaction = subFlow(buyer)
|
||||
// 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: " +
|
||||
"\n\n${Emoji.renderIfSupported(tradeTX.tx)}")
|
||||
@ -61,18 +60,11 @@ class BuyerFlow(val otherParty: Party) : FlowLogic<Unit>() {
|
||||
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.attachments).let {
|
||||
it.automaticallyExtractAttachments = true
|
||||
it.storePath
|
||||
}
|
||||
|
||||
cpIssuance.attachments.first().let {
|
||||
val p = attachmentsPath / "$it.jar"
|
||||
println("""
|
||||
|
||||
The issuance of the commercial paper came with an attachment. You can find it expanded in this directory:
|
||||
$p
|
||||
The issuance of the commercial paper came with an attachment. You can find it in the attachments directory: $it.jar
|
||||
|
||||
${Emoji.renderIfSupported(cpIssuance)}""")
|
||||
}
|
||||
|
@ -15,9 +15,11 @@ import net.corda.core.messaging.MessageRecipients
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.CordaPluginRegistry
|
||||
import net.corda.core.node.WorldMapLocation
|
||||
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.getTestPartyAndCertificate
|
||||
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.SimpleNotaryService
|
||||
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.ServiceAffinityExecutor
|
||||
import net.corda.testing.MOCK_VERSION_INFO
|
||||
@ -181,8 +182,6 @@ class MockNetwork(private val networkSendManuallyPumped: Boolean = false,
|
||||
trustRoot = trustRoot, caCertificates = *caCertificates)
|
||||
}
|
||||
|
||||
override fun makeVaultService(dataSourceProperties: Properties): VaultService = NodeVaultService(services, dataSourceProperties)
|
||||
|
||||
override fun makeKeyManagementService(identityService: IdentityService): KeyManagementService {
|
||||
return E2ETestKeyManagementService(identityService, partyKeys + (overrideServices?.values ?: emptySet()))
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ 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.api.WritableTransactionStorage
|
||||
import net.corda.node.services.database.HibernateConfiguration
|
||||
import net.corda.node.services.identity.InMemoryIdentityService
|
||||
import net.corda.node.services.keys.freshCertificate
|
||||
@ -35,8 +36,6 @@ import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.security.KeyPair
|
||||
import java.security.PrivateKey
|
||||
import java.security.PublicKey
|
||||
@ -66,7 +65,7 @@ open class MockServices(vararg val keys: KeyPair) : ServiceHub {
|
||||
}
|
||||
|
||||
override val attachments: AttachmentStorage = MockAttachmentStorage()
|
||||
override val validatedTransactions: TransactionStorage = MockTransactionStorage()
|
||||
override val validatedTransactions: WritableTransactionStorage = MockTransactionStorage()
|
||||
val stateMachineRecordedTransactionMapping: StateMachineRecordedTransactionMappingStorage = MockStateMachineRecordedTransactionMappingStorage()
|
||||
override final val identityService: IdentityService = InMemoryIdentityService(MOCK_IDENTITIES, trustRoot = DUMMY_CA.certificate)
|
||||
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>()
|
||||
override var automaticallyExtractAttachments = false
|
||||
override var storePath: Path = Paths.get("")
|
||||
|
||||
override fun openAttachment(id: SecureHash): Attachment? {
|
||||
val f = files[id] ?: return null
|
||||
@ -159,7 +156,7 @@ class MockStateMachineRecordedTransactionMappingStorage(
|
||||
val storage: StateMachineRecordedTransactionMappingStorage = InMemoryStateMachineRecordedTransactionMappingStorage()
|
||||
) : StateMachineRecordedTransactionMappingStorage by storage
|
||||
|
||||
open class MockTransactionStorage : TransactionStorage {
|
||||
open class MockTransactionStorage : WritableTransactionStorage, SingletonSerializeAsToken() {
|
||||
override fun track(): DataFeed<List<SignedTransaction>, SignedTransaction> {
|
||||
return DataFeed(txns.values.toList(), _updatesPublisher)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user