mirror of
https://github.com/corda/corda.git
synced 2025-01-29 15:43:55 +00:00
CORDA-2128: Moved all the helper methods added to NetworkParameters.kt into internal (#4466)
This commit is contained in:
parent
0388872175
commit
c08f65a92c
@ -1,12 +1,14 @@
|
|||||||
package net.corda.core.internal
|
package net.corda.core.internal
|
||||||
|
|
||||||
import net.corda.core.DeleteForDJVM
|
import net.corda.core.DeleteForDJVM
|
||||||
|
import net.corda.core.contracts.ContractClassName
|
||||||
import net.corda.core.cordapp.Cordapp
|
import net.corda.core.cordapp.Cordapp
|
||||||
import net.corda.core.cordapp.CordappConfig
|
import net.corda.core.cordapp.CordappConfig
|
||||||
import net.corda.core.cordapp.CordappContext
|
import net.corda.core.cordapp.CordappContext
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
import net.corda.core.flows.DataVendingFlow
|
import net.corda.core.flows.DataVendingFlow
|
||||||
import net.corda.core.flows.FlowLogic
|
import net.corda.core.flows.FlowLogic
|
||||||
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.ServicesForResolution
|
import net.corda.core.node.ServicesForResolution
|
||||||
import net.corda.core.node.ZoneVersionTooLowException
|
import net.corda.core.node.ZoneVersionTooLowException
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
@ -16,6 +18,7 @@ import net.corda.core.transactions.SignedTransaction
|
|||||||
import net.corda.core.transactions.TransactionBuilder
|
import net.corda.core.transactions.TransactionBuilder
|
||||||
import net.corda.core.transactions.WireTransaction
|
import net.corda.core.transactions.WireTransaction
|
||||||
import org.slf4j.MDC
|
import org.slf4j.MDC
|
||||||
|
import java.security.PublicKey
|
||||||
|
|
||||||
// *Internal* Corda-specific utilities.
|
// *Internal* Corda-specific utilities.
|
||||||
|
|
||||||
@ -71,9 +74,7 @@ private fun isPackageValid(packageName: String): Boolean {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Check if a string is a legal Java package name. */
|
||||||
* Check if a string is a legal Java package name.
|
|
||||||
*/
|
|
||||||
fun requirePackageValid(name: String) {
|
fun requirePackageValid(name: String) {
|
||||||
require(isPackageValid(name)) { "Invalid Java package name: `$name`." }
|
require(isPackageValid(name)) { "Invalid Java package name: `$name`." }
|
||||||
}
|
}
|
||||||
@ -85,3 +86,33 @@ fun requirePackageValid(name: String) {
|
|||||||
*/
|
*/
|
||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
object RetrieveAnyTransactionPayload : ArrayList<Any>()
|
object RetrieveAnyTransactionPayload : ArrayList<Any>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the [fullClassName] is in a subpackage of [packageName].
|
||||||
|
* E.g.: "com.megacorp" owns "com.megacorp.tokens.MegaToken"
|
||||||
|
*
|
||||||
|
* Note: The ownership check is ignoring case to prevent people from just releasing a jar with: "com.megaCorp.megatoken" and pretend they are MegaCorp.
|
||||||
|
* By making the check case insensitive, the node will require that the jar is signed by MegaCorp, so the attack fails.
|
||||||
|
*/
|
||||||
|
private fun owns(packageName: String, fullClassName: String): Boolean = fullClassName.startsWith("$packageName.", ignoreCase = true)
|
||||||
|
|
||||||
|
/** Returns the public key of the package owner of the [contractClassName], or null if not owned. */
|
||||||
|
internal fun NetworkParameters.getPackageOwnerOf(contractClassName: ContractClassName): PublicKey? {
|
||||||
|
return packageOwnership.entries.singleOrNull { owns(it.key, contractClassName) }?.value
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the public key of the package owner if any of [contractClassNames] match, or null if not owned.
|
||||||
|
*/
|
||||||
|
internal fun NetworkParameters.getPackageOwnerOf(contractClassNames: Set<ContractClassName>): PublicKey? {
|
||||||
|
for (contractClassName in contractClassNames) {
|
||||||
|
val owner = getPackageOwnerOf(contractClassName)
|
||||||
|
if (owner != null) return owner
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that packages don't overlap so that ownership is clear.
|
||||||
|
fun noPackageOverlap(packages: Collection<String>): Boolean {
|
||||||
|
return packages.all { outer -> packages.none { inner -> inner != outer && inner.startsWith("$outer.") } }
|
||||||
|
}
|
||||||
|
@ -2,23 +2,17 @@ package net.corda.core.node
|
|||||||
|
|
||||||
import net.corda.core.CordaRuntimeException
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.KeepForDJVM
|
import net.corda.core.KeepForDJVM
|
||||||
import net.corda.core.contracts.ContractClassName
|
|
||||||
import net.corda.core.crypto.toStringShort
|
import net.corda.core.crypto.toStringShort
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.internal.VisibleForTesting
|
import net.corda.core.internal.noPackageOverlap
|
||||||
import net.corda.core.internal.requirePackageValid
|
import net.corda.core.internal.requirePackageValid
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.node.services.AttachmentId
|
||||||
import net.corda.core.serialization.CordaSerializable
|
import net.corda.core.serialization.CordaSerializable
|
||||||
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
import net.corda.core.serialization.DeprecatedConstructorForDeserialization
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import java.lang.reflect.Method
|
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import kotlin.reflect.KProperty1
|
|
||||||
import kotlin.reflect.full.declaredMemberProperties
|
|
||||||
import kotlin.reflect.full.findAnnotation
|
|
||||||
import kotlin.reflect.jvm.javaGetter
|
|
||||||
|
|
||||||
// DOCSTART 1
|
// DOCSTART 1
|
||||||
/**
|
/**
|
||||||
@ -54,13 +48,13 @@ data class NetworkParameters(
|
|||||||
) {
|
) {
|
||||||
// DOCEND 1
|
// DOCEND 1
|
||||||
@DeprecatedConstructorForDeserialization(1)
|
@DeprecatedConstructorForDeserialization(1)
|
||||||
constructor (minimumPlatformVersion: Int,
|
constructor(minimumPlatformVersion: Int,
|
||||||
notaries: List<NotaryInfo>,
|
notaries: List<NotaryInfo>,
|
||||||
maxMessageSize: Int,
|
maxMessageSize: Int,
|
||||||
maxTransactionSize: Int,
|
maxTransactionSize: Int,
|
||||||
modifiedTime: Instant,
|
modifiedTime: Instant,
|
||||||
epoch: Int,
|
epoch: Int,
|
||||||
whitelistedContractImplementations: Map<String, List<AttachmentId>>
|
whitelistedContractImplementations: Map<String, List<AttachmentId>>
|
||||||
) : this(minimumPlatformVersion,
|
) : this(minimumPlatformVersion,
|
||||||
notaries,
|
notaries,
|
||||||
maxMessageSize,
|
maxMessageSize,
|
||||||
@ -73,14 +67,14 @@ data class NetworkParameters(
|
|||||||
)
|
)
|
||||||
|
|
||||||
@DeprecatedConstructorForDeserialization(2)
|
@DeprecatedConstructorForDeserialization(2)
|
||||||
constructor (minimumPlatformVersion: Int,
|
constructor(minimumPlatformVersion: Int,
|
||||||
notaries: List<NotaryInfo>,
|
notaries: List<NotaryInfo>,
|
||||||
maxMessageSize: Int,
|
maxMessageSize: Int,
|
||||||
maxTransactionSize: Int,
|
maxTransactionSize: Int,
|
||||||
modifiedTime: Instant,
|
modifiedTime: Instant,
|
||||||
epoch: Int,
|
epoch: Int,
|
||||||
whitelistedContractImplementations: Map<String, List<AttachmentId>>,
|
whitelistedContractImplementations: Map<String, List<AttachmentId>>,
|
||||||
eventHorizon: Duration
|
eventHorizon: Duration
|
||||||
) : this(minimumPlatformVersion,
|
) : this(minimumPlatformVersion,
|
||||||
notaries,
|
notaries,
|
||||||
maxMessageSize,
|
maxMessageSize,
|
||||||
@ -92,32 +86,6 @@ data class NetworkParameters(
|
|||||||
emptyMap()
|
emptyMap()
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val memberPropertyPartition = NetworkParameters::class.declaredMemberProperties.asSequence()
|
|
||||||
.partition { it.isAutoAcceptable() }
|
|
||||||
private val autoAcceptableNamesAndGetters = memberPropertyPartition.first.associateBy({ it.name }, { it.javaGetter })
|
|
||||||
private val nonAutoAcceptableGetters = memberPropertyPartition.second.map { it.javaGetter }
|
|
||||||
val autoAcceptablePropertyNames = autoAcceptableNamesAndGetters.keys
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the [fullClassName] is in a subpackage of [packageName].
|
|
||||||
* E.g.: "com.megacorp" owns "com.megacorp.tokens.MegaToken"
|
|
||||||
*
|
|
||||||
* Note: The ownership check is ignoring case to prevent people from just releasing a jar with: "com.megaCorp.megatoken" and pretend they are MegaCorp.
|
|
||||||
* By making the check case insensitive, the node will require that the jar is signed by MegaCorp, so the attack fails.
|
|
||||||
*/
|
|
||||||
private fun owns(packageName: String, fullClassName: String) = fullClassName.startsWith("$packageName.", ignoreCase = true)
|
|
||||||
|
|
||||||
// Make sure that packages don't overlap so that ownership is clear.
|
|
||||||
fun noOverlap(packages: Collection<String>) = packages.all { currentPackage ->
|
|
||||||
packages.none { otherPackage -> otherPackage != currentPackage && otherPackage.startsWith("$currentPackage.") }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean {
|
|
||||||
return this.findAnnotation<AutoAcceptable>() != null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
require(minimumPlatformVersion > 0) { "Minimum platform level must be at least 1" }
|
require(minimumPlatformVersion > 0) { "Minimum platform level must be at least 1" }
|
||||||
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
|
require(notaries.distinctBy { it.identity } == notaries) { "Duplicate notary identities" }
|
||||||
@ -126,7 +94,7 @@ data class NetworkParameters(
|
|||||||
require(maxTransactionSize > 0) { "Maximum transaction size must be at least 1" }
|
require(maxTransactionSize > 0) { "Maximum transaction size must be at least 1" }
|
||||||
require(!eventHorizon.isNegative) { "Event Horizon must be a positive value" }
|
require(!eventHorizon.isNegative) { "Event Horizon must be a positive value" }
|
||||||
packageOwnership.keys.forEach(::requirePackageValid)
|
packageOwnership.keys.forEach(::requirePackageValid)
|
||||||
require(noOverlap(packageOwnership.keys)) { "Multiple packages added to the packageOwnership overlap." }
|
require(noPackageOverlap(packageOwnership.keys)) { "Multiple packages added to the packageOwnership overlap." }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copy(minimumPlatformVersion: Int,
|
fun copy(minimumPlatformVersion: Int,
|
||||||
@ -137,14 +105,16 @@ data class NetworkParameters(
|
|||||||
epoch: Int,
|
epoch: Int,
|
||||||
whitelistedContractImplementations: Map<String, List<AttachmentId>>
|
whitelistedContractImplementations: Map<String, List<AttachmentId>>
|
||||||
): NetworkParameters {
|
): NetworkParameters {
|
||||||
return copy(minimumPlatformVersion = minimumPlatformVersion,
|
return copy(
|
||||||
|
minimumPlatformVersion = minimumPlatformVersion,
|
||||||
notaries = notaries,
|
notaries = notaries,
|
||||||
maxMessageSize = maxMessageSize,
|
maxMessageSize = maxMessageSize,
|
||||||
maxTransactionSize = maxTransactionSize,
|
maxTransactionSize = maxTransactionSize,
|
||||||
modifiedTime = modifiedTime,
|
modifiedTime = modifiedTime,
|
||||||
epoch = epoch,
|
epoch = epoch,
|
||||||
whitelistedContractImplementations = whitelistedContractImplementations,
|
whitelistedContractImplementations = whitelistedContractImplementations,
|
||||||
eventHorizon = eventHorizon)
|
eventHorizon = eventHorizon
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun copy(minimumPlatformVersion: Int,
|
fun copy(minimumPlatformVersion: Int,
|
||||||
@ -156,14 +126,16 @@ data class NetworkParameters(
|
|||||||
whitelistedContractImplementations: Map<String, List<AttachmentId>>,
|
whitelistedContractImplementations: Map<String, List<AttachmentId>>,
|
||||||
eventHorizon: Duration
|
eventHorizon: Duration
|
||||||
): NetworkParameters {
|
): NetworkParameters {
|
||||||
return copy(minimumPlatformVersion = minimumPlatformVersion,
|
return copy(
|
||||||
|
minimumPlatformVersion = minimumPlatformVersion,
|
||||||
notaries = notaries,
|
notaries = notaries,
|
||||||
maxMessageSize = maxMessageSize,
|
maxMessageSize = maxMessageSize,
|
||||||
maxTransactionSize = maxTransactionSize,
|
maxTransactionSize = maxTransactionSize,
|
||||||
modifiedTime = modifiedTime,
|
modifiedTime = modifiedTime,
|
||||||
epoch = epoch,
|
epoch = epoch,
|
||||||
whitelistedContractImplementations = whitelistedContractImplementations,
|
whitelistedContractImplementations = whitelistedContractImplementations,
|
||||||
eventHorizon = eventHorizon)
|
eventHorizon = eventHorizon
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
@ -176,43 +148,13 @@ data class NetworkParameters(
|
|||||||
${whitelistedContractImplementations.entries.joinToString("\n ")}
|
${whitelistedContractImplementations.entries.joinToString("\n ")}
|
||||||
}
|
}
|
||||||
eventHorizon=$eventHorizon
|
eventHorizon=$eventHorizon
|
||||||
modifiedTime=$modifiedTime
|
packageOwnership {
|
||||||
epoch=$epoch,
|
|
||||||
packageOwnership= {
|
|
||||||
${packageOwnership.entries.joinToString("\n ") { "$it.key -> ${it.value.toStringShort()}" }}
|
${packageOwnership.entries.joinToString("\n ") { "$it.key -> ${it.value.toStringShort()}" }}
|
||||||
}
|
}
|
||||||
|
modifiedTime=$modifiedTime
|
||||||
|
epoch=$epoch
|
||||||
}"""
|
}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the public key of the package owner of the [contractClassName], or null if not owned.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
internal fun getPackageOwnerOf(contractClassName: ContractClassName): PublicKey? = this.packageOwnership.filterKeys { packageName -> owns(packageName, contractClassName) }.values.singleOrNull()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the public key of the package owner if any of [contractClassName] match, or null if not owned.
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
internal fun getPackageOwnerOf(contractClassNames: Set<ContractClassName>): PublicKey? {
|
|
||||||
val ownerKeys = contractClassNames.map { getPackageOwnerOf(it) }
|
|
||||||
return ownerKeys.find { it != null }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the only properties changed in [newNetworkParameters] are [AutoAcceptable] and not
|
|
||||||
* included in the [excludedParameterNames]
|
|
||||||
*/
|
|
||||||
fun canAutoAccept(newNetworkParameters: NetworkParameters, excludedParameterNames: Set<String>): Boolean {
|
|
||||||
return nonAutoAcceptableGetters.none { valueChanged(newNetworkParameters, it) } &&
|
|
||||||
autoAcceptableNamesAndGetters.none { excludedParameterNames.contains(it.key) && valueChanged(newNetworkParameters, it.value) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun valueChanged(newNetworkParameters: NetworkParameters, getter: Method?): Boolean {
|
|
||||||
val propertyValue = getter?.invoke(this)
|
|
||||||
val newPropertyValue = getter?.invoke(newNetworkParameters)
|
|
||||||
return propertyValue != newPropertyValue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -229,7 +171,3 @@ data class NotaryInfo(val identity: Party, val validating: Boolean)
|
|||||||
* version.
|
* version.
|
||||||
*/
|
*/
|
||||||
class ZoneVersionTooLowException(message: String) : CordaRuntimeException(message)
|
class ZoneVersionTooLowException(message: String) : CordaRuntimeException(message)
|
||||||
|
|
||||||
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean {
|
|
||||||
return this.findAnnotation<AutoAcceptable>() != null
|
|
||||||
}
|
|
@ -1,7 +1,7 @@
|
|||||||
package net.corda.core.node
|
package net.corda.core.node
|
||||||
|
|
||||||
import net.corda.core.crypto.generateKeyPair
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.node.services.AttachmentId
|
import net.corda.core.internal.getPackageOwnerOf
|
||||||
import net.corda.core.utilities.OpaqueBytes
|
import net.corda.core.utilities.OpaqueBytes
|
||||||
import net.corda.core.utilities.days
|
import net.corda.core.utilities.days
|
||||||
import net.corda.core.utilities.getOrThrow
|
import net.corda.core.utilities.getOrThrow
|
||||||
@ -32,7 +32,8 @@ import kotlin.test.assertFails
|
|||||||
class NetworkParametersTest {
|
class NetworkParametersTest {
|
||||||
private val mockNet = InternalMockNetwork(
|
private val mockNet = InternalMockNetwork(
|
||||||
defaultParameters = MockNetworkParameters(networkSendManuallyPumped = true),
|
defaultParameters = MockNetworkParameters(networkSendManuallyPumped = true),
|
||||||
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME)))
|
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME))
|
||||||
|
)
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun tearDown() {
|
fun tearDown() {
|
||||||
@ -129,44 +130,8 @@ class NetworkParametersTest {
|
|||||||
assertEquals(params.getPackageOwnerOf("com.exam.something.MyClass"), null)
|
assertEquals(params.getPackageOwnerOf("com.exam.something.MyClass"), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `auto acceptance checks are correct`() {
|
|
||||||
val packageOwnership = mapOf(
|
|
||||||
"com.example1" to generateKeyPair().public,
|
|
||||||
"com.example2" to generateKeyPair().public)
|
|
||||||
val whitelistedContractImplementations = mapOf(
|
|
||||||
"example1" to listOf(AttachmentId.randomSHA256()),
|
|
||||||
"example2" to listOf(AttachmentId.randomSHA256()))
|
|
||||||
|
|
||||||
val netParams = testNetworkParameters()
|
|
||||||
val netParamsAutoAcceptable = netParams.copy(
|
|
||||||
packageOwnership = packageOwnership,
|
|
||||||
whitelistedContractImplementations = whitelistedContractImplementations)
|
|
||||||
val netParamsNotAutoAcceptable = netParamsAutoAcceptable.copy(
|
|
||||||
maxMessageSize = netParams.maxMessageSize + 1)
|
|
||||||
|
|
||||||
assert(netParams.canAutoAccept(netParams, emptySet())) {
|
|
||||||
"auto-acceptable if identical"
|
|
||||||
}
|
|
||||||
assert(netParams.canAutoAccept(netParams, NetworkParameters.autoAcceptablePropertyNames)) {
|
|
||||||
"auto acceptable if identical regardless of exclusions"
|
|
||||||
}
|
|
||||||
assert(netParams.canAutoAccept(netParamsAutoAcceptable, emptySet())) {
|
|
||||||
"auto-acceptable if only AutoAcceptable params have changed"
|
|
||||||
}
|
|
||||||
assert(netParams.canAutoAccept(netParamsAutoAcceptable, setOf("modifiedTime"))) {
|
|
||||||
"auto-acceptable if only AutoAcceptable params have changed and excluded param has not changed"
|
|
||||||
}
|
|
||||||
assert(!netParams.canAutoAccept(netParamsNotAutoAcceptable, emptySet())) {
|
|
||||||
"not auto-acceptable if non-AutoAcceptable param has changed"
|
|
||||||
}
|
|
||||||
assert(!netParams.canAutoAccept(netParamsAutoAcceptable, setOf("whitelistedContractImplementations"))) {
|
|
||||||
"not auto-acceptable if only AutoAcceptable params have changed but one has been added to the exclusion set"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
private fun dropParametersToDir(dir: Path, params: NetworkParameters) {
|
private fun dropParametersToDir(dir: Path, params: NetworkParameters) {
|
||||||
NetworkParametersCopier(params).install(dir)
|
NetworkParametersCopier(params).install(dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ import net.corda.core.crypto.SignedData
|
|||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.messaging.DataFeed
|
import net.corda.core.messaging.DataFeed
|
||||||
import net.corda.core.messaging.ParametersUpdateInfo
|
import net.corda.core.messaging.ParametersUpdateInfo
|
||||||
|
import net.corda.core.node.AutoAcceptable
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.services.KeyManagementService
|
import net.corda.core.node.services.KeyManagementService
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
@ -21,6 +22,7 @@ import net.corda.nodeapi.internal.SignedNodeInfo
|
|||||||
import net.corda.nodeapi.internal.network.*
|
import net.corda.nodeapi.internal.network.*
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
|
import java.lang.reflect.Method
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.StandardCopyOption
|
import java.nio.file.StandardCopyOption
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
@ -28,6 +30,10 @@ import java.time.Duration
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ScheduledThreadPoolExecutor
|
import java.util.concurrent.ScheduledThreadPoolExecutor
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import kotlin.reflect.KProperty1
|
||||||
|
import kotlin.reflect.full.declaredMemberProperties
|
||||||
|
import kotlin.reflect.full.findAnnotation
|
||||||
|
import kotlin.reflect.jvm.javaGetter
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
||||||
@ -78,7 +84,7 @@ class NetworkMapUpdater(private val networkMapCache: NetworkMapCacheInternal,
|
|||||||
this.autoAcceptNetworkParameters = networkParameterAcceptanceSettings.autoAcceptEnabled
|
this.autoAcceptNetworkParameters = networkParameterAcceptanceSettings.autoAcceptEnabled
|
||||||
this.excludedAutoAcceptNetworkParameters = networkParameterAcceptanceSettings.excludedAutoAcceptableParameters
|
this.excludedAutoAcceptNetworkParameters = networkParameterAcceptanceSettings.excludedAutoAcceptableParameters
|
||||||
|
|
||||||
val autoAcceptNetworkParametersNames = NetworkParameters.autoAcceptablePropertyNames - excludedAutoAcceptNetworkParameters
|
val autoAcceptNetworkParametersNames = autoAcceptablePropertyNames - excludedAutoAcceptNetworkParameters
|
||||||
if (autoAcceptNetworkParameters && autoAcceptNetworkParametersNames.isNotEmpty()) {
|
if (autoAcceptNetworkParameters && autoAcceptNetworkParametersNames.isNotEmpty()) {
|
||||||
logger.info("Auto-accept enabled for network parameter changes which modify only: $autoAcceptNetworkParametersNames")
|
logger.info("Auto-accept enabled for network parameter changes which modify only: $autoAcceptNetworkParametersNames")
|
||||||
}
|
}
|
||||||
@ -250,3 +256,27 @@ The node will shutdown now.""")
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val memberPropertyPartition = NetworkParameters::class.declaredMemberProperties.partition { it.isAutoAcceptable() }
|
||||||
|
private val autoAcceptableNamesAndGetters = memberPropertyPartition.first.associateBy({ it.name }, { it.javaGetter })
|
||||||
|
@VisibleForTesting
|
||||||
|
internal val autoAcceptablePropertyNames = autoAcceptableNamesAndGetters.keys
|
||||||
|
private val nonAutoAcceptableGetters = memberPropertyPartition.second.map { it.javaGetter }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the only properties changed in [newNetworkParameters] are [AutoAcceptable] and not
|
||||||
|
* included in the [excludedParameterNames]
|
||||||
|
*/
|
||||||
|
@VisibleForTesting
|
||||||
|
internal fun NetworkParameters.canAutoAccept(newNetworkParameters: NetworkParameters, excludedParameterNames: Set<String>): Boolean {
|
||||||
|
return nonAutoAcceptableGetters.none { valueChanged(newNetworkParameters, it) } &&
|
||||||
|
autoAcceptableNamesAndGetters.none { it.key in excludedParameterNames && valueChanged(newNetworkParameters, it.value) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KProperty1<out NetworkParameters, Any?>.isAutoAcceptable(): Boolean = findAnnotation<AutoAcceptable>() != null
|
||||||
|
|
||||||
|
private fun NetworkParameters.valueChanged(newNetworkParameters: NetworkParameters, getter: Method?): Boolean {
|
||||||
|
val propertyValue = getter?.invoke(this)
|
||||||
|
val newPropertyValue = getter?.invoke(newNetworkParameters)
|
||||||
|
return propertyValue != newPropertyValue
|
||||||
|
}
|
@ -5,6 +5,7 @@ import com.google.common.jimfs.Jimfs
|
|||||||
import com.nhaarman.mockito_kotlin.*
|
import com.nhaarman.mockito_kotlin.*
|
||||||
import net.corda.core.crypto.Crypto
|
import net.corda.core.crypto.Crypto
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.crypto.generateKeyPair
|
||||||
import net.corda.core.crypto.sign
|
import net.corda.core.crypto.sign
|
||||||
import net.corda.core.identity.CordaX500Name
|
import net.corda.core.identity.CordaX500Name
|
||||||
import net.corda.core.identity.Party
|
import net.corda.core.identity.Party
|
||||||
@ -13,17 +14,20 @@ import net.corda.core.internal.concurrent.openFuture
|
|||||||
import net.corda.core.messaging.ParametersUpdateInfo
|
import net.corda.core.messaging.ParametersUpdateInfo
|
||||||
import net.corda.core.node.NetworkParameters
|
import net.corda.core.node.NetworkParameters
|
||||||
import net.corda.core.node.NodeInfo
|
import net.corda.core.node.NodeInfo
|
||||||
|
import net.corda.core.node.services.AttachmentId
|
||||||
import net.corda.core.serialization.serialize
|
import net.corda.core.serialization.serialize
|
||||||
import net.corda.core.utilities.millis
|
import net.corda.core.utilities.millis
|
||||||
import net.corda.node.VersionInfo
|
import net.corda.node.VersionInfo
|
||||||
|
import net.corda.node.internal.NetworkParametersStorageInternal
|
||||||
import net.corda.node.services.api.NetworkMapCacheInternal
|
import net.corda.node.services.api.NetworkMapCacheInternal
|
||||||
import net.corda.node.services.config.NetworkParameterAcceptanceSettings
|
import net.corda.node.services.config.NetworkParameterAcceptanceSettings
|
||||||
import net.corda.core.internal.NODE_INFO_DIRECTORY
|
|
||||||
import net.corda.node.internal.NetworkParametersStorageInternal
|
|
||||||
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
import net.corda.nodeapi.internal.NodeInfoAndSigned
|
||||||
import net.corda.nodeapi.internal.SignedNodeInfo
|
import net.corda.nodeapi.internal.SignedNodeInfo
|
||||||
import net.corda.nodeapi.internal.crypto.X509Utilities
|
import net.corda.nodeapi.internal.crypto.X509Utilities
|
||||||
import net.corda.nodeapi.internal.network.*
|
import net.corda.nodeapi.internal.network.NETWORK_PARAMS_UPDATE_FILE_NAME
|
||||||
|
import net.corda.nodeapi.internal.network.NodeInfoFilesCopier
|
||||||
|
import net.corda.nodeapi.internal.network.SignedNetworkParameters
|
||||||
|
import net.corda.nodeapi.internal.network.verifiedNetworkParametersCert
|
||||||
import net.corda.testing.common.internal.testNetworkParameters
|
import net.corda.testing.common.internal.testNetworkParameters
|
||||||
import net.corda.testing.core.*
|
import net.corda.testing.core.*
|
||||||
import net.corda.testing.internal.DEV_ROOT_CA
|
import net.corda.testing.internal.DEV_ROOT_CA
|
||||||
@ -48,6 +52,8 @@ import java.util.*
|
|||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFalse
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class NetworkMapUpdaterTest {
|
class NetworkMapUpdaterTest {
|
||||||
@Rule
|
@Rule
|
||||||
@ -68,7 +74,7 @@ class NetworkMapUpdaterTest {
|
|||||||
private val networkParametersStorage: NetworkParametersStorageInternal = mock()
|
private val networkParametersStorage: NetworkParametersStorageInternal = mock()
|
||||||
private lateinit var server: NetworkMapServer
|
private lateinit var server: NetworkMapServer
|
||||||
private lateinit var networkMapClient: NetworkMapClient
|
private lateinit var networkMapClient: NetworkMapClient
|
||||||
private lateinit var updater: NetworkMapUpdater
|
private var updater: NetworkMapUpdater? = null
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
@ -82,7 +88,7 @@ class NetworkMapUpdaterTest {
|
|||||||
|
|
||||||
@After
|
@After
|
||||||
fun cleanUp() {
|
fun cleanUp() {
|
||||||
updater.close()
|
updater?.close()
|
||||||
fs.close()
|
fs.close()
|
||||||
server.close()
|
server.close()
|
||||||
}
|
}
|
||||||
@ -95,7 +101,7 @@ class NetworkMapUpdaterTest {
|
|||||||
networkParameters: NetworkParameters = server.networkParameters,
|
networkParameters: NetworkParameters = server.networkParameters,
|
||||||
autoAcceptNetworkParameters: Boolean = true,
|
autoAcceptNetworkParameters: Boolean = true,
|
||||||
excludedAutoAcceptNetworkParameters: Set<String> = emptySet()) {
|
excludedAutoAcceptNetworkParameters: Set<String> = emptySet()) {
|
||||||
updater.start(DEV_ROOT_CA.certificate,
|
updater!!.start(DEV_ROOT_CA.certificate,
|
||||||
server.networkParameters.serialize().hash,
|
server.networkParameters.serialize().hash,
|
||||||
ourNodeInfo,
|
ourNodeInfo,
|
||||||
networkParameters,
|
networkParameters,
|
||||||
@ -206,7 +212,7 @@ class NetworkMapUpdaterTest {
|
|||||||
@Test
|
@Test
|
||||||
fun `emit new parameters update info on parameters update from network map`() {
|
fun `emit new parameters update info on parameters update from network map`() {
|
||||||
setUpdater()
|
setUpdater()
|
||||||
val paramsFeed = updater.trackParametersUpdate()
|
val paramsFeed = updater!!.trackParametersUpdate()
|
||||||
val snapshot = paramsFeed.snapshot
|
val snapshot = paramsFeed.snapshot
|
||||||
val updates = paramsFeed.updates.bufferUntilSubscribed()
|
val updates = paramsFeed.updates.bufferUntilSubscribed()
|
||||||
assertEquals(null, snapshot)
|
assertEquals(null, snapshot)
|
||||||
@ -237,7 +243,7 @@ class NetworkMapUpdaterTest {
|
|||||||
val newHash = newParameters.serialize().hash
|
val newHash = newParameters.serialize().hash
|
||||||
val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME
|
val updateFile = baseDir / NETWORK_PARAMS_UPDATE_FILE_NAME
|
||||||
assert(!updateFile.exists()) { "network parameters should not be auto accepted" }
|
assert(!updateFile.exists()) { "network parameters should not be auto accepted" }
|
||||||
updater.acceptNewNetworkParameters(newHash) { it.serialize().sign(ourKeyPair) }
|
updater!!.acceptNewNetworkParameters(newHash) { it.serialize().sign(ourKeyPair) }
|
||||||
verify(networkParametersStorage, times(1)).saveParameters(any())
|
verify(networkParametersStorage, times(1)).saveParameters(any())
|
||||||
val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>()
|
val signedNetworkParams = updateFile.readObject<SignedNetworkParameters>()
|
||||||
val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
val paramsFromFile = signedNetworkParams.verifiedNetworkParametersCert(DEV_ROOT_CA.certificate)
|
||||||
@ -411,6 +417,32 @@ class NetworkMapUpdaterTest {
|
|||||||
assert(networkMapCache.allNodeHashes.size == 1)
|
assert(networkMapCache.allNodeHashes.size == 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `auto acceptance checks are correct`() {
|
||||||
|
val packageOwnership = mapOf(
|
||||||
|
"com.example1" to generateKeyPair().public,
|
||||||
|
"com.example2" to generateKeyPair().public
|
||||||
|
)
|
||||||
|
val whitelistedContractImplementations = mapOf(
|
||||||
|
"example1" to listOf(AttachmentId.randomSHA256()),
|
||||||
|
"example2" to listOf(AttachmentId.randomSHA256())
|
||||||
|
)
|
||||||
|
|
||||||
|
val netParams = testNetworkParameters()
|
||||||
|
val netParamsAutoAcceptable = netParams.copy(
|
||||||
|
packageOwnership = packageOwnership,
|
||||||
|
whitelistedContractImplementations = whitelistedContractImplementations
|
||||||
|
)
|
||||||
|
val netParamsNotAutoAcceptable = netParamsAutoAcceptable.copy(maxMessageSize = netParams.maxMessageSize + 1)
|
||||||
|
|
||||||
|
assertTrue(netParams.canAutoAccept(netParams, emptySet()), "auto-acceptable if identical")
|
||||||
|
assertTrue(netParams.canAutoAccept(netParams, autoAcceptablePropertyNames), "auto acceptable if identical regardless of exclusions")
|
||||||
|
assertTrue(netParams.canAutoAccept(netParamsAutoAcceptable, emptySet()), "auto-acceptable if only AutoAcceptable params have changed")
|
||||||
|
assertTrue(netParams.canAutoAccept(netParamsAutoAcceptable, setOf("modifiedTime")), "auto-acceptable if only AutoAcceptable params have changed and excluded param has not changed")
|
||||||
|
assertFalse(netParams.canAutoAccept(netParamsNotAutoAcceptable, emptySet()), "not auto-acceptable if non-AutoAcceptable param has changed")
|
||||||
|
assertFalse(netParams.canAutoAccept(netParamsAutoAcceptable, setOf("whitelistedContractImplementations")), "not auto-acceptable if only AutoAcceptable params have changed but one has been added to the exclusion set")
|
||||||
|
}
|
||||||
|
|
||||||
private fun createMockNetworkMapCache(): NetworkMapCacheInternal {
|
private fun createMockNetworkMapCache(): NetworkMapCacheInternal {
|
||||||
return mock {
|
return mock {
|
||||||
on { nodeReady }.thenReturn(nodeReadyFuture)
|
on { nodeReady }.thenReturn(nodeReadyFuture)
|
||||||
|
@ -6,9 +6,8 @@ import net.corda.common.configuration.parsing.internal.get
|
|||||||
import net.corda.common.configuration.parsing.internal.mapValid
|
import net.corda.common.configuration.parsing.internal.mapValid
|
||||||
import net.corda.common.configuration.parsing.internal.nested
|
import net.corda.common.configuration.parsing.internal.nested
|
||||||
import net.corda.common.validation.internal.Validated
|
import net.corda.common.validation.internal.Validated
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.noPackageOverlap
|
||||||
import net.corda.core.internal.requirePackageValid
|
import net.corda.core.internal.requirePackageValid
|
||||||
import net.corda.core.node.NetworkParameters
|
|
||||||
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
import net.corda.nodeapi.internal.crypto.loadKeyStore
|
||||||
import net.corda.nodeapi.internal.network.NetworkParametersOverrides
|
import net.corda.nodeapi.internal.network.NetworkParametersOverrides
|
||||||
import net.corda.nodeapi.internal.network.PackageOwner
|
import net.corda.nodeapi.internal.network.PackageOwner
|
||||||
@ -84,8 +83,12 @@ internal object NetworkParameterOverridesSpec : Configuration.Specification<Netw
|
|||||||
|
|
||||||
override fun parseValid(configuration: Config): Valid<NetworkParametersOverrides> {
|
override fun parseValid(configuration: Config): Valid<NetworkParametersOverrides> {
|
||||||
val packageOwnership = configuration[packageOwnership]
|
val packageOwnership = configuration[packageOwnership]
|
||||||
if (packageOwnership != null && !NetworkParameters.noOverlap(packageOwnership.map { it.javaPackageName })) {
|
if (packageOwnership != null && !noPackageOverlap(packageOwnership.map { it.javaPackageName })) {
|
||||||
return Validated.invalid(sequenceOf(Configuration.Validation.Error.BadValue.of("Package namespaces must not overlap", keyName = "packageOwnership", containingPath = listOf())).toSet())
|
return Validated.invalid(sequenceOf(Configuration.Validation.Error.BadValue.of(
|
||||||
|
"Package namespaces must not overlap",
|
||||||
|
keyName = "packageOwnership",
|
||||||
|
containingPath = listOf()
|
||||||
|
)).toSet())
|
||||||
}
|
}
|
||||||
return valid(NetworkParametersOverrides(
|
return valid(NetworkParametersOverrides(
|
||||||
minimumPlatformVersion = configuration[minimumPlatformVersion],
|
minimumPlatformVersion = configuration[minimumPlatformVersion],
|
||||||
|
Loading…
x
Reference in New Issue
Block a user