mirror of
https://github.com/corda/corda.git
synced 2025-06-18 15:18:16 +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:
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 }
|
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. */
|
/** Returns the location of this class. */
|
||||||
val Class<*>.location: URL get() = protectionDomain.codeSource.location
|
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 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()
|
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
|
* 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()
|
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> {
|
fun <T : Any> SerializedBytes<Any>.checkPayloadIs(type: Class<T>): UntrustworthyData<T> {
|
||||||
val payloadData: T = try {
|
val payloadData: T = try {
|
||||||
val serializer = SerializationDefaults.SERIALIZATION_FACTORY
|
val serializer = SerializationDefaults.SERIALIZATION_FACTORY
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package net.corda.core.node
|
package net.corda.core.node
|
||||||
|
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.node.services.AttachmentId
|
||||||
@ -106,3 +107,9 @@ data class NetworkParameters(
|
|||||||
@KeepForDJVM
|
@KeepForDJVM
|
||||||
@CordaSerializable
|
@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
|
package net.corda.core.transactions
|
||||||
|
|
||||||
import co.paralleluniverse.strands.Strand
|
import co.paralleluniverse.strands.Strand
|
||||||
|
import net.corda.core.CordaInternal
|
||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
import net.corda.core.contracts.*
|
import net.corda.core.contracts.*
|
||||||
import net.corda.core.cordapp.CordappProvider
|
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.crypto.SignatureMetadata
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.FlowStateMachine
|
import net.corda.core.internal.FlowStateMachine
|
||||||
|
import net.corda.core.internal.ensureMinimumPlatformVersion
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.ServiceHub
|
import net.corda.core.node.ServiceHub
|
||||||
import net.corda.core.node.ServicesForResolution
|
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.AttachmentId
|
||||||
import net.corda.core.node.services.KeyManagementService
|
import net.corda.core.node.services.KeyManagementService
|
||||||
import net.corda.core.serialization.SerializationContext
|
import net.corda.core.serialization.SerializationContext
|
||||||
@ -74,7 +77,7 @@ open class TransactionBuilder @JvmOverloads constructor(
|
|||||||
for (t in items) {
|
for (t in items) {
|
||||||
when (t) {
|
when (t) {
|
||||||
is StateAndRef<*> -> addInputState(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 SecureHash -> addAttachment(t)
|
||||||
is TransactionState<*> -> addOutputState(t)
|
is TransactionState<*> -> addOutputState(t)
|
||||||
is StateAndContract -> addOutputState(t.state, t.contract)
|
is StateAndContract -> addOutputState(t.state, t.contract)
|
||||||
@ -95,11 +98,18 @@ open class TransactionBuilder @JvmOverloads constructor(
|
|||||||
* [HashAttachmentConstraint].
|
* [HashAttachmentConstraint].
|
||||||
*
|
*
|
||||||
* @returns A new [WireTransaction] that will be unaffected by further changes to this [TransactionBuilder].
|
* @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)
|
@Throws(MissingContractAttachments::class)
|
||||||
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services)
|
fun toWireTransaction(services: ServicesForResolution): WireTransaction = toWireTransactionWithContext(services)
|
||||||
|
|
||||||
|
@CordaInternal
|
||||||
internal fun toWireTransactionWithContext(services: ServicesForResolution, serializationContext: SerializationContext? = null): WireTransaction {
|
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.
|
// 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
|
// 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 {
|
when {
|
||||||
state.constraint !== AutomaticHashConstraint -> state
|
state.constraint !== AutomaticHashConstraint -> state
|
||||||
useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint)
|
useWhitelistedByZoneAttachmentConstraint(state.contract, services.networkParameters) -> state.copy(constraint = WhitelistedByZoneAttachmentConstraint)
|
||||||
else -> services.cordappProvider.getContractAttachmentID(state.contract)?.let {
|
else -> {
|
||||||
|
services.cordappProvider.getContractAttachmentID(state.contract)?.let {
|
||||||
state.copy(constraint = HashAttachmentConstraint(it))
|
state.copy(constraint = HashAttachmentConstraint(it))
|
||||||
} ?: throw MissingContractAttachments(listOf(state))
|
} ?: throw MissingContractAttachments(listOf(state))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return SerializationFactory.defaultFactory.withCurrentContext(serializationContext) {
|
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.
|
* 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
|
* Note: Reference states are only supported on Corda networks running a minimum platform version of 4.
|
||||||
* platform version less than 4.
|
* [toWireTransaction] will throw an [IllegalStateException] if called in such an environment.
|
||||||
*
|
|
||||||
* @throws UncheckedVersionException
|
|
||||||
*/
|
*/
|
||||||
@Deprecated(message = "Feature not yet released. Pending stabilisation.")
|
|
||||||
open fun addReferenceState(referencedStateAndRef: ReferencedStateAndRef<*>): TransactionBuilder {
|
open fun addReferenceState(referencedStateAndRef: ReferencedStateAndRef<*>): TransactionBuilder {
|
||||||
val stateAndRef = referencedStateAndRef.stateAndRef
|
val stateAndRef = referencedStateAndRef.stateAndRef
|
||||||
referencesWithTransactionState.add(stateAndRef.state)
|
referencesWithTransactionState.add(stateAndRef.state)
|
||||||
@ -283,10 +303,10 @@ open class TransactionBuilder @JvmOverloads constructor(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns an immutable list of input [StateRefs]. */
|
/** Returns an immutable list of input [StateRef]s. */
|
||||||
fun inputStates(): List<StateRef> = ArrayList(inputs)
|
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)
|
fun referenceStates(): List<StateRef> = ArrayList(references)
|
||||||
|
|
||||||
/** Returns an immutable list of attachment hashes. */
|
/** 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
|
* Sign the built transaction and return it. This is an internal function for use by the service hub, please use
|
||||||
* [ServiceHub.signInitialTransaction] instead.
|
* [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 wtx = toWireTransaction(services)
|
||||||
val signableData = SignableData(wtx.id, signatureMetadata)
|
val signableData = SignableData(wtx.id, signatureMetadata)
|
||||||
val sig = keyManagementService.sign(signableData, publicKey)
|
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
|
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
|
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
|
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:
|
.. _changelog_v3.1:
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
private var _started: S? = null
|
private var _started: S? = null
|
||||||
|
|
||||||
private fun <T : Any> T.tokenize(): T {
|
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
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,10 +239,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
|
|
||||||
private fun initKeyStore(): X509Certificate {
|
private fun initKeyStore(): X509Certificate {
|
||||||
if (configuration.devMode) {
|
if (configuration.devMode) {
|
||||||
log.warn("The Corda node is running in developer mode. This is not suitable for production usage.")
|
|
||||||
configuration.configureWithDevSSLCertificate()
|
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()
|
return validateKeyStore()
|
||||||
}
|
}
|
||||||
@ -317,12 +314,12 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
}
|
}
|
||||||
|
|
||||||
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
|
val (nodeInfo, signedNodeInfo) = nodeInfoAndSigned
|
||||||
|
services.start(nodeInfo, netParams)
|
||||||
networkMapUpdater.start(trustRoot, signedNetParams.raw.hash, signedNodeInfo.raw.hash)
|
networkMapUpdater.start(trustRoot, signedNetParams.raw.hash, signedNodeInfo.raw.hash)
|
||||||
startMessagingService(rpcOps, nodeInfo, myNotaryIdentity, netParams)
|
startMessagingService(rpcOps, nodeInfo, myNotaryIdentity, netParams)
|
||||||
|
|
||||||
// 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.
|
||||||
return database.transaction {
|
return database.transaction {
|
||||||
services.start(nodeInfo, netParams)
|
|
||||||
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
identityService.loadIdentities(nodeInfo.legalIdentitiesAndCerts)
|
||||||
attachments.start()
|
attachments.start()
|
||||||
cordappProvider.start(netParams.whitelistedContractImplementations)
|
cordappProvider.start(netParams.whitelistedContractImplementations)
|
||||||
@ -731,7 +728,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
|||||||
protected open fun startDatabase() {
|
protected open fun startDatabase() {
|
||||||
val props = configuration.dataSourceProperties
|
val props = configuration.dataSourceProperties
|
||||||
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
|
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.
|
// Now log the vendor string as this will also cause a connection to be tested eagerly.
|
||||||
logVendorString(database, log)
|
logVendorString(database, log)
|
||||||
}
|
}
|
||||||
@ -994,7 +991,7 @@ fun configureDatabase(hikariProperties: Properties,
|
|||||||
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?,
|
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?,
|
||||||
schemaService: SchemaService = NodeSchemaService()): CordaPersistence {
|
schemaService: SchemaService = NodeSchemaService()): CordaPersistence {
|
||||||
val persistence = createCordaPersistence(databaseConfig, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous, schemaService, hikariProperties)
|
val persistence = createCordaPersistence(databaseConfig, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous, schemaService, hikariProperties)
|
||||||
persistence.hikariStart(hikariProperties)
|
persistence.startHikariPool(hikariProperties)
|
||||||
return persistence
|
return persistence
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1013,7 +1010,7 @@ fun createCordaPersistence(databaseConfig: DatabaseConfig,
|
|||||||
return CordaPersistence(databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters)
|
return CordaPersistence(databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun CordaPersistence.hikariStart(hikariProperties: Properties) {
|
fun CordaPersistence.startHikariPool(hikariProperties: Properties) {
|
||||||
try {
|
try {
|
||||||
start(DataSourceFactory.createDataSource(hikariProperties))
|
start(DataSourceFactory.createDataSource(hikariProperties))
|
||||||
} catch (ex: Exception) {
|
} 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.saveToKeyStore
|
||||||
import net.corda.node.utilities.saveToTrustStore
|
import net.corda.node.utilities.saveToTrustStore
|
||||||
import net.corda.nodeapi.internal.addShutdownHook
|
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.config.UnknownConfigurationKeysException
|
||||||
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
|
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
|
||||||
|
import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
|
||||||
import net.corda.tools.shell.InteractiveShell
|
import net.corda.tools.shell.InteractiveShell
|
||||||
import org.fusesource.jansi.Ansi
|
import org.fusesource.jansi.Ansi
|
||||||
import org.fusesource.jansi.AnsiConsole
|
import org.fusesource.jansi.AnsiConsole
|
||||||
@ -319,6 +319,8 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
Emoji.renderIfSupported {
|
Emoji.renderIfSupported {
|
||||||
Node.printWarning("This node is running in developer mode! ${Emoji.developer} This is not safe for production deployment.")
|
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()
|
val nodeInfo = node.start()
|
||||||
@ -332,7 +334,7 @@ open class NodeStartup(val args: Array<String>) {
|
|||||||
if (conf.shouldStartLocalShell()) {
|
if (conf.shouldStartLocalShell()) {
|
||||||
node.startupComplete.then {
|
node.startupComplete.then {
|
||||||
try {
|
try {
|
||||||
InteractiveShell.runLocalShell({ node.stop() })
|
InteractiveShell.runLocalShell(node::stop)
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
logger.error("Shell failed to start", e)
|
logger.error("Shell failed to start", e)
|
||||||
}
|
}
|
||||||
|
@ -70,9 +70,7 @@ class NetworkMapCacheImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Database-based network map cache. */
|
||||||
* Extremely simple in-memory cache of the network map.
|
|
||||||
*/
|
|
||||||
@ThreadSafe
|
@ThreadSafe
|
||||||
open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
|
open class PersistentNetworkMapCache(private val database: CordaPersistence) : SingletonSerializeAsToken(), NetworkMapCacheBaseInternal {
|
||||||
companion object {
|
companion object {
|
||||||
|
Reference in New Issue
Block a user