Merge branch 'release/os/4.12' into tom/ENT-11104

This commit is contained in:
tomstark99 2024-10-02 16:30:39 +01:00
commit d9c09f21f5
73 changed files with 1339 additions and 284 deletions

View File

@ -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"
}

View File

@ -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(' ')
}

View File

@ -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"
}
}
}

View File

@ -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')

View File

@ -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')

View File

@ -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(),

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -47,6 +47,7 @@ publishing {
maven(MavenPublication) {
artifactId 'corda-confidential-identities'
from components.cordapp
artifact javadocJar
}
}
}

View File

@ -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

View File

@ -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}"

View File

@ -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) {

View File

@ -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())))
}
}
}

View File

@ -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)
}
}

View File

@ -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()

View File

@ -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
}
}

View 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
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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?>

View File

@ -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>) {

View File

@ -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

View File

@ -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
)
}
}

View File

@ -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

View File

@ -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()
})
}

View File

@ -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

View File

@ -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 && \

View File

@ -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')
}
}
}

View File

@ -64,6 +64,7 @@ publishing {
maven(MavenPublication) {
artifactId 'corda-finance-contracts'
from components.cordapp
artifact javadocJar
}
}
}

View File

@ -93,6 +93,7 @@ publishing {
maven(MavenPublication) {
artifactId 'corda-finance-workflows'
from components.cordapp
artifact javadocJar
}
}
}

View File

@ -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"

View File

@ -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) {

View File

@ -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)) {

View File

@ -70,7 +70,7 @@ class RoundTripObservableSerializerTests {
subscriptionMap(id),
clientAddressToObservables = ConcurrentHashMap(),
deduplicationIdentity = "thisIsATest",
clientAddress = SimpleString("clientAddress"))
clientAddress = SimpleString.of("clientAddress"))
val serverSerializer = serializationScheme.rpcServerSerializerFactory(serverObservableContext)

View File

@ -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())

View File

@ -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()
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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")

View File

@ -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))
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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."
}

View File

@ -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>()

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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) {

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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())

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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

View File

@ -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)
}

View File

@ -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)"
}
}

View File

@ -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

View File

@ -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())) {

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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(

View File

@ -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')

View File

@ -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}"

View File

@ -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 =

View File

@ -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')

View File

@ -4,6 +4,8 @@ plugins {
id 'corda.common-publishing'
}
description 'Corda Network Builder module'
apply plugin: 'org.openjfx.javafxplugin'
javafx {

View File

@ -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

View File

@ -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) {