mirror of
https://github.com/corda/corda.git
synced 2024-12-25 07:31:10 +00:00
CORDA-1845: Check for min plaform version of 4 when building transactions with reference states (#3705)
Also includes some minor cleanup brought up in a previous PR.
This commit is contained in:
parent
d42b9f51ac
commit
994fe0dbdc
57
core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt
Normal file
57
core/src/main/kotlin/net/corda/core/internal/CordaUtils.kt
Normal file
@ -0,0 +1,57 @@
|
||||
package net.corda.core.internal
|
||||
|
||||
import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.cordapp.CordappConfig
|
||||
import net.corda.core.cordapp.CordappContext
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.ZoneVersionTooLowException
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.transactions.TransactionBuilder
|
||||
import net.corda.core.transactions.WireTransaction
|
||||
import org.slf4j.MDC
|
||||
|
||||
// *Internal* Corda-specific utilities
|
||||
|
||||
fun ServicesForResolution.ensureMinimumPlatformVersion(requiredMinPlatformVersion: Int, feature: String) {
|
||||
val currentMinPlatformVersion = networkParameters.minimumPlatformVersion
|
||||
if (currentMinPlatformVersion < requiredMinPlatformVersion) {
|
||||
throw ZoneVersionTooLowException(
|
||||
"$feature requires all nodes on the Corda compatibility zone to be running at least platform version " +
|
||||
"$requiredMinPlatformVersion. The current zone is only enforcing a minimum platform version of " +
|
||||
"$currentMinPlatformVersion. Please contact your zone operator."
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Provide access to internal method for AttachmentClassLoaderTests */
|
||||
@DeleteForDJVM
|
||||
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
|
||||
return toWireTransactionWithContext(services, serializationContext)
|
||||
}
|
||||
|
||||
/** Provide access to internal method for AttachmentClassLoaderTests */
|
||||
@DeleteForDJVM
|
||||
fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext): LedgerTransaction {
|
||||
return toLedgerTransactionWithContext(services, serializationContext)
|
||||
}
|
||||
|
||||
fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext {
|
||||
return CordappContext(cordapp, attachmentId, classLoader, config)
|
||||
}
|
||||
|
||||
/** Checks if this flow is an idempotent flow. */
|
||||
fun Class<out FlowLogic<*>>.isIdempotentFlow(): Boolean {
|
||||
return IdempotentFlow::class.java.isAssignableFrom(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures each log entry from the current thread will contain id of the transaction in the MDC.
|
||||
*/
|
||||
internal fun SignedTransaction.pushToLoggingContext() {
|
||||
MDC.put("tx_id", id.toString())
|
||||
}
|
@ -388,18 +388,6 @@ fun <T, U : T> uncheckedCast(obj: T) = obj as U
|
||||
|
||||
fun <K, V> Iterable<Pair<K, V>>.toMultiMap(): Map<K, List<V>> = this.groupBy({ it.first }) { it.second }
|
||||
|
||||
/** Provide access to internal method for AttachmentClassLoaderTests */
|
||||
@DeleteForDJVM
|
||||
fun TransactionBuilder.toWireTransaction(services: ServicesForResolution, serializationContext: SerializationContext): WireTransaction {
|
||||
return toWireTransactionWithContext(services, serializationContext)
|
||||
}
|
||||
|
||||
/** Provide access to internal method for AttachmentClassLoaderTests */
|
||||
@DeleteForDJVM
|
||||
fun TransactionBuilder.toLedgerTransaction(services: ServicesForResolution, serializationContext: SerializationContext): LedgerTransaction {
|
||||
return toLedgerTransactionWithContext(services, serializationContext)
|
||||
}
|
||||
|
||||
/** Returns the location of this class. */
|
||||
val Class<*>.location: URL get() = protectionDomain.codeSource.location
|
||||
|
||||
@ -499,29 +487,13 @@ fun <T : Any> SerializedBytes<T>.sign(keyPair: KeyPair): SignedData<T> = SignedD
|
||||
|
||||
fun ByteBuffer.copyBytes(): ByteArray = ByteArray(remaining()).also { get(it) }
|
||||
|
||||
fun createCordappContext(cordapp: Cordapp, attachmentId: SecureHash?, classLoader: ClassLoader, config: CordappConfig): CordappContext {
|
||||
return CordappContext(cordapp, attachmentId, classLoader, config)
|
||||
}
|
||||
|
||||
val PublicKey.hash: SecureHash get() = encoded.sha256()
|
||||
|
||||
/** Checks if this flow is an idempotent flow. */
|
||||
fun Class<out FlowLogic<*>>.isIdempotentFlow(): Boolean {
|
||||
return IdempotentFlow::class.java.isAssignableFrom(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension method for providing a sumBy method that processes and returns a Long
|
||||
*/
|
||||
fun <T> Iterable<T>.sumByLong(selector: (T) -> Long): Long = this.map { selector(it) }.sum()
|
||||
|
||||
/**
|
||||
* Ensures each log entry from the current thread will contain id of the transaction in the MDC.
|
||||
*/
|
||||
internal fun SignedTransaction.pushToLoggingContext() {
|
||||
MDC.put("tx_id", id.toString())
|
||||
}
|
||||
|
||||
fun <T : Any> SerializedBytes<Any>.checkPayloadIs(type: Class<T>): UntrustworthyData<T> {
|
||||
val payloadData: T = try {
|
||||
val serializer = SerializationDefaults.SERIALIZATION_FACTORY
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.corda.core.node
|
||||
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.KeepForDJVM
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
@ -105,4 +106,10 @@ data class NetworkParameters(
|
||||
*/
|
||||
@KeepForDJVM
|
||||
@CordaSerializable
|
||||
data class NotaryInfo(val identity: Party, val validating: Boolean)
|
||||
data class NotaryInfo(val identity: Party, val validating: Boolean)
|
||||
|
||||
/**
|
||||
* When a Corda feature cannot be used due to the node's compatibility zone not enforcing a high enough minimum platform
|
||||
* version.
|
||||
*/
|
||||
class ZoneVersionTooLowException(message: String) : CordaRuntimeException(message)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.core.transactions
|
||||
|
||||
import co.paralleluniverse.strands.Strand
|
||||
import net.corda.core.CordaInternal
|
||||
import net.corda.core.DeleteForDJVM
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
@ -9,9 +10,11 @@ import net.corda.core.crypto.SignableData
|
||||
import net.corda.core.crypto.SignatureMetadata
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.FlowStateMachine
|
||||
import net.corda.core.internal.ensureMinimumPlatformVersion
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.ZoneVersionTooLowException
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
@ -74,7 +77,7 @@ open class TransactionBuilder @JvmOverloads constructor(
|
||||
for (t in items) {
|
||||
when (t) {
|
||||
is StateAndRef<*> -> addInputState(t)
|
||||
is ReferencedStateAndRef<*> -> @Suppress("DEPRECATION") addReferenceState(t) // Will remove when feature finalised.
|
||||
is ReferencedStateAndRef<*> -> addReferenceState(t)
|
||||
is SecureHash -> addAttachment(t)
|
||||
is TransactionState<*> -> addOutputState(t)
|
||||
is StateAndContract -> addOutputState(t.state, t.contract)
|
||||
@ -95,11 +98,18 @@ open class TransactionBuilder @JvmOverloads constructor(
|
||||
* [HashAttachmentConstraint].
|
||||
*
|
||||
* @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder].
|
||||
*
|
||||
* @throws ZoneVersionTooLowException if there are reference states and the zone minimum platform version is less than 4.
|
||||
*/
|
||||
@Throws(MissingContractAttachments::class)
|
||||
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services)
|
||||
|
||||
@CordaInternal
|
||||
internal fun toWireTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext? = null): WireTransaction {
|
||||
val referenceStates = referenceStates()
|
||||
if (referenceStates.isNotEmpty()) {
|
||||
services.ensureMinimumPlatformVersion(4, "Reference states")
|
||||
}
|
||||
|
||||
// Resolves the AutomaticHashConstraints to HashAttachmentConstraints or WhitelistedByZoneAttachmentConstraint based on a global parameter.
|
||||
// The AutomaticHashConstraint allows for less boiler plate when constructing transactions since for the typical case the named contract
|
||||
@ -109,14 +119,27 @@ open class TransactionBuilder @JvmOverloads constructor(
|
||||
when {
|
||||
state.constraint !== AutomaticHashConstraint -> state
|
||||
useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint)
|
||||
else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let {
|
||||
state.copy(constraint = HashAttachmentConstraint(it))
|
||||
} ?: throw MissingContractAttachments(listOf(state))
|
||||
else -> {
|
||||
services.cordappProvider.getContractAttachmentID(state.contract)?.let {
|
||||
state.copy(constraint = HashAttachmentConstraint(it))
|
||||
} ?: throw MissingContractAttachments(listOf(state))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
|
||||
WireTransaction(WireTransaction.createComponentGroups(inputStates(), resolvedOutputs, commands, attachments + makeContractAttachments(services.cordappProvider), notary, window, referenceStates()), privacySalt)
|
||||
WireTransaction(
|
||||
WireTransaction.createComponentGroups(
|
||||
inputStates(),
|
||||
resolvedOutputs,
|
||||
commands,
|
||||
attachments + makeContractAttachments(services.cordappProvider),
|
||||
notary,
|
||||
window,
|
||||
referenceStates
|
||||
),
|
||||
privacySalt
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,12 +192,9 @@ open class TransactionBuilder @JvmOverloads constructor(
|
||||
/**
|
||||
* Adds a reference input [StateRef] to the transaction.
|
||||
*
|
||||
* This feature was added in version 4 of Corda, so will throw an exception for any Corda networks with a minimum
|
||||
* platform version less than 4.
|
||||
*
|
||||
* @throws UncheckedVersionException
|
||||
* Note: Reference states are only supported on Corda networks running a minimum platform version of 4.
|
||||
* [toWireTransaction] will throw an [IllegalStateException] if called in such an environment.
|
||||
*/
|
||||
@Deprecated(message = "Feature not yet released. Pending stabilisation.")
|
||||
open fun addReferenceState(referencedStateAndRef: ReferencedStateAndRef<*>): TransactionBuilder {
|
||||
val stateAndRef = referencedStateAndRef.stateAndRef
|
||||
referencesWithTransactionState.add(stateAndRef.state)
|
||||
@ -283,10 +303,10 @@ open class TransactionBuilder @JvmOverloads constructor(
|
||||
return this
|
||||
}
|
||||
|
||||
/** Returns an immutable list of input [StateRefs]. */
|
||||
/** Returns an immutable list of input [StateRef]s. */
|
||||
fun inputStates(): List<StateRef> = ArrayList(inputs)
|
||||
|
||||
/** Returns an immutable list of reference input [StateRefs]. */
|
||||
/** Returns an immutable list of reference input [StateRef]s. */
|
||||
fun referenceStates(): List<StateRef> = ArrayList(references)
|
||||
|
||||
/** Returns an immutable list of attachment hashes. */
|
||||
@ -302,7 +322,10 @@ open class TransactionBuilder @JvmOverloads constructor(
|
||||
* Sign the built transaction and return it. This is an internal function for use by the service hub, please use
|
||||
* [ServiceHub.signInitialTransaction] instead.
|
||||
*/
|
||||
fun toSignedTransaction(keyManagementService: KeyManagementService, publicKey: PublicKey, signatureMetadata: SignatureMetadata, services: ServicesForResolution): SignedTransaction {
|
||||
fun toSignedTransaction(keyManagementService: KeyManagementService,
|
||||
publicKey: PublicKey,
|
||||
signatureMetadata: SignatureMetadata,
|
||||
services: ServicesForResolution): SignedTransaction {
|
||||
val wtx = toWireTransaction(services)
|
||||
val signableData = SignableData(wtx.id, signatureMetadata)
|
||||
val sig = keyManagementService.sign(signableData, publicKey)
|
||||
|
@ -0,0 +1,85 @@
|
||||
package net.corda.core.transactions
|
||||
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.cordapp.CordappProvider
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.ZoneVersionTooLowException
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.contracts.DummyState
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.DummyCommandData
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.internal.rigorousMock
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.api.Assertions.assertThatThrownBy
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class TransactionBuilderTest {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
private val notary = TestIdentity(DUMMY_NOTARY_NAME).party
|
||||
private val services = rigorousMock<ServicesForResolution>()
|
||||
private val contractAttachmentId = SecureHash.randomSHA256()
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
val cordappProvider = rigorousMock<CordappProvider>()
|
||||
doReturn(cordappProvider).whenever(services).cordappProvider
|
||||
doReturn(contractAttachmentId).whenever(cordappProvider).getContractAttachmentID(DummyContract.PROGRAM_ID)
|
||||
doReturn(testNetworkParameters()).whenever(services).networkParameters
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `bare minimum issuance tx`() {
|
||||
val outputState = TransactionState(
|
||||
data = DummyState(),
|
||||
contract = DummyContract.PROGRAM_ID,
|
||||
notary = notary,
|
||||
constraint = HashAttachmentConstraint(contractAttachmentId)
|
||||
)
|
||||
val builder = TransactionBuilder()
|
||||
.addOutputState(outputState)
|
||||
.addCommand(DummyCommandData, notary.owningKey)
|
||||
val wtx = builder.toWireTransaction(services)
|
||||
assertThat(wtx.outputs).containsOnly(outputState)
|
||||
assertThat(wtx.commands).containsOnly(Command(DummyCommandData, notary.owningKey))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `automatic hash constraint`() {
|
||||
val outputState = TransactionState(data = DummyState(), contract = DummyContract.PROGRAM_ID, notary = notary)
|
||||
val builder = TransactionBuilder()
|
||||
.addOutputState(outputState)
|
||||
.addCommand(DummyCommandData, notary.owningKey)
|
||||
val wtx = builder.toWireTransaction(services)
|
||||
assertThat(wtx.outputs).containsOnly(outputState.copy(constraint = HashAttachmentConstraint(contractAttachmentId)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reference states`() {
|
||||
val referenceState = TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary)
|
||||
val referenceStateRef = StateRef(SecureHash.randomSHA256(), 1)
|
||||
val builder = TransactionBuilder(notary)
|
||||
.addReferenceState(StateAndRef(referenceState, referenceStateRef).referenced())
|
||||
.addOutputState(TransactionState(DummyState(), DummyContract.PROGRAM_ID, notary))
|
||||
.addCommand(DummyCommandData, notary.owningKey)
|
||||
|
||||
doReturn(testNetworkParameters(minimumPlatformVersion = 3)).whenever(services).networkParameters
|
||||
assertThatThrownBy { builder.toWireTransaction(services) }
|
||||
.isInstanceOf(ZoneVersionTooLowException::class.java)
|
||||
.hasMessageContaining("Reference states")
|
||||
|
||||
doReturn(testNetworkParameters(minimumPlatformVersion = 4)).whenever(services).networkParameters
|
||||
val wtx = builder.toWireTransaction(services)
|
||||
assertThat(wtx.references).containsOnly(referenceStateRef)
|
||||
}
|
||||
}
|
@ -188,7 +188,8 @@ Unreleased
|
||||
to in a transaction by the contracts of input and output states but whose contract is not executed as part of the
|
||||
transaction verification process and is not consumed when the transaction is committed to the ledger but is checked
|
||||
for "current-ness". In other words, the contract logic isn't run for the referencing transaction only. It's still a
|
||||
normal state when it occurs in an input or output position.
|
||||
normal state when it occurs in an input or output position. *This feature is only available on Corda networks running
|
||||
with a minimum platform version of 4.*
|
||||
|
||||
.. _changelog_v3.1:
|
||||
|
||||
|
@ -220,7 +220,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
private var _started: S? = null
|
||||
|
||||
private fun <T : Any> T.tokenize(): T {
|
||||
tokenizableServices?.add(this) ?: throw IllegalStateException("The tokenisable services list has already been finialised")
|
||||
tokenizableServices?.add(this) ?: throw IllegalStateException("The tokenisable services list has already been finalised")
|
||||
return this
|
||||
}
|
||||
|
||||
@ -239,10 +239,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
|
||||
private fun initKeyStore(): X509Certificate {
|
||||
if (configuration.devMode) {
|
||||
log.warn("The Corda node is running in developer mode. This is not suitable for production usage.")
|
||||
configuration.configureWithDevSSLCertificate()
|
||||
} else {
|
||||
log.info("The Corda node is running in production mode. If this is a developer environment you can set 'devMode=true' in the node.conf file.")
|
||||
}
|
||||
return validateKeyStore()
|
||||
}
|
||||
@ -317,12 +314,12 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
}
|
||||
|
||||
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
|
||||
services.start(nodeInfo, netParams)
|
||||
networkMapUpdater.start(trustRoot, signedNetParams.raw.hash, signedNodeInfo.raw.hash)
|
||||
startMessagingService(rpcOps, nodeInfo, myNotaryIdentity, netParams)
|
||||
|
||||
// Do all of this in a database transaction so anything that might need a connection has one.
|
||||
return database.transaction {
|
||||
services.start(nodeInfo, netParams)
|
||||
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
||||
attachments.start()
|
||||
cordappProvider.start(netParams.whitelistedContractImplementations)
|
||||
@ -731,7 +728,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
protected open fun startDatabase() {
|
||||
val props = configuration.dataSourceProperties
|
||||
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
|
||||
database.hikariStart(props)
|
||||
database.startHikariPool(props)
|
||||
// Now log the vendor string as this will also cause a connection to be tested eagerly.
|
||||
logVendorString(database, log)
|
||||
}
|
||||
@ -994,7 +991,7 @@ fun configureDatabase(hikariProperties: Properties,
|
||||
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?,
|
||||
schemaService: SchemaService = NodeSchemaService()): CordaPersistence {
|
||||
val persistence = createCordaPersistence(databaseConfig, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous, schemaService, hikariProperties)
|
||||
persistence.hikariStart(hikariProperties)
|
||||
persistence.startHikariPool(hikariProperties)
|
||||
return persistence
|
||||
}
|
||||
|
||||
@ -1013,7 +1010,7 @@ fun createCordaPersistence(databaseConfig: DatabaseConfig,
|
||||
return CordaPersistence(databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters)
|
||||
}
|
||||
|
||||
fun CordaPersistence.hikariStart(hikariProperties: Properties) {
|
||||
fun CordaPersistence.startHikariPool(hikariProperties: Properties) {
|
||||
try {
|
||||
start(DataSourceFactory.createDataSource(hikariProperties))
|
||||
} catch (ex: Exception) {
|
||||
|
@ -27,9 +27,9 @@ import net.corda.node.utilities.registration.UnableToRegisterNodeWithDoormanExce
|
||||
import net.corda.node.utilities.saveToKeyStore
|
||||
import net.corda.node.utilities.saveToTrustStore
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException
|
||||
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
|
||||
import net.corda.tools.shell.InteractiveShell
|
||||
import org.fusesource.jansi.Ansi
|
||||
import org.fusesource.jansi.AnsiConsole
|
||||
@ -319,6 +319,8 @@ open class NodeStartup(val args: Array<String>) {
|
||||
Emoji.renderIfSupported {
|
||||
Node.printWarning("This node is running in developer mode! ${Emoji.developer} This is not safe for production deployment.")
|
||||
}
|
||||
} else {
|
||||
logger.info("The Corda node is running in production mode. If this is a developer environment you can set 'devMode=true' in the node.conf file.")
|
||||
}
|
||||
|
||||
val nodeInfo = node.start()
|
||||
@ -332,7 +334,7 @@ open class NodeStartup(val args: Array<String>) {
|
||||
if (conf.shouldStartLocalShell()) {
|
||||
node.startupComplete.then {
|
||||
try {
|
||||
InteractiveShell.runLocalShell({ node.stop() })
|
||||
InteractiveShell.runLocalShell(node::stop)
|
||||
} catch (e: Throwable) {
|
||||
logger.error("Shell failed to start", e)
|
||||
}
|
||||
|
@ -70,9 +70,7 @@ class NetworkMapCacheImpl(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extremely simple in-memory cache of the network map.
|
||||
*/
|
||||
/** Database-based network map cache. */
|
||||
@ThreadSafe
|
||||
open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
|
||||
companion object {
|
||||
|
Loading…
Reference in New Issue
Block a user