mirror of
https://github.com/corda/corda.git
synced 2024-12-19 21:17:58 +00:00
Merge branch 'release/os/4.12' into tom/ENT-11104
This commit is contained in:
commit
d9c09f21f5
@ -34,6 +34,10 @@ pipeline {
|
||||
// in the name
|
||||
ARTIFACTORY_BUILD_NAME = "Corda / Publish / Publish Nightly to Artifactory"
|
||||
.replaceAll("/", " :: ")
|
||||
BUILD_CACHE_CREDENTIALS = credentials('gradle-ent-cache-credentials')
|
||||
BUILD_CACHE_PASSWORD = "${env.BUILD_CACHE_CREDENTIALS_PSW}"
|
||||
BUILD_CACHE_USERNAME = "${env.BUILD_CACHE_CREDENTIALS_USR}"
|
||||
USE_CACHE = 'corda-remotes'
|
||||
DOCKER_URL = "https://index.docker.io/v1/"
|
||||
JAVA_HOME = "/usr/lib/jvm/java-17-amazon-corretto"
|
||||
}
|
||||
|
2
.ci/dev/regression/Jenkinsfile
vendored
2
.ci/dev/regression/Jenkinsfile
vendored
@ -329,7 +329,7 @@ pipeline {
|
||||
'./gradlew',
|
||||
COMMON_GRADLE_PARAMS,
|
||||
'docker:pushDockerImage',
|
||||
'-Pdocker.image.repository=corda/community',
|
||||
'-Pdocker.image.repository=corda/open-source',
|
||||
'--image OFFICIAL'
|
||||
].join(' ')
|
||||
}
|
||||
|
@ -165,7 +165,9 @@ buildscript {
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven {
|
||||
url "${publicArtifactURL}/jcenter-backup"
|
||||
}
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
@ -406,7 +408,9 @@ allprojects {
|
||||
}
|
||||
}
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
maven {
|
||||
url "${publicArtifactURL}/jcenter-backup"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,38 @@ import groovy.transform.CompileStatic
|
||||
if (System.getenv('CORDA_ARTIFACTORY_USERNAME') != null || project.hasProperty('cordaArtifactoryUsername')) {
|
||||
logger.info("Internal R3 user - resolving publication build dependencies from internal plugins")
|
||||
pluginManager.apply('com.r3.internal.gradle.plugins.r3Publish')
|
||||
afterEvaluate {
|
||||
publishing {
|
||||
publications {
|
||||
configureEach {
|
||||
def repo = "https://github.com/corda/corda"
|
||||
pom {
|
||||
description = project.description
|
||||
name = project.name
|
||||
url = repo
|
||||
scm {
|
||||
url = repo
|
||||
}
|
||||
licenses {
|
||||
license {
|
||||
name = 'Apache-2.0'
|
||||
url = 'https://www.apache.org/licenses/LICENSE-2.0'
|
||||
distribution = 'repo'
|
||||
}
|
||||
}
|
||||
|
||||
developers {
|
||||
developer {
|
||||
id = 'R3'
|
||||
name = 'R3'
|
||||
email = 'dev@corda.net'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.info("External user - using standard maven publishing")
|
||||
pluginManager.apply('maven-publish')
|
||||
|
@ -3,6 +3,8 @@ apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
apply plugin: 'corda.api-scanner'
|
||||
|
||||
description 'Corda Jackson module'
|
||||
|
||||
dependencies {
|
||||
api project(':core')
|
||||
|
||||
|
@ -552,7 +552,7 @@ class RPCStabilityTests {
|
||||
// Construct an RPC session manually so that we can hang in the message handler
|
||||
val myQueue = "${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.test.${random63BitValue()}"
|
||||
val session = startArtemisSession(server.broker.hostAndPort!!)
|
||||
session.createQueue(QueueConfiguration(myQueue)
|
||||
session.createQueue(QueueConfiguration.of(myQueue)
|
||||
.setRoutingType(ActiveMQDefaultConfiguration.getDefaultRoutingType())
|
||||
.setAddress(myQueue)
|
||||
.setTemporary(true)
|
||||
@ -569,7 +569,7 @@ class RPCStabilityTests {
|
||||
|
||||
val message = session.createMessage(false)
|
||||
val request = RPCApi.ClientToServer.RpcRequest(
|
||||
clientAddress = SimpleString(myQueue),
|
||||
clientAddress = SimpleString.of(myQueue),
|
||||
methodName = SlowConsumerRPCOps::streamAtInterval.name,
|
||||
serialisedArguments = listOf(100.millis, 1234).serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT),
|
||||
replyId = Trace.InvocationId.newInstance(),
|
||||
@ -593,7 +593,7 @@ class RPCStabilityTests {
|
||||
// Construct an RPC client session manually
|
||||
val myQueue = "${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.test.${random63BitValue()}"
|
||||
val session = startArtemisSession(server.broker.hostAndPort!!)
|
||||
session.createQueue(QueueConfiguration(myQueue)
|
||||
session.createQueue(QueueConfiguration.of(myQueue)
|
||||
.setRoutingType(ActiveMQDefaultConfiguration.getDefaultRoutingType())
|
||||
.setAddress(myQueue)
|
||||
.setTemporary(true)
|
||||
@ -612,7 +612,7 @@ class RPCStabilityTests {
|
||||
|
||||
val message = session.createMessage(false)
|
||||
val request = RPCApi.ClientToServer.RpcRequest(
|
||||
clientAddress = SimpleString(myQueue),
|
||||
clientAddress = SimpleString.of(myQueue),
|
||||
methodName = DummyOps::protocolVersion.name,
|
||||
serialisedArguments = emptyList<Any>().serialize(context = SerializationDefaults.RPC_SERVER_CONTEXT),
|
||||
replyId = Trace.InvocationId.newInstance(),
|
||||
|
@ -652,9 +652,9 @@ internal class RPCClientProxyHandler(
|
||||
producerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, DEFAULT_ACK_BATCH_SIZE)
|
||||
rpcProducer = producerSession!!.createProducer(RPCApi.RPC_SERVER_QUEUE_NAME)
|
||||
consumerSession = sessionFactory!!.createSession(rpcUsername, rpcPassword, false, true, true, false, 16384)
|
||||
clientAddress = SimpleString("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$rpcUsername.${random63BitValue()}")
|
||||
clientAddress = SimpleString.of("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.$rpcUsername.${random63BitValue()}")
|
||||
log.debug { "Client address: $clientAddress" }
|
||||
consumerSession!!.createQueue(QueueConfiguration(clientAddress).setAddress(clientAddress).setRoutingType(RoutingType.ANYCAST)
|
||||
consumerSession!!.createQueue(QueueConfiguration.of(clientAddress).setAddress(clientAddress).setRoutingType(RoutingType.ANYCAST)
|
||||
.setTemporary(true).setDurable(false))
|
||||
rpcConsumer = consumerSession!!.createConsumer(clientAddress)
|
||||
rpcConsumer!!.setMessageHandler(this::artemisMessageHandler)
|
||||
|
@ -1,6 +1,8 @@
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Corda common-configuration-parsing module'
|
||||
|
||||
dependencies {
|
||||
implementation group: "org.jetbrains.kotlin", name: "kotlin-reflect", version: kotlin_version
|
||||
|
||||
|
@ -3,6 +3,8 @@ import org.apache.tools.ant.filters.ReplaceTokens
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Corda common-logging module'
|
||||
|
||||
dependencies {
|
||||
implementation group: "org.jetbrains.kotlin", name: "kotlin-reflect", version: kotlin_version
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Corda common-validation module'
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
}
|
||||
|
@ -47,6 +47,7 @@ publishing {
|
||||
maven(MavenPublication) {
|
||||
artifactId 'corda-confidential-identities'
|
||||
from components.cordapp
|
||||
artifact javadocJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ openSourceSamplesBranch=https://github.com/corda/samples/blob/release-V4
|
||||
jolokiaAgentVersion=1.6.1
|
||||
detektVersion=1.0.1
|
||||
tcnativeVersion=2.0.48.Final
|
||||
commonsConfiguration2Version=2.10.1
|
||||
commonsConfiguration2Version=2.11.0
|
||||
commonsTextVersion=1.10.0
|
||||
|
||||
# ENT-6607 all third party version in here now
|
||||
@ -45,9 +45,9 @@ commonsTextVersion=1.10.0
|
||||
# We must configure it manually to use the latest capsule version.
|
||||
capsuleVersion=1.0.4_r3
|
||||
asmVersion=9.5
|
||||
artemisVersion=2.32.0
|
||||
artemisVersion=2.36.0
|
||||
# TODO Upgrade Jackson only when corda is using kotlin 1.3.10
|
||||
jacksonVersion=2.17.0
|
||||
jacksonVersion=2.17.2
|
||||
jacksonKotlinVersion=2.17.0
|
||||
jettyVersion=12.0.7
|
||||
jerseyVersion=3.1.6
|
||||
|
@ -108,6 +108,7 @@ dependencies {
|
||||
testImplementation "org.bouncycastle:bcprov-lts8on:${bouncycastle_version}"
|
||||
testImplementation "io.netty:netty-common:$netty_version"
|
||||
testImplementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
|
||||
testImplementation "io.dropwizard.metrics:metrics-jmx:$metrics_version"
|
||||
|
||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${junit_vintage_version}"
|
||||
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:${junit_jupiter_version}"
|
||||
|
@ -7,6 +7,7 @@ import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.CordaRotatedKeys
|
||||
import net.corda.core.contracts.HashAttachmentConstraint
|
||||
import net.corda.core.contracts.NoConstraintPropagation
|
||||
import net.corda.core.contracts.SignatureAttachmentConstraint
|
||||
@ -341,52 +342,53 @@ class ConstraintsPropagationTests {
|
||||
|
||||
// propagation check
|
||||
// TODO - enable once the logic to transition has been added.
|
||||
assertFalse(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(HashAttachmentConstraint(allOnesHash), attachmentSigned))
|
||||
assertFalse(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(HashAttachmentConstraint(allOnesHash), attachmentSigned, CordaRotatedKeys.keys))
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Attachment canBeTransitionedFrom behaves as expected`() {
|
||||
|
||||
// signed attachment (for signature constraint)
|
||||
val rotatedKeys = CordaRotatedKeys.keys
|
||||
val attachment = mock<ContractAttachment>()
|
||||
whenever(attachment.signerKeys).thenReturn(listOf(ALICE_PARTY.owningKey))
|
||||
whenever(attachment.allContracts).thenReturn(setOf(propagatingContractClassName))
|
||||
|
||||
// Exhaustive positive check
|
||||
assertTrue(HashAttachmentConstraint(SecureHash.randomSHA256()).canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment))
|
||||
assertTrue(HashAttachmentConstraint(SecureHash.randomSHA256()).canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment))
|
||||
assertTrue(HashAttachmentConstraint(SecureHash.randomSHA256()).canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment, rotatedKeys))
|
||||
assertTrue(HashAttachmentConstraint(SecureHash.randomSHA256()).canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment, rotatedKeys))
|
||||
|
||||
assertTrue(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment))
|
||||
assertTrue(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment))
|
||||
assertTrue(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment, rotatedKeys))
|
||||
assertTrue(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment, rotatedKeys))
|
||||
|
||||
assertTrue(WhitelistedByZoneAttachmentConstraint.canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment))
|
||||
assertTrue(WhitelistedByZoneAttachmentConstraint.canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment, rotatedKeys))
|
||||
|
||||
assertTrue(AlwaysAcceptAttachmentConstraint.canBeTransitionedFrom(AlwaysAcceptAttachmentConstraint, attachment))
|
||||
assertTrue(AlwaysAcceptAttachmentConstraint.canBeTransitionedFrom(AlwaysAcceptAttachmentConstraint, attachment, rotatedKeys))
|
||||
|
||||
// Exhaustive negative check
|
||||
assertFalse(HashAttachmentConstraint(SecureHash.randomSHA256()).canBeTransitionedFrom(AlwaysAcceptAttachmentConstraint, attachment))
|
||||
assertFalse(WhitelistedByZoneAttachmentConstraint.canBeTransitionedFrom(AlwaysAcceptAttachmentConstraint, attachment))
|
||||
assertFalse(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(AlwaysAcceptAttachmentConstraint, attachment))
|
||||
assertFalse(HashAttachmentConstraint(SecureHash.randomSHA256()).canBeTransitionedFrom(AlwaysAcceptAttachmentConstraint, attachment, rotatedKeys))
|
||||
assertFalse(WhitelistedByZoneAttachmentConstraint.canBeTransitionedFrom(AlwaysAcceptAttachmentConstraint, attachment, rotatedKeys))
|
||||
assertFalse(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(AlwaysAcceptAttachmentConstraint, attachment, rotatedKeys))
|
||||
|
||||
assertFalse(HashAttachmentConstraint(SecureHash.randomSHA256()).canBeTransitionedFrom(HashAttachmentConstraint(SecureHash.randomSHA256()), attachment))
|
||||
assertFalse(HashAttachmentConstraint(SecureHash.randomSHA256()).canBeTransitionedFrom(HashAttachmentConstraint(SecureHash.randomSHA256()), attachment, rotatedKeys))
|
||||
|
||||
assertFalse(WhitelistedByZoneAttachmentConstraint.canBeTransitionedFrom(HashAttachmentConstraint(SecureHash.randomSHA256()), attachment))
|
||||
assertFalse(WhitelistedByZoneAttachmentConstraint.canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment))
|
||||
assertFalse(WhitelistedByZoneAttachmentConstraint.canBeTransitionedFrom(HashAttachmentConstraint(SecureHash.randomSHA256()), attachment, rotatedKeys))
|
||||
assertFalse(WhitelistedByZoneAttachmentConstraint.canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment, rotatedKeys))
|
||||
|
||||
assertFalse(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(HashAttachmentConstraint(SecureHash.randomSHA256()), attachment))
|
||||
assertFalse(SignatureAttachmentConstraint(BOB_PUBKEY).canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment))
|
||||
assertFalse(SignatureAttachmentConstraint(BOB_PUBKEY).canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment))
|
||||
assertFalse(SignatureAttachmentConstraint(ALICE_PUBKEY).canBeTransitionedFrom(HashAttachmentConstraint(SecureHash.randomSHA256()), attachment, rotatedKeys))
|
||||
assertFalse(SignatureAttachmentConstraint(BOB_PUBKEY).canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment, rotatedKeys))
|
||||
assertFalse(SignatureAttachmentConstraint(BOB_PUBKEY).canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment, rotatedKeys))
|
||||
|
||||
assertFalse(AlwaysAcceptAttachmentConstraint.canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment))
|
||||
assertFalse(AlwaysAcceptAttachmentConstraint.canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment))
|
||||
assertFalse(AlwaysAcceptAttachmentConstraint.canBeTransitionedFrom(HashAttachmentConstraint(SecureHash.randomSHA256()), attachment))
|
||||
assertFalse(AlwaysAcceptAttachmentConstraint.canBeTransitionedFrom(SignatureAttachmentConstraint(ALICE_PUBKEY), attachment, rotatedKeys))
|
||||
assertFalse(AlwaysAcceptAttachmentConstraint.canBeTransitionedFrom(WhitelistedByZoneAttachmentConstraint, attachment, rotatedKeys))
|
||||
assertFalse(AlwaysAcceptAttachmentConstraint.canBeTransitionedFrom(HashAttachmentConstraint(SecureHash.randomSHA256()), attachment, rotatedKeys))
|
||||
|
||||
// Fail when encounter a AutomaticPlaceholderConstraint
|
||||
assertFailsWith<IllegalArgumentException> {
|
||||
HashAttachmentConstraint(SecureHash.randomSHA256())
|
||||
.canBeTransitionedFrom(AutomaticPlaceholderConstraint, attachment)
|
||||
.canBeTransitionedFrom(AutomaticPlaceholderConstraint, attachment, rotatedKeys)
|
||||
}
|
||||
assertFailsWith<IllegalArgumentException> { AutomaticPlaceholderConstraint.canBeTransitionedFrom(AutomaticPlaceholderConstraint, attachment) }
|
||||
assertFailsWith<IllegalArgumentException> { AutomaticPlaceholderConstraint.canBeTransitionedFrom(AutomaticPlaceholderConstraint, attachment, rotatedKeys) }
|
||||
}
|
||||
|
||||
private fun MockServices.recordTransaction(wireTransaction: WireTransaction) {
|
||||
|
@ -0,0 +1,279 @@
|
||||
package net.corda.coretests.contracts
|
||||
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
|
||||
import net.corda.testing.core.internal.SelfCleaningDir
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class RotatedKeysTest {
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output keys are the same canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKey = file.path.generateKey()
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertTrue(rotatedKeys.canBeTransitioned(publicKey, publicKey))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output keys are the same and output is a list canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKey = file.path.generateKey()
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertTrue(rotatedKeys.canBeTransitioned(publicKey, listOf(publicKey)))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output keys are different and output is a list canBeTransitioned returns false`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey("AAAA")
|
||||
val publicKeyB = file.path.generateKey("BBBB")
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertFalse(rotatedKeys.canBeTransitioned(publicKeyA, listOf(publicKeyB)))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output keys are different and rotated and output is a list canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey("AAAA")
|
||||
val publicKeyB = file.path.generateKey("BBBB")
|
||||
val rotatedKeys = RotatedKeys(listOf((listOf(publicKeyA.hash.sha256(), publicKeyB.hash.sha256()))))
|
||||
assertTrue(rotatedKeys.canBeTransitioned(publicKeyA, listOf(publicKeyB)))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output keys are the same and both are lists canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKey = file.path.generateKey()
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertTrue(rotatedKeys.canBeTransitioned(listOf(publicKey), listOf(publicKey)))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output keys are different and rotated and both are lists canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val rotatedKeys = RotatedKeys(listOf((listOf(publicKeyA.hash.sha256(), publicKeyB.hash.sha256()))))
|
||||
assertTrue(rotatedKeys.canBeTransitioned(listOf(publicKeyA), listOf(publicKeyB)))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output keys are different canBeTransitioned returns false`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertFalse(rotatedKeys.canBeTransitioned(publicKeyA, publicKeyB))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output keys are different but are rotated canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val rotatedKeysData = listOf((listOf(publicKeyA.hash.sha256(), publicKeyB.hash.sha256())))
|
||||
val rotatedKeys = RotatedKeys(rotatedKeysData)
|
||||
assertTrue(rotatedKeys.canBeTransitioned(publicKeyA, publicKeyB))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output keys are different with multiple rotations canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
val publicKeyD = file.path.generateKey(alias = "DDDD")
|
||||
val rotatedKeysData = listOf(listOf(publicKeyA.hash.sha256(), publicKeyB.hash.sha256()),
|
||||
listOf(publicKeyC.hash.sha256(), publicKeyD.hash.sha256()))
|
||||
val rotatedKeys = RotatedKeys(rotatedKeysData)
|
||||
assertTrue(rotatedKeys.canBeTransitioned(publicKeyA, publicKeyB))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when multiple input and output keys are different with multiple rotations canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
val publicKeyD = file.path.generateKey(alias = "DDDD")
|
||||
val rotatedKeysData = listOf(listOf(publicKeyA.hash.sha256(), publicKeyC.hash.sha256()),
|
||||
listOf(publicKeyB.hash.sha256(), publicKeyD.hash.sha256()))
|
||||
val rotatedKeys = RotatedKeys(rotatedKeysData)
|
||||
val compositeKeyInput = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val compositeKeyOutput = CompositeKey.Builder().addKeys(publicKeyC, publicKeyD).build()
|
||||
assertTrue(rotatedKeys.canBeTransitioned(compositeKeyInput, compositeKeyOutput))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when multiple input and output keys are diff and diff ordering with multiple rotations canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
val publicKeyD = file.path.generateKey(alias = "DDDD")
|
||||
val rotatedKeysData = listOf(listOf(publicKeyA.hash.sha256(), publicKeyC.hash.sha256()),
|
||||
listOf(publicKeyB.hash.sha256(), publicKeyD.hash.sha256()))
|
||||
val rotatedKeys = RotatedKeys(rotatedKeysData)
|
||||
|
||||
val compositeKeyInput = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val compositeKeyOutput = CompositeKey.Builder().addKeys(publicKeyD, publicKeyC).build()
|
||||
assertTrue(rotatedKeys.canBeTransitioned(compositeKeyInput, compositeKeyOutput))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output key are composite and the same canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val compositeKey = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertTrue(rotatedKeys.canBeTransitioned(compositeKey, compositeKey))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output key are composite and different canBeTransitioned returns false`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
val compositeKeyInput = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val compositeKeyOutput = CompositeKey.Builder().addKeys(publicKeyA, publicKeyC).build()
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertFalse(rotatedKeys.canBeTransitioned(compositeKeyInput, compositeKeyOutput))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output key are composite and different but key is rotated canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
val compositeKeyInput = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val compositeKeyOutput = CompositeKey.Builder().addKeys(publicKeyA, publicKeyC).build()
|
||||
val rotatedKeys = RotatedKeys(listOf((listOf(publicKeyB.hash.sha256(), publicKeyC.hash.sha256()))))
|
||||
assertTrue(rotatedKeys.canBeTransitioned(compositeKeyInput, compositeKeyOutput))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output key are composite and different and diff key is rotated canBeTransitioned returns false`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
val compositeKeyInput = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val compositeKeyOutput = CompositeKey.Builder().addKeys(publicKeyA, publicKeyC).build()
|
||||
val rotatedKeys = RotatedKeys(listOf((listOf(publicKeyA.hash.sha256(), publicKeyC.hash.sha256()))))
|
||||
assertFalse(rotatedKeys.canBeTransitioned(compositeKeyInput, compositeKeyOutput))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input is composite (1 key) and output is composite (2 keys) canBeTransitioned returns false`() {
|
||||
// For composite keys number of input and output leaves must be the same, in canBeTransitioned check.
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val compositeKeyInput = CompositeKey.Builder().addKeys(publicKeyA).build()
|
||||
val compositeKeyOutput = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertFalse(rotatedKeys.canBeTransitioned(compositeKeyInput, compositeKeyOutput))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output key are composite with 2 levels and the same canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
val publicKeyD = file.path.generateKey(alias = "DDDD")
|
||||
val compositeKeyA = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val compositeKeyB = CompositeKey.Builder().addKeys(publicKeyC, publicKeyD).build()
|
||||
val compositeKeyC = CompositeKey.Builder().addKeys(compositeKeyA, compositeKeyB).build()
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertTrue(rotatedKeys.canBeTransitioned(compositeKeyC, compositeKeyC))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output key are different & composite & rotated with 2 levels canBeTransitioned returns true`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
val publicKeyD = file.path.generateKey(alias = "DDDD")
|
||||
|
||||
// in output DDDD has rotated to EEEE
|
||||
val publicKeyE = file.path.generateKey(alias = "EEEE")
|
||||
val compositeKeyA = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val compositeKeyB = CompositeKey.Builder().addKeys(publicKeyC, publicKeyD).build()
|
||||
val compositeKeyC = CompositeKey.Builder().addKeys(publicKeyC, publicKeyE).build()
|
||||
|
||||
val compositeKeyInput = CompositeKey.Builder().addKeys(compositeKeyA, compositeKeyB).build()
|
||||
val compositeKeyOutput = CompositeKey.Builder().addKeys(compositeKeyA, compositeKeyC).build()
|
||||
|
||||
val rotatedKeys = RotatedKeys(listOf((listOf(publicKeyD.hash.sha256(), publicKeyE.hash.sha256()))))
|
||||
assertTrue(rotatedKeys.canBeTransitioned(compositeKeyInput, compositeKeyOutput))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `when input and output key are different & composite & not rotated with 2 levels canBeTransitioned returns false`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
val publicKeyD = file.path.generateKey(alias = "DDDD")
|
||||
|
||||
// in output DDDD has rotated to EEEE
|
||||
val publicKeyE = file.path.generateKey(alias = "EEEE")
|
||||
val compositeKeyA = CompositeKey.Builder().addKeys(publicKeyA, publicKeyB).build()
|
||||
val compositeKeyB = CompositeKey.Builder().addKeys(publicKeyC, publicKeyD).build()
|
||||
val compositeKeyC = CompositeKey.Builder().addKeys(publicKeyC, publicKeyE).build()
|
||||
|
||||
val compositeKeyInput = CompositeKey.Builder().addKeys(compositeKeyA, compositeKeyB).build()
|
||||
val compositeKeyOutput = CompositeKey.Builder().addKeys(compositeKeyA, compositeKeyC).build()
|
||||
|
||||
val rotatedKeys = RotatedKeys()
|
||||
assertFalse(rotatedKeys.canBeTransitioned(compositeKeyInput, compositeKeyOutput))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000, expected = IllegalStateException::class)
|
||||
fun `when key is repeated in rotated list, throws exception`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
RotatedKeys(listOf(listOf(publicKeyA.hash.sha256(), publicKeyB.hash.sha256(), publicKeyA.hash.sha256())))
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000, expected = IllegalStateException::class)
|
||||
fun `when key is repeated across rotated lists, throws exception`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val publicKeyA = file.path.generateKey(alias = "AAAA")
|
||||
val publicKeyB = file.path.generateKey(alias = "BBBB")
|
||||
val publicKeyC = file.path.generateKey(alias = "CCCC")
|
||||
RotatedKeys(listOf(listOf(publicKeyA.hash.sha256(), publicKeyB.hash.sha256()), listOf(publicKeyC.hash.sha256(), publicKeyA.hash.sha256())))
|
||||
}
|
||||
}
|
||||
}
|
@ -518,7 +518,8 @@ class FinalityFlowTests : WithFinality {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val handleNotaryError = otherSide.receive<Boolean>().unwrap { it }
|
||||
subFlow(ReceiveFinalityFlow(otherSide, handlePropagatedNotaryError = handleNotaryError))
|
||||
val stx = subFlow(ReceiveFinalityFlow(otherSide, handlePropagatedNotaryError = handleNotaryError))
|
||||
stx.verify(serviceHub)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -73,7 +73,7 @@ class AttachmentsClassLoaderTests {
|
||||
}
|
||||
val ALICE = TestIdentity(ALICE_NAME, 70).party
|
||||
val BOB = TestIdentity(BOB_NAME, 80).party
|
||||
val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
|
||||
private val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
|
||||
val DUMMY_NOTARY get() = dummyNotary.party
|
||||
const val PROGRAM_ID = "net.corda.testing.contracts.MyDummyContract"
|
||||
}
|
||||
@ -344,141 +344,6 @@ class AttachmentsClassLoaderTests {
|
||||
createClassloader(untrustedAttachment).use {}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Cannot load an untrusted contract jar if no other attachment exists that was signed with the same keys`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
val keyPairB = Crypto.generateKeyPair()
|
||||
val untrustedClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted"
|
||||
).inputStream()
|
||||
val untrustedAttachment = storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
untrustedClassJar,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
createClassloader(untrustedAttachment).use {}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Cannot load an untrusted contract jar if no other attachment exists that was signed with the same keys and uploaded by a trusted uploader`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
val keyPairB = Crypto.generateKeyPair()
|
||||
val classJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted with the same keys"
|
||||
).inputStream()
|
||||
storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
classJar,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
|
||||
val untrustedClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted"
|
||||
).inputStream()
|
||||
val untrustedAttachment = storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
untrustedClassJar,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
createClassloader(untrustedAttachment).use {}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Attachments with inherited trust do not grant trust to attachments being loaded (no chain of trust)`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
val keyPairB = Crypto.generateKeyPair()
|
||||
val keyPairC = Crypto.generateKeyPair()
|
||||
val classJar = fakeAttachment(
|
||||
"/com/example/something/TrustedClass.class",
|
||||
"Signed by someone untrusted with the same keys"
|
||||
).inputStream()
|
||||
storage.importContractAttachment(
|
||||
listOf("TrustedClass.class"),
|
||||
"app",
|
||||
classJar,
|
||||
signers = listOf(keyPairA.public)
|
||||
)
|
||||
|
||||
val inheritedTrustClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone who inherits trust"
|
||||
).inputStream()
|
||||
val inheritedTrustAttachment = storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
inheritedTrustClassJar,
|
||||
signers = listOf(keyPairB.public, keyPairA.public)
|
||||
)
|
||||
|
||||
val untrustedClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted"
|
||||
).inputStream()
|
||||
val untrustedAttachment = storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
untrustedClassJar,
|
||||
signers = listOf(keyPairB.public, keyPairC.public)
|
||||
)
|
||||
|
||||
// pass the inherited trust attachment through the classloader first to ensure it does not affect the next loaded attachment
|
||||
createClassloader(inheritedTrustAttachment).use {
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
createClassloader(untrustedAttachment).use {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Cannot load an untrusted contract jar if it is signed by a blacklisted key even if there is another attachment signed by the same keys that is trusted`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
val keyPairB = Crypto.generateKeyPair()
|
||||
|
||||
attachmentTrustCalculator = NodeAttachmentTrustCalculator(
|
||||
storage.toInternal(),
|
||||
cacheFactory,
|
||||
blacklistedAttachmentSigningKeys = listOf(keyPairA.public.hash)
|
||||
)
|
||||
|
||||
val classJar = fakeAttachment(
|
||||
"/com/example/something/TrustedClass.class",
|
||||
"Signed by someone trusted"
|
||||
).inputStream()
|
||||
storage.importContractAttachment(
|
||||
listOf("TrustedClass.class"),
|
||||
"rpc",
|
||||
classJar,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
|
||||
val untrustedClassJar = fakeAttachment(
|
||||
"/com/example/something/UntrustedClass.class",
|
||||
"Signed by someone untrusted"
|
||||
).inputStream()
|
||||
val untrustedAttachment = storage.importContractAttachment(
|
||||
listOf("UntrustedClass.class"),
|
||||
"untrusted",
|
||||
untrustedClassJar,
|
||||
signers = listOf(keyPairA.public, keyPairB.public)
|
||||
)
|
||||
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
createClassloader(untrustedAttachment).use {}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Allow loading a trusted attachment that is signed by a blacklisted key`() {
|
||||
val keyPairA = Crypto.generateKeyPair()
|
||||
|
@ -0,0 +1,237 @@
|
||||
package net.corda.coretests.transactions
|
||||
|
||||
import com.codahale.metrics.MetricRegistry
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.AttachmentTrustCalculator
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.internal.verification.NodeVerificationSupport
|
||||
import net.corda.core.node.NetworkParameters
|
||||
import net.corda.core.node.services.AttachmentId
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoader
|
||||
import net.corda.coretesting.internal.rigorousMock
|
||||
import net.corda.node.services.attachments.NodeAttachmentTrustCalculator
|
||||
import net.corda.node.services.persistence.NodeAttachmentService
|
||||
import net.corda.node.services.persistence.toInternal
|
||||
import net.corda.nodeapi.internal.persistence.CordaPersistence
|
||||
import net.corda.nodeapi.internal.persistence.DatabaseConfig
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.core.TestIdentity
|
||||
import net.corda.testing.core.internal.ContractJarTestUtils
|
||||
import net.corda.testing.core.internal.ContractJarTestUtils.signContractJar
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.signJar
|
||||
import net.corda.testing.core.internal.SelfCleaningDir
|
||||
import net.corda.testing.internal.TestingNamedCacheFactory
|
||||
import net.corda.testing.internal.configureDatabase
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import java.net.URL
|
||||
import kotlin.test.assertFailsWith
|
||||
|
||||
class AttachmentsClassLoaderWithStoragePersistenceTests {
|
||||
companion object {
|
||||
val ISOLATED_CONTRACTS_JAR_PATH_V4: URL = AttachmentsClassLoaderWithStoragePersistenceTests::class.java.getResource("isolated-4.0.jar")!!
|
||||
private val dummyNotary = TestIdentity(DUMMY_NOTARY_NAME, 20)
|
||||
val DUMMY_NOTARY get() = dummyNotary.party
|
||||
const val PROGRAM_ID = "net.corda.testing.contracts.MyDummyContract"
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
|
||||
private lateinit var database: CordaPersistence
|
||||
private lateinit var storage: NodeAttachmentService
|
||||
private lateinit var attachmentTrustCalculator: AttachmentTrustCalculator
|
||||
private lateinit var attachmentTrustCalculator2: AttachmentTrustCalculator
|
||||
private val networkParameters = testNetworkParameters()
|
||||
private val cacheFactory = TestingNamedCacheFactory(1)
|
||||
private val cacheFactory2 = TestingNamedCacheFactory()
|
||||
private val nodeVerificationSupport = rigorousMock<NodeVerificationSupport>().also {
|
||||
doReturn(testNetworkParameters()).whenever(it).networkParameters
|
||||
}
|
||||
|
||||
private fun createClassloader(
|
||||
attachment: AttachmentId,
|
||||
params: NetworkParameters = networkParameters
|
||||
): AttachmentsClassLoader {
|
||||
return createClassloader(listOf(attachment), params)
|
||||
}
|
||||
|
||||
private fun createClassloader(
|
||||
attachments: List<AttachmentId>,
|
||||
params: NetworkParameters = networkParameters
|
||||
): AttachmentsClassLoader {
|
||||
return AttachmentsClassLoader(
|
||||
attachments.map { storage.openAttachment(it)!! },
|
||||
params,
|
||||
SecureHash.zeroHash,
|
||||
attachmentTrustCalculator2::calculate
|
||||
)
|
||||
}
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
val dataSourceProperties = MockServices.makeTestDataSourceProperties()
|
||||
database = configureDatabase(dataSourceProperties, DatabaseConfig(), { null }, { null })
|
||||
storage = NodeAttachmentService(MetricRegistry(), TestingNamedCacheFactory(), database).also {
|
||||
database.transaction {
|
||||
it.start()
|
||||
}
|
||||
}
|
||||
storage.nodeVerificationSupport = nodeVerificationSupport
|
||||
attachmentTrustCalculator = NodeAttachmentTrustCalculator(storage.toInternal(), cacheFactory)
|
||||
attachmentTrustCalculator2 = NodeAttachmentTrustCalculator(storage, database, cacheFactory2)
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Cannot load an untrusted contract jar if no other attachment exists that was signed with the same keys and uploaded by a trusted uploader`() {
|
||||
val signedJar = signContractJar(ISOLATED_CONTRACTS_JAR_PATH_V4, copyFirst = true)
|
||||
val isolatedSignedId = storage.importAttachment(signedJar.first.toUri().toURL().openStream(), "untrusted", "isolated-signed.jar" )
|
||||
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
createClassloader(isolatedSignedId).use {}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Cannot load an untrusted contract jar if no other attachment exists that was signed with the same keys`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val path = file.path
|
||||
val alias1 = "AAAA"
|
||||
val alias2 = "BBBB"
|
||||
val password = "testPassword"
|
||||
|
||||
path.generateKey(alias1, password)
|
||||
path.generateKey(alias2, password)
|
||||
|
||||
val contractName = "net.corda.testing.contracts.MyDummyContract"
|
||||
val content = createContractString(contractName)
|
||||
val contractJarPath = ContractJarTestUtils.makeTestContractJar(path, contractName, content = content, version = 2)
|
||||
path.signJar(contractJarPath.toAbsolutePath().toString(), alias1, password)
|
||||
path.signJar(contractJarPath.toAbsolutePath().toString(), alias2, password)
|
||||
val untrustedAttachment = storage.importAttachment(contractJarPath.toUri().toURL().openStream(), "untrusted", "contract.jar")
|
||||
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
createClassloader(untrustedAttachment).use {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Attachments with inherited trust do not grant trust to attachments being loaded (no chain of trust)`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
val path = file.path
|
||||
val alias1 = "AAAA"
|
||||
val alias2 = "BBBB"
|
||||
val alias3 = "CCCC"
|
||||
val password = "testPassword"
|
||||
|
||||
path.generateKey(alias1, password)
|
||||
path.generateKey(alias2, password)
|
||||
path.generateKey(alias3, password)
|
||||
|
||||
val contractName1 = "net.corda.testing.contracts.MyDummyContract1"
|
||||
val contractName2 = "net.corda.testing.contracts.MyDummyContract2"
|
||||
val contractName3 = "net.corda.testing.contracts.MyDummyContract3"
|
||||
|
||||
val content = createContractString(contractName1)
|
||||
val contractJar = ContractJarTestUtils.makeTestContractJar(path, contractName1, content = content)
|
||||
path.signJar(contractJar.toAbsolutePath().toString(), alias1, password)
|
||||
storage.privilegedImportAttachment(contractJar.toUri().toURL().openStream(), "app", "contract.jar")
|
||||
|
||||
val content2 = createContractString(contractName2)
|
||||
val contractJarPath2 = ContractJarTestUtils.makeTestContractJar(path, contractName2, content = content2, version = 2)
|
||||
path.signJar(contractJarPath2.toAbsolutePath().toString(), alias1, password)
|
||||
path.signJar(contractJarPath2.toAbsolutePath().toString(), alias2, password)
|
||||
val inheritedTrustAttachment = storage.importAttachment(contractJarPath2.toUri().toURL().openStream(), "untrusted", "dummy-contract.jar")
|
||||
|
||||
val content3 = createContractString(contractName3)
|
||||
val contractJarPath3 = ContractJarTestUtils.makeTestContractJar(path, contractName3, content = content3, version = 3)
|
||||
path.signJar(contractJarPath3.toAbsolutePath().toString(), alias2, password)
|
||||
path.signJar(contractJarPath3.toAbsolutePath().toString(), alias3, password)
|
||||
val untrustedAttachment = storage.importAttachment(contractJarPath3.toUri().toURL()
|
||||
.openStream(), "untrusted", "contract.jar")
|
||||
|
||||
// pass the inherited trust attachment through the classloader first to ensure it does not affect the next loaded attachment
|
||||
createClassloader(inheritedTrustAttachment).use {
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
createClassloader(untrustedAttachment).use {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `Cannot load an untrusted contract jar if it is signed by a blacklisted key even if there is another attachment signed by the same keys that is trusted`() {
|
||||
SelfCleaningDir().use { file ->
|
||||
|
||||
val path = file.path
|
||||
val aliasA = "AAAA"
|
||||
val aliasB = "BBBB"
|
||||
val password = "testPassword"
|
||||
|
||||
val publicKeyA = path.generateKey(aliasA, password)
|
||||
path.generateKey(aliasB, password)
|
||||
|
||||
attachmentTrustCalculator2 = NodeAttachmentTrustCalculator(
|
||||
storage,
|
||||
cacheFactory,
|
||||
blacklistedAttachmentSigningKeys = listOf(publicKeyA.hash)
|
||||
)
|
||||
|
||||
val contractName1 = "net.corda.testing.contracts.MyDummyContract1"
|
||||
val contractName2 = "net.corda.testing.contracts.MyDummyContract2"
|
||||
|
||||
val contentTrusted = createContractString(contractName1)
|
||||
val classJar = ContractJarTestUtils.makeTestContractJar(path, contractName1, content = contentTrusted)
|
||||
path.signJar(classJar.toAbsolutePath().toString(), aliasA, password)
|
||||
path.signJar(classJar.toAbsolutePath().toString(), aliasB, password)
|
||||
storage.privilegedImportAttachment(classJar.toUri().toURL().openStream(), "app", "contract.jar")
|
||||
|
||||
val contentUntrusted = createContractString(contractName2)
|
||||
val untrustedClassJar = ContractJarTestUtils.makeTestContractJar(path, contractName2, content = contentUntrusted)
|
||||
path.signJar(untrustedClassJar.toAbsolutePath().toString(), aliasA, password)
|
||||
path.signJar(untrustedClassJar.toAbsolutePath().toString(), aliasB, password)
|
||||
val untrustedAttachment = storage.importAttachment(untrustedClassJar.toUri().toURL()
|
||||
.openStream(), "untrusted", "untrusted-contract.jar")
|
||||
|
||||
assertFailsWith(TransactionVerificationException.UntrustedAttachmentsException::class) {
|
||||
createClassloader(untrustedAttachment).use {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createContractString(contractName: String, versionSeed: Int = 0): String {
|
||||
val pkgs = contractName.split(".")
|
||||
val className = pkgs.last()
|
||||
val packages = pkgs.subList(0, pkgs.size - 1)
|
||||
|
||||
val output = """package ${packages.joinToString(".")};
|
||||
import net.corda.core.contracts.*;
|
||||
import net.corda.core.transactions.*;
|
||||
import java.net.URL;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class $className implements Contract {
|
||||
private int seed = $versionSeed;
|
||||
@Override
|
||||
public void verify(LedgerTransaction tx) throws IllegalArgumentException {
|
||||
System.gc();
|
||||
InputStream str = this.getClass().getClassLoader().getResourceAsStream("importantDoc.pdf");
|
||||
if (str == null) throw new IllegalStateException("Could not find importantDoc.pdf");
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
println(output)
|
||||
return output
|
||||
}
|
||||
}
|
117
core/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt
Normal file
117
core/src/main/kotlin/net/corda/core/contracts/RotatedKeys.kt
Normal file
@ -0,0 +1,117 @@
|
||||
package net.corda.core.contracts
|
||||
|
||||
import net.corda.core.crypto.CompositeKey
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.serialization.CordaSerializable
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
import java.security.PublicKey
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
|
||||
object CordaRotatedKeys {
|
||||
val keys = RotatedKeys()
|
||||
}
|
||||
|
||||
// The current development CorDapp code signing public key hash
|
||||
const val DEV_CORDAPP_CODE_SIGNING_STR = "AA59D829F2CA8FDDF5ABEA40D815F937E3E54E572B65B93B5C216AE6594E7D6B"
|
||||
// The non production CorDapp code signing public key hash
|
||||
const val NEW_NON_PROD_CORDAPP_CODE_SIGNING_STR = "B710A80780A12C52DF8A0B4C2247E08907CCA5D0F19AB1E266FE7BAEA9036790"
|
||||
// The production CorDapp code signing public key hash
|
||||
const val PROD_CORDAPP_CODE_SIGNING_STR = "EB4989E7F861FEBEC242E6C24CF0B51C41E108D2C4479D296C5570CB8DAD3EE0"
|
||||
// The new production CorDapp code signing public key hash
|
||||
const val NEW_PROD_CORDAPP_CODE_SIGNING_STR = "01EFA14B42700794292382C1EEAC9788A26DAFBCCC98992C01D5BC30EEAACD28"
|
||||
|
||||
// Rotations used by Corda
|
||||
private val CORDA_SIGNING_KEY_ROTATIONS = listOf(
|
||||
listOf(SecureHash.create(DEV_CORDAPP_CODE_SIGNING_STR).sha256(), SecureHash.create(NEW_NON_PROD_CORDAPP_CODE_SIGNING_STR).sha256()),
|
||||
listOf(SecureHash.create(PROD_CORDAPP_CODE_SIGNING_STR).sha256(), SecureHash.create(NEW_PROD_CORDAPP_CODE_SIGNING_STR).sha256())
|
||||
)
|
||||
|
||||
/**
|
||||
* This class represents the rotated CorDapp signing keys known by this node.
|
||||
*
|
||||
* A public key in this class is identified by its SHA-256 hash of the public key encoded bytes (@see PublicKey.getEncoded()).
|
||||
* A sequence of rotated keys is represented by a list of hashes of those public keys. The list of those lists represents
|
||||
* each unrelated set of rotated keys. A key should not appear more than once, either in the same list of in multiple lists.
|
||||
*
|
||||
* For the purposes of SignatureConstraints this means we treat all entries in a list of key hashes as equivalent.
|
||||
* For two keys to be equivalent, they must be equal, or they must appear in the same list of hashes.
|
||||
*
|
||||
* @param rotatedSigningKeys A List of rotated keys. With a rotated key being represented by a list of hashes. This list comes from
|
||||
* node.conf.
|
||||
*
|
||||
*/
|
||||
@CordaSerializable
|
||||
data class RotatedKeys(val rotatedSigningKeys: List<List<SecureHash>> = emptyList()): SingletonSerializeAsToken() {
|
||||
private val canBeTransitionedMap: ConcurrentMap<Pair<PublicKey, PublicKey>, Boolean> = ConcurrentHashMap()
|
||||
private val rotateMap: Map<SecureHash, SecureHash> = HashMap<SecureHash, SecureHash>().apply {
|
||||
(rotatedSigningKeys + CORDA_SIGNING_KEY_ROTATIONS).forEach { rotatedKeyList ->
|
||||
rotatedKeyList.forEach { key ->
|
||||
if (this.containsKey(key)) throw IllegalStateException("The key with sha256(hash) $key appears in the rotated keys configuration more than once.")
|
||||
this[key] = rotatedKeyList.last()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun canBeTransitioned(inputKey: PublicKey, outputKeys: List<PublicKey>): Boolean {
|
||||
return canBeTransitioned(inputKey, CompositeKey.Builder().addKeys(outputKeys).build())
|
||||
}
|
||||
|
||||
fun canBeTransitioned(inputKeys: List<PublicKey>, outputKeys: List<PublicKey>): Boolean {
|
||||
return canBeTransitioned(CompositeKey.Builder().addKeys(inputKeys).build(), CompositeKey.Builder().addKeys(outputKeys).build())
|
||||
}
|
||||
|
||||
fun canBeTransitioned(inputKey: PublicKey, outputKey: PublicKey): Boolean {
|
||||
// Need to handle if inputKey and outputKey are composite keys. They could be if part of SignatureConstraints
|
||||
return canBeTransitionedMap.getOrPut(Pair(inputKey, outputKey)) {
|
||||
when {
|
||||
(inputKey is CompositeKey && outputKey is CompositeKey) -> compareKeys(inputKey, outputKey)
|
||||
(inputKey is CompositeKey && outputKey !is CompositeKey) -> compareKeys(inputKey, outputKey)
|
||||
(inputKey !is CompositeKey && outputKey is CompositeKey) -> compareKeys(inputKey, outputKey)
|
||||
else -> isRotatedEquals(inputKey, outputKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun rotate(key: SecureHash): SecureHash {
|
||||
return rotateMap[key] ?: key
|
||||
}
|
||||
|
||||
private fun isRotatedEquals(inputKey: PublicKey, outputKey: PublicKey): Boolean {
|
||||
return when {
|
||||
inputKey == outputKey -> true
|
||||
rotate(inputKey.hash.sha256()) == rotate(outputKey.hash.sha256()) -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun compareKeys(inputKey: CompositeKey, outputKey: PublicKey): Boolean {
|
||||
if (inputKey.leafKeys.size == 1) {
|
||||
return canBeTransitioned(inputKey.leafKeys.first(), outputKey)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun compareKeys(inputKey: PublicKey, outputKey: CompositeKey): Boolean {
|
||||
if (outputKey.leafKeys.size == 1) {
|
||||
return canBeTransitioned(inputKey, outputKey.leafKeys.first())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun compareKeys(inputKey: CompositeKey, outputKey: CompositeKey): Boolean {
|
||||
if (inputKey.leafKeys.size != outputKey.leafKeys.size) {
|
||||
return false
|
||||
}
|
||||
else {
|
||||
inputKey.leafKeys.forEach { inputLeafKey ->
|
||||
if (!outputKey.leafKeys.any { outputLeafKey -> canBeTransitioned(inputLeafKey, outputLeafKey) }) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
@ -75,8 +75,8 @@ open class ReceiveTransactionFlow constructor(private val otherSideSession: Flow
|
||||
val stx = resolvePayload(payload)
|
||||
stx.pushToLoggingContext()
|
||||
logger.info("Received transaction acknowledgement request from party ${otherSideSession.counterparty}.")
|
||||
checkParameterHash(stx.networkParametersHash)
|
||||
subFlow(ResolveTransactionsFlow(stx, otherSideSession, statesToRecord, deferredAck))
|
||||
checkParameterHash(stx.networkParametersHash)
|
||||
logger.info("Transaction dependencies resolution completed.")
|
||||
verifyTx(stx, checkSufficientSignatures)
|
||||
if (checkSufficientSignatures) {
|
||||
@ -127,6 +127,7 @@ open class ReceiveTransactionFlow constructor(private val otherSideSession: Flow
|
||||
(serviceHub as ServiceHubCoreInternal).finalizeTransactionWithExtraSignatures(stx, notarySignatures, statesToRecord)
|
||||
logger.info("Peer finalised transaction with notary signature.")
|
||||
}
|
||||
return stx + notarySignatures
|
||||
} catch (e: NotaryException) {
|
||||
logger.info("Peer received notary error.")
|
||||
val overrideHandlePropagatedNotaryError = handlePropagatedNotaryError
|
||||
|
@ -57,7 +57,8 @@ val ContractState.requiredContractClassName: String? get() {
|
||||
* JAR are required to sign in the future.
|
||||
*
|
||||
*/
|
||||
fun AttachmentConstraint.canBeTransitionedFrom(input: AttachmentConstraint, attachment: ContractAttachment): Boolean {
|
||||
@Suppress("ComplexMethod")
|
||||
fun AttachmentConstraint.canBeTransitionedFrom(input: AttachmentConstraint, attachment: ContractAttachment, rotatedKeys: RotatedKeys): Boolean {
|
||||
val output = this
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
@ -83,7 +84,7 @@ fun AttachmentConstraint.canBeTransitionedFrom(input: AttachmentConstraint, atta
|
||||
|
||||
// The SignatureAttachmentConstraint allows migration from a Signature constraint with the same key.
|
||||
// TODO - we don't support currently third party signers. When we do, the output key will have to be stronger then the input key.
|
||||
input is SignatureAttachmentConstraint && output is SignatureAttachmentConstraint -> input.key == output.key
|
||||
input is SignatureAttachmentConstraint && output is SignatureAttachmentConstraint -> rotatedKeys.canBeTransitioned(input.key, output.key)
|
||||
|
||||
// HashAttachmentConstraint can be transformed to a SignatureAttachmentConstraint when hash constraint verification checking disabled.
|
||||
HashAttachmentConstraint.disableHashConstraints && input is HashAttachmentConstraint && output is SignatureAttachmentConstraint -> true
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.core.internal.verification
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
@ -24,6 +25,8 @@ interface VerificationSupport {
|
||||
|
||||
val attachmentsClassLoaderCache: AttachmentsClassLoaderCache? get() = null
|
||||
|
||||
val rotatedKeys: RotatedKeys
|
||||
|
||||
// TODO Use SequencedCollection if upgraded to Java 21
|
||||
fun getParties(keys: Collection<PublicKey>): List<Party?>
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
package net.corda.core.internal.verification
|
||||
|
||||
import net.corda.core.contracts.AttachmentConstraint
|
||||
import net.corda.core.contracts.Contract
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.HashAttachmentConstraint
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.contracts.SignatureAttachmentConstraint
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
@ -89,7 +91,7 @@ abstract class AbstractVerifier(
|
||||
* Because we create a separate [LedgerTransaction] onto which we need to perform verification, it becomes important we don't verify the
|
||||
* wrong object instance. This class helps avoid that.
|
||||
*/
|
||||
private class Validator(private val ltx: LedgerTransaction, private val transactionClassLoader: ClassLoader) {
|
||||
private class Validator(private val ltx: LedgerTransaction, private val transactionClassLoader: ClassLoader, private val rotatedKeys: RotatedKeys) {
|
||||
private val inputStates: List<TransactionState<*>> = ltx.inputs.map(StateAndRef<ContractState>::state)
|
||||
private val allStates: List<TransactionState<*>> = inputStates + ltx.references.map(StateAndRef<ContractState>::state) + ltx.outputs
|
||||
|
||||
@ -376,7 +378,7 @@ private class Validator(private val ltx: LedgerTransaction, private val transact
|
||||
|
||||
outputConstraints.forEach { outputConstraint ->
|
||||
inputConstraints.forEach { inputConstraint ->
|
||||
if (!(outputConstraint.canBeTransitionedFrom(inputConstraint, contractAttachment))) {
|
||||
if (!(outputConstraint.canBeTransitionedFrom(inputConstraint, contractAttachment, rotatedKeys))) {
|
||||
throw ConstraintPropagationRejection(
|
||||
ltx.id,
|
||||
contractClassName,
|
||||
@ -430,8 +432,20 @@ private class Validator(private val ltx: LedgerTransaction, private val transact
|
||||
|
||||
if (HashAttachmentConstraint.disableHashConstraints && constraint is HashAttachmentConstraint)
|
||||
logger.warnOnce("Skipping hash constraints verification.")
|
||||
else if (!constraint.isSatisfiedBy(constraintAttachment))
|
||||
throw ContractConstraintRejection(ltx.id, contract)
|
||||
else if (!constraint.isSatisfiedBy(constraintAttachment)) {
|
||||
verifyConstraintUsingRotatedKeys(constraint, constraintAttachment, contract)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyConstraintUsingRotatedKeys(constraint: AttachmentConstraint, constraintAttachment: AttachmentWithContext, contract: ContractClassName ) {
|
||||
// constraint could be an input constraint so we manually have to rotate to updated constraint
|
||||
if (constraint is SignatureAttachmentConstraint && rotatedKeys.canBeTransitioned(constraint.key, constraintAttachment.signerKeys)) {
|
||||
val constraintWithRotatedKeys = SignatureAttachmentConstraint.create(CompositeKey.Builder().addKeys(constraintAttachment.signerKeys).build())
|
||||
if (!constraintWithRotatedKeys.isSatisfiedBy(constraintAttachment)) throw ContractConstraintRejection(ltx.id, contract)
|
||||
}
|
||||
else {
|
||||
throw ContractConstraintRejection(ltx.id, contract)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -465,7 +479,7 @@ class TransactionVerifier(private val transactionClassLoader: ClassLoader) : Fun
|
||||
}
|
||||
|
||||
private fun validateTransaction(ltx: LedgerTransaction) {
|
||||
Validator(ltx, transactionClassLoader).validate()
|
||||
Validator(ltx, transactionClassLoader, ltx.rotatedKeys).validate()
|
||||
}
|
||||
|
||||
override fun apply(transactionFactory: Supplier<LedgerTransaction>) {
|
||||
|
@ -4,6 +4,8 @@ import com.github.benmanes.caffeine.cache.Cache
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.CordaRotatedKeys
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.contracts.TransactionVerificationException
|
||||
import net.corda.core.contracts.TransactionVerificationException.OverlappingAttachmentsException
|
||||
import net.corda.core.contracts.TransactionVerificationException.PackageOwnershipException
|
||||
@ -337,7 +339,8 @@ object AttachmentsClassLoaderBuilder {
|
||||
block: (SerializationContext) -> T): T {
|
||||
val attachmentIds = attachments.mapTo(LinkedHashSet(), Attachment::id)
|
||||
|
||||
val cache = attachmentsClassLoaderCache ?: fallBackCache
|
||||
val cache = if (attachmentsClassLoaderCache is AttachmentsClassLoaderForRotatedKeysOnlyImpl) fallBackCache else
|
||||
attachmentsClassLoaderCache ?: fallBackCache
|
||||
val cachedSerializationContext = cache.computeIfAbsent(AttachmentsClassLoaderKey(attachmentIds, params)) { key ->
|
||||
// Create classloader and load serializers, whitelisted classes
|
||||
val transactionClassLoader = AttachmentsClassLoader(attachments, key.params, txId, isAttachmentTrusted, parent)
|
||||
@ -453,14 +456,14 @@ private class AttachmentsHolderImpl : AttachmentsHolder {
|
||||
}
|
||||
|
||||
interface AttachmentsClassLoaderCache {
|
||||
val rotatedKeys: RotatedKeys
|
||||
fun computeIfAbsent(
|
||||
key: AttachmentsClassLoaderKey,
|
||||
mappingFunction: (AttachmentsClassLoaderKey) -> SerializationContext
|
||||
): SerializationContext
|
||||
}
|
||||
|
||||
class AttachmentsClassLoaderCacheImpl(cacheFactory: NamedCacheFactory) : SingletonSerializeAsToken(), AttachmentsClassLoaderCache {
|
||||
|
||||
class AttachmentsClassLoaderCacheImpl(cacheFactory: NamedCacheFactory, override val rotatedKeys: RotatedKeys = CordaRotatedKeys.keys) : SingletonSerializeAsToken(), AttachmentsClassLoaderCache {
|
||||
private class ToBeClosed(
|
||||
serializationContext: SerializationContext,
|
||||
val classLoaderToClose: AutoCloseable,
|
||||
@ -513,7 +516,7 @@ class AttachmentsClassLoaderCacheImpl(cacheFactory: NamedCacheFactory) : Singlet
|
||||
}
|
||||
}
|
||||
|
||||
class AttachmentsClassLoaderSimpleCacheImpl(cacheSize: Int) : AttachmentsClassLoaderCache {
|
||||
class AttachmentsClassLoaderSimpleCacheImpl(cacheSize: Int, override val rotatedKeys: RotatedKeys = CordaRotatedKeys.keys) : AttachmentsClassLoaderCache {
|
||||
private val cache: MutableMap<AttachmentsClassLoaderKey, SerializationContext>
|
||||
= createSimpleCache<AttachmentsClassLoaderKey, SerializationContext>(cacheSize).toSynchronised()
|
||||
|
||||
@ -525,6 +528,12 @@ class AttachmentsClassLoaderSimpleCacheImpl(cacheSize: Int) : AttachmentsClassLo
|
||||
}
|
||||
}
|
||||
|
||||
class AttachmentsClassLoaderForRotatedKeysOnlyImpl(override val rotatedKeys: RotatedKeys = CordaRotatedKeys.keys) : AttachmentsClassLoaderCache {
|
||||
override fun computeIfAbsent(key: AttachmentsClassLoaderKey, mappingFunction: (AttachmentsClassLoaderKey) -> SerializationContext): SerializationContext {
|
||||
throw NotImplementedError("AttachmentsClassLoaderForRotatedKeysOnlyImpl.computeIfAbsent should never be called. Should be replaced by the fallback cache")
|
||||
}
|
||||
}
|
||||
|
||||
// We use a set here because the ordering of attachments doesn't affect code execution, due to the no
|
||||
// overlap rule, and attachments don't have any particular ordering enforced by the builders. So we
|
||||
// can just do unordered comparisons here. But the same attachments run with different network parameters
|
||||
|
@ -7,7 +7,9 @@ import net.corda.core.contracts.CommandData
|
||||
import net.corda.core.contracts.CommandWithParties
|
||||
import net.corda.core.contracts.ComponentGroupEnum
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.CordaRotatedKeys
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.contracts.TransactionState
|
||||
@ -31,6 +33,7 @@ import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.SerializationFactory
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderBuilder
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderCache
|
||||
import net.corda.core.serialization.internal.AttachmentsClassLoaderForRotatedKeysOnlyImpl
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import java.util.Collections.unmodifiableList
|
||||
import java.util.function.Predicate
|
||||
@ -96,6 +99,8 @@ private constructor(
|
||||
val digestService: DigestService
|
||||
) : FullTransaction() {
|
||||
|
||||
val rotatedKeys = attachmentsClassLoaderCache?.rotatedKeys ?: CordaRotatedKeys.keys
|
||||
|
||||
/**
|
||||
* Old version of [LedgerTransaction] constructor for ABI compatibility.
|
||||
*/
|
||||
@ -195,7 +200,8 @@ private constructor(
|
||||
privacySalt: PrivacySalt,
|
||||
networkParameters: NetworkParameters?,
|
||||
references: List<StateAndRef<ContractState>>,
|
||||
digestService: DigestService): LedgerTransaction {
|
||||
digestService: DigestService,
|
||||
rotatedKeys: RotatedKeys): LedgerTransaction {
|
||||
return LedgerTransaction(
|
||||
inputs = protect(inputs),
|
||||
outputs = protect(outputs),
|
||||
@ -212,7 +218,7 @@ private constructor(
|
||||
serializedReferences = null,
|
||||
isAttachmentTrusted = { true },
|
||||
verifierFactory = ::NoOpVerifier,
|
||||
attachmentsClassLoaderCache = null,
|
||||
attachmentsClassLoaderCache = AttachmentsClassLoaderForRotatedKeysOnlyImpl(rotatedKeys),
|
||||
digestService = digestService
|
||||
// This check accesses input states and must run on the LedgerTransaction
|
||||
// instance that is verified, not on the outer LedgerTransaction shell.
|
||||
@ -872,7 +878,8 @@ private class DefaultVerifier(
|
||||
privacySalt = ltx.privacySalt,
|
||||
networkParameters = ltx.networkParameters,
|
||||
references = deserializedReferences,
|
||||
digestService = ltx.digestService
|
||||
digestService = ltx.digestService,
|
||||
rotatedKeys = ltx.rotatedKeys
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -532,10 +532,10 @@ open class TransactionBuilder(
|
||||
}
|
||||
|
||||
// This is the logic to determine the constraint which will replace the AutomaticPlaceholderConstraint.
|
||||
val defaultOutputConstraint = selectAttachmentConstraint(contractClassName, inputStates, selectedAttachment.currentAttachment, serviceHub)
|
||||
val (defaultOutputConstraint, constraintAttachment) = selectDefaultOutputConstraintAndConstraintAttachment(contractClassName,
|
||||
inputStates, selectedAttachment.currentAttachment, serviceHub)
|
||||
|
||||
// Sanity check that the selected attachment actually passes.
|
||||
val constraintAttachment = AttachmentWithContext(selectedAttachment.currentAttachment, contractClassName, serviceHub.networkParameters.whitelistedContractImplementations)
|
||||
require(defaultOutputConstraint.isSatisfiedBy(constraintAttachment)) {
|
||||
"Selected output constraint: $defaultOutputConstraint not satisfying $selectedAttachment"
|
||||
}
|
||||
@ -547,7 +547,7 @@ open class TransactionBuilder(
|
||||
} else {
|
||||
// If the constraint on the output state is already set, and is not a valid transition or can't be transitioned, then fail early.
|
||||
inputStates?.forEach { input ->
|
||||
require(outputConstraint.canBeTransitionedFrom(input.constraint, selectedAttachment.currentAttachment)) {
|
||||
require(outputConstraint.canBeTransitionedFrom(input.constraint, selectedAttachment.currentAttachment, serviceHub.toVerifyingServiceHub().rotatedKeys)) {
|
||||
"Output state constraint $outputConstraint cannot be transitioned from ${input.constraint}"
|
||||
}
|
||||
}
|
||||
@ -559,6 +559,27 @@ open class TransactionBuilder(
|
||||
return Pair(selectedAttachment, resolvedOutputStates)
|
||||
}
|
||||
|
||||
private fun selectDefaultOutputConstraintAndConstraintAttachment( contractClassName: ContractClassName,
|
||||
inputStates: List<TransactionState<ContractState>>?,
|
||||
attachmentToUse: ContractAttachment,
|
||||
services: ServicesForResolution): Pair<AttachmentConstraint, AttachmentWithContext> {
|
||||
|
||||
val constraintAttachment = AttachmentWithContext(attachmentToUse, contractClassName, services.networkParameters.whitelistedContractImplementations)
|
||||
|
||||
// This is the logic to determine the constraint which will replace the AutomaticPlaceholderConstraint.
|
||||
val defaultOutputConstraint = selectAttachmentConstraint(contractClassName, inputStates, attachmentToUse, services)
|
||||
|
||||
// Sanity check that the selected attachment actually passes.
|
||||
|
||||
if (!defaultOutputConstraint.isSatisfiedBy(constraintAttachment)) {
|
||||
// The defaultOutputConstraint is the input constraint by the attachment in use currently may have a rotated key
|
||||
if (defaultOutputConstraint is SignatureAttachmentConstraint && services.toVerifyingServiceHub().rotatedKeys.canBeTransitioned(defaultOutputConstraint.key, constraintAttachment.signerKeys)) {
|
||||
return Pair(makeSignatureAttachmentConstraint(attachmentToUse.signerKeys), constraintAttachment)
|
||||
}
|
||||
}
|
||||
return Pair(defaultOutputConstraint, constraintAttachment)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current transaction can migrate from a [HashAttachmentConstraint] to a
|
||||
* [SignatureAttachmentConstraint]. This is only possible in very specific scenarios. Most
|
||||
|
@ -11,6 +11,7 @@ import net.corda.core.contracts.ComponentGroupEnum.OUTPUTS_GROUP
|
||||
import net.corda.core.contracts.ComponentGroupEnum.SIGNERS_GROUP
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.PrivacySalt
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TimeWindow
|
||||
import net.corda.core.contracts.TransactionResolutionException
|
||||
@ -181,6 +182,7 @@ class WireTransaction(componentGroups: List<ComponentGroup>, val privacySalt: Pr
|
||||
override val appClassLoader: ClassLoader get() = throw AbstractMethodError()
|
||||
override fun getTrustedClassAttachments(className: String) = throw AbstractMethodError()
|
||||
override fun fixupAttachmentIds(attachmentIds: Collection<SecureHash>) = throw AbstractMethodError()
|
||||
override val rotatedKeys: RotatedKeys get() = throw AbstractMethodError()
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM azul/zulu-openjdk:17.0.8.1
|
||||
FROM azul/zulu-openjdk:17.0.12
|
||||
|
||||
## Remove Azul Zulu repo, as it is gone by now
|
||||
RUN rm -rf /etc/apt/sources.list.d/zulu.list
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM azul/zulu-openjdk:17.0.8.1
|
||||
FROM azul/zulu-openjdk:17.0.12
|
||||
|
||||
## Add packages, clean cache, create dirs, create corda user and change ownership
|
||||
RUN apt-get update && \
|
||||
|
@ -1,6 +1,8 @@
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
apply plugin: 'org.jetbrains.dokka'
|
||||
apply plugin: 'maven-publish'
|
||||
apply plugin: 'com.jfrog.artifactory'
|
||||
|
||||
dependencies {
|
||||
implementation rootProject
|
||||
@ -56,7 +58,10 @@ dokkaJavadoc {
|
||||
url.set(new URL("https://docs.oracle.com/javafx/2/api/"))
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://www.bouncycastle.org/docs/docs1.5on/"))
|
||||
url.set(new URL("https://downloads.bouncycastle.org/java/docs/bcpkix-jdk18on-javadoc/"))
|
||||
}
|
||||
externalDocumentationLink {
|
||||
url.set(new URL("https://downloads.bouncycastle.org/java/docs/bcprov-jdk18on-javadoc/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,8 +102,6 @@ task archiveApiDocs(type: Tar) {
|
||||
publishing {
|
||||
publications {
|
||||
if (System.getProperty('publishApiDocs') != null) {
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
archivedApiDocs(MavenPublication) {
|
||||
artifact archiveApiDocs {
|
||||
artifactId archivedApiDocsBaseFilename
|
||||
@ -107,3 +110,20 @@ publishing {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
artifactoryPublish {
|
||||
publications('archivedApiDocs')
|
||||
version = version.replaceAll('-SNAPSHOT', '')
|
||||
publishPom = false
|
||||
}
|
||||
|
||||
artifactory {
|
||||
publish {
|
||||
contextUrl = artifactory_contextUrl
|
||||
repository {
|
||||
repoKey = 'corda-dependencies-dev'
|
||||
username = System.getenv('CORDA_ARTIFACTORY_USERNAME')
|
||||
password = System.getenv('CORDA_ARTIFACTORY_PASSWORD')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,6 +64,7 @@ publishing {
|
||||
maven(MavenPublication) {
|
||||
artifactId 'corda-finance-contracts'
|
||||
from components.cordapp
|
||||
artifact javadocJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -93,6 +93,7 @@ publishing {
|
||||
maven(MavenPublication) {
|
||||
artifactId 'corda-finance-workflows'
|
||||
from components.cordapp
|
||||
artifact javadocJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,18 +42,18 @@ class ArtemisMessagingComponent {
|
||||
// We should probably try to unify our notion of "topic" (really, just a string that identifies an endpoint
|
||||
// that will handle messages, like a URL) with the terminology used by underlying MQ libraries, to avoid
|
||||
// confusion.
|
||||
val topicProperty = SimpleString("platform-topic")
|
||||
val cordaVendorProperty = SimpleString("corda-vendor")
|
||||
val releaseVersionProperty = SimpleString("release-version")
|
||||
val platformVersionProperty = SimpleString("platform-version")
|
||||
val senderUUID = SimpleString("sender-uuid")
|
||||
val senderSeqNo = SimpleString("send-seq-no")
|
||||
val topicProperty = SimpleString.of("platform-topic")
|
||||
val cordaVendorProperty = SimpleString.of("corda-vendor")
|
||||
val releaseVersionProperty = SimpleString.of("release-version")
|
||||
val platformVersionProperty = SimpleString.of("platform-version")
|
||||
val senderUUID = SimpleString.of("sender-uuid")
|
||||
val senderSeqNo = SimpleString.of("send-seq-no")
|
||||
/**
|
||||
* In the operation mode where we have an out of process bridge we cannot correctly populate the Artemis validated user header
|
||||
* as the TLS does not terminate directly onto Artemis. We therefore use this internal only header to forward
|
||||
* the equivalent information from the Float.
|
||||
*/
|
||||
val bridgedCertificateSubject = SimpleString("sender-subject-name")
|
||||
val bridgedCertificateSubject = SimpleString.of("sender-subject-name")
|
||||
|
||||
object Type {
|
||||
const val KEY = "corda_p2p_message_type"
|
||||
|
@ -88,7 +88,7 @@ class BridgeControlListener(private val keyStore: CertificateStore,
|
||||
registerBridgeControlListener(artemisSession)
|
||||
registerBridgeDuplicateChecker(artemisSession)
|
||||
// Attempt to read available inboxes directly from Artemis before requesting updates from connected nodes
|
||||
validInboundQueues.addAll(artemisSession.addressQuery(SimpleString("$P2P_PREFIX#")).queueNames.map { it.toString() })
|
||||
validInboundQueues.addAll(artemisSession.addressQuery(SimpleString.of("$P2P_PREFIX#")).queueNames.map { it.toString() })
|
||||
log.info("Found inboxes: $validInboundQueues")
|
||||
if (active) {
|
||||
_activeChange.onNext(true)
|
||||
@ -107,7 +107,7 @@ class BridgeControlListener(private val keyStore: CertificateStore,
|
||||
private fun registerBridgeControlListener(artemisSession: ClientSession) {
|
||||
try {
|
||||
artemisSession.createQueue(
|
||||
QueueConfiguration(bridgeControlQueue).setAddress(BRIDGE_CONTROL).setRoutingType(RoutingType.MULTICAST)
|
||||
QueueConfiguration.of(bridgeControlQueue).setAddress(BRIDGE_CONTROL).setRoutingType(RoutingType.MULTICAST)
|
||||
.setTemporary(true).setDurable(false))
|
||||
} catch (ex: ActiveMQQueueExistsException) {
|
||||
// Ignore if there is a queue still not cleaned up
|
||||
@ -129,7 +129,7 @@ class BridgeControlListener(private val keyStore: CertificateStore,
|
||||
private fun registerBridgeDuplicateChecker(artemisSession: ClientSession) {
|
||||
try {
|
||||
artemisSession.createQueue(
|
||||
QueueConfiguration(bridgeNotifyQueue).setAddress(BRIDGE_NOTIFY).setRoutingType(RoutingType.MULTICAST)
|
||||
QueueConfiguration.of(bridgeNotifyQueue).setAddress(BRIDGE_NOTIFY).setRoutingType(RoutingType.MULTICAST)
|
||||
.setTemporary(true).setDurable(false))
|
||||
} catch (ex: ActiveMQQueueExistsException) {
|
||||
// Ignore if there is a queue still not cleaned up
|
||||
@ -189,11 +189,11 @@ class BridgeControlListener(private val keyStore: CertificateStore,
|
||||
}
|
||||
|
||||
private fun validateInboxQueueName(queueName: String): Boolean {
|
||||
return queueName.startsWith(P2P_PREFIX) && artemis!!.started!!.session.queueQuery(SimpleString(queueName)).isExists
|
||||
return queueName.startsWith(P2P_PREFIX) && artemis!!.started!!.session.queueQuery(SimpleString.of(queueName)).isExists
|
||||
}
|
||||
|
||||
private fun validateBridgingQueueName(queueName: String): Boolean {
|
||||
return queueName.startsWith(PEERS_PREFIX) && artemis!!.started!!.session.queueQuery(SimpleString(queueName)).isExists
|
||||
return queueName.startsWith(PEERS_PREFIX) && artemis!!.started!!.session.queueQuery(SimpleString.of(queueName)).isExists
|
||||
}
|
||||
|
||||
private fun processControlMessage(msg: ClientMessage) {
|
||||
|
@ -136,7 +136,7 @@ class LoopbackBridgeManager(keyStore: CertificateStore,
|
||||
private fun clientArtemisMessageHandler(artemisMessage: ClientMessage) {
|
||||
logDebugWithMDC { "Loopback Send to ${legalNames.first()} uuid: ${artemisMessage.getObjectProperty(MESSAGE_ID_KEY)}" }
|
||||
val peerInbox = translateLocalQueueToInboxAddress(queueName)
|
||||
producer?.send(SimpleString(peerInbox), artemisMessage) { artemisMessage.individualAcknowledge() }
|
||||
producer?.send(SimpleString.of(peerInbox), artemisMessage) { artemisMessage.individualAcknowledge() }
|
||||
bridgeMetricsService?.let { metricsService ->
|
||||
val properties = ArtemisMessagingComponent.Companion.P2PMessagingHeaders.whitelistedHeaders.mapNotNull { key ->
|
||||
if (artemisMessage.containsProperty(key)) {
|
||||
|
@ -70,7 +70,7 @@ class RoundTripObservableSerializerTests {
|
||||
subscriptionMap(id),
|
||||
clientAddressToObservables = ConcurrentHashMap(),
|
||||
deduplicationIdentity = "thisIsATest",
|
||||
clientAddress = SimpleString("clientAddress"))
|
||||
clientAddress = SimpleString.of("clientAddress"))
|
||||
|
||||
val serverSerializer = serializationScheme.rpcServerSerializerFactory(serverObservableContext)
|
||||
|
||||
|
@ -49,7 +49,7 @@ class RpcServerObservableSerializerTests {
|
||||
subscriptionMap(),
|
||||
clientAddressToObservables = ConcurrentHashMap(),
|
||||
deduplicationIdentity = "thisIsATest",
|
||||
clientAddress = SimpleString("clientAddress"))
|
||||
clientAddress = SimpleString.of("clientAddress"))
|
||||
|
||||
val newContext = RpcServerObservableSerializer.createContext(serializationContext, observable)
|
||||
|
||||
@ -65,7 +65,7 @@ class RpcServerObservableSerializerTests {
|
||||
subscriptionMap(),
|
||||
clientAddressToObservables = ConcurrentHashMap(),
|
||||
deduplicationIdentity = "thisIsATest",
|
||||
clientAddress = SimpleString(testClientAddress))
|
||||
clientAddress = SimpleString.of(testClientAddress))
|
||||
|
||||
val sf = SerializerFactoryBuilder.build(AllWhitelist, javaClass.classLoader).apply {
|
||||
register(RpcServerObservableSerializer())
|
||||
|
@ -0,0 +1,135 @@
|
||||
package net.corda.node
|
||||
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.hash
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.finance.workflows.getCashBalance
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.RotatedCorDappSignerKeyConfiguration
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.core.ALICE_NAME
|
||||
import net.corda.testing.core.BOB_NAME
|
||||
import net.corda.testing.core.DUMMY_NOTARY_NAME
|
||||
import net.corda.testing.core.internal.JarSignatureTestUtils.generateKey
|
||||
import net.corda.testing.core.internal.SelfCleaningDir
|
||||
import net.corda.testing.node.MockNetworkNotarySpec
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
import net.corda.testing.node.internal.MockNodeArgs
|
||||
import net.corda.testing.node.internal.TestStartedNode
|
||||
import net.corda.testing.node.internal.cordappWithPackages
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
import org.apache.commons.io.FileUtils.deleteDirectory
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import kotlin.io.path.div
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class ContractWithRotatedKeyTest {
|
||||
private val ref = OpaqueBytes.of(0x01)
|
||||
|
||||
private val TestStartedNode.party get() = info.legalIdentities.first()
|
||||
|
||||
private lateinit var mockNet: InternalMockNetwork
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
mockNet = InternalMockNetwork(initialNetworkParameters = testNetworkParameters(minimumPlatformVersion = 8), notarySpecs = listOf(MockNetworkNotarySpec(
|
||||
DUMMY_NOTARY_NAME,
|
||||
validating = false
|
||||
)))
|
||||
}
|
||||
|
||||
@After
|
||||
fun shutdown() {
|
||||
mockNet.stopNodes()
|
||||
}
|
||||
|
||||
private fun restartNodeAndDeleteOldCorDapps(network: InternalMockNetwork,
|
||||
node: TestStartedNode,
|
||||
parameters: InternalMockNodeParameters = InternalMockNodeParameters(),
|
||||
nodeFactory: (MockNodeArgs) -> InternalMockNetwork.MockNode = network.defaultFactory
|
||||
): TestStartedNode {
|
||||
node.internals.disableDBCloseOnStop()
|
||||
node.dispose()
|
||||
val cordappsDir = network.baseDirectory(node) / "cordapps"
|
||||
deleteDirectory(cordappsDir.toFile())
|
||||
return network.createNode(
|
||||
parameters.copy(legalName = node.internals.configuration.myLegalName, forcedID = node.internals.id),
|
||||
nodeFactory
|
||||
)
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `cordapp with rotated key continues to transact`() {
|
||||
val keyStoreDir1 = SelfCleaningDir()
|
||||
val keyStoreDir2 = SelfCleaningDir()
|
||||
|
||||
val packageOwnerKey1 = keyStoreDir1.path.generateKey(alias="1-testcordapp-rsa")
|
||||
val packageOwnerKey2 = keyStoreDir2.path.generateKey(alias="1-testcordapp-rsa")
|
||||
|
||||
val unsignedFinanceCorDapp1 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services")
|
||||
val unsignedFinanceCorDapp2 = cordappWithPackages("net.corda.finance", "migration", "META-INF.services").copy(versionId = 2)
|
||||
|
||||
val signedFinanceCorDapp1 = unsignedFinanceCorDapp1.signed( keyStoreDir1.path )
|
||||
val signedFinanceCorDapp2 = unsignedFinanceCorDapp2.signed( keyStoreDir2.path )
|
||||
|
||||
val configOverrides = { conf: NodeConfiguration ->
|
||||
val rotatedKeys = listOf(RotatedCorDappSignerKeyConfiguration(listOf(packageOwnerKey1.hash.sha256().toString(), packageOwnerKey2.hash.sha256().toString())))
|
||||
doReturn(rotatedKeys).whenever(conf).rotatedCordappSignerKeys
|
||||
}
|
||||
|
||||
val alice = mockNet.createNode(InternalMockNodeParameters(legalName = ALICE_NAME, additionalCordapps = listOf(signedFinanceCorDapp1), configOverrides = configOverrides))
|
||||
val bob = mockNet.createNode(InternalMockNodeParameters(legalName = BOB_NAME, additionalCordapps = listOf(signedFinanceCorDapp1), configOverrides = configOverrides))
|
||||
|
||||
val flow1 = alice.services.startFlow(CashIssueAndPaymentFlow(300.DOLLARS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
|
||||
val flow2 = alice.services.startFlow(CashIssueAndPaymentFlow(1000.DOLLARS, ref, bob.party, false, mockNet.defaultNotaryIdentity))
|
||||
val flow3 = bob.services.startFlow(CashIssueAndPaymentFlow(300.POUNDS, ref, bob.party, false, mockNet.defaultNotaryIdentity))
|
||||
val flow4 = bob.services.startFlow(CashIssueAndPaymentFlow(1000.POUNDS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
|
||||
mockNet.runNetwork()
|
||||
flow1.resultFuture.getOrThrow()
|
||||
flow2.resultFuture.getOrThrow()
|
||||
flow3.resultFuture.getOrThrow()
|
||||
flow4.resultFuture.getOrThrow()
|
||||
|
||||
val alice2 = restartNodeAndDeleteOldCorDapps(mockNet, alice, parameters = InternalMockNodeParameters(additionalCordapps = listOf(signedFinanceCorDapp2), configOverrides = configOverrides))
|
||||
val bob2 = restartNodeAndDeleteOldCorDapps(mockNet, bob, parameters = InternalMockNodeParameters(additionalCordapps = listOf(signedFinanceCorDapp2), configOverrides = configOverrides))
|
||||
|
||||
assertEquals(alice.party, alice2.party)
|
||||
assertEquals(bob.party, bob2.party)
|
||||
assertEquals(alice2.party, alice2.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
|
||||
assertEquals(bob2.party, alice2.services.identityService.wellKnownPartyFromX500Name(BOB_NAME))
|
||||
assertEquals(alice2.party, bob2.services.identityService.wellKnownPartyFromX500Name(ALICE_NAME))
|
||||
assertEquals(bob2.party, bob2.services.identityService.wellKnownPartyFromX500Name(BOB_NAME))
|
||||
|
||||
val flow5 = alice2.services.startFlow(CashPaymentFlow(300.DOLLARS, bob2.party, false))
|
||||
val flow6 = bob2.services.startFlow(CashPaymentFlow(300.POUNDS, alice2.party, false))
|
||||
mockNet.runNetwork()
|
||||
val flow7 = bob2.services.startFlow(CashPaymentFlow(1300.DOLLARS, alice2.party, false))
|
||||
val flow8 = alice2.services.startFlow(CashPaymentFlow(1300.POUNDS, bob2.party, false))
|
||||
mockNet.runNetwork()
|
||||
|
||||
flow5.resultFuture.getOrThrow()
|
||||
flow6.resultFuture.getOrThrow()
|
||||
flow7.resultFuture.getOrThrow()
|
||||
flow8.resultFuture.getOrThrow()
|
||||
|
||||
assertEquals(1300.DOLLARS, alice2.services.getCashBalance(USD))
|
||||
assertEquals(0.POUNDS, alice2.services.getCashBalance(GBP))
|
||||
assertEquals(0.DOLLARS, bob2.services.getCashBalance(USD))
|
||||
assertEquals(1300.POUNDS, bob2.services.getCashBalance(GBP))
|
||||
|
||||
keyStoreDir1.close()
|
||||
keyStoreDir2.close()
|
||||
}
|
||||
}
|
@ -62,7 +62,7 @@ class AMQPBridgeTest {
|
||||
putIntProperty(P2PMessagingHeaders.senderUUID, i)
|
||||
writeBodyBufferBytes("Test$i".toByteArray())
|
||||
// Use the magic deduplication property built into Artemis as our message identity too
|
||||
putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
|
||||
putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString.of(UUID.randomUUID().toString()))
|
||||
}
|
||||
artemis.producer.send(sourceQueueName, artemisMessage)
|
||||
}
|
||||
@ -139,7 +139,7 @@ class AMQPBridgeTest {
|
||||
putIntProperty(P2PMessagingHeaders.senderUUID, 3)
|
||||
writeBodyBufferBytes("Test3".toByteArray())
|
||||
// Use the magic deduplication property built into Artemis as our message identity too
|
||||
putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString(UUID.randomUUID().toString()))
|
||||
putStringProperty(HDR_DUPLICATE_DETECTION_ID, SimpleString.of(UUID.randomUUID().toString()))
|
||||
}
|
||||
artemis.producer.send(sourceQueueName, artemisMessage)
|
||||
|
||||
@ -224,7 +224,7 @@ class AMQPBridgeTest {
|
||||
if (sourceQueueName != null) {
|
||||
// Local queue for outgoing messages
|
||||
artemis.session.createQueue(
|
||||
QueueConfiguration(sourceQueueName).setRoutingType(RoutingType.ANYCAST).setAddress(sourceQueueName).setDurable(true))
|
||||
QueueConfiguration.of(sourceQueueName).setRoutingType(RoutingType.ANYCAST).setAddress(sourceQueueName).setDurable(true))
|
||||
bridgeManager.deployBridge(ALICE_NAME.toString(), sourceQueueName, listOf(amqpAddress), setOf(bob.name))
|
||||
}
|
||||
return Triple(artemisServer, artemisClient, bridgeManager)
|
||||
|
@ -499,7 +499,7 @@ class ArtemisServerRevocationTest : AbstractServerRevocationTest() {
|
||||
|
||||
val queueName = "${P2P_PREFIX}Test"
|
||||
artemisNode.client.started!!.session.createQueue(
|
||||
QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST).setAddress(queueName).setDurable(true)
|
||||
QueueConfiguration.of(queueName).setRoutingType(RoutingType.ANYCAST).setAddress(queueName).setDurable(true)
|
||||
)
|
||||
|
||||
val clientConnectionChangeStatus = client.waitForInitialConnectionAndCaptureChanges(expectedConnectedStatus)
|
||||
|
@ -374,7 +374,7 @@ class ProtonWrapperTests {
|
||||
assertEquals(CHARLIE_NAME, CordaX500Name.build(clientConnected.get().remoteCert!!.subjectX500Principal))
|
||||
val artemis = artemisClient.started!!
|
||||
val sendAddress = P2P_PREFIX + "Test"
|
||||
artemis.session.createQueue(QueueConfiguration("queue")
|
||||
artemis.session.createQueue(QueueConfiguration.of("queue")
|
||||
.setRoutingType(RoutingType.ANYCAST).setAddress(sendAddress).setDurable(true))
|
||||
val consumer = artemis.session.createConsumer("queue")
|
||||
val testData = "Test".toByteArray()
|
||||
@ -404,7 +404,7 @@ class ProtonWrapperTests {
|
||||
assertEquals(CHARLIE_NAME, CordaX500Name.build(clientConnected.get().remoteCert!!.subjectX500Principal))
|
||||
val artemis = artemisClient.started!!
|
||||
val sendAddress = P2P_PREFIX + "Test"
|
||||
artemis.session.createQueue(QueueConfiguration("queue")
|
||||
artemis.session.createQueue(QueueConfiguration.of("queue")
|
||||
.setRoutingType(RoutingType.ANYCAST).setAddress(sendAddress).setDurable(true))
|
||||
val consumer = artemis.session.createConsumer("queue")
|
||||
|
||||
|
@ -1,13 +1,30 @@
|
||||
package net.corda.node.services.identity
|
||||
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import co.paralleluniverse.fibers.Suspendable
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.FlowLogic
|
||||
import net.corda.core.flows.FlowSession
|
||||
import net.corda.core.flows.InitiatedBy
|
||||
import net.corda.core.flows.InitiatingFlow
|
||||
import net.corda.core.flows.ReceiveTransactionFlow
|
||||
import net.corda.core.flows.SendTransactionFlow
|
||||
import net.corda.core.flows.StartableByRPC
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.node.StatesToRecord
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.node.services.queryBy
|
||||
import net.corda.core.node.services.vault.QueryCriteria
|
||||
import net.corda.core.node.services.vault.builder
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.DOLLARS
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.contracts.asset.Cash
|
||||
import net.corda.finance.flows.CashIssueAndPaymentFlow
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
import net.corda.finance.flows.CashPaymentFlow
|
||||
import net.corda.finance.schemas.CashSchemaV1
|
||||
import net.corda.finance.workflows.getCashBalance
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.nodeapi.internal.DevIdentityGenerator
|
||||
@ -24,13 +41,19 @@ import net.corda.testing.node.internal.FINANCE_CORDAPPS
|
||||
import net.corda.testing.node.internal.InternalMockNetwork
|
||||
import net.corda.testing.node.internal.InternalMockNodeParameters
|
||||
import net.corda.testing.node.internal.TestStartedNode
|
||||
import net.corda.testing.node.internal.enclosedCordapp
|
||||
import net.corda.testing.node.internal.startFlow
|
||||
import org.junit.After
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
import org.mockito.kotlin.doReturn
|
||||
import org.mockito.kotlin.whenever
|
||||
import java.util.Currency
|
||||
import kotlin.io.path.createDirectories
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertNull
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class NotaryCertificateRotationTest(private val validating: Boolean) {
|
||||
@ -91,8 +114,9 @@ class NotaryCertificateRotationTest(private val validating: Boolean) {
|
||||
val bob2 = mockNet.restartNode(bob)
|
||||
val charlie = mockNet.createPartyNode(CHARLIE_NAME)
|
||||
|
||||
// Save previous network parameters for subsequent backchain verification.
|
||||
mockNet.nodes.forEach { it.services.networkParametersService.saveParameters(ca.sign(mockNet.networkParameters)) }
|
||||
// Save previous network parameters for subsequent backchain verification, because not persistent in mock network
|
||||
alice2.internals.services.networkParametersService.saveParameters(ca.sign(mockNet.networkParameters))
|
||||
bob2.internals.services.networkParametersService.saveParameters(ca.sign(mockNet.networkParameters))
|
||||
|
||||
// Verify that notary identity has been changed.
|
||||
assertEquals(listOf(newNotaryIdentity), alice2.services.networkMapCache.notaryIdentities)
|
||||
@ -126,4 +150,116 @@ class NotaryCertificateRotationTest(private val validating: Boolean) {
|
||||
assertEquals(0.DOLLARS, bob2.services.getCashBalance(USD))
|
||||
assertEquals(7300.DOLLARS, charlie.services.getCashBalance(USD))
|
||||
}
|
||||
|
||||
@Test(timeout = 300_000)
|
||||
fun `rotate notary identity and new node receives netparams and understands old notary`() {
|
||||
mockNet = InternalMockNetwork(
|
||||
cordappsForAllNodes = FINANCE_CORDAPPS + enclosedCordapp(),
|
||||
notarySpecs = listOf(MockNetworkNotarySpec(DUMMY_NOTARY_NAME, validating)),
|
||||
initialNetworkParameters = testNetworkParameters()
|
||||
)
|
||||
val alice = mockNet.createPartyNode(ALICE_NAME)
|
||||
val bob = mockNet.createPartyNode(BOB_NAME)
|
||||
|
||||
// Issue states and notarize them with initial notary identity.
|
||||
alice.services.startFlow(CashIssueFlow(1000.DOLLARS, ref, mockNet.defaultNotaryIdentity))
|
||||
alice.services.startFlow(CashIssueAndPaymentFlow(2000.DOLLARS, ref, alice.party, false, mockNet.defaultNotaryIdentity))
|
||||
alice.services.startFlow(CashIssueAndPaymentFlow(4000.DOLLARS, ref, bob.party, false, mockNet.defaultNotaryIdentity))
|
||||
mockNet.runNetwork()
|
||||
|
||||
val oldHash = alice.services.networkParametersService.currentHash
|
||||
|
||||
// Rotate notary identity and update network parameters.
|
||||
val newNotaryIdentity = DevIdentityGenerator.installKeyStoreWithNodeIdentity(
|
||||
mockNet.baseDirectory(mockNet.nextNodeId),
|
||||
DUMMY_NOTARY_NAME
|
||||
)
|
||||
val newNetworkParameters = testNetworkParameters(epoch = 2)
|
||||
.addNotary(mockNet.defaultNotaryIdentity, validating)
|
||||
.addNotary(newNotaryIdentity, validating)
|
||||
val ca = createDevNetworkMapCa()
|
||||
NetworkParametersCopier(newNetworkParameters, ca, overwriteFile = true).apply {
|
||||
install(mockNet.baseDirectory(alice))
|
||||
install(mockNet.baseDirectory(bob))
|
||||
install(mockNet.baseDirectory(mockNet.nextNodeId))
|
||||
install(mockNet.baseDirectory(mockNet.nextNodeId + 1).apply { createDirectories() })
|
||||
}
|
||||
|
||||
// Start notary with new identity and restart nodes.
|
||||
mockNet.createNode(InternalMockNodeParameters(
|
||||
legalName = DUMMY_NOTARY_NAME,
|
||||
configOverrides = { doReturn(NotaryConfig(validating)).whenever(it).notary }
|
||||
))
|
||||
val alice2 = mockNet.restartNode(alice)
|
||||
val bob2 = mockNet.restartNode(bob)
|
||||
// We hide the old notary as trying to simulate it's replacement
|
||||
mockNet.hideNode(mockNet.defaultNotaryNode)
|
||||
val charlie = mockNet.createPartyNode(CHARLIE_NAME)
|
||||
|
||||
// Save previous network parameters for subsequent backchain verification, because not persistent in mock network
|
||||
alice2.internals.services.networkParametersService.saveParameters(ca.sign(mockNet.networkParameters))
|
||||
bob2.internals.services.networkParametersService.saveParameters(ca.sign(mockNet.networkParameters))
|
||||
|
||||
assertNotNull(alice2.services.networkParametersService.lookup(oldHash))
|
||||
assertNotNull(bob2.services.networkParametersService.lookup(oldHash))
|
||||
assertNull(charlie.services.networkParametersService.lookup(oldHash))
|
||||
|
||||
// Verify that notary identity has been changed.
|
||||
assertEquals(listOf(newNotaryIdentity), alice2.services.networkMapCache.notaryIdentities)
|
||||
assertEquals(listOf(newNotaryIdentity), bob2.services.networkMapCache.notaryIdentities)
|
||||
assertEquals(listOf(newNotaryIdentity), charlie.services.networkMapCache.notaryIdentities)
|
||||
|
||||
assertEquals(newNotaryIdentity, alice2.services.identityService.wellKnownPartyFromX500Name(DUMMY_NOTARY_NAME))
|
||||
assertEquals(newNotaryIdentity, bob2.services.identityService.wellKnownPartyFromX500Name(DUMMY_NOTARY_NAME))
|
||||
assertEquals(newNotaryIdentity, charlie.services.identityService.wellKnownPartyFromX500Name(DUMMY_NOTARY_NAME))
|
||||
|
||||
assertEquals(newNotaryIdentity, alice2.services.identityService.wellKnownPartyFromAnonymous(mockNet.defaultNotaryIdentity))
|
||||
assertEquals(newNotaryIdentity, bob2.services.identityService.wellKnownPartyFromAnonymous(mockNet.defaultNotaryIdentity))
|
||||
assertEquals(newNotaryIdentity, charlie.services.identityService.wellKnownPartyFromAnonymous(mockNet.defaultNotaryIdentity))
|
||||
|
||||
// Now send an existing transaction on Bob (from before rotation) to Charlie
|
||||
val bobVault: Vault.Page<Cash.State> = bob2.services.vaultService.queryBy(generateCashCriteria(USD))
|
||||
assertEquals(1, bobVault.states.size)
|
||||
val handle = bob2.services.startFlow(RpcSendTransactionFlow(bobVault.states[0].ref.txhash, charlie.party))
|
||||
mockNet.runNetwork()
|
||||
// Check flow completed successfully
|
||||
assertEquals(handle.resultFuture.getOrThrow(), Unit)
|
||||
|
||||
// Check Charlie recorded it in the vault (could resolve notary, for example)
|
||||
val charlieVault: Vault.Page<Cash.State> = charlie.services.vaultService.queryBy(generateCashCriteria(USD))
|
||||
assertEquals(1, charlieVault.states.size)
|
||||
|
||||
// Check Charlie gained the network parameters from before the rotation
|
||||
assertNotNull(charlie.services.networkParametersService.lookup(oldHash))
|
||||
|
||||
// We unhide the old notary so it can be shutdown
|
||||
mockNet.unhideNode(mockNet.defaultNotaryNode)
|
||||
}
|
||||
|
||||
private fun generateCashCriteria(currency: Currency): QueryCriteria {
|
||||
val stateCriteria = QueryCriteria.FungibleAssetQueryCriteria()
|
||||
val ccyIndex = builder { CashSchemaV1.PersistentCashState::currency.equal(currency.currencyCode) }
|
||||
// This query should only return cash states the calling node is a participant of (meaning they can be modified/spent).
|
||||
val ccyCriteria = QueryCriteria.VaultCustomQueryCriteria(ccyIndex, relevancyStatus = Vault.RelevancyStatus.ALL)
|
||||
return stateCriteria.and(ccyCriteria)
|
||||
}
|
||||
|
||||
@StartableByRPC
|
||||
@InitiatingFlow
|
||||
class RpcSendTransactionFlow(private val tx: SecureHash, private val party: Party) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
val session = initiateFlow(party)
|
||||
val stx: SignedTransaction = serviceHub.validatedTransactions.getTransaction(tx)!!
|
||||
subFlow(SendTransactionFlow(session, stx))
|
||||
}
|
||||
}
|
||||
|
||||
@InitiatedBy(RpcSendTransactionFlow::class)
|
||||
class RpcSendTransactionResponderFlow(private val otherSide: FlowSession) : FlowLogic<Unit>() {
|
||||
@Suspendable
|
||||
override fun call() {
|
||||
subFlow(ReceiveTransactionFlow(otherSide, statesToRecord = StatesToRecord.ALL_VISIBLE))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +117,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
|
||||
|
||||
fun loginToRPCAndGetClientQueue(): String {
|
||||
loginToRPC(alice.node.configuration.rpcOptions.address, rpcUser)
|
||||
val clientQueueQuery = SimpleString("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.${rpcUser.username}.*")
|
||||
val clientQueueQuery = SimpleString.of("${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.${rpcUser.username}.*")
|
||||
val client = clientTo(alice.node.configuration.rpcOptions.address)
|
||||
client.start(rpcUser.username, rpcUser.password, false)
|
||||
return client.session.addressQuery(clientQueueQuery).queueNames.single().toString()
|
||||
@ -131,7 +131,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
|
||||
|
||||
fun assertTempQueueCreationAttackFails(queue: String) {
|
||||
assertAttackFails(queue, "CREATE_NON_DURABLE_QUEUE") {
|
||||
attacker.session.createQueue(QueueConfiguration(queue)
|
||||
attacker.session.createQueue(QueueConfiguration.of(queue)
|
||||
.setRoutingType(RoutingType.MULTICAST)
|
||||
.setAddress(queue)
|
||||
.setTemporary(true)
|
||||
@ -153,7 +153,7 @@ abstract class MQSecurityTest : NodeBasedTest() {
|
||||
val permission = if (durable) "CREATE_DURABLE_QUEUE" else "CREATE_NON_DURABLE_QUEUE"
|
||||
assertAttackFails(queue, permission) {
|
||||
attacker.session.createQueue(
|
||||
QueueConfiguration(queue).setAddress(queue).setRoutingType(RoutingType.MULTICAST).setDurable(durable))
|
||||
QueueConfiguration.of(queue).setAddress(queue).setRoutingType(RoutingType.MULTICAST).setDurable(durable))
|
||||
}
|
||||
// Double-check
|
||||
assertThatExceptionOfType(ActiveMQNonExistentQueueException::class.java).isThrownBy {
|
||||
|
@ -12,6 +12,7 @@ import net.corda.confidential.SwapIdentitiesFlow
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.context.InvocationContext
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.crypto.DigitalSignature
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.newSecureRandom
|
||||
@ -246,7 +247,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
private val notaryLoader = configuration.notary?.let {
|
||||
NotaryLoader(it, versionInfo)
|
||||
}
|
||||
val cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo).closeOnStop(false)
|
||||
val rotatedKeys = makeRotatedKeysService(configuration).tokenize()
|
||||
val cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo, rotatedKeys).closeOnStop(false)
|
||||
val telemetryService: TelemetryServiceImpl = TelemetryServiceImpl().also {
|
||||
val openTelemetryComponent = OpenTelemetryComponent(configuration.myLegalName.toString(), configuration.telemetry.spanStartEndEventsEnabled, configuration.telemetry.copyBaggageToTags)
|
||||
if (configuration.telemetry.openTelemetryEnabled && openTelemetryComponent.isEnabled()) {
|
||||
@ -290,7 +292,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
database,
|
||||
configuration.devMode
|
||||
).tokenize()
|
||||
val attachmentTrustCalculator = makeAttachmentTrustCalculator(configuration, database)
|
||||
val attachmentTrustCalculator = makeAttachmentTrustCalculator(configuration, database, rotatedKeys)
|
||||
@Suppress("LeakingThis")
|
||||
val networkParametersStorage = makeNetworkParametersStorage()
|
||||
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
|
||||
@ -303,7 +305,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
// TODO Cancelling parameters updates - if we do that, how we ensure that no one uses cancelled parameters in the transactions?
|
||||
val networkMapUpdater = makeNetworkMapUpdater()
|
||||
|
||||
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory).tokenize()
|
||||
private val attachmentsClassLoaderCache: AttachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(cacheFactory, rotatedKeys).tokenize()
|
||||
val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize()
|
||||
val auditService = DummyAuditService().tokenize()
|
||||
@Suppress("LeakingThis")
|
||||
@ -842,7 +844,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
unfinishedSchedules = busyNodeLatch
|
||||
).tokenize()
|
||||
|
||||
private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
|
||||
private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo, rotatedKeys: RotatedKeys): CordappLoader {
|
||||
val generatedCordapps = mutableListOf(VirtualCordapp.generateCore(versionInfo))
|
||||
notaryLoader?.builtInNotary?.let { notaryImpl ->
|
||||
generatedCordapps += notaryImpl
|
||||
@ -858,7 +860,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
(configuration.baseDirectory / LEGACY_CONTRACTS_DIR_NAME).takeIf { it.exists() },
|
||||
versionInfo,
|
||||
extraCordapps = generatedCordapps,
|
||||
signerKeyFingerprintBlacklist = blacklistedKeys
|
||||
signerKeyFingerprintBlacklist = blacklistedKeys,
|
||||
rotatedKeys = rotatedKeys
|
||||
)
|
||||
}
|
||||
|
||||
@ -873,9 +876,16 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
private fun makeRotatedKeysService( configuration: NodeConfiguration ): RotatedKeys {
|
||||
return RotatedKeys(configuration.rotatedCordappSignerKeys.map { rotatedKeysConfiguration ->
|
||||
parseSecureHashConfiguration(rotatedKeysConfiguration.rotatedKeys) { "Error while parsing rotated keys $it"}
|
||||
}.toList())
|
||||
}
|
||||
|
||||
private fun makeAttachmentTrustCalculator(
|
||||
configuration: NodeConfiguration,
|
||||
database: CordaPersistence
|
||||
database: CordaPersistence,
|
||||
rotatedKeys: RotatedKeys
|
||||
): AttachmentTrustCalculator {
|
||||
val blacklistedAttachmentSigningKeys: List<SecureHash> =
|
||||
parseSecureHashConfiguration(configuration.blacklistedAttachmentSigningKeys) { "Error while adding signing key $it to blacklistedAttachmentSigningKeys" }
|
||||
@ -883,7 +893,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
attachmentStorage = attachments,
|
||||
database = database,
|
||||
cacheFactory = cacheFactory,
|
||||
blacklistedAttachmentSigningKeys = blacklistedAttachmentSigningKeys
|
||||
blacklistedAttachmentSigningKeys = blacklistedAttachmentSigningKeys,
|
||||
rotatedKeys = rotatedKeys
|
||||
).tokenize()
|
||||
}
|
||||
|
||||
@ -1192,6 +1203,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
|
||||
override val externalOperationExecutor: ExecutorService get() = this@AbstractNode.externalOperationExecutor
|
||||
override val notaryService: NotaryService? get() = this@AbstractNode.notaryService
|
||||
override val telemetryService: TelemetryService get() = this@AbstractNode.telemetryService
|
||||
override val rotatedKeys: RotatedKeys get() = this@AbstractNode.rotatedKeys
|
||||
|
||||
private lateinit var _myInfo: NodeInfo
|
||||
override val myInfo: NodeInfo get() = _myInfo
|
||||
|
@ -7,6 +7,7 @@ import io.github.classgraph.ScanResult
|
||||
import net.corda.common.logging.errorReporting.CordappErrors
|
||||
import net.corda.common.logging.errorReporting.ErrorCode
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.cordapp.Cordapp
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.sha256
|
||||
@ -72,12 +73,13 @@ import kotlin.reflect.KProperty1
|
||||
* @property cordappJars The classpath of cordapp JARs
|
||||
* @property legacyContractJars Legacy contract CorDapps (4.11 or earlier) needed for backwards compatibility with 4.11 nodes.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
@Suppress("TooManyFunctions", "LongParameterList")
|
||||
class JarScanningCordappLoader(private val cordappJars: Set<Path>,
|
||||
private val legacyContractJars: Set<Path> = emptySet(),
|
||||
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
||||
private val extraCordapps: List<CordappImpl> = emptyList(),
|
||||
private val signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()) : CordappLoader {
|
||||
private val signerKeyFingerprintBlacklist: List<SecureHash> = emptyList(),
|
||||
private val rotatedKeys: RotatedKeys = RotatedKeys()) : CordappLoader {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
|
||||
@ -93,14 +95,15 @@ class JarScanningCordappLoader(private val cordappJars: Set<Path>,
|
||||
legacyContractsDir: Path? = null,
|
||||
versionInfo: VersionInfo = VersionInfo.UNKNOWN,
|
||||
extraCordapps: List<CordappImpl> = emptyList(),
|
||||
signerKeyFingerprintBlacklist: List<SecureHash> = emptyList()): JarScanningCordappLoader {
|
||||
signerKeyFingerprintBlacklist: List<SecureHash> = emptyList(),
|
||||
rotatedKeys: RotatedKeys = RotatedKeys()): JarScanningCordappLoader {
|
||||
logger.info("Looking for CorDapps in ${cordappDirs.toSet().joinToString(", ", "[", "]")}")
|
||||
val cordappJars = cordappDirs
|
||||
.asSequence()
|
||||
.flatMap { if (it.exists()) it.listDirectoryEntries("*.jar") else emptyList() }
|
||||
.toSet()
|
||||
val legacyContractJars = legacyContractsDir?.useDirectoryEntries("*.jar") { it.toSet() } ?: emptySet()
|
||||
return JarScanningCordappLoader(cordappJars, legacyContractJars, versionInfo, extraCordapps, signerKeyFingerprintBlacklist)
|
||||
return JarScanningCordappLoader(cordappJars, legacyContractJars, versionInfo, extraCordapps, signerKeyFingerprintBlacklist, rotatedKeys)
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,7 +220,7 @@ class JarScanningCordappLoader(private val cordappJars: Set<Path>,
|
||||
private fun checkSignersMatch(legacyCordapp: CordappImpl, nonLegacyCordapp: CordappImpl) {
|
||||
val legacySigners = legacyCordapp.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectSigners)
|
||||
val nonLegacySigners = nonLegacyCordapp.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectSigners)
|
||||
check(legacySigners == nonLegacySigners) {
|
||||
check(rotatedKeys.canBeTransitioned(legacySigners, nonLegacySigners)) {
|
||||
"Newer contract CorDapp '${nonLegacyCordapp.jarFile}' signers do not match legacy contract CorDapp " +
|
||||
"'${legacyCordapp.jarFile}' signers."
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ package net.corda.node.services.attachments
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractAttachment
|
||||
import net.corda.core.contracts.CordaRotatedKeys
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.internal.AbstractAttachment
|
||||
import net.corda.core.internal.AttachmentTrustCalculator
|
||||
@ -30,15 +32,17 @@ class NodeAttachmentTrustCalculator(
|
||||
private val attachmentStorage: AttachmentStorageInternal,
|
||||
private val database: CordaPersistence?,
|
||||
cacheFactory: NamedCacheFactory,
|
||||
private val blacklistedAttachmentSigningKeys: List<SecureHash> = emptyList()
|
||||
private val blacklistedAttachmentSigningKeys: List<SecureHash> = emptyList(),
|
||||
private val rotatedKeys: RotatedKeys = CordaRotatedKeys.keys
|
||||
) : AttachmentTrustCalculator, SingletonSerializeAsToken() {
|
||||
|
||||
@VisibleForTesting
|
||||
constructor(
|
||||
attachmentStorage: AttachmentStorageInternal,
|
||||
cacheFactory: NamedCacheFactory,
|
||||
blacklistedAttachmentSigningKeys: List<SecureHash> = emptyList()
|
||||
) : this(attachmentStorage, null, cacheFactory, blacklistedAttachmentSigningKeys)
|
||||
attachmentStorage: AttachmentStorageInternal,
|
||||
cacheFactory: NamedCacheFactory,
|
||||
blacklistedAttachmentSigningKeys: List<SecureHash> = emptyList(),
|
||||
rotatedKeys: RotatedKeys = CordaRotatedKeys.keys
|
||||
) : this(attachmentStorage, null, cacheFactory, blacklistedAttachmentSigningKeys, rotatedKeys)
|
||||
|
||||
// A cache for caching whether a signing key is trusted
|
||||
private val trustedKeysCache = cacheFactory.buildNamed<PublicKey, Boolean>("NodeAttachmentTrustCalculator_trustedKeysCache")
|
||||
@ -55,11 +59,33 @@ class NodeAttachmentTrustCalculator(
|
||||
signersCondition = Builder.equal(listOf(signer)),
|
||||
uploaderCondition = Builder.`in`(TRUSTED_UPLOADERS)
|
||||
)
|
||||
attachmentStorage.queryAttachments(queryCriteria).isNotEmpty()
|
||||
(attachmentStorage.queryAttachments(queryCriteria).isNotEmpty() ||
|
||||
calculateTrustUsingRotatedKeys(signer))
|
||||
}!!
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateTrustUsingRotatedKeys(signer: PublicKey): Boolean {
|
||||
val db = checkNotNull(database) {
|
||||
// This should never be hit, except for tests that have not been setup correctly to test internal code
|
||||
"CordaPersistence has not been set"
|
||||
}
|
||||
return db.transaction {
|
||||
getTrustedAttachments().use { trustedAttachments ->
|
||||
for ((_, trustedAttachmentFromDB) in trustedAttachments) {
|
||||
if (canTrustedAttachmentAndAttachmentSignerBeTransitioned(trustedAttachmentFromDB, signer)) {
|
||||
return@transaction true
|
||||
}
|
||||
}
|
||||
}
|
||||
return@transaction false
|
||||
}
|
||||
}
|
||||
|
||||
private fun canTrustedAttachmentAndAttachmentSignerBeTransitioned(trustedAttachmentFromDB: Attachment, signer: PublicKey): Boolean {
|
||||
return trustedAttachmentFromDB.signerKeys.any { signerKeyFromDB -> rotatedKeys.canBeTransitioned(signerKeyFromDB, signer) }
|
||||
}
|
||||
|
||||
override fun calculateAllTrustInfo(): List<AttachmentTrustInfo> {
|
||||
|
||||
val publicKeyToTrustRootMap = mutableMapOf<PublicKey, TrustedAttachment>()
|
||||
|
@ -86,6 +86,13 @@ interface NodeConfiguration : ConfigurationWithOptionsContainer {
|
||||
|
||||
val cordappSignerKeyFingerprintBlacklist: List<String>
|
||||
|
||||
/**
|
||||
* Represents a list of rotated CorDapp attachment JAR signing key configurations. Each configuration describes a set of equivalent
|
||||
* keys. Logically there should be no overlap between configurations, since that would mean they should be one combined list,
|
||||
* and this is enforced.
|
||||
*/
|
||||
val rotatedCordappSignerKeys: List<RotatedCorDappSignerKeyConfiguration>
|
||||
|
||||
val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings?
|
||||
|
||||
val networkParametersPath: Path
|
||||
@ -222,6 +229,13 @@ data class TelemetryConfiguration(
|
||||
val copyBaggageToTags: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents a list of rotated CorDapp attachment signing keys.
|
||||
*
|
||||
* @param rotatedKeys This is a list of public key hashes (SHA-256) in uppercase hexidecimal, that are all equivalent.
|
||||
*/
|
||||
data class RotatedCorDappSignerKeyConfiguration(val rotatedKeys: List<String>)
|
||||
|
||||
internal typealias Valid<TARGET> = Validated<TARGET, Configuration.Validation.Error>
|
||||
|
||||
fun Config.parseAsNodeConfiguration(options: Configuration.Options = Configuration.Options(strict = true)): Valid<NodeConfiguration> = V1NodeConfigurationSpec.parse(this, options)
|
||||
|
@ -80,6 +80,7 @@ data class NodeConfigurationImpl(
|
||||
override val jmxReporterType: JmxReporterType? = Defaults.jmxReporterType,
|
||||
override val flowOverrides: FlowOverrideConfig?,
|
||||
override val cordappSignerKeyFingerprintBlacklist: List<String> = Defaults.cordappSignerKeyFingerprintBlacklist,
|
||||
override val rotatedCordappSignerKeys: List<RotatedCorDappSignerKeyConfiguration> = Defaults.rotatedCordappSignerKeys,
|
||||
override val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings? =
|
||||
Defaults.networkParameterAcceptanceSettings,
|
||||
override val blacklistedAttachmentSigningKeys: List<String> = Defaults.blacklistedAttachmentSigningKeys,
|
||||
@ -122,6 +123,7 @@ data class NodeConfigurationImpl(
|
||||
val flowMonitorSuspensionLoggingThresholdMillis: Duration = NodeConfiguration.DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS
|
||||
val jmxReporterType: JmxReporterType = NodeConfiguration.defaultJmxReporterType
|
||||
val cordappSignerKeyFingerprintBlacklist: List<String> = DEV_PUB_KEY_HASHES.map { it.toString() }
|
||||
val rotatedCordappSignerKeys: List<RotatedCorDappSignerKeyConfiguration> = emptyList()
|
||||
val networkParameterAcceptanceSettings: NetworkParameterAcceptanceSettings = NetworkParameterAcceptanceSettings()
|
||||
val blacklistedAttachmentSigningKeys: List<String> = emptyList()
|
||||
const val flowExternalOperationThreadPoolSize: Int = 1
|
||||
|
@ -27,6 +27,7 @@ import net.corda.node.services.config.NodeH2Settings
|
||||
import net.corda.node.services.config.NodeRpcSettings
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.config.PasswordEncryption
|
||||
import net.corda.node.services.config.RotatedCorDappSignerKeyConfiguration
|
||||
import net.corda.node.services.config.SecurityConfiguration
|
||||
import net.corda.node.services.config.SecurityConfiguration.AuthService.Companion.defaultAuthServiceId
|
||||
import net.corda.node.services.config.TelemetryConfiguration
|
||||
@ -225,6 +226,14 @@ internal object TelemetryConfigurationSpec : Configuration.Specification<Telemet
|
||||
}
|
||||
}
|
||||
|
||||
internal object RotatedSignerKeySpec : Configuration.Specification<RotatedCorDappSignerKeyConfiguration>("RotatedCorDappSignerKeyConfiguration") {
|
||||
private val rotatedKeys by string().listOrEmpty()
|
||||
override fun parseValid(configuration: Config, options: Configuration.Options): Valid<RotatedCorDappSignerKeyConfiguration> {
|
||||
val config = configuration.withOptions(options)
|
||||
return valid(RotatedCorDappSignerKeyConfiguration(config[rotatedKeys]))
|
||||
}
|
||||
}
|
||||
|
||||
internal object NotaryConfigSpec : Configuration.Specification<NotaryConfig>("NotaryConfig") {
|
||||
private val validating by boolean()
|
||||
private val serviceLegalName by string().mapValid(::toCordaX500Name).optional()
|
||||
|
@ -60,6 +60,7 @@ internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfig
|
||||
private val jarDirs by string().list().optional().withDefaultValue(Defaults.jarDirs)
|
||||
private val cordappDirectories by string().mapValid(::toPath).list().optional()
|
||||
private val cordappSignerKeyFingerprintBlacklist by string().list().optional().withDefaultValue(Defaults.cordappSignerKeyFingerprintBlacklist)
|
||||
private val rotatedCordappSignerKeys by nested(RotatedSignerKeySpec).listOrEmpty()
|
||||
private val blacklistedAttachmentSigningKeys by string().list().optional().withDefaultValue(Defaults.blacklistedAttachmentSigningKeys)
|
||||
private val networkParameterAcceptanceSettings by nested(NetworkParameterAcceptanceSettingsSpec)
|
||||
.optional()
|
||||
@ -138,7 +139,8 @@ internal object V1NodeConfigurationSpec : Configuration.Specification<NodeConfig
|
||||
flowExternalOperationThreadPoolSize = config[flowExternalOperationThreadPoolSize],
|
||||
quasarExcludePackages = config[quasarExcludePackages],
|
||||
reloadCheckpointAfterSuspend = config[reloadCheckpointAfterSuspend],
|
||||
networkParametersPath = networkParametersPath
|
||||
networkParametersPath = networkParametersPath,
|
||||
rotatedCordappSignerKeys = config[rotatedCordappSignerKeys]
|
||||
))
|
||||
} catch (e: Exception) {
|
||||
return when (e) {
|
||||
|
@ -399,7 +399,12 @@ class PersistentIdentityService(cacheFactory: NamedCacheFactory) : SingletonSeri
|
||||
if (candidate != null && candidate != party) {
|
||||
// Party doesn't match existing well-known party: check that the key is registered, otherwise return null.
|
||||
require(party.name == candidate.name) { "Candidate party $candidate does not match expected $party" }
|
||||
keyToParty[party.owningKey.toStringShort()]?.let { candidate }
|
||||
// If the party is a whitelisted notary, then it was just a rotated notary key
|
||||
if (party in notaryIdentityCache) {
|
||||
candidate
|
||||
} else {
|
||||
keyToParty[party.owningKey.toStringShort()]?.let { candidate }
|
||||
}
|
||||
} else {
|
||||
// Party is a well-known party or well-known party doesn't exist: skip checks.
|
||||
// If the notary is not in the network map cache, try getting it from the network parameters
|
||||
|
@ -169,7 +169,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
||||
journalBufferTimeout_NIO = journalBufferTimeout ?: ActiveMQDefaultConfiguration.getDefaultJournalBufferTimeoutNio()
|
||||
journalBufferTimeout_AIO = journalBufferTimeout ?: ActiveMQDefaultConfiguration.getDefaultJournalBufferTimeoutAio()
|
||||
journalFileSize = maxMessageSize + JOURNAL_HEADER_SIZE// The size of each journal file in bytes. Artemis default is 10MiB.
|
||||
managementNotificationAddress = SimpleString(NOTIFICATIONS_ADDRESS)
|
||||
managementNotificationAddress = SimpleString.of(NOTIFICATIONS_ADDRESS)
|
||||
|
||||
// JMX enablement
|
||||
if (config.jmxMonitoringHttpPort != null) {
|
||||
@ -189,7 +189,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
||||
* 4. Verifiers. These are given read access to the verification request queue and write access to the response queue.
|
||||
*/
|
||||
private fun ConfigurationImpl.configureAddressSecurity(): Configuration {
|
||||
val nodeInternalRole = Role(NODE_P2P_ROLE, true, true, true, true, true, true, true, true, true, true)
|
||||
val nodeInternalRole = Role(NODE_P2P_ROLE, true, true, true, true, true, true, true, true, true, true, false, false)
|
||||
securityRoles["$INTERNAL_PREFIX#"] = setOf(nodeInternalRole) // Do not add any other roles here as it's only for the node
|
||||
securityRoles["$P2P_PREFIX#"] = setOf(nodeInternalRole, restrictedRole(PEER_ROLE, send = true))
|
||||
securityInvalidationInterval = SECURITY_INVALIDATION_INTERVAL
|
||||
@ -200,7 +200,7 @@ class ArtemisMessagingServer(private val config: NodeConfiguration,
|
||||
deleteDurableQueue: Boolean = false, createNonDurableQueue: Boolean = false,
|
||||
deleteNonDurableQueue: Boolean = false, manage: Boolean = false, browse: Boolean = false): Role {
|
||||
return Role(name, send, consume, createDurableQueue, deleteDurableQueue, createNonDurableQueue,
|
||||
deleteNonDurableQueue, manage, browse, createDurableQueue || createNonDurableQueue, deleteDurableQueue || deleteNonDurableQueue)
|
||||
deleteNonDurableQueue, manage, browse, createDurableQueue || createNonDurableQueue, deleteDurableQueue || deleteNonDurableQueue, false, false)
|
||||
}
|
||||
|
||||
private fun createArtemisSecurityManager(): ActiveMQJAASSecurityManager {
|
||||
|
@ -33,8 +33,8 @@ class MessagingExecutor(
|
||||
val resolver: AddressToArtemisQueueResolver,
|
||||
val ourSenderUUID: String
|
||||
) {
|
||||
private val cordaVendor = SimpleString(versionInfo.vendor)
|
||||
private val releaseVersion = SimpleString(versionInfo.releaseVersion)
|
||||
private val cordaVendor = SimpleString.of(versionInfo.vendor)
|
||||
private val releaseVersion = SimpleString.of(versionInfo.releaseVersion)
|
||||
private val ourSenderSeqNo = AtomicLong()
|
||||
|
||||
private companion object {
|
||||
@ -50,7 +50,7 @@ class MessagingExecutor(
|
||||
"Send to: $mqAddress topic: ${message.topic} " +
|
||||
"sessionID: ${message.topic} id: ${message.uniqueMessageId}"
|
||||
}
|
||||
producer.send(SimpleString(mqAddress), artemisMessage)
|
||||
producer.send(SimpleString.of(mqAddress), artemisMessage)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
@ -72,13 +72,13 @@ class MessagingExecutor(
|
||||
putStringProperty(P2PMessagingHeaders.cordaVendorProperty, cordaVendor)
|
||||
putStringProperty(P2PMessagingHeaders.releaseVersionProperty, releaseVersion)
|
||||
putIntProperty(P2PMessagingHeaders.platformVersionProperty, versionInfo.platformVersion)
|
||||
putStringProperty(P2PMessagingHeaders.topicProperty, SimpleString(message.topic))
|
||||
putStringProperty(P2PMessagingHeaders.topicProperty, SimpleString.of(message.topic))
|
||||
writeBodyBufferBytes(message.data.bytes)
|
||||
// Use the magic deduplication property built into Artemis as our message identity too
|
||||
putStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID, SimpleString(message.uniqueMessageId.toString))
|
||||
putStringProperty(org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID, SimpleString.of(message.uniqueMessageId.toString))
|
||||
// If we are the sender (ie. we are not going through recovery of some sort), use sequence number short cut.
|
||||
if (ourSenderUUID == message.senderUUID) {
|
||||
putStringProperty(P2PMessagingHeaders.senderUUID, SimpleString(ourSenderUUID))
|
||||
putStringProperty(P2PMessagingHeaders.senderUUID, SimpleString.of(ourSenderUUID))
|
||||
putLongProperty(P2PMessagingHeaders.senderSeqNo, ourSenderSeqNo.getAndIncrement())
|
||||
}
|
||||
// For demo purposes - if set then add a delay to messages in order to demonstrate that the flows are doing as intended
|
||||
|
@ -279,8 +279,8 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
||||
|
||||
private fun InnerState.registerBridgeControl(session: ClientSession, inboxes: List<String>) {
|
||||
val bridgeNotifyQueue = "$BRIDGE_NOTIFY.${myIdentity.toStringShort()}"
|
||||
if (!session.queueQuery(SimpleString(bridgeNotifyQueue)).isExists) {
|
||||
session.createQueue(QueueConfiguration(bridgeNotifyQueue).setAddress(BRIDGE_NOTIFY).setRoutingType(RoutingType.MULTICAST)
|
||||
if (!session.queueQuery(SimpleString.of(bridgeNotifyQueue)).isExists) {
|
||||
session.createQueue(QueueConfiguration.of(bridgeNotifyQueue).setAddress(BRIDGE_NOTIFY).setRoutingType(RoutingType.MULTICAST)
|
||||
.setTemporary(true).setDurable(false))
|
||||
}
|
||||
val bridgeConsumer = session.createConsumer(bridgeNotifyQueue)
|
||||
@ -316,7 +316,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
||||
node.legalIdentitiesAndCerts.map { partyAndCertificate ->
|
||||
val messagingAddress = NodeAddress(partyAndCertificate.party.owningKey)
|
||||
BridgeEntry(messagingAddress.queueName, node.addresses, node.legalIdentities.map { it.name }, serviceAddress = false)
|
||||
}.filter { producerSession!!.queueQuery(SimpleString(it.queueName)).isExists }.asSequence()
|
||||
}.filter { producerSession!!.queueQuery(SimpleString.of(it.queueName)).isExists }.asSequence()
|
||||
}
|
||||
}
|
||||
|
||||
@ -360,7 +360,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
||||
}
|
||||
}
|
||||
|
||||
val queues = session.addressQuery(SimpleString("$PEERS_PREFIX#")).queueNames
|
||||
val queues = session.addressQuery(SimpleString.of("$PEERS_PREFIX#")).queueNames
|
||||
knownQueues.clear()
|
||||
for (queue in queues) {
|
||||
val queueQuery = session.queueQuery(queue)
|
||||
@ -604,10 +604,10 @@ class P2PMessagingClient(val config: NodeConfiguration,
|
||||
sendBridgeCreateMessage()
|
||||
delayStartQueues -= queueName
|
||||
} else {
|
||||
val queueQuery = session.queueQuery(SimpleString(queueName))
|
||||
val queueQuery = session.queueQuery(SimpleString.of(queueName))
|
||||
if (!queueQuery.isExists) {
|
||||
log.info("Create fresh queue $queueName bound on same address")
|
||||
session.createQueue(QueueConfiguration(queueName).setRoutingType(RoutingType.ANYCAST).setAddress(queueName)
|
||||
session.createQueue(QueueConfiguration.of(queueName).setRoutingType(RoutingType.ANYCAST).setAddress(queueName)
|
||||
.setDurable(true).setAutoCreated(false)
|
||||
.setMaxConsumers(ActiveMQDefaultConfiguration.getDefaultMaxQueueConsumers())
|
||||
.setPurgeOnNoConsumers(ActiveMQDefaultConfiguration.getDefaultPurgeOnNoConsumers())
|
||||
|
@ -321,14 +321,14 @@ class RPCServer(
|
||||
require(notificationType == CoreNotificationType.BINDING_REMOVED.name){"Message contained notification type of $notificationType instead of expected ${CoreNotificationType.BINDING_REMOVED.name}"}
|
||||
val clientAddress = artemisMessage.getStringProperty(ManagementHelper.HDR_ROUTING_NAME)
|
||||
log.info("Detected RPC client disconnect on address $clientAddress, scheduling for reaping")
|
||||
invalidateClient(SimpleString(clientAddress))
|
||||
invalidateClient(SimpleString.of(clientAddress))
|
||||
}
|
||||
|
||||
private fun bindingAdditionArtemisMessageHandler(artemisMessage: ClientMessage) {
|
||||
lifeCycle.requireState(State.STARTED)
|
||||
val notificationType = artemisMessage.getStringProperty(ManagementHelper.HDR_NOTIFICATION_TYPE)
|
||||
require(notificationType == CoreNotificationType.BINDING_ADDED.name){"Message contained notification type of $notificationType instead of expected ${CoreNotificationType.BINDING_ADDED.name}"}
|
||||
val clientAddress = SimpleString(artemisMessage.getStringProperty(ManagementHelper.HDR_ROUTING_NAME))
|
||||
val clientAddress = SimpleString.of(artemisMessage.getStringProperty(ManagementHelper.HDR_ROUTING_NAME))
|
||||
log.debug("RPC client queue created on address $clientAddress")
|
||||
|
||||
val buffer = stopBuffering(clientAddress)
|
||||
|
@ -39,7 +39,7 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
|
||||
|
||||
queueConfigs = queueConfigurations()
|
||||
|
||||
managementNotificationAddress = SimpleString(ArtemisMessagingComponent.NOTIFICATIONS_ADDRESS)
|
||||
managementNotificationAddress = SimpleString.of(ArtemisMessagingComponent.NOTIFICATIONS_ADDRESS)
|
||||
addressSettings = mapOf(
|
||||
"${RPCApi.RPC_CLIENT_QUEUE_NAME_PREFIX}.#" to AddressSettings().apply {
|
||||
maxSizeBytes = 5L * maxMessageSize
|
||||
@ -51,7 +51,7 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
|
||||
globalMaxSize = Runtime.getRuntime().maxMemory() / 8
|
||||
initialiseSettings(maxMessageSize, journalBufferTimeout)
|
||||
|
||||
val nodeInternalRole = Role(BrokerJaasLoginModule.NODE_RPC_ROLE, true, true, true, true, true, true, true, true, true, true)
|
||||
val nodeInternalRole = Role(BrokerJaasLoginModule.NODE_RPC_ROLE, true, true, true, true, true, true, true, true, true, true, false, false)
|
||||
|
||||
val addRPCRoleToUsers = if (shouldStartLocalShell) listOf(INTERNAL_SHELL_USER) else emptyList()
|
||||
val rolesAdderOnLogin = RolesAdderOnLogin(addRPCRoleToUsers) { username ->
|
||||
@ -127,12 +127,12 @@ internal class RpcBrokerConfiguration(baseDirectory: Path, maxMessageSize: Int,
|
||||
}
|
||||
|
||||
private fun queueConfiguration(name: String, address: String = name, filter: String? = null, durable: Boolean): QueueConfiguration {
|
||||
return QueueConfiguration(name).setAddress(address).setFilterString(filter).setDurable(durable)
|
||||
return QueueConfiguration.of(name).setAddress(address).setFilterString(filter).setDurable(durable)
|
||||
}
|
||||
|
||||
private fun restrictedRole(name: String, send: Boolean = false, consume: Boolean = false, createDurableQueue: Boolean = false,
|
||||
deleteDurableQueue: Boolean = false, createNonDurableQueue: Boolean = false,
|
||||
deleteNonDurableQueue: Boolean = false, manage: Boolean = false, browse: Boolean = false): Role {
|
||||
return Role(name, send, consume, createDurableQueue, deleteDurableQueue, createNonDurableQueue, deleteNonDurableQueue, manage, browse, createDurableQueue || createNonDurableQueue, deleteDurableQueue || deleteNonDurableQueue)
|
||||
return Role(name, send, consume, createDurableQueue, deleteDurableQueue, createNonDurableQueue, deleteNonDurableQueue, manage, browse, createDurableQueue || createNonDurableQueue, deleteDurableQueue || deleteNonDurableQueue, false, false)
|
||||
}
|
||||
}
|
@ -237,7 +237,8 @@ class ExternalVerifierHandleImpl(
|
||||
customSerializerClassNames = cordapps.customSerializers.mapToSet { it.javaClass.name },
|
||||
serializationWhitelistClassNames = cordapps.serializationWhitelists.mapToSet { it.javaClass.name },
|
||||
System.getProperty("experimental.corda.customSerializationScheme"), // See Node#initialiseSerialization
|
||||
serializedCurrentNetworkParameters = verificationSupport.networkParameters.serialize()
|
||||
serializedCurrentNetworkParameters = verificationSupport.networkParameters.serialize(),
|
||||
serializedRotatedKeys = verificationSupport.rotatedKeys.serialize()
|
||||
)
|
||||
channel.writeCordaSerializable(initialisation)
|
||||
}
|
||||
|
@ -1,3 +1,6 @@
|
||||
jira==2.0.0
|
||||
keyring==13.1.0
|
||||
termcolor==1.1.0
|
||||
urllib3>=2.2.2 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
requests>=2.32.2 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
setuptools>=70.0.0 # not directly required, pinned by Snyk to avoid a vulnerability
|
||||
|
@ -1276,7 +1276,7 @@ class SerializationOutputTests(private val compression: CordaSerializationEncodi
|
||||
)
|
||||
factory2.register(net.corda.serialization.internal.amqp.custom.SimpleStringSerializer)
|
||||
|
||||
val obj = SimpleString("Bob")
|
||||
val obj = SimpleString.of("Bob")
|
||||
serdes(obj, factory, factory2)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.serialization.internal.verifier
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.toStringShort
|
||||
@ -23,6 +24,7 @@ import kotlin.math.min
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
typealias SerializedNetworkParameters = SerializedBytes<NetworkParameters>
|
||||
typealias SerializedRotatedKeys = SerializedBytes<RotatedKeys>
|
||||
|
||||
@CordaSerializable
|
||||
sealed class ExternalVerifierInbound {
|
||||
@ -30,16 +32,19 @@ sealed class ExternalVerifierInbound {
|
||||
val customSerializerClassNames: Set<String>,
|
||||
val serializationWhitelistClassNames: Set<String>,
|
||||
val customSerializationSchemeClassName: String?,
|
||||
val serializedCurrentNetworkParameters: SerializedNetworkParameters
|
||||
val serializedCurrentNetworkParameters: SerializedNetworkParameters,
|
||||
val serializedRotatedKeys: SerializedRotatedKeys
|
||||
) : ExternalVerifierInbound() {
|
||||
val currentNetworkParameters: NetworkParameters by lazy { serializedCurrentNetworkParameters.deserialize() }
|
||||
val rotatedKeys: RotatedKeys by lazy { serializedRotatedKeys.deserialize() }
|
||||
|
||||
override fun toString(): String {
|
||||
return "Initialisation(" +
|
||||
"customSerializerClassNames=$customSerializerClassNames, " +
|
||||
"serializationWhitelistClassNames=$serializationWhitelistClassNames, " +
|
||||
"customSerializationSchemeClassName=$customSerializationSchemeClassName, " +
|
||||
"currentNetworkParameters=$currentNetworkParameters)"
|
||||
"currentNetworkParameters=$currentNetworkParameters, " +
|
||||
"rotatedKeys=$rotatedKeys)"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
apply plugin: 'corda.api-scanner'
|
||||
|
||||
description 'Corda Node Driver module'
|
||||
|
||||
//noinspection GroovyAssignabilityCheck
|
||||
configurations {
|
||||
integrationTestImplementation.extendsFrom testImplementation
|
||||
|
@ -140,16 +140,42 @@ class DriverTests {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The uniqueness of nodes by the DSL is checked using the node organisation name and, if specified,
|
||||
* the organisation unit name.
|
||||
* All other X500 components are ignored in this regard.
|
||||
*/
|
||||
@Test(timeout=300_000)
|
||||
fun `driver rejects multiple nodes with the same organisation name`() {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
newNode(CordaX500Name(commonName = "Notary", organisation = "R3CEV", locality = "New York", country = "US"))().getOrThrow()
|
||||
assertThatIllegalArgumentException().isThrownBy {
|
||||
newNode(CordaX500Name(commonName = "Regulator", organisation = "R3CEV", locality = "New York", country = "US"))().getOrThrow()
|
||||
newNode(CordaX500Name(commonName = "Regulator", organisation = "R3CEV", locality = "Newcastle", country = "GB"))().getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `driver allows multiple nodes with the same organisation name but different organisation unit name`() {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
newNode(CordaX500Name(commonName = "Notary", organisation = "R3CEV", organisationUnit = "Eric", locality = "New York", country = "US", state = null))().getOrThrow()
|
||||
assertThatCode {
|
||||
newNode(CordaX500Name(commonName = "Regulator", organisation = "R3CEV", organisationUnit = "Ernie", locality = "Newcastle", country = "GB", state = null))().getOrThrow()
|
||||
}.doesNotThrowAnyException()
|
||||
}
|
||||
}
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `driver rejects multiple nodes with the same organisation name and organisation unit name`() {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
newNode(CordaX500Name(commonName = "Notary", organisation = "R3CEV", organisationUnit = "Eric", locality = "New York", country = "US", state = null))().getOrThrow()
|
||||
assertThatIllegalArgumentException().isThrownBy {
|
||||
newNode(CordaX500Name(commonName = "Regulator", organisation = "R3CEV", organisationUnit = "Eric", locality = "Newcastle", country = "GB", state = null))().getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
/** **** **/
|
||||
|
||||
@Test(timeout=300_000)
|
||||
fun `driver allows reusing names of nodes that have been stopped`() {
|
||||
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
|
||||
|
@ -5,6 +5,7 @@ import net.corda.core.CordaInternal
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.ContractClassName
|
||||
import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.contracts.TransactionState
|
||||
@ -128,8 +129,8 @@ open class MockServices private constructor(
|
||||
)
|
||||
) : ServiceHub {
|
||||
companion object {
|
||||
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): CordappLoader {
|
||||
return JarScanningCordappLoader(cordappsForPackages(packages).mapToSet { it.jarFile }, versionInfo = versionInfo)
|
||||
private fun cordappLoaderForPackages(packages: Iterable<String>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, rotatedKeys: RotatedKeys = RotatedKeys()): CordappLoader {
|
||||
return JarScanningCordappLoader(cordappsForPackages(packages).mapToSet { it.jarFile }, versionInfo = versionInfo, rotatedKeys = rotatedKeys)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -500,6 +501,7 @@ open class MockServices private constructor(
|
||||
protected val servicesForResolution: ServicesForResolution get() = verifyingView
|
||||
|
||||
private val verifyingView: VerifyingServiceHub by lazy { VerifyingView(this) }
|
||||
val rotatedKeys: RotatedKeys = RotatedKeys()
|
||||
|
||||
internal fun makeVaultService(schemaService: SchemaService, database: CordaPersistence, cordappLoader: CordappLoader): VaultServiceInternal {
|
||||
return NodeVaultService(
|
||||
@ -564,10 +566,10 @@ open class MockServices private constructor(
|
||||
private class VerifyingView(private val mockServices: MockServices) : VerifyingServiceHub, ServiceHub by mockServices {
|
||||
override val attachmentTrustCalculator = NodeAttachmentTrustCalculator(
|
||||
attachmentStorage = mockServices.attachments.toInternal(),
|
||||
cacheFactory = TestingNamedCacheFactory()
|
||||
cacheFactory = TestingNamedCacheFactory(), rotatedKeys = mockServices.rotatedKeys
|
||||
)
|
||||
|
||||
override val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory())
|
||||
override val attachmentsClassLoaderCache = AttachmentsClassLoaderCacheImpl(TestingNamedCacheFactory(), mockServices.rotatedKeys)
|
||||
|
||||
override val cordappProvider: CordappProviderInternal get() = mockServices.mockCordappProvider
|
||||
|
||||
@ -579,6 +581,8 @@ open class MockServices private constructor(
|
||||
|
||||
override val externalVerifierHandle: ExternalVerifierHandle
|
||||
get() = throw UnsupportedOperationException("`Verification of legacy transactions is not supported by MockServices. Use MockNode instead.")
|
||||
|
||||
override val rotatedKeys: RotatedKeys = mockServices.rotatedKeys
|
||||
}
|
||||
|
||||
|
||||
|
@ -664,7 +664,11 @@ class DriverDSLImpl(
|
||||
}
|
||||
|
||||
override fun baseDirectory(nodeName: CordaX500Name): Path {
|
||||
val nodeDirectoryName = nodeName.organisation.filter { !it.isWhitespace() }
|
||||
val nodeDirectoryName = if (nodeName.organisationUnit != null) {
|
||||
"${nodeName.organisation}-${nodeName.organisationUnit}"
|
||||
} else {
|
||||
nodeName.organisation
|
||||
}.filter { !it.isWhitespace() }
|
||||
return driverDirectory / nodeDirectoryName
|
||||
}
|
||||
|
||||
@ -1142,11 +1146,16 @@ private class NetworkVisibilityController {
|
||||
|
||||
fun register(name: CordaX500Name): VisibilityHandle {
|
||||
val handle = VisibilityHandle()
|
||||
val handleName = if (name.organisationUnit != null) {
|
||||
"${name.organisation}-${name.organisationUnit}"
|
||||
} else {
|
||||
name.organisation
|
||||
}
|
||||
nodeVisibilityHandles.locked {
|
||||
require(name.organisation !in keys) {
|
||||
"Node with organisation name ${name.organisation} is already started or starting"
|
||||
require(handleName !in keys) {
|
||||
"Node with the organisation name (+ unit name) \"$handleName\" is already started or starting"
|
||||
}
|
||||
put(name.organisation, handle)
|
||||
put(handleName, handle)
|
||||
}
|
||||
return handle
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ import net.corda.node.services.config.FlowTimeoutConfiguration
|
||||
import net.corda.node.services.config.NetworkParameterAcceptanceSettings
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.config.RotatedCorDappSignerKeyConfiguration
|
||||
import net.corda.node.services.config.TelemetryConfiguration
|
||||
import net.corda.node.services.config.VerifierType
|
||||
import net.corda.node.services.identity.PersistentIdentityService
|
||||
@ -499,6 +500,18 @@ open class InternalMockNetwork(cordappPackages: List<String> = emptyList(),
|
||||
return node
|
||||
}
|
||||
|
||||
fun hideNode(
|
||||
node: TestStartedNode
|
||||
) {
|
||||
_nodes.remove(node.internals)
|
||||
}
|
||||
|
||||
fun unhideNode(
|
||||
node: TestStartedNode
|
||||
) {
|
||||
_nodes.add(node.internals)
|
||||
}
|
||||
|
||||
fun restartNode(
|
||||
node: TestStartedNode,
|
||||
parameters: InternalMockNodeParameters = InternalMockNodeParameters(),
|
||||
@ -658,6 +671,7 @@ private fun mockNodeConfiguration(certificatesDirectory: Path): NodeConfiguratio
|
||||
doReturn(rigorousMock<ConfigurationWithOptions>()).whenever(it).configurationWithOptions
|
||||
doReturn(2).whenever(it).flowExternalOperationThreadPoolSize
|
||||
doReturn(false).whenever(it).reloadCheckpointAfterSuspend
|
||||
doReturn(emptyList<RotatedCorDappSignerKeyConfiguration>()).whenever(it).rotatedCordappSignerKeys
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -192,16 +192,16 @@ data class RPCDriverDSL(
|
||||
|
||||
private fun ConfigurationImpl.configureCommonSettings(maxFileSize: Int, maxBufferedBytesPerClient: Long) {
|
||||
name = "RPCDriver"
|
||||
managementNotificationAddress = SimpleString(NOTIFICATION_ADDRESS)
|
||||
managementNotificationAddress = SimpleString.of(NOTIFICATION_ADDRESS)
|
||||
isPopulateValidatedUser = true
|
||||
journalBufferSize_NIO = maxFileSize
|
||||
journalBufferSize_AIO = maxFileSize
|
||||
journalFileSize = maxFileSize
|
||||
queueConfigs = listOf(
|
||||
QueueConfiguration(RPCApi.RPC_SERVER_QUEUE_NAME).setAddress(RPCApi.RPC_SERVER_QUEUE_NAME).setDurable(false),
|
||||
QueueConfiguration(RPCApi.RPC_CLIENT_BINDING_REMOVALS).setAddress(NOTIFICATION_ADDRESS)
|
||||
QueueConfiguration.of(RPCApi.RPC_SERVER_QUEUE_NAME).setAddress(RPCApi.RPC_SERVER_QUEUE_NAME).setDurable(false),
|
||||
QueueConfiguration.of(RPCApi.RPC_CLIENT_BINDING_REMOVALS).setAddress(NOTIFICATION_ADDRESS)
|
||||
.setFilterString(RPCApi.RPC_CLIENT_BINDING_REMOVAL_FILTER_EXPRESSION).setDurable(false),
|
||||
QueueConfiguration(RPCApi.RPC_CLIENT_BINDING_ADDITIONS).setAddress(NOTIFICATION_ADDRESS)
|
||||
QueueConfiguration.of(RPCApi.RPC_CLIENT_BINDING_ADDITIONS).setAddress(NOTIFICATION_ADDRESS)
|
||||
.setFilterString(RPCApi.RPC_CLIENT_BINDING_ADDITION_FILTER_EXPRESSION).setDurable(false)
|
||||
)
|
||||
addressSettings = mapOf(
|
||||
|
@ -3,6 +3,8 @@ apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
apply plugin: 'corda.api-scanner'
|
||||
|
||||
description 'Corda Test Common module'
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation project(':node-api')
|
||||
|
@ -2,6 +2,8 @@ apply plugin: 'net.corda.plugins.api-scanner'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
apply plugin: 'corda.api-scanner'
|
||||
|
||||
description 'Corda test-db module'
|
||||
|
||||
dependencies {
|
||||
implementation "org.junit.jupiter:junit-jupiter-api:${junit_jupiter_version}"
|
||||
|
||||
|
@ -14,6 +14,7 @@ import net.corda.core.internal.*
|
||||
import net.corda.core.internal.cordapp.CordappProviderInternal
|
||||
import net.corda.core.internal.notary.NotaryService
|
||||
import net.corda.core.internal.verification.ExternalVerifierHandle
|
||||
import net.corda.core.internal.verification.toVerifyingServiceHub
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.ServicesForResolution
|
||||
import net.corda.core.node.StatesToRecord
|
||||
@ -108,11 +109,13 @@ data class TestTransactionDSLInterpreter private constructor(
|
||||
ThreadFactoryBuilder().setNameFormat("flow-external-operation-thread").build()
|
||||
)
|
||||
|
||||
override val rotatedKeys: RotatedKeys = ledgerInterpreter.services.toVerifyingServiceHub().rotatedKeys
|
||||
|
||||
override val attachmentTrustCalculator: AttachmentTrustCalculator =
|
||||
ledgerInterpreter.services.attachments.let {
|
||||
// Wrapping to a [InternalMockAttachmentStorage] is needed to prevent leaking internal api
|
||||
// while still allowing the tests to work
|
||||
NodeAttachmentTrustCalculator(attachmentStorage = it.toInternal(), cacheFactory = TestingNamedCacheFactory())
|
||||
NodeAttachmentTrustCalculator(attachmentStorage = it.toInternal(), cacheFactory = TestingNamedCacheFactory(), rotatedKeys = rotatedKeys)
|
||||
}
|
||||
|
||||
override fun createTransactionsResolver(flow: ResolveTransactionsFlow): TransactionsResolver =
|
||||
|
@ -1,6 +1,8 @@
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'corda.common-publishing'
|
||||
|
||||
description 'Corda blob inspector module'
|
||||
|
||||
dependencies {
|
||||
implementation project(':core')
|
||||
implementation project(':serialization')
|
||||
|
@ -4,6 +4,8 @@ plugins {
|
||||
id 'corda.common-publishing'
|
||||
}
|
||||
|
||||
description 'Corda Network Builder module'
|
||||
|
||||
apply plugin: 'org.openjfx.javafxplugin'
|
||||
|
||||
javafx {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.corda.verifier
|
||||
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
@ -14,7 +15,8 @@ class ExternalVerificationContext(
|
||||
override val appClassLoader: ClassLoader,
|
||||
override val attachmentsClassLoaderCache: AttachmentsClassLoaderCache,
|
||||
private val externalVerifier: ExternalVerifier,
|
||||
private val transactionInputsAndReferences: Map<StateRef, SerializedTransactionState>
|
||||
private val transactionInputsAndReferences: Map<StateRef, SerializedTransactionState>,
|
||||
override val rotatedKeys: RotatedKeys
|
||||
) : VerificationSupport {
|
||||
override val isInProcess: Boolean get() = false
|
||||
|
||||
|
@ -2,6 +2,7 @@ package net.corda.verifier
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache
|
||||
import net.corda.core.contracts.Attachment
|
||||
import net.corda.core.contracts.RotatedKeys
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.loadClassOfType
|
||||
@ -70,6 +71,7 @@ class ExternalVerifier(private val baseDirectory: Path, private val channel: Soc
|
||||
|
||||
private lateinit var appClassLoader: ClassLoader
|
||||
private lateinit var currentNetworkParameters: NetworkParameters
|
||||
private lateinit var rotatedKeys: RotatedKeys
|
||||
|
||||
init {
|
||||
val cacheFactory = ExternalVerifierNamedCacheFactory()
|
||||
@ -117,7 +119,7 @@ class ExternalVerifier(private val baseDirectory: Path, private val channel: Soc
|
||||
|
||||
currentNetworkParameters = initialisation.currentNetworkParameters
|
||||
networkParametersMap.put(initialisation.serializedCurrentNetworkParameters.hash, Optional.of(currentNetworkParameters))
|
||||
|
||||
rotatedKeys = initialisation.rotatedKeys
|
||||
log.info("External verifier initialised")
|
||||
}
|
||||
|
||||
@ -132,7 +134,8 @@ class ExternalVerifier(private val baseDirectory: Path, private val channel: Soc
|
||||
|
||||
@Suppress("INVISIBLE_MEMBER")
|
||||
private fun verifyTransaction(request: VerificationRequest) {
|
||||
val verificationContext = ExternalVerificationContext(appClassLoader, attachmentsClassLoaderCache, this, request.ctxInputsAndReferences)
|
||||
val verificationContext = ExternalVerificationContext(appClassLoader, attachmentsClassLoaderCache, this,
|
||||
request.ctxInputsAndReferences, rotatedKeys)
|
||||
val result: Try<Unit> = try {
|
||||
val ctx = request.ctx
|
||||
when (ctx) {
|
||||
|
Loading…
Reference in New Issue
Block a user