Merge branch 'master' into tudor_merge_os_24_10

# Conflicts:
#	core/src/main/kotlin/net/corda/core/internal/JarSignatureCollector.kt
#	core/src/main/kotlin/net/corda/core/transactions/LedgerTransaction.kt
#	core/src/main/kotlin/net/corda/core/transactions/TransactionBuilder.kt
#	core/src/main/kotlin/net/corda/core/utilities/KotlinUtils.kt
#	core/src/test/kotlin/net/corda/core/contracts/PackageOwnershipVerificationTests.kt
#	core/src/test/kotlin/net/corda/core/internal/JarSignatureCollectorTest.kt
#	node/src/main/kotlin/net/corda/node/internal/cordapp/JarScanningCordappLoader.kt
#	node/src/test/kotlin/net/corda/node/services/persistence/NodeAttachmentServiceTest.kt
#	testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TestDSL.kt
#	testing/test-utils/src/main/kotlin/net/corda/testing/dsl/TransactionDSLInterpreter.kt
#	testing/test-utils/src/main/kotlin/net/corda/testing/internal/MockCordappProvider.kt
This commit is contained in:
tudor.malene@gmail.com
2018-10-24 17:09:30 +01:00
532 changed files with 13655 additions and 5037 deletions

View File

@ -25,7 +25,7 @@ class NodeKeystoreCheckTest {
}
@Test
fun `node should throw exception if cert path doesn't chain to the trust root`() {
fun `node should throw exception if cert path does not chain to the trust root`() {
driver(DriverParameters(startNodesInProcess = true, notarySpecs = emptyList())) {
// Create keystores.
val keystorePassword = "password"
@ -49,9 +49,9 @@ class NodeKeystoreCheckTest {
// Self signed root.
val badRootKeyPair = Crypto.generateKeyPair()
val badRoot = X509Utilities.createSelfSignedCACertificate(X500Principal("O=Bad Root,L=Lodnon,C=GB"), badRootKeyPair)
val nodeCA = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA)
val nodeCA = getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, signingCertStore.entryPassword)
val badNodeCACert = X509Utilities.createCertificate(CertificateType.NODE_CA, badRoot, badRootKeyPair, ALICE_NAME.x500Principal, nodeCA.keyPair.public)
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, listOf(badNodeCACert, badRoot))
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeCA.keyPair.private, listOf(badNodeCACert, badRoot), signingCertStore.entryPassword)
}
assertThatThrownBy {

View File

@ -423,17 +423,17 @@ class CertificateRevocationListNodeTests {
val signingCertificateStore = first
val p2pSslConfiguration = second
val nodeKeyStore = signingCertificateStore.get()
val (nodeCert, nodeKeys) = nodeKeyStore.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA) }
val (nodeCert, nodeKeys) = nodeKeyStore.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_CA, nodeKeyStore.entryPassword) }
val newNodeCert = replaceCrlDistPointCaCertificate(nodeCert, CertificateType.NODE_CA, INTERMEDIATE_CA.keyPair, nodeCaCrlDistPoint)
val nodeCertChain = listOf(newNodeCert, INTERMEDIATE_CA.certificate, *nodeKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_CA) }.drop(2).toTypedArray())
nodeKeyStore.update {
internal.deleteEntry(X509Utilities.CORDA_CLIENT_CA)
}
nodeKeyStore.update {
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeKeys.private, nodeCertChain)
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, nodeKeys.private, nodeCertChain, nodeKeyStore.entryPassword)
}
val sslKeyStore = p2pSslConfiguration.keyStore.get()
val (tlsCert, tlsKeys) = sslKeyStore.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS) }
val (tlsCert, tlsKeys) = sslKeyStore.query { getCertificateAndKeyPair(X509Utilities.CORDA_CLIENT_TLS, sslKeyStore.entryPassword) }
val newTlsCert = replaceCrlDistPointCaCertificate(tlsCert, CertificateType.TLS, nodeKeys, tlsCrlDistPoint, X500Name.getInstance(ROOT_CA.certificate.subjectX500Principal.encoded))
val sslCertChain = listOf(newTlsCert, newNodeCert, INTERMEDIATE_CA.certificate, *sslKeyStore.query { getCertificateChain(X509Utilities.CORDA_CLIENT_TLS) }.drop(3).toTypedArray())
@ -441,7 +441,7 @@ class CertificateRevocationListNodeTests {
internal.deleteEntry(X509Utilities.CORDA_CLIENT_TLS)
}
sslKeyStore.update {
setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeys.private, sslCertChain)
setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeys.private, sslCertChain, sslKeyStore.entryPassword)
}
return newNodeCert
}

View File

@ -4,7 +4,6 @@ import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.packageName
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
@ -12,8 +11,8 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.driver.driver
import net.corda.testing.node.internal.cordappForClasses
import org.junit.Test
import kotlin.test.assertEquals
@ -46,23 +45,18 @@ class AsymmetricCorDappsTests {
}
@Test
fun noSharedCorDappsWithAsymmetricSpecificClasses() {
fun `no shared cordapps with asymmetric specific classes`() {
driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = emptySet())) {
val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java)))).getOrThrow()
val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(TestCorDapp.Factory.create("Szymon CorDapp", "1.0", classes = setOf(Ping::class.java, Pong::class.java)))).getOrThrow()
val nodeA = startNode(providedName = ALICE_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java))).getOrThrow()
val nodeB = startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForClasses(Ping::class.java, Pong::class.java))).getOrThrow()
nodeA.rpc.startFlow(::Ping, nodeB.nodeInfo.singleIdentity(), 1).returnValue.getOrThrow()
}
}
@Test
fun sharedCorDappsWithAsymmetricSpecificClasses() {
val resourceName = "cordapp.properties"
val cordappPropertiesResource = this::class.java.getResource(resourceName)
val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource)
val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java))
fun `shared cordapps with asymmetric specific classes`() {
val sharedCordapp = cordappForClasses(Ping::class.java)
val cordappForNodeB = cordappForClasses(Pong::class.java)
driver(DriverParameters(startNodesInProcess = false, cordappsForAllNodes = setOf(sharedCordapp))) {
val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()
@ -71,12 +65,9 @@ class AsymmetricCorDappsTests {
}
@Test
fun sharedCorDappsWithAsymmetricSpecificClassesInProcess() {
val resourceName = "cordapp.properties"
val cordappPropertiesResource = this::class.java.getResource(resourceName)
val sharedCordapp = TestCorDapp.Factory.create("shared", "1.0", classes = setOf(Ping::class.java)).plusResource("${AsymmetricCorDappsTests::class.java.packageName}.$resourceName", cordappPropertiesResource)
val cordappForNodeB = TestCorDapp.Factory.create("nodeB_only", "1.0", classes = setOf(Pong::class.java))
fun `shared cordapps with asymmetric specific classes in process`() {
val sharedCordapp = cordappForClasses(Ping::class.java)
val cordappForNodeB = cordappForClasses(Pong::class.java)
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = setOf(sharedCordapp))) {
val (nodeA, nodeB) = listOf(startNode(providedName = ALICE_NAME), startNode(providedName = BOB_NAME, additionalCordapps = setOf(cordappForNodeB))).transpose().getOrThrow()

View File

@ -1,27 +1,30 @@
package net.corda.node.flows
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.div
import net.corda.core.internal.list
import net.corda.core.internal.moveTo
import net.corda.core.internal.readLines
import net.corda.core.messaging.startTrackedFlow
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.CheckpointIncompatibleException
import net.corda.node.internal.NodeStartup
import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.node.services.Permissions.Companion.startFlow
import net.corda.testMessage.Message
import net.corda.testMessage.MessageState
import net.corda.testMessage.*
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.TestCorDapp
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import net.corda.testing.node.TestCordapp
import net.corda.testing.node.internal.ListenProcessDeathException
import net.corda.testing.node.internal.TestCordappDirectories
import net.corda.testing.node.internal.cordappForClasses
import net.test.cordapp.v1.Record
import net.test.cordapp.v1.SendMessageFlow
import org.junit.Test
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
import java.util.*
import java.util.concurrent.TimeUnit
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
@ -30,125 +33,110 @@ import kotlin.test.assertNotNull
class FlowCheckpointVersionNodeStartupCheckTest {
companion object {
val message = Message("Hello world!")
val classes = setOf(net.corda.testMessage.MessageState::class.java,
net.corda.testMessage.MessageContract::class.java,
net.test.cordapp.v1.SendMessageFlow::class.java,
net.corda.testMessage.MessageSchema::class.java,
net.corda.testMessage.MessageSchemaV1::class.java,
net.test.cordapp.v1.Record::class.java)
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery"), invokeRpc("vaultTrack")))
val defaultCordapp = cordappForClasses(
MessageState::class.java,
MessageContract::class.java,
SendMessageFlow::class.java,
MessageSchema::class.java,
MessageSchemaV1::class.java,
Record::class.java
)
}
@Test
fun `restart node successfully with suspended flow`() {
val cordapps = setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes))
return driver(DriverParameters(isDebug = true, startNodesInProcess = false, inMemoryDB = false, cordappsForAllNodes = cordapps)) {
{
val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME).getOrThrow()
val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME).getOrThrow()
alice.stop()
CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use {
val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress
//wait until Bob progresses as far as possible because alice node is off
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
}
bob.stop()
}()
val result = {
//Bob will resume the flow
val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false)).getOrThrow()
startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false)).getOrThrow()
CordaRPCClient(alice.rpcAddress).start(user.username, user.password).use {
val page = it.proxy.vaultTrack(MessageState::class.java)
if (page.snapshot.states.isNotEmpty()) {
page.snapshot.states.first()
} else {
val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single()
if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first()
}
}
}()
return driver(parametersForRestartingNodes(listOf(defaultCordapp))) {
createSuspendedFlowInBob(cordapps = emptySet())
// Bob will resume the flow
val alice = startNode(providedName = ALICE_NAME).getOrThrow()
startNode(providedName = BOB_NAME).getOrThrow()
val page = alice.rpc.vaultTrack(MessageState::class.java)
val result = if (page.snapshot.states.isNotEmpty()) {
page.snapshot.states.first()
} else {
val r = page.updates.timeout(5, TimeUnit.SECONDS).take(1).toBlocking().single()
if (r.consumed.isNotEmpty()) r.consumed.first() else r.produced.first()
}
assertNotNull(result)
assertEquals(message, result.state.data.message)
}
}
private fun assertNodeRestartFailure(
cordapps: Set<TestCorDapp>?,
cordappsVersionAtStartup: Set<TestCorDapp>,
cordappsVersionAtRestart: Set<TestCorDapp>,
reuseAdditionalCordappsAtRestart: Boolean,
assertNodeLogs: String
) {
@Test
fun `restart node with incompatible version of suspended flow due to different jar name`() {
driver(parametersForRestartingNodes()) {
val cordapp = defaultCordapp.withName("different-jar-name-test-${UUID.randomUUID()}")
// Create the CorDapp jar file manually first to get hold of the directory that will contain it so that we can
// rename the filename later. The cordappDir, which acts as pointer to the jar file, does not get renamed.
val cordappDir = TestCordappDirectories.getJarDirectory(cordapp)
val cordappJar = cordappDir.list().single { it.toString().endsWith(".jar") }
return driver(DriverParameters(
startNodesInProcess = false, // start nodes in separate processes to ensure CordappLoader is not shared between restarts
inMemoryDB = false, // ensure database is persisted between node restarts so we can keep suspended flow in Bob's node
cordappsForAllNodes = cordapps)
) {
val bobLogFolder = {
val alice = startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow()
val bob = startNode(rpcUsers = listOf(user), providedName = BOB_NAME, additionalCordapps = cordappsVersionAtStartup).getOrThrow()
alice.stop()
CordaRPCClient(bob.rpcAddress).start(user.username, user.password).use {
val flowTracker = it.proxy.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress
// wait until Bob progresses as far as possible because Alice node is offline
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
}
val logFolder = bob.baseDirectory / NodeStartup.LOGS_DIRECTORY_NAME
// SendMessageFlow suspends in Bob node
bob.stop()
logFolder
}()
createSuspendedFlowInBob(setOf(cordapp))
startNode(rpcUsers = listOf(user), providedName = ALICE_NAME, customOverrides = mapOf("devMode" to false),
additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow()
// Rename the jar file.
cordappJar.moveTo(cordappDir / "renamed-${cordappJar.fileName}")
assertFailsWith(ListenProcessDeathException::class) {
startNode(providedName = BOB_NAME, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to false),
additionalCordapps = cordappsVersionAtRestart, regenerateCordappsOnStart = !reuseAdditionalCordappsAtRestart).getOrThrow()
}
val logFile = bobLogFolder.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() }
val numberOfNodesThatLogged = logFile.readLines { it.filter { assertNodeLogs in it }.count() }
assertEquals(1, numberOfNodesThatLogged)
assertBobFailsToStartWithLogMessage(
setOf(cordapp),
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message
)
}
}
@Test
fun `restart nodes with incompatible version of suspended flow due to different jar name`() {
fun `restart node with incompatible version of suspended flow due to different jar hash`() {
driver(parametersForRestartingNodes()) {
val originalCordapp = defaultCordapp.withName("different-jar-hash-test-${UUID.randomUUID()}")
val originalCordappJar = TestCordappDirectories.getJarDirectory(originalCordapp).list().single { it.toString().endsWith(".jar") }
assertNodeRestartFailure(
emptySet(),
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
setOf(TestCorDapp.Factory.create("testJar2", "1.0", classes = classes)),
false,
CheckpointIncompatibleException.FlowNotInstalledException(SendMessageFlow::class.java).message)
createSuspendedFlowInBob(setOf(originalCordapp))
// The vendor is part of the MANIFEST so changing it is sufficient to change the jar hash
val modifiedCordapp = originalCordapp.withVendor("${originalCordapp.vendor}-modified")
val modifiedCordappJar = TestCordappDirectories.getJarDirectory(modifiedCordapp).list().single { it.toString().endsWith(".jar") }
modifiedCordappJar.moveTo(originalCordappJar, REPLACE_EXISTING)
assertBobFailsToStartWithLogMessage(
setOf(originalCordapp),
// The part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
"that is incompatible with the current installed version of"
)
}
}
@Test
fun `restart nodes with incompatible version of suspended flow`() {
assertNodeRestartFailure(
emptySet(),
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes + net.test.cordapp.v1.SendMessageFlow::class.java)),
false,
// the part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
"that is incompatible with the current installed version of")
private fun DriverDSL.createSuspendedFlowInBob(cordapps: Set<TestCordapp>) {
val (alice, bob) = listOf(ALICE_NAME, BOB_NAME)
.map { startNode(providedName = it, additionalCordapps = cordapps) }
.transpose()
.getOrThrow()
alice.stop()
val flowTracker = bob.rpc.startTrackedFlow(::SendMessageFlow, message, defaultNotaryIdentity, alice.nodeInfo.singleIdentity()).progress
// Wait until Bob progresses as far as possible because Alice node is offline
flowTracker.takeFirst { it == SendMessageFlow.Companion.FINALISING_TRANSACTION.label }.toBlocking().single()
bob.stop()
}
@Test
fun `restart nodes with incompatible version of suspended flow due to different timestamps only`() {
private fun DriverDSL.assertBobFailsToStartWithLogMessage(cordapps: Collection<TestCordapp>, logMessage: String) {
assertFailsWith(ListenProcessDeathException::class) {
startNode(
providedName = BOB_NAME,
customOverrides = mapOf("devMode" to false),
additionalCordapps = cordapps,
regenerateCordappsOnStart = true
).getOrThrow()
}
assertNodeRestartFailure(
emptySet(),
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
setOf(TestCorDapp.Factory.create("testJar", "1.0", classes = classes)),
false,
// the part of the log message generated by CheckpointIncompatibleException.FlowVersionIncompatibleException
"that is incompatible with the current installed version of")
val logDir = baseDirectory(BOB_NAME) / NodeStartup.LOGS_DIRECTORY_NAME
val logFile = logDir.list { it.filter { it.fileName.toString().endsWith(".log") }.findAny().get() }
val matchingLineCount = logFile.readLines { it.filter { line -> logMessage in line }.count() }
assertEquals(1, matchingLineCount)
}
}
private fun parametersForRestartingNodes(cordappsForAllNodes: List<TestCordapp> = emptyList()): DriverParameters {
return DriverParameters(
startNodesInProcess = false, // Start nodes in separate processes to ensure CordappLoader is not shared between restarts
inMemoryDB = false, // Ensure database is persisted between node restarts so we can keep suspended flows
cordappsForAllNodes = cordappsForAllNodes
)
}
}

View File

@ -0,0 +1,85 @@
package net.corda.node.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.internal.cordappForClasses
import org.hamcrest.CoreMatchers.`is`
import org.junit.Assert
import org.junit.Test
class FlowOverrideTests {
@StartableByRPC
@InitiatingFlow
class Ping(private val pongParty: Party) : FlowLogic<String>() {
@Suspendable
override fun call(): String {
val pongSession = initiateFlow(pongParty)
return pongSession.sendAndReceive<String>("PING").unwrap { it }
}
}
@InitiatedBy(Ping::class)
open class Pong(private val pingSession: FlowSession) : FlowLogic<Unit>() {
companion object {
val PONG = "PONG"
}
@Suspendable
override fun call() {
pingSession.send(PONG)
}
}
@InitiatedBy(Ping::class)
class Pong2(private val pingSession: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
pingSession.send("PONGPONG")
}
}
@InitiatedBy(Ping::class)
class Pongiest(private val pingSession: FlowSession) : Pong(pingSession) {
companion object {
val GORGONZOLA = "Gorgonzola"
}
@Suspendable
override fun call() {
pingSession.send(GORGONZOLA)
}
}
private val nodeAClasses = setOf(Ping::class.java,
Pong::class.java, Pongiest::class.java)
private val nodeBClasses = setOf(Ping::class.java, Pong::class.java)
@Test
fun `should use the most specific implementation of a responding flow`() {
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = emptySet())) {
val nodeA = startNode(additionalCordapps = setOf(cordappForClasses(*nodeAClasses.toTypedArray()))).getOrThrow()
val nodeB = startNode(additionalCordapps = setOf(cordappForClasses(*nodeBClasses.toTypedArray()))).getOrThrow()
Assert.assertThat(nodeB.rpc.startFlow(::Ping, nodeA.nodeInfo.singleIdentity()).returnValue.getOrThrow(), `is`(net.corda.node.flows.FlowOverrideTests.Pongiest.GORGONZOLA))
}
}
@Test
fun `should use the overriden implementation of a responding flow`() {
val flowOverrides = mapOf(Ping::class.java to Pong::class.java)
driver(DriverParameters(startNodesInProcess = true, cordappsForAllNodes = emptySet())) {
val nodeA = startNode(additionalCordapps = setOf(cordappForClasses(*nodeAClasses.toTypedArray())), flowOverrides = flowOverrides).getOrThrow()
val nodeB = startNode(additionalCordapps = setOf(cordappForClasses(*nodeBClasses.toTypedArray()))).getOrThrow()
Assert.assertThat(nodeB.rpc.startFlow(::Ping, nodeA.nodeInfo.singleIdentity()).returnValue.getOrThrow(), `is`(net.corda.node.flows.FlowOverrideTests.Pong.PONG))
}
}
}

View File

@ -2,8 +2,14 @@ package net.corda.node.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.CordaRuntimeException
import net.corda.core.concurrent.CordaFuture
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.FlowAsyncOperation
import net.corda.core.internal.IdempotentFlow
import net.corda.core.internal.concurrent.doneFuture
import net.corda.core.internal.executeAsync
import net.corda.core.messaging.startFlow
import net.corda.core.serialization.CordaSerializable
import net.corda.core.utilities.ProgressTracker
@ -16,6 +22,8 @@ import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.node.User
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.hibernate.exception.ConstraintViolationException
import org.junit.Before
import org.junit.Test
import java.lang.management.ManagementFactory
@ -51,6 +59,57 @@ class FlowRetryTest {
assertNotNull(result)
assertEquals("$numSessions:$numIterations", result)
}
@Test
fun `async operation deduplication id is stable accross retries`() {
val user = User("mark", "dadada", setOf(Permissions.startFlow<AsyncRetryFlow>()))
driver(DriverParameters(
startNodesInProcess = isQuasarAgentSpecified(),
notarySpecs = emptyList()
)) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use {
it.proxy.startFlow(::AsyncRetryFlow).returnValue.getOrThrow()
}
}
}
@Test
fun `flow gives up after number of exceptions, even if this is the first line of the flow`() {
val user = User("mark", "dadada", setOf(Permissions.startFlow<RetryFlow>()))
assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy {
driver(DriverParameters(
startNodesInProcess = isQuasarAgentSpecified(),
notarySpecs = emptyList()
)) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val result = CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use {
it.proxy.startFlow(::RetryFlow).returnValue.getOrThrow()
}
result
}
}
}
@Test
fun `flow that throws in constructor throw for the RPC client that attempted to start them`() {
val user = User("mark", "dadada", setOf(Permissions.startFlow<ThrowingFlow>()))
assertThatExceptionOfType(CordaRuntimeException::class.java).isThrownBy {
driver(DriverParameters(
startNodesInProcess = isQuasarAgentSpecified(),
notarySpecs = emptyList()
)) {
val nodeAHandle = startNode(providedName = ALICE_NAME, rpcUsers = listOf(user)).getOrThrow()
val result = CordaRPCClient(nodeAHandle.rpcAddress).start(user.username, user.password).use {
it.proxy.startFlow(::ThrowingFlow).returnValue.getOrThrow()
}
result
}
}
}
}
fun isQuasarAgentSpecified(): Boolean {
@ -60,6 +119,8 @@ fun isQuasarAgentSpecified(): Boolean {
class ExceptionToCauseRetry : SQLException("deadlock")
class ExceptionToCauseFiniteRetry : ConstraintViolationException("Faked violation", SQLException("Fake"), "Fake name")
@StartableByRPC
@InitiatingFlow
class InitiatorFlow(private val sessionsCount: Int, private val iterationsCount: Int, private val other: Party) : FlowLogic<Any>() {
@ -157,3 +218,72 @@ data class SessionInfo(val sessionNum: Int, val iterationsCount: Int)
enum class Step { First, BeforeInitiate, AfterInitiate, AfterInitiateSendReceive, BeforeSend, AfterSend, BeforeReceive, AfterReceive }
data class Visited(val sessionNum: Int, val iterationNum: Int, val step: Step)
@StartableByRPC
class RetryFlow() : FlowLogic<String>(), IdempotentFlow {
companion object {
object FIRST_STEP : ProgressTracker.Step("Step one")
fun tracker() = ProgressTracker(FIRST_STEP)
}
override val progressTracker = tracker()
@Suspendable
override fun call(): String {
progressTracker.currentStep = FIRST_STEP
throw ExceptionToCauseFiniteRetry()
return "Result"
}
}
@StartableByRPC
class AsyncRetryFlow() : FlowLogic<String>(), IdempotentFlow {
companion object {
object FIRST_STEP : ProgressTracker.Step("Step one")
fun tracker() = ProgressTracker(FIRST_STEP)
val deduplicationIds = mutableSetOf<String>()
}
class RecordDeduplicationId: FlowAsyncOperation<String> {
override fun execute(deduplicationId: String): CordaFuture<String> {
val dedupeIdIsNew = deduplicationIds.add(deduplicationId)
if (dedupeIdIsNew) {
throw ExceptionToCauseFiniteRetry()
}
return doneFuture(deduplicationId)
}
}
override val progressTracker = tracker()
@Suspendable
override fun call(): String {
progressTracker.currentStep = FIRST_STEP
executeAsync(RecordDeduplicationId())
return "Result"
}
}
@StartableByRPC
class ThrowingFlow() : FlowLogic<String>(), IdempotentFlow {
companion object {
object FIRST_STEP : ProgressTracker.Step("Step one")
fun tracker() = ProgressTracker(FIRST_STEP)
}
override val progressTracker = tracker()
init {
throw IllegalStateException("This flow can never be ")
}
@Suspendable
override fun call(): String {
progressTracker.currentStep = FIRST_STEP
return "Result"
}
}

View File

@ -1,10 +1,8 @@
package net.corda.node.modes.draining
import co.paralleluniverse.fibers.Suspendable
import net.corda.testMessage.MESSAGE_CONTRACT_PROGRAM_ID
import net.corda.testMessage.Message
import net.corda.testMessage.MessageContract
import net.corda.testMessage.MessageState
import net.corda.RpcInfo
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.contracts.Command
import net.corda.core.contracts.StateAndContract
import net.corda.core.flows.*
@ -15,9 +13,11 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.RpcInfo
import net.corda.client.rpc.CordaRPCClient
import net.corda.node.services.Permissions.Companion.all
import net.corda.testMessage.MESSAGE_CONTRACT_PROGRAM_ID
import net.corda.testMessage.Message
import net.corda.testMessage.MessageContract
import net.corda.testMessage.MessageState
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
@ -53,7 +53,11 @@ class FlowsDrainingModeContentionTest {
@Test
fun `draining mode does not deadlock with acks between 2 nodes`() {
val message = "Ground control to Major Tom"
driver(DriverParameters(startNodesInProcess = true, portAllocation = portAllocation, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) {
driver(DriverParameters(
startNodesInProcess = true,
portAllocation = portAllocation,
extraCordappPackagesToScan = listOf(MessageState::class.packageName)
)) {
val nodeA = startNode(providedName = ALICE_NAME, rpcUsers = users).getOrThrow()
val nodeB = startNode(providedName = BOB_NAME, rpcUsers = users).getOrThrow()
@ -70,11 +74,12 @@ class FlowsDrainingModeContentionTest {
@StartableByRPC
@InitiatingFlow
class ProposeTransactionAndWaitForCommit(private val data: String, private val myRpcInfo: RpcInfo, private val counterParty: Party, private val notary: Party) : FlowLogic<SignedTransaction>() {
class ProposeTransactionAndWaitForCommit(private val data: String,
private val myRpcInfo: RpcInfo,
private val counterParty: Party,
private val notary: Party) : FlowLogic<SignedTransaction>() {
@Suspendable
override fun call(): SignedTransaction {
val session = initiateFlow(counterParty)
val messageState = MessageState(message = Message(data), by = ourIdentity)
val command = Command(MessageContract.Commands.Send(), messageState.participants.map { it.owningKey })
@ -91,10 +96,8 @@ class ProposeTransactionAndWaitForCommit(private val data: String, private val m
@InitiatedBy(ProposeTransactionAndWaitForCommit::class)
class SignTransactionTriggerDrainingModeAndFinality(private val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val tx = subFlow(ReceiveTransactionFlow(session))
val signedTx = serviceHub.addSignature(tx)
val initiatingRpcInfo = session.receive<RpcInfo>().unwrap { it }
@ -105,9 +108,8 @@ class SignTransactionTriggerDrainingModeAndFinality(private val session: FlowSes
}
private fun triggerDrainingModeForInitiatingNode(initiatingRpcInfo: RpcInfo) {
CordaRPCClient(initiatingRpcInfo.address).start(initiatingRpcInfo.username, initiatingRpcInfo.password).use {
it.proxy.setFlowsDrainingModeEnabled(true)
}
}
}
}

View File

@ -3,17 +3,11 @@ package net.corda.node.services
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.CordaRuntimeException
import net.corda.core.contracts.Contract
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.PartyAndReference
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TransactionState
import net.corda.core.contracts.*
import net.corda.core.cordapp.CordappProvider
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.transpose
import net.corda.core.internal.toLedgerTransaction
import net.corda.core.node.NetworkParameters
import net.corda.core.node.ServicesForResolution
@ -21,20 +15,16 @@ import net.corda.core.node.services.AttachmentStorage
import net.corda.core.node.services.IdentityService
import net.corda.core.serialization.SerializationFactory
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import net.corda.node.VersionInfo
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.nodeapi.internal.PLATFORM_VERSION
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.DUMMY_NOTARY_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.TestIdentity
import net.corda.testing.driver.DriverDSL
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver
import net.corda.testing.internal.MockCordappConfigProvider
import net.corda.testing.internal.rigorousMock
@ -60,7 +50,6 @@ class AttachmentLoadingTests {
private val appContext get() = provider.getAppContext(cordapp)
private companion object {
private val logger = contextLogger()
val isolatedJAR = AttachmentLoadingTests::class.java.getResource("isolated.jar")!!
const val ISOLATED_CONTRACT_ID = "net.corda.finance.contracts.isolated.AnotherDummyContract"
@ -71,12 +60,6 @@ class AttachmentLoadingTests {
.asSubclass(FlowLogic::class.java)
val DUMMY_BANK_A = TestIdentity(DUMMY_BANK_A_NAME, 40).party
val DUMMY_NOTARY = TestIdentity(DUMMY_NOTARY_NAME, 20).party
private fun DriverDSL.createTwoNodes(): List<NodeHandle> {
return listOf(
startNode(providedName = bankAName),
startNode(providedName = bankBName)
).transpose().getOrThrow()
}
}
private val services = object : ServicesForResolution {

View File

@ -1,221 +0,0 @@
package net.corda.node.services
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.contracts.AlwaysAcceptAttachmentConstraint
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.*
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.deleteIfExists
import net.corda.core.internal.div
import net.corda.core.node.NotaryInfo
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.Try
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.transactions.minClusterSize
import net.corda.node.services.transactions.minCorrectReplicas
import net.corda.nodeapi.internal.DevIdentityGenerator
import net.corda.nodeapi.internal.network.NetworkParametersCopier
import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.TestClock
import net.corda.testing.node.internal.*
import org.hamcrest.Matchers.instanceOf
import org.junit.AfterClass
import org.junit.Assert.assertThat
import org.junit.BeforeClass
import org.junit.Test
import java.nio.file.Paths
import java.time.Duration
import java.time.Instant
import java.util.concurrent.ExecutionException
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class BFTNotaryServiceTests {
companion object {
private lateinit var mockNet: InternalMockNetwork
private lateinit var notary: Party
private lateinit var node: TestStartedNode
@BeforeClass
@JvmStatic
fun before() {
mockNet = InternalMockNetwork(cordappsForAllNodes = cordappsForPackages("net.corda.testing.contracts"))
val clusterSize = minClusterSize(1)
val started = startBftClusterAndNode(clusterSize, mockNet)
notary = started.first
node = started.second
}
@AfterClass
@JvmStatic
fun stopNodes() {
mockNet.stopNodes()
}
fun startBftClusterAndNode(clusterSize: Int, mockNet: InternalMockNetwork, exposeRaces: Boolean = false): Pair<Party, TestStartedNode> {
(Paths.get("config") / "currentView").deleteIfExists() // XXX: Make config object warn if this exists?
val replicaIds = (0 until clusterSize)
val notaryIdentity = DevIdentityGenerator.generateDistributedNotaryCompositeIdentity(
replicaIds.map { mockNet.baseDirectory(mockNet.nextNodeId + it) },
CordaX500Name("BFT", "Zurich", "CH"))
val networkParameters = NetworkParametersCopier(testNetworkParameters(listOf(NotaryInfo(notaryIdentity, false))))
val clusterAddresses = replicaIds.map { NetworkHostAndPort("localhost", 11000 + it * 10) }
val nodes = replicaIds.map { replicaId ->
mockNet.createUnstartedNode(InternalMockNodeParameters(configOverrides = {
val notary = NotaryConfig(validating = false, bftSMaRt = BFTSMaRtConfiguration(replicaId, clusterAddresses, exposeRaces = exposeRaces))
doReturn(notary).whenever(it).notary
}))
} + mockNet.createUnstartedNode()
// MockNetwork doesn't support BFT clusters, so we create all the nodes we need unstarted, and then install the
// network-parameters in their directories before they're started.
val node = nodes.map { node ->
networkParameters.install(mockNet.baseDirectory(node.id))
node.start()
}.last()
return Pair(notaryIdentity, node)
}
}
@Test
fun `detect double spend`() {
node.run {
val issueTx = signInitialTransaction(notary) {
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
}
services.recordTransactions(issueTx)
val spendTxs = (1..10).map {
signInitialTransaction(notary) {
addInputState(issueTx.tx.outRef<ContractState>(0))
}
}
assertEquals(spendTxs.size, spendTxs.map { it.id }.distinct().size)
val flows = spendTxs.map { NotaryFlow.Client(it) }
val stateMachines = flows.map { services.startFlow(it) }
mockNet.runNetwork()
val results = stateMachines.map { Try.on { it.resultFuture.getOrThrow() } }
val successfulIndex = results.mapIndexedNotNull { index, result ->
if (result is Try.Success) {
val signers = result.value.map { it.by }
assertEquals(minCorrectReplicas(3), signers.size)
signers.forEach {
assertTrue(it in (notary.owningKey as CompositeKey).leafKeys)
}
index
} else {
null
}
}.single()
spendTxs.zip(results).forEach { (tx, result) ->
if (result is Try.Failure) {
val exception = result.exception as NotaryException
val error = exception.error as NotaryError.Conflict
assertEquals(tx.id, error.txId)
val (stateRef, cause) = error.consumedStates.entries.single()
assertEquals(StateRef(issueTx.id, 0), stateRef)
assertEquals(spendTxs[successfulIndex].id.sha256(), cause.hashOfTransactionId)
}
}
}
}
@Test
fun `transactions outside their time window are rejected`() {
node.run {
val issueTx = signInitialTransaction(notary) {
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
}
services.recordTransactions(issueTx)
val spendTx = signInitialTransaction(notary) {
addInputState(issueTx.tx.outRef<ContractState>(0))
setTimeWindow(TimeWindow.fromOnly(Instant.MAX))
}
val flow = NotaryFlow.Client(spendTx)
val resultFuture = services.startFlow(flow).resultFuture
mockNet.runNetwork()
val exception = assertFailsWith<ExecutionException> { resultFuture.get() }
assertThat(exception.cause, instanceOf(NotaryException::class.java))
val error = (exception.cause as NotaryException).error
assertThat(error, instanceOf(NotaryError.TimeWindowInvalid::class.java))
}
}
@Test
fun `notarise issue tx with time-window`() {
node.run {
val issueTx = signInitialTransaction(notary) {
setTimeWindow(services.clock.instant(), 30.seconds)
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
}
val resultFuture = services.startFlow(NotaryFlow.Client(issueTx)).resultFuture
mockNet.runNetwork()
val signatures = resultFuture.get()
verifySignatures(signatures, issueTx.id)
}
}
@Test
fun `transactions can be re-notarised outside their time window`() {
node.run {
val issueTx = signInitialTransaction(notary) {
addOutputState(DummyContract.SingleOwnerState(owner = info.singleIdentity()), DummyContract.PROGRAM_ID, AlwaysAcceptAttachmentConstraint)
}
services.recordTransactions(issueTx)
val spendTx = signInitialTransaction(notary) {
addInputState(issueTx.tx.outRef<ContractState>(0))
setTimeWindow(TimeWindow.untilOnly(Instant.now() + Duration.ofHours(1)))
}
val resultFuture = services.startFlow(NotaryFlow.Client(spendTx)).resultFuture
mockNet.runNetwork()
val signatures = resultFuture.get()
verifySignatures(signatures, spendTx.id)
for (node in mockNet.nodes) {
(node.started!!.services.clock as TestClock).advanceBy(Duration.ofDays(1))
}
val resultFuture2 = services.startFlow(NotaryFlow.Client(spendTx)).resultFuture
mockNet.runNetwork()
val signatures2 = resultFuture2.get()
verifySignatures(signatures2, spendTx.id)
}
}
private fun verifySignatures(signatures: List<TransactionSignature>, txId: SecureHash) {
notary.owningKey.isFulfilledBy(signatures.map { it.by })
signatures.forEach { it.verify(txId) }
}
private fun TestStartedNode.signInitialTransaction(notary: Party, block: TransactionBuilder.() -> Any?): SignedTransaction {
return services.signInitialTransaction(
TransactionBuilder(notary).apply {
addCommand(dummyCommand(services.myInfo.singleIdentity().owningKey))
block()
}
)
}
}

View File

@ -1,86 +0,0 @@
package net.corda.node.services
import net.corda.core.contracts.StateAndRef
import net.corda.core.contracts.StateRef
import net.corda.core.flows.NotaryError
import net.corda.core.flows.NotaryException
import net.corda.core.flows.NotaryFlow
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.map
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.seconds
import net.corda.testing.contracts.DummyContract
import net.corda.testing.core.DUMMY_BANK_A_NAME
import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.InProcess
import net.corda.testing.driver.driver
import net.corda.testing.node.ClusterSpec
import net.corda.testing.node.NotarySpec
import org.junit.Test
import java.util.*
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
class RaftNotaryServiceTests {
private val notaryName = CordaX500Name("RAFT Notary Service", "London", "GB")
@Test
fun `detect double spend`() {
driver(DriverParameters(
startNodesInProcess = true,
extraCordappPackagesToScan = listOf("net.corda.testing.contracts"),
notarySpecs = listOf(NotarySpec(notaryName, cluster = ClusterSpec.Raft(clusterSize = 3)))
)) {
val bankA = startNode(providedName = DUMMY_BANK_A_NAME).map { (it as InProcess) }.getOrThrow()
val inputState = issueState(bankA, defaultNotaryIdentity)
val firstTxBuilder = TransactionBuilder(defaultNotaryIdentity)
.addInputState(inputState)
.addCommand(dummyCommand(bankA.services.myInfo.singleIdentity().owningKey))
val firstSpendTx = bankA.services.signInitialTransaction(firstTxBuilder)
val firstSpend = bankA.startFlow(NotaryFlow.Client(firstSpendTx))
firstSpend.getOrThrow()
val secondSpendBuilder = TransactionBuilder(defaultNotaryIdentity).withItems(inputState).run {
val dummyState = DummyContract.SingleOwnerState(0, bankA.services.myInfo.singleIdentity())
addOutputState(dummyState, DummyContract.PROGRAM_ID)
addCommand(dummyCommand(bankA.services.myInfo.singleIdentity().owningKey))
this
}
val secondSpendTx = bankA.services.signInitialTransaction(secondSpendBuilder)
val secondSpend = bankA.startFlow(NotaryFlow.Client(secondSpendTx))
val ex = assertFailsWith(NotaryException::class) { secondSpend.getOrThrow() }
val error = ex.error as NotaryError.Conflict
assertEquals(error.txId, secondSpendTx.id)
}
}
@Test
fun `notarise issue tx with time-window`() {
driver(DriverParameters(
startNodesInProcess = true,
extraCordappPackagesToScan = listOf("net.corda.testing.contracts"),
notarySpecs = listOf(NotarySpec(notaryName, cluster = ClusterSpec.Raft(clusterSize = 3)))
)) {
val bankA = startNode(providedName = DUMMY_BANK_A_NAME).map { (it as InProcess) }.getOrThrow()
val builder = DummyContract.generateInitial(Random().nextInt(), defaultNotaryIdentity, bankA.services.myInfo.singleIdentity().ref(0))
.setTimeWindow(bankA.services.clock.instant(), 30.seconds)
val issueTx = bankA.services.signInitialTransaction(builder)
bankA.startFlow(NotaryFlow.Client(issueTx)).getOrThrow()
}
}
private fun issueState(nodeHandle: InProcess, notary: Party): StateAndRef<*> {
val builder = DummyContract.generateInitial(Random().nextInt(), notary, nodeHandle.services.myInfo.singleIdentity().ref(0))
val stx = nodeHandle.services.signInitialTransaction(builder)
nodeHandle.services.recordTransactions(stx)
return StateAndRef(stx.coreTransaction.outputs.first(), StateRef(stx.id, 0))
}
}

View File

@ -42,7 +42,7 @@ class DistributedServiceTests {
invokeRpc(CordaRPCOps::stateMachinesFeed))
)
driver(DriverParameters(
extraCordappPackagesToScan = listOf("net.corda.finance.contracts", "net.corda.finance.schemas"),
extraCordappPackagesToScan = listOf("net.corda.finance.contracts", "net.corda.finance.schemas", "net.corda.notary.raft"),
notarySpecs = listOf(
NotarySpec(
DUMMY_NOTARY_NAME,

View File

@ -32,11 +32,14 @@ import kotlin.test.assertEquals
class ScheduledFlowIntegrationTests {
@StartableByRPC
class InsertInitialStateFlow(private val destination: Party, private val notary: Party, private val identity: Int = 1, private val scheduledFor: Instant? = null) : FlowLogic<Unit>() {
class InsertInitialStateFlow(private val destination: Party,
private val notary: Party,
private val identity: Int = 1,
private val scheduledFor: Instant? = null) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val scheduledState = ScheduledState(scheduledFor
?: serviceHub.clock.instant(), ourIdentity, destination, identity.toString())
val creationTime = scheduledFor ?: serviceHub.clock.instant()
val scheduledState = ScheduledState(creationTime, ourIdentity, destination, identity.toString())
val builder = TransactionBuilder(notary)
.addOutputState(scheduledState, DummyContract.PROGRAM_ID)
.addCommand(dummyCommand(ourIdentity.owningKey))
@ -90,8 +93,20 @@ class ScheduledFlowIntegrationTests {
val scheduledFor = Instant.now().plusSeconds(10)
val initialiseFutures = mutableListOf<CordaFuture<*>>()
for (i in 0 until N) {
initialiseFutures.add(aliceClient.proxy.startFlow(::InsertInitialStateFlow, bob.nodeInfo.legalIdentities.first(), defaultNotaryIdentity, i, scheduledFor).returnValue)
initialiseFutures.add(bobClient.proxy.startFlow(::InsertInitialStateFlow, alice.nodeInfo.legalIdentities.first(), defaultNotaryIdentity, i + 100, scheduledFor).returnValue)
initialiseFutures.add(aliceClient.proxy.startFlow(
::InsertInitialStateFlow,
bob.nodeInfo.legalIdentities.first(),
defaultNotaryIdentity,
i,
scheduledFor
).returnValue)
initialiseFutures.add(bobClient.proxy.startFlow(
::InsertInitialStateFlow,
alice.nodeInfo.legalIdentities.first(),
defaultNotaryIdentity,
i + 100,
scheduledFor
).returnValue)
}
initialiseFutures.getOrThrowAll()

View File

@ -7,14 +7,12 @@ import net.corda.core.crypto.generateKeyPair
import net.corda.core.internal.div
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
import net.corda.node.internal.configureDatabase
import net.corda.node.services.config.FlowTimeoutConfiguration
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.ALICE_NAME
@ -22,6 +20,8 @@ import net.corda.testing.core.MAX_MESSAGE_SIZE
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.stubs.CertificateStoreStubs
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties

View File

@ -47,18 +47,22 @@ class NetworkMapTest(var initFunc: (URL, NetworkMapServer) -> CompatibilityZoneP
@JvmStatic
@Parameterized.Parameters(name = "{0}")
fun runParams() = listOf(
{ addr: URL, nms: NetworkMapServer ->
SharedCompatibilityZoneParams(
{
addr: URL,
nms: NetworkMapServer -> SharedCompatibilityZoneParams(
addr,
pnm = null,
publishNotaries = {
nms.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue()), epoch = 2)
}
)
},
{ addr: URL, nms: NetworkMapServer ->
SplitCompatibilityZoneParams(
{
addr: URL,
nms: NetworkMapServer -> SplitCompatibilityZoneParams (
doormanURL = URL("http://I/Don't/Exist"),
networkMapURL = addr,
pnm = null,
publishNotaries = {
nms.networkParameters = testNetworkParameters(it, modifiedTime = Instant.ofEpochMilli(random63BitValue()), epoch = 2)
}

View File

@ -2,13 +2,13 @@ package net.corda.node.services.network
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.configureDatabase
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.identity.InMemoryIdentityService
import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.DEV_ROOT_CA
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.*
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.configureDatabase
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatIllegalArgumentException

View File

@ -23,6 +23,8 @@ import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.User
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.TestingNamedCacheFactory
import net.corda.testing.internal.fromUserList
import net.corda.testing.internal.p2pSslOptions
import org.apache.activemq.artemis.api.core.ActiveMQConnectionTimedOutException
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
@ -128,7 +130,7 @@ class ArtemisRpcTests {
private fun <OPS : RPCOps> InternalRPCMessagingClient.start(ops: OPS, securityManager: RPCSecurityManager, brokerControl: ActiveMQServerControl) {
apply {
init(ops, securityManager)
init(ops, securityManager, TestingNamedCacheFactory())
start(brokerControl)
}
}

View File

@ -7,10 +7,11 @@ import net.corda.core.flows.InitiatingFlow
import net.corda.core.identity.Party
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.NodeBasedTest
import net.corda.node.internal.NodeFlowManager
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.singleIdentity
import net.corda.testing.node.internal.NodeBasedTest
import net.corda.testing.node.internal.startFlow
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
@ -18,9 +19,10 @@ import org.junit.Test
class FlowVersioningTest : NodeBasedTest() {
@Test
fun `getFlowContext returns the platform version for core flows`() {
val bobFlowManager = NodeFlowManager()
val alice = startNode(ALICE_NAME, platformVersion = 2)
val bob = startNode(BOB_NAME, platformVersion = 3)
bob.node.installCoreFlow(PretendInitiatingCoreFlow::class, ::PretendInitiatedCoreFlow)
val bob = startNode(BOB_NAME, platformVersion = 3, flowManager = bobFlowManager)
bobFlowManager.registerInitiatedCoreFlowFactory(PretendInitiatingCoreFlow::class, ::PretendInitiatedCoreFlow)
val (alicePlatformVersionAccordingToBob, bobPlatformVersionAccordingToAlice) = alice.services.startFlow(
PretendInitiatingCoreFlow(bob.info.singleIdentity())).resultFuture.getOrThrow()
assertThat(alicePlatformVersionAccordingToBob).isEqualTo(2)
@ -45,4 +47,5 @@ class FlowVersioningTest : NodeBasedTest() {
@Suspendable
override fun call() = otherSideSession.send(otherSideSession.getCounterpartyFlowInfo().flowVersion)
}
}

View File

@ -1,180 +0,0 @@
package net.corda.node.services.transactions
import io.atomix.catalyst.transport.Address
import io.atomix.copycat.client.ConnectionStrategies
import io.atomix.copycat.client.CopycatClient
import io.atomix.copycat.server.CopycatServer
import io.atomix.copycat.server.storage.Storage
import io.atomix.copycat.server.storage.StorageLevel
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.NotaryError
import net.corda.core.internal.concurrent.asCordaFuture
import net.corda.core.internal.concurrent.transpose
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.utilities.TestingNamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
import net.corda.testing.internal.LogHelper
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.hamcrest.Matchers.instanceOf
import org.junit.After
import org.junit.Assert.assertThat
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.time.Clock
import java.time.Instant
import java.util.concurrent.CompletableFuture
import kotlin.test.assertEquals
import kotlin.test.assertNull
class RaftTransactionCommitLogTests {
data class Member(val client: CopycatClient, val server: CopycatServer)
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private val databases: MutableList<CordaPersistence> = mutableListOf()
private val portAllocation = PortAllocation.Incremental(10000)
private lateinit var cluster: List<Member>
@Before
fun setup() {
LogHelper.setLevel("-org.apache.activemq")
LogHelper.setLevel("+io.atomix")
cluster = setUpCluster()
}
@After
fun tearDown() {
LogHelper.reset("org.apache.activemq", "io.atomix")
cluster.map { it.client.close().asCordaFuture() }.transpose().getOrThrow()
cluster.map { it.server.shutdown().asCordaFuture() }.transpose().getOrThrow()
databases.forEach { it.close() }
}
@Test
fun `stores entries correctly`() {
val client = cluster.last().client
val states = listOf(StateRef(SecureHash.randomSHA256(), 0), StateRef(SecureHash.randomSHA256(), 0))
val txId: SecureHash = SecureHash.randomSHA256()
val requestingPartyName = ALICE_NAME
val requestSignature = ByteArray(1024)
val commitCommand = RaftTransactionCommitLog.Commands.CommitTransaction(states, txId, requestingPartyName.toString(), requestSignature)
val commitError = client.submit(commitCommand).getOrThrow()
assertNull(commitError)
val value1 = client.submit(RaftTransactionCommitLog.Commands.Get(states[0]))
val value2 = client.submit(RaftTransactionCommitLog.Commands.Get(states[1]))
assertEquals(value1.getOrThrow(), txId)
assertEquals(value2.getOrThrow(), txId)
}
@Test
fun `returns conflict for duplicate entries`() {
val client = cluster.last().client
val states = listOf(StateRef(SecureHash.randomSHA256(), 0), StateRef(SecureHash.randomSHA256(), 0))
val txIdFirst = SecureHash.randomSHA256()
val txIdSecond = SecureHash.randomSHA256()
val requestingPartyName = ALICE_NAME
val requestSignature = ByteArray(1024)
val commitCommandFirst = RaftTransactionCommitLog.Commands.CommitTransaction(states, txIdFirst, requestingPartyName.toString(), requestSignature)
var commitError = client.submit(commitCommandFirst).getOrThrow()
assertNull(commitError)
val commitCommandSecond = RaftTransactionCommitLog.Commands.CommitTransaction(states, txIdSecond, requestingPartyName.toString(), requestSignature)
commitError = client.submit(commitCommandSecond).getOrThrow()
val conflict = commitError as NotaryError.Conflict
assertEquals(states.toSet(), conflict.consumedStates.keys)
}
@Test
fun `transactions outside their time window are rejected`() {
val client = cluster.last().client
val states = listOf(StateRef(SecureHash.randomSHA256(), 0), StateRef(SecureHash.randomSHA256(), 0))
val txId: SecureHash = SecureHash.randomSHA256()
val requestingPartyName = ALICE_NAME
val requestSignature = ByteArray(1024)
val timeWindow = TimeWindow.fromOnly(Instant.MAX)
val commitCommand = RaftTransactionCommitLog.Commands.CommitTransaction(
states, txId, requestingPartyName.toString(), requestSignature, timeWindow
)
val commitError = client.submit(commitCommand).getOrThrow()
assertThat(commitError, instanceOf(NotaryError.TimeWindowInvalid::class.java))
}
@Test
fun `transactions can be re-notarised outside their time window`() {
val client = cluster.last().client
val states = listOf(StateRef(SecureHash.randomSHA256(), 0), StateRef(SecureHash.randomSHA256(), 0))
val txId: SecureHash = SecureHash.randomSHA256()
val requestingPartyName = ALICE_NAME
val requestSignature = ByteArray(1024)
val timeWindow = TimeWindow.fromOnly(Instant.MIN)
val commitCommand = RaftTransactionCommitLog.Commands.CommitTransaction(
states, txId, requestingPartyName.toString(), requestSignature, timeWindow
)
val commitError = client.submit(commitCommand).getOrThrow()
assertNull(commitError)
val expiredTimeWindow = TimeWindow.untilOnly(Instant.MIN)
val commitCommand2 = RaftTransactionCommitLog.Commands.CommitTransaction(
states, txId, requestingPartyName.toString(), requestSignature, expiredTimeWindow
)
val commitError2 = client.submit(commitCommand2).getOrThrow()
assertNull(commitError2)
}
private fun setUpCluster(nodeCount: Int = 3): List<Member> {
val clusterAddress = portAllocation.nextHostAndPort()
val cluster = mutableListOf(createReplica(clusterAddress))
for (i in 1..nodeCount) cluster.add(createReplica(portAllocation.nextHostAndPort(), clusterAddress))
return cluster.map { it.getOrThrow() }
}
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
val address = Address(myAddress.host, myAddress.port)
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), { null }, { null }, NodeSchemaService(includeNotarySchemas = true))
databases.add(database)
val stateMachineFactory = { RaftTransactionCommitLog(database, Clock.systemUTC(), { RaftUniquenessProvider.createMap(TestingNamedCacheFactory()) }) }
val server = CopycatServer.builder(address)
.withStateMachine(stateMachineFactory)
.withStorage(storage)
.withSerializer(RaftTransactionCommitLog.serializer)
.build()
val serverInitFuture = if (clusterAddress != null) {
val cluster = Address(clusterAddress.host, clusterAddress.port)
server.join(cluster)
} else {
server.bootstrap()
}
val client = CopycatClient.builder(address)
.withConnectionStrategy(ConnectionStrategies.EXPONENTIAL_BACKOFF)
.withSerializer(RaftTransactionCommitLog.serializer)
.build()
return serverInitFuture.thenCompose { client.connect(address) }.thenApply { Member(it, server) }
}
}

View File

@ -81,6 +81,7 @@ class NodeRegistrationTest {
fun `node registration correct root cert`() {
val compatibilityZone = SharedCompatibilityZoneParams(
URL("http://$serverHostAndPort"),
null,
publishNotaries = { server.networkParameters = testNetworkParameters(it) },
rootCert = DEV_ROOT_CA.certificate)
internalDriver(

View File

@ -105,11 +105,11 @@ class MQSecurityAsNodeTest : P2PMQSecurityTest() {
val clientTLSCert = X509Utilities.createCertificate(CertificateType.TLS, clientCACert, clientKeyPair, CordaX500Name("MiniCorp", "London", "GB").x500Principal, tlsKeyPair.public)
signingCertStore.get(createNew = true).update {
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, clientKeyPair.private, listOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
setPrivateKey(X509Utilities.CORDA_CLIENT_CA, clientKeyPair.private, listOf(clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate), signingCertStore.entryPassword)
}
p2pSslConfig.keyStore.get(createNew = true).update {
setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeyPair.private, listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate))
setPrivateKey(X509Utilities.CORDA_CLIENT_TLS, tlsKeyPair.private, listOf(clientTLSCert, clientCACert, DEV_INTERMEDIATE_CA.certificate, DEV_ROOT_CA.certificate), p2pSslConfig.keyStore.entryPassword)
}
val attacker = clientTo(alice.node.configuration.p2pAddress, p2pSslConfig)

View File

@ -40,6 +40,7 @@ class P2PMessagingTest {
private fun startDriverWithDistributedService(dsl: DriverDSL.(List<InProcess>) -> Unit) {
driver(DriverParameters(
startNodesInProcess = true,
extraCordappPackagesToScan = listOf("net.corda.notary.raft"),
notarySpecs = listOf(NotarySpec(DISTRIBUTED_SERVICE_NAME, cluster = ClusterSpec.Raft(clusterSize = 2)))
)) {
dsl(defaultNotaryHandle.nodeHandles.getOrThrow().map { (it as InProcess) })

View File

@ -46,12 +46,12 @@ class SendMessageFlow(private val message: Message, private val notary: Party, p
progressTracker.currentStep = FINALISING_TRANSACTION
if (reciepent != null) {
return if (reciepent != null) {
val session = initiateFlow(reciepent)
subFlow(SendTransactionFlow(session, signedTx))
return subFlow(FinalityFlow(signedTx, setOf(reciepent), FINALISING_TRANSACTION.childProgressTracker()))
subFlow(FinalityFlow(signedTx, setOf(reciepent), FINALISING_TRANSACTION.childProgressTracker()))
} else {
return subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker()))
subFlow(FinalityFlow(signedTx, FINALISING_TRANSACTION.childProgressTracker()))
}
}
}
@ -59,10 +59,9 @@ class SendMessageFlow(private val message: Message, private val notary: Party, p
@InitiatedBy(SendMessageFlow::class)
class Record(private val session: FlowSession) : FlowLogic<Unit>() {
@Suspendable
override fun call() {
val tx = subFlow(ReceiveTransactionFlow(session, statesToRecord = StatesToRecord.ALL_VISIBLE))
serviceHub.addSignature(tx)
}
}
}

View File

@ -4,11 +4,11 @@
package net.corda.node
import net.corda.cliutils.start
import net.corda.node.internal.NodeStartup
import net.corda.node.internal.NodeStartupCli
fun main(args: Array<String>) {
// Pass the arguments to the Node factory. In the Enterprise edition, this line is modified to point to a subclass.
// It will exit the process in case of startup failure and is not intended to be used by embedders. If you want
// to embed Node in your own container, instantiate it directly and set up the configuration objects yourself.
NodeStartup().start(args)
NodeStartupCli().start(args)
}

View File

@ -2,18 +2,18 @@ package net.corda.node
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigRenderOptions
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.utilities.Try
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NodeConfigurationImpl
import net.corda.node.services.config.parseAsNodeConfiguration
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import picocli.CommandLine.Option
import java.nio.file.Path
import java.nio.file.Paths
class NodeCmdLineOptions {
open class SharedNodeCmdLineOptions {
@Option(
names = ["-b", "--base-directory"],
description = ["The node working directory where all the files are kept."]
@ -27,6 +27,53 @@ class NodeCmdLineOptions {
private var _configFile: Path? = null
val configFile: Path get() = _configFile ?: (baseDirectory / "node.conf")
@Option(
names = ["--on-unknown-config-keys"],
description = ["How to behave on unknown node configuration. \${COMPLETION-CANDIDATES}"]
)
var unknownConfigKeysPolicy: UnknownConfigKeysPolicy = UnknownConfigKeysPolicy.FAIL
@Option(
names = ["-d", "--dev-mode"],
description = ["Runs the node in development mode. Unsafe for production."]
)
var devMode: Boolean? = null
open fun loadConfig(): NodeConfiguration {
return getRawConfig().parseAsNodeConfiguration(unknownConfigKeysPolicy::handle)
}
protected fun getRawConfig(): Config {
val rawConfig = ConfigHelper.loadConfig(
baseDirectory,
configFile
)
if (devMode == true) {
println("Config:\n${rawConfig.root().render(ConfigRenderOptions.defaults())}")
}
return rawConfig
}
fun copyFrom(other: SharedNodeCmdLineOptions) {
baseDirectory = other.baseDirectory
_configFile = other._configFile
unknownConfigKeysPolicy= other.unknownConfigKeysPolicy
devMode = other.devMode
}
}
class InitialRegistrationCmdLineOptions : SharedNodeCmdLineOptions() {
override fun loadConfig(): NodeConfiguration {
return getRawConfig().parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
require(!config.devMode) { "Registration cannot occur in development mode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
}
}
}
}
open class NodeCmdLineOptions : SharedNodeCmdLineOptions() {
@Option(
names = ["--sshd"],
description = ["If set, enables SSH server for node administration."]
@ -45,90 +92,66 @@ class NodeCmdLineOptions {
)
var noLocalShell: Boolean = false
@Option(
names = ["--initial-registration"],
description = ["Start initial node registration with Corda network to obtain certificate from the permissioning server."]
)
var isRegistration: Boolean = false
@Option(
names = ["-t", "--network-root-truststore"],
description = ["Network root trust store obtained from network operator."]
)
private var _networkRootTrustStorePath: Path? = null
val networkRootTrustStorePath: Path get() = _networkRootTrustStorePath ?: baseDirectory / "certificates" / "network-root-truststore.jks"
@Option(
names = ["-p", "--network-root-truststore-password"],
description = ["Network root trust store password obtained from network operator."]
)
var networkRootTrustStorePassword: String? = null
@Option(
names = ["--on-unknown-config-keys"],
description = ["How to behave on unknown node configuration. \${COMPLETION-CANDIDATES}"]
)
var unknownConfigKeysPolicy: UnknownConfigKeysPolicy = UnknownConfigKeysPolicy.FAIL
@Option(
names = ["-d", "--dev-mode"],
description = ["Run the node in developer mode. Unsafe for production."]
)
var devMode: Boolean? = null
@Option(
names = ["--just-generate-node-info"],
description = ["Perform the node start-up task necessary to generate its node info, save it to disk, then quit"]
description = ["DEPRECATED. Performs the node start-up tasks necessary to generate the nodeInfo file, saves it to disk, then exits."],
hidden = true
)
var justGenerateNodeInfo: Boolean = false
@Option(
names = ["--just-generate-rpc-ssl-settings"],
description = ["Generate the SSL key and trust stores for a secure RPC connection."]
description = ["DEPRECATED. Generates the SSL key and trust stores for a secure RPC connection."],
hidden = true
)
var justGenerateRpcSslCerts: Boolean = false
@Option(
names = ["--bootstrap-raft-cluster"],
description = ["Bootstraps Raft cluster. The node forms a single node cluster (ignoring otherwise configured peer addresses), acting as a seed for other nodes to join the cluster."]
)
var bootstrapRaftCluster: Boolean = false
@Option(
names = ["-c", "--clear-network-map-cache"],
description = ["Clears local copy of network map, on node startup it will be restored from server or file system."]
names = ["--clear-network-map-cache"],
description = ["DEPRECATED. Clears local copy of network map, on node startup it will be restored from server or file system."],
hidden = true
)
var clearNetworkMapCache: Boolean = false
val nodeRegistrationOption: NodeRegistrationOption? by lazy {
if (isRegistration) {
requireNotNull(networkRootTrustStorePassword) { "Network root trust store password must be provided in registration mode using --network-root-truststore-password." }
require(networkRootTrustStorePath.exists()) { "Network root trust store path: '$networkRootTrustStorePath' doesn't exist" }
NodeRegistrationOption(networkRootTrustStorePath, networkRootTrustStorePassword!!)
} else {
null
}
}
@Option(
names = ["--initial-registration"],
description = ["DEPRECATED. Starts initial node registration with Corda network to obtain certificate from the permissioning server."],
hidden = true
)
var isRegistration: Boolean = false
fun loadConfig(): Pair<Config, Try<NodeConfiguration>> {
@Option(
names = ["-t", "--network-root-truststore"],
description = ["DEPRECATED. Network root trust store obtained from network operator."],
hidden = true
)
var networkRootTrustStorePathParameter: Path? = null
@Option(
names = ["-p", "--network-root-truststore-password"],
description = ["DEPRECATED. Network root trust store password obtained from network operator."],
hidden = true
)
var networkRootTrustStorePassword: String? = null
override fun loadConfig(): NodeConfiguration {
val rawConfig = ConfigHelper.loadConfig(
baseDirectory,
configFile,
configOverrides = ConfigFactory.parseMap(mapOf("noLocalShell" to this.noLocalShell) +
if (sshdServer) mapOf("sshd" to mapOf("port" to sshdServerPort.toString())) else emptyMap<String, Any>() +
if (devMode != null) mapOf("devMode" to this.devMode) else emptyMap())
if (devMode != null) mapOf("devMode" to this.devMode) else emptyMap())
)
return rawConfig to Try.on {
rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
if (nodeRegistrationOption != null) {
require(!config.devMode) { "Registration cannot occur in devMode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
}
return rawConfig.parseAsNodeConfiguration(unknownConfigKeysPolicy::handle).also { config ->
if (isRegistration) {
require(!config.devMode) { "Registration cannot occur in development mode" }
require(config.compatibilityZoneURL != null || config.networkServices != null) {
"compatibilityZoneURL or networkServices must be present in the node configuration file in registration mode."
}
}
}
}
}
data class NodeRegistrationOption(val networkRootTrustStorePath: Path, val networkRootTrustStorePassword: String)

View File

@ -1,6 +1,6 @@
package net.corda.node
import net.corda.nodeapi.internal.PLATFORM_VERSION
import net.corda.core.internal.PLATFORM_VERSION
/**
* Encapsulates various pieces of version information of the node.

View File

@ -17,6 +17,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.concurrent.map
import net.corda.core.internal.concurrent.openFuture
@ -29,14 +30,16 @@ import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationWhitelist
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.*
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.days
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.minutes
import net.corda.node.CordaClock
import net.corda.node.SerialFilter
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.internal.cordapp.CordappConfigFileProvider
import net.corda.node.internal.cordapp.CordappProviderImpl
import net.corda.node.internal.cordapp.CordappProviderInternal
import net.corda.node.internal.cordapp.*
import net.corda.node.internal.rpc.proxies.AuthenticatedRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ExceptionMaskingRpcOpsProxy
import net.corda.node.internal.rpc.proxies.ExceptionSerialisingRpcOpsProxy
@ -44,9 +47,12 @@ import net.corda.node.services.ContractUpgradeHandler
import net.corda.node.services.FinalityHandler
import net.corda.node.services.NotaryChangeHandler
import net.corda.node.services.api.*
import net.corda.node.services.config.*
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NotaryConfig
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.node.services.config.shell.toShellConfig
import net.corda.node.services.config.shouldInitCrashShell
import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.events.ScheduledActivityObserver
import net.corda.node.services.identity.PersistentIdentityService
@ -61,10 +67,12 @@ import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.persistence.*
import net.corda.node.services.schema.NodeSchemaService
import net.corda.node.services.statemachine.*
import net.corda.node.services.transactions.*
import net.corda.node.services.transactions.InMemoryTransactionVerifierService
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.NodeVaultService
import net.corda.node.utilities.*
import net.corda.nodeapi.internal.DEV_CERTIFICATES
import net.corda.nodeapi.internal.NodeInfoAndSigned
import net.corda.nodeapi.internal.SignedNodeInfo
import net.corda.nodeapi.internal.config.CertificateStore
@ -83,6 +91,7 @@ import org.slf4j.Logger
import rx.Observable
import rx.Scheduler
import java.io.IOException
import java.lang.UnsupportedOperationException
import java.lang.reflect.InvocationTargetException
import java.nio.file.Paths
import java.security.KeyPair
@ -94,13 +103,10 @@ import java.time.Clock
import java.time.Duration
import java.time.format.DateTimeParseException
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit.MINUTES
import java.util.concurrent.TimeUnit.SECONDS
import kotlin.collections.set
import kotlin.reflect.KClass
import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
/**
@ -113,14 +119,13 @@ import net.corda.core.crypto.generateKeyPair as cryptoGenerateKeyPair
// TODO Log warning if this node is a notary but not one of the ones specified in the network parameters, both for core and custom
abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val platformClock: CordaClock,
cacheFactoryPrototype: NamedCacheFactory,
cacheFactoryPrototype: BindableNamedCacheFactory,
protected val versionInfo: VersionInfo,
protected val cordappLoader: CordappLoader,
protected val flowManager: FlowManager,
protected val serverThread: AffinityExecutor.ServiceAffinityExecutor,
private val busyNodeLatch: ReusableLatch = ReusableLatch()) : SingletonSerializeAsToken() {
protected abstract val log: Logger
@Suppress("LeakingThis")
private var tokenizableServices: MutableList<Any>? = mutableListOf(platformClock, this)
@ -141,15 +146,17 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas, configuration.notary != null).tokenize()
protected val cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo)
val schemaService = NodeSchemaService(cordappLoader.cordappSchemas).tokenize()
val identityService = PersistentIdentityService(cacheFactory).tokenize()
val database: CordaPersistence = createCordaPersistence(
configuration.database,
identityService::wellKnownPartyFromX500Name,
identityService::wellKnownPartyFromAnonymous,
schemaService,
configuration.dataSourceProperties
)
configuration.dataSourceProperties,
cacheFactory)
init {
// TODO Break cyclic dependency
identityService.database = database
@ -161,7 +168,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val transactionStorage = makeTransactionStorage(configuration.transactionCacheSizeBytes).tokenize()
val networkMapClient: NetworkMapClient? = configuration.networkServices?.let { NetworkMapClient(it.networkMapURL, versionInfo) }
val attachments = NodeAttachmentService(metricRegistry, cacheFactory, database).tokenize()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(), attachments).tokenize()
val cordappProvider = CordappProviderImpl(cordappLoader, CordappConfigFileProvider(configuration.cordappDirectories), attachments).tokenize()
@Suppress("LeakingThis")
val keyManagementService = makeKeyManagementService(identityService).tokenize()
val servicesForResolution = ServicesForResolutionImpl(identityService, attachments, cordappProvider, transactionStorage).also {
@ -169,7 +176,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
@Suppress("LeakingThis")
val vaultService = makeVaultService(keyManagementService, servicesForResolution, database).tokenize()
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database)
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database, cacheFactory)
val flowLogicRefFactory = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader)
val networkMapUpdater = NetworkMapUpdater(
networkMapCache,
@ -185,7 +192,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
).closeOnStop()
@Suppress("LeakingThis")
val transactionVerifierService = InMemoryTransactionVerifierService(transactionVerifierWorkerCount).tokenize()
val contractUpgradeService = ContractUpgradeServiceImpl().tokenize()
val contractUpgradeService = ContractUpgradeServiceImpl(cacheFactory).tokenize()
val auditService = DummyAuditService().tokenize()
@Suppress("LeakingThis")
protected val network: MessagingService = makeMessagingService().tokenize()
@ -205,7 +212,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
).tokenize().closeOnStop()
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
private val flowFactories = ConcurrentHashMap<Class<out FlowLogic<*>>, InitiatedFlowFactory<*>>()
private val shutdownExecutor = Executors.newSingleThreadExecutor()
protected abstract val transactionVerifierWorkerCount: Int
@ -231,7 +237,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private var _started: S? = null
private fun <T : Any> T.tokenize(): T {
tokenizableServices?.add(this) ?: throw IllegalStateException("The tokenisable services list has already been finalised")
tokenizableServices?.add(this)
?: throw IllegalStateException("The tokenisable services list has already been finalised")
return this
}
@ -264,7 +271,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
check(started == null) { "Node has already been started" }
log.info("Generating nodeInfo ...")
val trustRoot = initKeyStores()
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
val (identity, identityKeyPair) = obtainIdentity()
startDatabase()
val nodeCa = configuration.signingCertificateStore.get()[CORDA_CLIENT_CA]
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
@ -319,7 +326,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
networkMapCache.start(netParams.notaries)
startDatabase()
val (identity, identityKeyPair) = obtainIdentity(notaryConfig = null)
val (identity, identityKeyPair) = obtainIdentity()
identityService.start(trustRoot, listOf(identity.certificate, nodeCa))
val (keyPairs, nodeInfoAndSigned, myNotaryIdentity) = database.transaction {
@ -396,8 +403,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val keyPairs = mutableSetOf(identityKeyPair)
val myNotaryIdentity = configuration.notary?.let {
if (it.isClusterConfig) {
val (notaryIdentity, notaryIdentityKeyPair) = obtainIdentity(it)
if (it.serviceLegalName != null) {
val (notaryIdentity, notaryIdentityKeyPair) = loadNotaryClusterIdentity(it.serviceLegalName)
keyPairs += notaryIdentityKeyPair
notaryIdentity
} else {
@ -500,13 +507,33 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
)
}
private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
val generatedCordapps = mutableListOf(VirtualCordapp.generateCoreCordapp(versionInfo))
if (isRunningSimpleNotaryService(configuration)) {
// For backwards compatibility purposes the single node notary implementation is built-in: a virtual
// CorDapp will be generated.
generatedCordapps += VirtualCordapp.generateSimpleNotaryCordapp(versionInfo)
}
val blacklistedCerts = if (configuration.devMode) emptyList() else DEV_CERTIFICATES
return JarScanningCordappLoader.fromDirectories(
configuration.cordappDirectories,
versionInfo,
extraCordapps = generatedCordapps,
blacklistedCerts = blacklistedCerts
)
}
private fun isRunningSimpleNotaryService(configuration: NodeConfiguration): Boolean {
return configuration.notary != null && configuration.notary?.className == SimpleNotaryService::class.java.name
}
private class ServiceInstantiationException(cause: Throwable?) : CordaException("Service Instantiation Error", cause)
private fun installCordaServices(myNotaryIdentity: PartyAndCertificate?) {
val loadedServices = cordappLoader.cordapps.flatMap { it.services }
filterServicesToInstall(loadedServices).forEach {
loadedServices.forEach {
try {
installCordaService(flowStarter, it, myNotaryIdentity)
installCordaService(flowStarter, it)
} catch (e: NoSuchMethodException) {
log.error("${it.name}, as a Corda service, must have a constructor with a single parameter of type " +
ServiceHub::class.java.name)
@ -518,24 +545,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
}
private fun filterServicesToInstall(loadedServices: List<Class<out SerializeAsToken>>): List<Class<out SerializeAsToken>> {
val customNotaryServiceList = loadedServices.filter { isNotaryService(it) }
if (customNotaryServiceList.isNotEmpty()) {
if (configuration.notary?.custom == true) {
require(customNotaryServiceList.size == 1) {
"Attempting to install more than one notary service: ${customNotaryServiceList.joinToString()}"
}
} else return loadedServices - customNotaryServiceList
}
return loadedServices
}
/**
* If the [serviceClass] is a notary service, it will only be enabled if the "custom" flag is set in
* the notary configuration.
*/
private fun isNotaryService(serviceClass: Class<*>) = NotaryService::class.java.isAssignableFrom(serviceClass)
/**
* This customizes the ServiceHub for each CordaService that is initiating flows.
*/
@ -576,139 +585,52 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
override fun hashCode() = Objects.hash(serviceHub, flowStarter, serviceInstance)
}
private fun <T : SerializeAsToken> installCordaService(flowStarter: FlowStarter, serviceClass: Class<T>, myNotaryIdentity: PartyAndCertificate?) {
private fun <T : SerializeAsToken> installCordaService(flowStarter: FlowStarter, serviceClass: Class<T>) {
serviceClass.requireAnnotation<CordaService>()
val service = try {
if (isNotaryService(serviceClass)) {
myNotaryIdentity ?: throw IllegalStateException("Trying to install a notary service but no notary identity specified")
try {
val constructor = serviceClass.getDeclaredConstructor(ServiceHubInternal::class.java, PublicKey::class.java).apply { isAccessible = true }
constructor.newInstance(services, myNotaryIdentity.owningKey )
} catch (ex: NoSuchMethodException) {
val constructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java, PublicKey::class.java).apply { isAccessible = true }
val serviceContext = AppServiceHubImpl<T>(services, flowStarter)
val service = constructor.newInstance(serviceContext, myNotaryIdentity.owningKey)
serviceContext.serviceInstance = service
service
}
} else {
try {
val serviceContext = AppServiceHubImpl<T>(services, flowStarter)
val extendedServiceConstructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java).apply { isAccessible = true }
val service = extendedServiceConstructor.newInstance(serviceContext)
serviceContext.serviceInstance = service
service
} catch (ex: NoSuchMethodException) {
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java).apply { isAccessible = true }
log.warn("${serviceClass.name} is using legacy CordaService constructor with ServiceHub parameter. " +
"Upgrade to an AppServiceHub parameter to enable updated API features.")
constructor.newInstance(services)
}
}
val serviceContext = AppServiceHubImpl<T>(services, flowStarter)
val extendedServiceConstructor = serviceClass.getDeclaredConstructor(AppServiceHub::class.java).apply { isAccessible = true }
val service = extendedServiceConstructor.newInstance(serviceContext)
serviceContext.serviceInstance = service
service
} catch (ex: NoSuchMethodException) {
val constructor = serviceClass.getDeclaredConstructor(ServiceHub::class.java).apply { isAccessible = true }
log.warn("${serviceClass.name} is using legacy CordaService constructor with ServiceHub parameter. " +
"Upgrade to an AppServiceHub parameter to enable updated API features.")
constructor.newInstance(services)
} catch (e: InvocationTargetException) {
throw ServiceInstantiationException(e.cause)
}
cordappServices.putInstance(serviceClass, service)
if (service is NotaryService) handleCustomNotaryService(service)
service.tokenize()
log.info("Installed ${serviceClass.name} Corda service")
}
private fun handleCustomNotaryService(service: NotaryService) {
runOnStop += service::stop
installCoreFlow(NotaryFlow.Client::class, service::createServiceFlow)
service.start()
}
private fun registerCordappFlows() {
cordappLoader.cordapps.flatMap { it.initiatedFlows }
.forEach {
cordappLoader.cordapps.forEach { cordapp ->
cordapp.initiatedFlows.groupBy { it.requireAnnotation<InitiatedBy>().value.java }.forEach { initiator, responders ->
responders.forEach { responder ->
try {
registerInitiatedFlowInternal(smm, it, track = false)
flowManager.registerInitiatedFlow(initiator, responder)
} catch (e: NoSuchMethodException) {
log.error("${it.name}, as an initiated flow, must have a constructor with a single parameter " +
log.error("${responder.name}, as an initiated flow, must have a constructor with a single parameter " +
"of type ${Party::class.java.name}")
} catch (e: Exception) {
log.error("Unable to register initiated flow ${it.name}", e)
throw e
}
}
}
fun <T : FlowLogic<*>> registerInitiatedFlow(smm: StateMachineManager, initiatedFlowClass: Class<T>): Observable<T> {
return registerInitiatedFlowInternal(smm, initiatedFlowClass, track = true)
}
// TODO remove once not needed
private fun deprecatedFlowConstructorMessage(flowClass: Class<*>): String {
return "Installing flow factory for $flowClass accepting a ${Party::class.java.simpleName}, which is deprecated. " +
"It should accept a ${FlowSession::class.java.simpleName} instead"
}
private fun <F : FlowLogic<*>> registerInitiatedFlowInternal(smm: StateMachineManager, initiatedFlow: Class<F>, track: Boolean): Observable<F> {
val constructors = initiatedFlow.declaredConstructors.associateBy { it.parameterTypes.toList() }
val flowSessionCtor = constructors[listOf(FlowSession::class.java)]?.apply { isAccessible = true }
val ctor: (FlowSession) -> F = if (flowSessionCtor == null) {
// Try to fallback to a Party constructor
val partyCtor = constructors[listOf(Party::class.java)]?.apply { isAccessible = true }
if (partyCtor == null) {
throw IllegalArgumentException("$initiatedFlow must have a constructor accepting a ${FlowSession::class.java.name}")
} else {
log.warn(deprecatedFlowConstructorMessage(initiatedFlow))
}
{ flowSession: FlowSession -> uncheckedCast(partyCtor.newInstance(flowSession.counterparty)) }
} else {
{ flowSession: FlowSession -> uncheckedCast(flowSessionCtor.newInstance(flowSession)) }
}
val initiatingFlow = initiatedFlow.requireAnnotation<InitiatedBy>().value.java
val (version, classWithAnnotation) = initiatingFlow.flowVersionAndInitiatingClass
require(classWithAnnotation == initiatingFlow) {
"${InitiatedBy::class.java.name} must point to ${classWithAnnotation.name} and not ${initiatingFlow.name}"
}
val flowFactory = InitiatedFlowFactory.CorDapp(version, initiatedFlow.appName, ctor)
val observable = internalRegisterFlowFactory(smm, initiatingFlow, flowFactory, initiatedFlow, track)
log.info("Registered ${initiatingFlow.name} to initiate ${initiatedFlow.name} (version $version)")
return observable
}
protected fun <F : FlowLogic<*>> internalRegisterFlowFactory(smm: StateMachineManager,
initiatingFlowClass: Class<out FlowLogic<*>>,
flowFactory: InitiatedFlowFactory<F>,
initiatedFlowClass: Class<F>,
track: Boolean): Observable<F> {
val observable = if (track) {
smm.changes.filter { it is StateMachineManager.Change.Add }.map { it.logic }.ofType(initiatedFlowClass)
} else {
Observable.empty()
}
check(initiatingFlowClass !in flowFactories.keys) {
"$initiatingFlowClass is attempting to register multiple initiated flows"
}
flowFactories[initiatingFlowClass] = flowFactory
return observable
}
/**
* Installs a flow that's core to the Corda platform. Unlike CorDapp flows which are versioned individually using
* [InitiatingFlow.version], core flows have the same version as the node's platform version. To cater for backwards
* compatibility [flowFactory] provides a second parameter which is the platform version of the initiating party.
*/
@VisibleForTesting
fun installCoreFlow(clientFlowClass: KClass<out FlowLogic<*>>, flowFactory: (FlowSession) -> FlowLogic<*>) {
require(clientFlowClass.java.flowVersionAndInitiatingClass.first == 1) {
"${InitiatingFlow::class.java.name}.version not applicable for core flows; their version is the node's platform version"
}
flowFactories[clientFlowClass.java] = InitiatedFlowFactory.Core(flowFactory)
log.debug { "Installed core flow ${clientFlowClass.java.name}" }
flowManager.validateRegistrations()
}
private fun installCoreFlows() {
installCoreFlow(FinalityFlow::class, ::FinalityHandler)
installCoreFlow(NotaryChangeFlow::class, ::NotaryChangeHandler)
installCoreFlow(ContractUpgradeFlow.Initiate::class, ::ContractUpgradeHandler)
installCoreFlow(SwapIdentitiesFlow::class, ::SwapIdentitiesHandler)
flowManager.registerInitiatedCoreFlowFactory(FinalityFlow::class, FinalityHandler::class, ::FinalityHandler)
flowManager.registerInitiatedCoreFlowFactory(NotaryChangeFlow::class, NotaryChangeHandler::class, ::NotaryChangeHandler)
flowManager.registerInitiatedCoreFlowFactory(ContractUpgradeFlow.Initiate::class, NotaryChangeHandler::class, ::ContractUpgradeHandler)
flowManager.registerInitiatedCoreFlowFactory(SwapIdentitiesFlow::class, SwapIdentitiesHandler::class, ::SwapIdentitiesHandler)
}
protected open fun makeTransactionStorage(transactionCacheSizeBytes: Long): WritableTransactionStorage {
@ -725,9 +647,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val identitiesKeyStore = configuration.signingCertificateStore.get()
val trustStore = configuration.p2pSslOptions.trustStore.get()
AllCertificateStores(trustStore, sslKeyStore, identitiesKeyStore)
} catch (e: KeyStoreException) {
log.warn("At least one of the keystores or truststore passwords does not match configuration.")
null
} catch (e: IOException) {
log.error("IO exception while trying to validate keystores and truststore", e)
null
@ -738,11 +657,15 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
private fun validateKeyStores(): X509Certificate {
// Step 1. Check trustStore, sslKeyStore and identitiesKeyStore exist.
val certStores = requireNotNull(getCertificateStores()) {
"One or more keyStores (identity or TLS) or trustStore not found. " +
"Please either copy your existing keys and certificates from another node, " +
"or if you don't have one yet, fill out the config file and run corda.jar --initial-registration. " +
"Read more at: https://docs.corda.net/permissioning.html"
val certStores = try {
requireNotNull(getCertificateStores()) {
"One or more keyStores (identity or TLS) or trustStore not found. " +
"Please either copy your existing keys and certificates from another node, " +
"or if you don't have one yet, fill out the config file and run corda.jar initial-registration. " +
"Read more at: https://docs.corda.net/permissioning.html"
}
} catch (e: KeyStoreException) {
throw IllegalArgumentException("At least one of the keystores or truststore passwords does not match configuration.")
}
// Step 2. Check that trustStore contains the correct key-alias entry.
require(CORDA_ROOT_CA in certStores.trustStore) {
@ -781,17 +704,53 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
private fun makeNotaryService(myNotaryIdentity: PartyAndCertificate?): NotaryService? {
return configuration.notary?.let {
makeCoreNotaryService(it, myNotaryIdentity).also {
it.tokenize()
runOnStop += it::stop
installCoreFlow(NotaryFlow.Client::class, it::createServiceFlow)
log.info("Running core notary: ${it.javaClass.name}")
it.start()
return configuration.notary?.let { notaryConfig ->
val serviceClass = getNotaryServiceClass(notaryConfig.className)
log.info("Starting notary service: $serviceClass")
val notaryKey = myNotaryIdentity?.owningKey
?: throw IllegalArgumentException("Unable to start notary service $serviceClass: notary identity not found")
/** Some notary implementations only work with Java serialization. */
maybeInstallSerializationFilter(serviceClass)
val constructor = serviceClass.getDeclaredConstructor(ServiceHubInternal::class.java, PublicKey::class.java).apply { isAccessible = true }
val service = constructor.newInstance(services, notaryKey) as NotaryService
service.run {
tokenize()
runOnStop += ::stop
flowManager.registerInitiatedCoreFlowFactory(NotaryFlow.Client::class, ::createServiceFlow)
start()
}
return service
}
}
/** Installs a custom serialization filter defined by a notary service implementation. Only supported in dev mode. */
private fun maybeInstallSerializationFilter(serviceClass: Class<out NotaryService>) {
try {
@Suppress("UNCHECKED_CAST")
val filter = serviceClass.getDeclaredMethod("getSerializationFilter").invoke(null) as ((Class<*>) -> Boolean)
if (configuration.devMode) {
log.warn("Installing a custom Java serialization filter, required by ${serviceClass.name}. " +
"Note this is only supported in dev mode a production node will fail to start if serialization filters are used.")
SerialFilter.install(filter)
} else {
throw UnsupportedOperationException("Unable to install a custom Java serialization filter, not in dev mode.")
}
} catch (e: NoSuchMethodException) {
// No custom serialization filter declared
}
}
private fun getNotaryServiceClass(className: String): Class<out NotaryService> {
val loadedImplementations = cordappLoader.cordapps.mapNotNull { it.notaryService }
log.debug("Notary service implementations found: ${loadedImplementations.joinToString(", ")}")
return loadedImplementations.firstOrNull { it.name == className }
?: throw IllegalArgumentException("The notary service implementation specified in the configuration: $className is not found. Available implementations: ${loadedImplementations.joinToString(", ")}}")
}
protected open fun makeKeyManagementService(identityService: PersistentIdentityService): KeyManagementServiceInternal {
// Place the long term identity key in the KMS. Eventually, this is likely going to be separated again because
// the KMS is meant for derived temporary keys used in transactions, and we're not supposed to sign things with
@ -799,32 +758,6 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
return PersistentKeyManagementService(cacheFactory, identityService, database)
}
private fun makeCoreNotaryService(notaryConfig: NotaryConfig, myNotaryIdentity: PartyAndCertificate?): NotaryService {
val notaryKey = myNotaryIdentity?.owningKey
?: throw IllegalArgumentException("No notary identity initialized when creating a notary service")
return notaryConfig.run {
when {
raft != null -> {
val uniquenessProvider = RaftUniquenessProvider(configuration.baseDirectory, configuration.p2pSslOptions, database, platformClock, monitoringService.metrics, cacheFactory, raft)
(if (validating) ::RaftValidatingNotaryService else ::RaftNonValidatingNotaryService)(services, notaryKey, uniquenessProvider)
}
bftSMaRt != null -> {
if (validating) throw IllegalArgumentException("Validating BFTSMaRt notary not supported")
BFTNonValidatingNotaryService(services, notaryKey, bftSMaRt, makeBFTCluster(notaryKey, bftSMaRt))
}
else -> (if (validating) ::ValidatingNotaryService else ::SimpleNotaryService)(services, notaryKey)
}
}
}
protected open fun makeBFTCluster(notaryKey: PublicKey, bftSMaRtConfig: BFTSMaRtConfiguration): BFTSMaRt.Cluster {
return object : BFTSMaRt.Cluster {
override fun waitUntilAllReplicasHaveInitialized() {
log.warn("A BFT replica may still be initializing, in which case the upcoming consensus change may cause it to spin.")
}
}
}
open fun stop() {
// TODO: We need a good way of handling "nice to have" shutdown events, especially those that deal with the
// network, including unsubscribing from updates from remote services. Possibly some sort of parameter to stop()
@ -848,55 +781,56 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
myNotaryIdentity: PartyAndCertificate?,
networkParameters: NetworkParameters)
private fun obtainIdentity(notaryConfig: NotaryConfig?): Pair<PartyAndCertificate, KeyPair> {
/** Loads or generates the node's legal identity and key-pair. */
private fun obtainIdentity(): Pair<PartyAndCertificate, KeyPair> {
val keyStore = configuration.signingCertificateStore.get()
val legalName = configuration.myLegalName
val (id, singleName) = if (notaryConfig == null || !notaryConfig.isClusterConfig) {
// Node's main identity or if it's a single node notary.
Pair(NODE_IDENTITY_ALIAS_PREFIX, configuration.myLegalName)
} else {
// The node is part of a distributed notary whose identity must already be generated beforehand.
Pair(DISTRIBUTED_NOTARY_ALIAS_PREFIX, null)
}
// TODO: Integrate with Key management service?
val privateKeyAlias = "$id-private-key"
val privateKeyAlias = "$NODE_IDENTITY_ALIAS_PREFIX-private-key"
if (privateKeyAlias !in keyStore) {
// We shouldn't have a distributed notary at this stage, so singleName should NOT be null.
requireNotNull(singleName) {
"Unable to find in the key store the identity of the distributed notary the node is part of"
}
log.info("$privateKeyAlias not found in key store, generating fresh key!")
keyStore.storeLegalIdentity(privateKeyAlias, generateKeyPair())
}
val (x509Cert, keyPair) = keyStore.query { getCertificateAndKeyPair(privateKeyAlias) }
val (x509Cert, keyPair) = keyStore.query { getCertificateAndKeyPair(privateKeyAlias, keyStore.entryPassword) }
// TODO: Use configuration to indicate composite key should be used instead of public key for the identity.
val compositeKeyAlias = "$id-composite-key"
val certificates = keyStore.query { getCertificateChain(privateKeyAlias) }
check(certificates.first() == x509Cert) {
"Certificates from key store do not line up!"
}
val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
if (subject != legalName) {
throw ConfigurationException("The name '$legalName' for $NODE_IDENTITY_ALIAS_PREFIX doesn't match what's in the key store: $subject")
}
val certPath = X509Utilities.buildCertPath(certificates)
return Pair(PartyAndCertificate(certPath), keyPair)
}
/** Loads pre-generated notary service cluster identity. */
private fun loadNotaryClusterIdentity(serviceLegalName: CordaX500Name): Pair<PartyAndCertificate, KeyPair> {
val keyStore = configuration.signingCertificateStore.get()
val privateKeyAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-private-key"
val keyPair = keyStore.query { getCertificateAndKeyPair(privateKeyAlias, keyStore.entryPassword) }.keyPair
val compositeKeyAlias = "$DISTRIBUTED_NOTARY_ALIAS_PREFIX-composite-key"
val certificates = if (compositeKeyAlias in keyStore) {
// Use composite key instead if it exists.
val certificate = keyStore[compositeKeyAlias]
// We have to create the certificate chain for the composite key manually, this is because we don't have a keystore
// provider that understand compositeKey-privateKey combo. The cert chain is created using the composite key certificate +
// the tail of the private key certificates, as they are both signed by the same certificate chain.
listOf(certificate) + keyStore.query { getCertificateChain(privateKeyAlias) }.drop(1)
} else {
keyStore.query { getCertificateChain(privateKeyAlias) }.let {
check(it[0] == x509Cert) { "Certificates from key store do not line up!" }
it
}
}
} else throw IllegalStateException("The identity public key for the notary service $serviceLegalName was not found in the key store.")
val subject = CordaX500Name.build(certificates[0].subjectX500Principal)
if (singleName != null && subject != singleName) {
throw ConfigurationException("The name '$singleName' for $id doesn't match what's in the key store: $subject")
} else if (notaryConfig != null && notaryConfig.isClusterConfig && notaryConfig.serviceLegalName != null && subject != notaryConfig.serviceLegalName) {
// Note that we're not checking if `notaryConfig.serviceLegalName` is not present for backwards compatibility.
throw ConfigurationException("The name of the notary service '${notaryConfig.serviceLegalName}' for $id doesn't " +
val subject = CordaX500Name.build(certificates.first().subjectX500Principal)
if (subject != serviceLegalName) {
throw ConfigurationException("The name of the notary service '$serviceLegalName' for $DISTRIBUTED_NOTARY_ALIAS_PREFIX doesn't " +
"match what's in the key store: $subject. You might need to adjust the configuration of `notary.serviceLegalName`.")
}
val certPath = X509Utilities.buildCertPath(certificates)
return Pair(PartyAndCertificate(certPath), keyPair)
}
@ -966,7 +900,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
}
override fun getFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? {
return flowFactories[initiatingFlowClass]
return flowManager.getFlowFactoryForInitiatingFlow(initiatingFlowClass)
}
override fun jdbcSession(): Connection = database.createSession()
@ -1032,23 +966,12 @@ class FlowStarterImpl(private val smm: StateMachineManager, private val flowLogi
class ConfigurationException(message: String) : CordaException(message)
// TODO This is no longer used by AbstractNode and can be moved elsewhere
fun configureDatabase(hikariProperties: Properties,
databaseConfig: DatabaseConfig,
wellKnownPartyFromX500Name: (CordaX500Name) -> Party?,
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?,
schemaService: SchemaService = NodeSchemaService(),
internalSchemas: Set<MappedSchema> = NodeSchemaService().internalSchemas()): CordaPersistence {
val persistence = createCordaPersistence(databaseConfig, wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous, schemaService, hikariProperties)
persistence.startHikariPool(hikariProperties, databaseConfig, internalSchemas)
return persistence
}
fun createCordaPersistence(databaseConfig: DatabaseConfig,
wellKnownPartyFromX500Name: (CordaX500Name) -> Party?,
wellKnownPartyFromAnonymous: (AbstractParty) -> Party?,
schemaService: SchemaService,
hikariProperties: Properties): CordaPersistence {
hikariProperties: Properties,
cacheFactory: NamedCacheFactory): CordaPersistence {
// Register the AbstractPartyDescriptor so Hibernate doesn't warn when encountering AbstractParty. Unfortunately
// Hibernate warns about not being able to find a descriptor if we don't provide one, but won't use it by default
// so we end up providing both descriptor and converter. We should re-examine this in later versions to see if
@ -1056,7 +979,7 @@ fun createCordaPersistence(databaseConfig: DatabaseConfig,
JavaTypeDescriptorRegistry.INSTANCE.addDescriptor(AbstractPartyDescriptor(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous))
val attributeConverters = listOf(PublicKeyToTextConverter(), AbstractPartyToX500NameAsStringConverter(wellKnownPartyFromX500Name, wellKnownPartyFromAnonymous))
val jdbcUrl = hikariProperties.getProperty("dataSource.url", "")
return CordaPersistence(databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters)
return CordaPersistence(databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, cacheFactory, attributeConverters)
}
fun CordaPersistence.startHikariPool(hikariProperties: Properties, databaseConfig: DatabaseConfig, schemas: Set<MappedSchema>, metricRegistry: MetricRegistry? = null) {
@ -1082,4 +1005,4 @@ fun clientSslOptionsCompatibleWith(nodeRpcOptions: NodeRpcOptions): ClientRpcSsl
}
// Here we're using the node's RPC key store as the RPC client's trust store.
return ClientRpcSslOptions(trustStorePath = nodeRpcOptions.sslConfig!!.keyStorePath, trustStorePassword = nodeRpcOptions.sslConfig!!.keyStorePassword)
}
}

View File

@ -4,7 +4,6 @@ import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.internal.CheckpointSerializationDefaults
import net.corda.core.serialization.internal.checkpointDeserialize
import net.corda.node.services.api.CheckpointStorage
@ -21,7 +20,7 @@ object CheckpointVerifier {
*/
fun verifyCheckpointsCompatible(checkpointStorage: CheckpointStorage, currentCordapps: List<Cordapp>, platformVersion: Int, serviceHub: ServiceHub, tokenizableServices: List<Any>) {
val checkpointSerializationContext = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext(
CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZATION_FACTORY, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub)
CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZER, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub)
)
checkpointStorage.getAllCheckpoints().forEach { (_, serializedCheckpoint) ->

View File

@ -0,0 +1,222 @@
package net.corda.node.internal
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.identity.Party
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.node.services.config.FlowOverrideConfig
import net.corda.node.services.statemachine.appName
import net.corda.node.services.statemachine.flowVersionAndInitiatingClass
import javax.annotation.concurrent.ThreadSafe
import kotlin.reflect.KClass
/**
*
* This class is responsible for organising which flow should respond to a specific @InitiatingFlow
*
* There are two main ways to modify the behaviour of a cordapp with regards to responding with a different flow
*
* 1.) implementing a new subclass. For example, if we have a ResponderFlow similar to @InitiatedBy(Sender) MyBaseResponder : FlowLogic
* If we subclassed a new Flow with specific logic for DB2, it would be similar to IBMB2Responder() : MyBaseResponder
* When these two flows are encountered by the classpath scan for @InitiatedBy, they will both be selected for responding to Sender
* This implementation will sort them for responding in order of their "depth" from FlowLogic - see: FlowWeightComparator
* So IBMB2Responder would win and it would be selected for responding
*
* 2.) It is possible to specify a flowOverride key in the node configuration. Say we configure a node to have
* flowOverrides{
* "Sender" = "MyBaseResponder"
* }
* In this case, FlowWeightComparator would detect that there is an override in action, and it will assign MyBaseResponder a maximum weight
* This will result in MyBaseResponder being selected for responding to Sender
*
*
*/
interface FlowManager {
fun registerInitiatedCoreFlowFactory(initiatingFlowClass: KClass<out FlowLogic<*>>, flowFactory: (FlowSession) -> FlowLogic<*>)
fun registerInitiatedCoreFlowFactory(initiatingFlowClass: KClass<out FlowLogic<*>>, initiatedFlowClass: KClass<out FlowLogic<*>>?, flowFactory: (FlowSession) -> FlowLogic<*>)
fun registerInitiatedCoreFlowFactory(initiatingFlowClass: KClass<out FlowLogic<*>>, initiatedFlowClass: KClass<out FlowLogic<*>>?, flowFactory: InitiatedFlowFactory.Core<FlowLogic<*>>)
fun <F : FlowLogic<*>> registerInitiatedFlow(initiator: Class<out FlowLogic<*>>, responder: Class<F>)
fun <F : FlowLogic<*>> registerInitiatedFlow(responder: Class<F>)
fun getFlowFactoryForInitiatingFlow(initiatedFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>?
fun validateRegistrations()
}
@ThreadSafe
open class NodeFlowManager(flowOverrides: FlowOverrideConfig? = null) : FlowManager {
private val flowFactories = HashMap<Class<out FlowLogic<*>>, MutableList<RegisteredFlowContainer>>()
private val flowOverrides = (flowOverrides
?: FlowOverrideConfig()).overrides.map { it.initiator to it.responder }.toMutableMap()
companion object {
private val log = contextLogger()
}
@Synchronized
override fun getFlowFactoryForInitiatingFlow(initiatedFlowClass: Class<out FlowLogic<*>>): InitiatedFlowFactory<*>? {
return flowFactories[initiatedFlowClass]?.firstOrNull()?.flowFactory
}
@Synchronized
override fun <F : FlowLogic<*>> registerInitiatedFlow(responder: Class<F>) {
return registerInitiatedFlow(responder.requireAnnotation<InitiatedBy>().value.java, responder)
}
@Synchronized
override fun <F : FlowLogic<*>> registerInitiatedFlow(initiator: Class<out FlowLogic<*>>, responder: Class<F>) {
val constructors = responder.declaredConstructors.associateBy { it.parameterTypes.toList() }
val flowSessionCtor = constructors[listOf(FlowSession::class.java)]?.apply { isAccessible = true }
val ctor: (FlowSession) -> F = if (flowSessionCtor == null) {
// Try to fallback to a Party constructor
val partyCtor = constructors[listOf(Party::class.java)]?.apply { isAccessible = true }
if (partyCtor == null) {
throw IllegalArgumentException("$responder must have a constructor accepting a ${FlowSession::class.java.name}")
} else {
log.warn("Installing flow factory for $responder accepting a ${Party::class.java.simpleName}, which is deprecated. " +
"It should accept a ${FlowSession::class.java.simpleName} instead")
}
{ flowSession: FlowSession -> uncheckedCast(partyCtor.newInstance(flowSession.counterparty)) }
} else {
{ flowSession: FlowSession -> uncheckedCast(flowSessionCtor.newInstance(flowSession)) }
}
val (version, classWithAnnotation) = initiator.flowVersionAndInitiatingClass
require(classWithAnnotation == initiator) {
"${InitiatedBy::class.java.name} must point to ${classWithAnnotation.name} and not ${initiator.name}"
}
val flowFactory = InitiatedFlowFactory.CorDapp(version, responder.appName, ctor)
registerInitiatedFlowFactory(initiator, flowFactory, responder)
log.info("Registered ${initiator.name} to initiate ${responder.name} (version $version)")
}
private fun <F : FlowLogic<*>> registerInitiatedFlowFactory(initiatingFlowClass: Class<out FlowLogic<*>>,
flowFactory: InitiatedFlowFactory<F>,
initiatedFlowClass: Class<F>?) {
check(flowFactory !is InitiatedFlowFactory.Core) { "This should only be used for Cordapp flows" }
val listOfFlowsForInitiator = flowFactories.computeIfAbsent(initiatingFlowClass) { mutableListOf() }
if (listOfFlowsForInitiator.isNotEmpty() && listOfFlowsForInitiator.first().type == FlowType.CORE) {
throw IllegalStateException("Attempting to register over an existing platform flow: $initiatingFlowClass")
}
synchronized(listOfFlowsForInitiator) {
val flowToAdd = RegisteredFlowContainer(initiatingFlowClass, initiatedFlowClass, flowFactory, FlowType.CORDAPP)
val flowWeightComparator = FlowWeightComparator(initiatingFlowClass, flowOverrides)
listOfFlowsForInitiator.add(flowToAdd)
listOfFlowsForInitiator.sortWith(flowWeightComparator)
if (listOfFlowsForInitiator.size > 1) {
log.warn("Multiple flows are registered for InitiatingFlow: $initiatingFlowClass, currently using: ${listOfFlowsForInitiator.first().initiatedFlowClass}")
}
}
}
// TODO Harmonise use of these methods - 99% of invocations come from tests.
@Synchronized
override fun registerInitiatedCoreFlowFactory(initiatingFlowClass: KClass<out FlowLogic<*>>, initiatedFlowClass: KClass<out FlowLogic<*>>?, flowFactory: (FlowSession) -> FlowLogic<*>) {
registerInitiatedCoreFlowFactory(initiatingFlowClass, initiatedFlowClass, InitiatedFlowFactory.Core(flowFactory))
}
@Synchronized
override fun registerInitiatedCoreFlowFactory(initiatingFlowClass: KClass<out FlowLogic<*>>, flowFactory: (FlowSession) -> FlowLogic<*>) {
registerInitiatedCoreFlowFactory(initiatingFlowClass, null, InitiatedFlowFactory.Core(flowFactory))
}
@Synchronized
override fun registerInitiatedCoreFlowFactory(initiatingFlowClass: KClass<out FlowLogic<*>>, initiatedFlowClass: KClass<out FlowLogic<*>>?, flowFactory: InitiatedFlowFactory.Core<FlowLogic<*>>) {
require(initiatingFlowClass.java.flowVersionAndInitiatingClass.first == 1) {
"${InitiatingFlow::class.java.name}.version not applicable for core flows; their version is the node's platform version"
}
flowFactories.computeIfAbsent(initiatingFlowClass.java) { mutableListOf() }.add(
RegisteredFlowContainer(
initiatingFlowClass.java,
initiatedFlowClass?.java,
flowFactory,
FlowType.CORE)
)
log.debug { "Installed core flow ${initiatingFlowClass.java.name}" }
}
// To verify the integrity of the current state, it is important that the tip of the responders is a unique weight
// if there are multiple flows with the same weight as the tip, it means that it is impossible to reliably pick one as the responder
private fun validateInvariants(toValidate: List<RegisteredFlowContainer>) {
val currentTip = toValidate.first()
val flowWeightComparator = FlowWeightComparator(currentTip.initiatingFlowClass, flowOverrides)
val equalWeightAsCurrentTip = toValidate.map { flowWeightComparator.compare(currentTip, it) to it }.filter { it.first == 0 }.map { it.second }
if (equalWeightAsCurrentTip.size > 1) {
val message = "Unable to determine which flow to use when responding to: ${currentTip.initiatingFlowClass.canonicalName}. ${equalWeightAsCurrentTip.map { it.initiatedFlowClass!!.canonicalName }} are all registered with equal weight."
throw IllegalStateException(message)
}
}
@Synchronized
override fun validateRegistrations() {
flowFactories.values.forEach {
validateInvariants(it)
}
}
private enum class FlowType {
CORE, CORDAPP
}
private data class RegisteredFlowContainer(val initiatingFlowClass: Class<out FlowLogic<*>>,
val initiatedFlowClass: Class<out FlowLogic<*>>?,
val flowFactory: InitiatedFlowFactory<FlowLogic<*>>,
val type: FlowType)
// this is used to sort the responding flows in order of "importance"
// the logic is as follows
// IF responder is a specific lambda (like for notary implementations / testing code) always return that responder
// ELSE IF responder is present in the overrides list, always return that responder
// ELSE compare responding flows by their depth from FlowLogic, always return the flow which is most specific (IE, has the most hops to FlowLogic)
private open class FlowWeightComparator(val initiatingFlowClass: Class<out FlowLogic<*>>, val flowOverrides: Map<String, String>) : Comparator<NodeFlowManager.RegisteredFlowContainer> {
override fun compare(o1: NodeFlowManager.RegisteredFlowContainer, o2: NodeFlowManager.RegisteredFlowContainer): Int {
if (o1.initiatedFlowClass == null && o2.initiatedFlowClass != null) {
return Int.MIN_VALUE
}
if (o1.initiatedFlowClass != null && o2.initiatedFlowClass == null) {
return Int.MAX_VALUE
}
if (o1.initiatedFlowClass == null && o2.initiatedFlowClass == null) {
return 0
}
val hopsTo1 = calculateHopsToFlowLogic(initiatingFlowClass, o1.initiatedFlowClass!!)
val hopsTo2 = calculateHopsToFlowLogic(initiatingFlowClass, o2.initiatedFlowClass!!)
return hopsTo1.compareTo(hopsTo2) * -1
}
private fun calculateHopsToFlowLogic(initiatingFlowClass: Class<out FlowLogic<*>>,
initiatedFlowClass: Class<out FlowLogic<*>>): Int {
val overriddenClassName = flowOverrides[initiatingFlowClass.canonicalName]
return if (overriddenClassName == initiatedFlowClass.canonicalName) {
Int.MAX_VALUE
} else {
var currentClass: Class<*> = initiatedFlowClass
var count = 0
while (currentClass != FlowLogic::class.java) {
currentClass = currentClass.superclass
count++
}
count;
}
}
}
}
private fun <X, Y> Iterable<Pair<X, Y>>.toMutableMap(): MutableMap<X, Y> {
return this.toMap(HashMap())
}

View File

@ -4,10 +4,12 @@ import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
sealed class InitiatedFlowFactory<out F : FlowLogic<*>> {
protected abstract val factory: (FlowSession) -> F
fun createFlow(initiatingFlowSession: FlowSession): F = factory(initiatingFlowSession)
data class Core<out F : FlowLogic<*>>(override val factory: (FlowSession) -> F) : InitiatedFlowFactory<F>()
data class CorDapp<out F : FlowLogic<*>>(val flowVersion: Int,
val appName: String,
override val factory: (FlowSession) -> F) : InitiatedFlowFactory<F>()

View File

@ -6,6 +6,7 @@ import com.codahale.metrics.MetricRegistry
import com.palominolabs.metrics.newrelic.AllEnabledMetricAttributeFilter
import com.palominolabs.metrics.newrelic.NewRelicReporter
import net.corda.client.rpc.internal.serialization.amqp.AMQPClientSerializationScheme
import net.corda.cliutils.ShellConstants
import net.corda.core.concurrent.CordaFuture
import net.corda.core.flows.FlowLogic
import net.corda.core.identity.CordaX500Name
@ -21,24 +22,21 @@ import net.corda.core.messaging.RPCOps
import net.corda.core.node.NetworkParameters
import net.corda.core.node.NodeInfo
import net.corda.core.node.ServiceHub
import net.corda.core.serialization.internal.CheckpointSerializationFactory
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
import net.corda.core.serialization.internal.SerializationEnvironment
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.node.CordaClock
import net.corda.node.SimpleClock
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.artemis.ArtemisBroker
import net.corda.node.internal.artemis.BrokerAddresses
import net.corda.node.internal.cordapp.JarScanningCordappLoader
import net.corda.node.internal.security.RPCSecurityManager
import net.corda.node.internal.security.RPCSecurityManagerImpl
import net.corda.node.internal.security.RPCSecurityManagerWithAdditionalUser
import net.corda.node.serialization.amqp.AMQPServerSerializationScheme
import net.corda.node.serialization.kryo.KRYO_CHECKPOINT_CONTEXT
import net.corda.node.serialization.kryo.KryoSerializationScheme
import net.corda.node.serialization.kryo.KryoCheckpointSerializer
import net.corda.node.services.Permissions
import net.corda.node.services.api.FlowStarter
import net.corda.node.services.api.ServiceHubInternal
@ -46,6 +44,7 @@ import net.corda.node.services.api.StartedNodeServices
import net.corda.node.services.config.*
import net.corda.node.services.messaging.*
import net.corda.node.services.rpc.ArtemisRpcBroker
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.node.utilities.*
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.INTERNAL_SHELL_USER
import net.corda.nodeapi.internal.ShutdownHook
@ -59,7 +58,6 @@ import org.apache.commons.lang.SystemUtils
import org.h2.jdbc.JdbcSQLException
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import rx.Observable
import rx.Scheduler
import rx.schedulers.Schedulers
import java.net.BindException
@ -75,8 +73,7 @@ import kotlin.system.exitProcess
class NodeWithInfo(val node: Node, val info: NodeInfo) {
val services: StartedNodeServices = object : StartedNodeServices, ServiceHubInternal by node.services, FlowStarter by node.flowStarter {}
fun dispose() = node.stop()
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<T>): Observable<T> =
node.registerInitiatedFlow(node.smm, initiatedFlowClass)
fun <T : FlowLogic<*>> registerInitiatedFlow(initiatedFlowClass: Class<T>) = node.registerInitiatedFlow(node.smm, initiatedFlowClass)
}
/**
@ -88,14 +85,14 @@ class NodeWithInfo(val node: Node, val info: NodeInfo) {
open class Node(configuration: NodeConfiguration,
versionInfo: VersionInfo,
private val initialiseSerialization: Boolean = true,
cordappLoader: CordappLoader = makeCordappLoader(configuration, versionInfo),
cacheFactoryPrototype: NamedCacheFactory = DefaultNamedCacheFactory()
flowManager: FlowManager = NodeFlowManager(configuration.flowOverrides),
cacheFactoryPrototype: BindableNamedCacheFactory = DefaultNamedCacheFactory()
) : AbstractNode<NodeInfo>(
configuration,
createClock(configuration),
cacheFactoryPrototype,
versionInfo,
cordappLoader,
flowManager,
// Under normal (non-test execution) it will always be "1"
AffinityExecutor.ServiceAffinityExecutor("Node thread-${sameVmNodeCounter.incrementAndGet()}", 1)
) {
@ -114,9 +111,13 @@ open class Node(configuration: NodeConfiguration,
LoggerFactory.getLogger(loggerName).info(msg)
}
fun printInRed(message: String) {
println("${ShellConstants.RED}$message${ShellConstants.RESET}")
}
fun printWarning(message: String) {
Emoji.renderIfSupported {
println("${Emoji.warningSign} ATTENTION: $message")
printInRed("${Emoji.warningSign} ATTENTION: $message")
}
staticLog.warn(message)
}
@ -133,20 +134,16 @@ open class Node(configuration: NodeConfiguration,
private val sameVmNodeCounter = AtomicInteger()
private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
return JarScanningCordappLoader.fromDirectories(configuration.cordappDirectories, versionInfo)
}
// TODO: make this configurable.
const val MAX_RPC_MESSAGE_SIZE = 10485760
fun isValidJavaVersion(): Boolean {
fun isInvalidJavaVersion(): Boolean {
if (!hasMinimumJavaVersion()) {
println("You are using a version of Java that is not supported (${SystemUtils.JAVA_VERSION}). Please upgrade to the latest version of Java 8.")
println("Corda will now exit...")
return false
return true
}
return true
return false
}
private fun hasMinimumJavaVersion(): Boolean {
@ -211,7 +208,8 @@ open class Node(configuration: NodeConfiguration,
return P2PMessagingClient(
config = configuration,
versionInfo = versionInfo,
serverAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("localhost", configuration.p2pAddress.port),
serverAddress = configuration.messagingServerAddress
?: NetworkHostAndPort("localhost", configuration.p2pAddress.port),
nodeExecutor = serverThread,
database = database,
networkMap = networkMapCache,
@ -232,12 +230,13 @@ open class Node(configuration: NodeConfiguration,
val securityManagerConfig = configuration.security?.authService
?: SecurityConfiguration.AuthService.fromUsers(configuration.rpcUsers)
val securityManager = with(RPCSecurityManagerImpl(securityManagerConfig)) {
val securityManager = with(RPCSecurityManagerImpl(securityManagerConfig, cacheFactory)) {
if (configuration.shouldStartLocalShell()) RPCSecurityManagerWithAdditionalUser(this, User(INTERNAL_SHELL_USER, INTERNAL_SHELL_USER, setOf(Permissions.all()))) else this
}
val messageBroker = if (!configuration.messagingServerExternal) {
val brokerBindAddress = configuration.messagingServerAddress ?: NetworkHostAndPort("0.0.0.0", configuration.p2pAddress.port)
val brokerBindAddress = configuration.messagingServerAddress
?: NetworkHostAndPort("0.0.0.0", configuration.p2pAddress.port)
ArtemisMessagingServer(configuration, brokerBindAddress, networkParameters.maxMessageSize)
} else {
null
@ -276,7 +275,7 @@ open class Node(configuration: NodeConfiguration,
// Start up the MQ clients.
internalRpcMessagingClient?.run {
closeOnStop()
init(rpcOps, securityManager)
init(rpcOps, securityManager, cacheFactory)
}
network.closeOnStop()
network.start(
@ -451,7 +450,7 @@ open class Node(configuration: NodeConfiguration,
}.build().start()
}
private fun registerNewRelicReporter (registry: MetricRegistry) {
private fun registerNewRelicReporter(registry: MetricRegistry) {
log.info("Registering New Relic JMX Reporter:")
val reporter = NewRelicReporter.forRegistry(registry)
.name("New Relic Reporter")
@ -470,17 +469,19 @@ open class Node(configuration: NodeConfiguration,
private fun initialiseSerialization() {
if (!initialiseSerialization) return
val classloader = cordappLoader.appClassLoader
nodeSerializationEnv = SerializationEnvironmentImpl(
nodeSerializationEnv = SerializationEnvironment.with(
SerializationFactoryImpl().apply {
registerScheme(AMQPServerSerializationScheme(cordappLoader.cordapps))
registerScheme(AMQPClientSerializationScheme(cordappLoader.cordapps))
},
checkpointSerializationFactory = CheckpointSerializationFactory(KryoSerializationScheme),
p2pContext = AMQP_P2P_CONTEXT.withClassLoader(classloader),
rpcServerContext = AMQP_RPC_SERVER_CONTEXT.withClassLoader(classloader),
rpcClientContext = if (configuration.shouldInitCrashShell()) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classloader) else null, //even Shell embeded in the node connects via RPC to the node
storageContext = AMQP_STORAGE_CONTEXT.withClassLoader(classloader),
checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader),
rpcClientContext = if (configuration.shouldInitCrashShell()) AMQP_RPC_CLIENT_CONTEXT.withClassLoader(classloader) else null) //even Shell embeded in the node connects via RPC to the node
checkpointSerializer = KryoCheckpointSerializer,
checkpointContext = KRYO_CHECKPOINT_CONTEXT.withClassLoader(classloader)
)
}
/** Starts a blocking event loop for message dispatch. */
@ -511,4 +512,8 @@ open class Node(configuration: NodeConfiguration,
log.info("Shutdown complete")
}
fun <T : FlowLogic<*>> registerInitiatedFlow(smm: StateMachineManager, initiatedFlowClass: Class<T>) {
this.flowManager.registerInitiatedFlow(initiatedFlowClass)
}
}

View File

@ -1,34 +1,24 @@
package net.corda.node.internal
import com.typesafe.config.Config
import com.typesafe.config.ConfigException
import com.typesafe.config.ConfigRenderOptions
import io.netty.channel.unix.Errors
import net.corda.cliutils.CordaCliWrapper
import net.corda.cliutils.CordaVersionProvider
import net.corda.cliutils.ExitCodes
import net.corda.cliutils.*
import net.corda.core.crypto.Crypto
import net.corda.core.internal.*
import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.errors.AddressBindingException
import net.corda.core.utilities.Try
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.loggerFor
import net.corda.node.*
import net.corda.node.internal.Node.Companion.isValidJavaVersion
import net.corda.node.internal.Node.Companion.isInvalidJavaVersion
import net.corda.node.internal.cordapp.MultipleCordappsForFlowException
import net.corda.node.internal.subcommands.*
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NodeConfigurationImpl
import net.corda.node.services.config.shouldStartLocalShell
import net.corda.node.services.config.shouldStartSSHDaemon
import net.corda.node.services.transactions.bftSMaRtSerialFilter
import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationException
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.node.utilities.saveToKeyStore
import net.corda.node.utilities.saveToTrustStore
import net.corda.nodeapi.internal.PLATFORM_VERSION
import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
@ -36,10 +26,8 @@ import net.corda.nodeapi.internal.persistence.DatabaseIncompatibleException
import net.corda.tools.shell.InteractiveShell
import org.fusesource.jansi.Ansi
import org.slf4j.bridge.SLF4JBridgeHandler
import picocli.CommandLine.Mixin
import picocli.CommandLine.*
import sun.misc.VMSupport
import java.io.Console
import java.io.File
import java.io.IOException
import java.io.RandomAccessFile
import java.lang.management.ManagementFactory
@ -48,215 +36,153 @@ import java.nio.file.Path
import java.time.DayOfWeek
import java.time.ZonedDateTime
import java.util.*
import kotlin.system.exitProcess
/** This class is responsible for starting a Node from command line arguments. */
open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
/** An interface that can be implemented to tell the node what to do once it's intitiated. */
interface RunAfterNodeInitialisation {
fun run(node: Node)
}
/** Base class for subcommands to derive from that initialises the logs and provides standard options. */
abstract class NodeCliCommand(alias: String, description: String, val startup: NodeStartup) : CliWrapperBase(alias, description), NodeStartupLogging {
companion object {
private val logger by lazy { loggerFor<Node>() } // I guess this is lazy to allow for logging init, but why Node?
const val LOGS_DIRECTORY_NAME = "logs"
const val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
private const val INITIAL_REGISTRATION_MARKER = ".initialregistration"
}
override fun initLogging() = this.initLogging(cmdLineOptions.baseDirectory)
@Mixin
val cmdLineOptions = SharedNodeCmdLineOptions()
}
/** Main corda entry point. */
open class NodeStartupCli : CordaCliWrapper("corda", "Runs a Corda Node") {
val startup = NodeStartup()
private val networkCacheCli = ClearNetworkCacheCli(startup)
private val justGenerateNodeInfoCli = GenerateNodeInfoCli(startup)
private val justGenerateRpcSslCertsCli = GenerateRpcSslCertsCli(startup)
private val initialRegistrationCli = InitialRegistrationCli(startup)
override fun initLogging() = this.initLogging(cmdLineOptions.baseDirectory)
override fun additionalSubCommands() = setOf(networkCacheCli, justGenerateNodeInfoCli, justGenerateRpcSslCertsCli, initialRegistrationCli)
override fun runProgram(): Int {
return when {
InitialRegistration.checkRegistrationMode(cmdLineOptions.baseDirectory) -> {
println("Node was started before in `initial-registration` mode, but the registration was not completed.\nResuming registration.")
initialRegistrationCli.cmdLineOptions.copyFrom(cmdLineOptions)
initialRegistrationCli.runProgram()
}
//deal with legacy flags and redirect to subcommands
cmdLineOptions.isRegistration -> {
Node.printWarning("The --initial-registration flag has been deprecated and will be removed in a future version. Use the initial-registration command instead.")
requireNotNull(cmdLineOptions.networkRootTrustStorePassword) { "Network root trust store password must be provided in registration mode using --network-root-truststore-password." }
initialRegistrationCli.networkRootTrustStorePassword = cmdLineOptions.networkRootTrustStorePassword!!
initialRegistrationCli.networkRootTrustStorePathParameter = cmdLineOptions.networkRootTrustStorePathParameter
initialRegistrationCli.cmdLineOptions.copyFrom(cmdLineOptions)
initialRegistrationCli.runProgram()
}
cmdLineOptions.clearNetworkMapCache -> {
Node.printWarning("The --clear-network-map-cache flag has been deprecated and will be removed in a future version. Use the clear-network-cache command instead.")
networkCacheCli.cmdLineOptions.copyFrom(cmdLineOptions)
networkCacheCli.runProgram()
}
cmdLineOptions.justGenerateNodeInfo -> {
Node.printWarning("The --just-generate-node-info flag has been deprecated and will be removed in a future version. Use the generate-node-info command instead.")
justGenerateNodeInfoCli.cmdLineOptions.copyFrom(cmdLineOptions)
justGenerateNodeInfoCli.runProgram()
}
cmdLineOptions.justGenerateRpcSslCerts -> {
Node.printWarning("The --just-generate-rpc-ssl-settings flag has been deprecated and will be removed in a future version. Use the generate-rpc-ssl-settings command instead.")
justGenerateRpcSslCertsCli.cmdLineOptions.copyFrom(cmdLineOptions)
justGenerateRpcSslCertsCli.runProgram()
}
else -> startup.initialiseAndRun(cmdLineOptions, object : RunAfterNodeInitialisation {
val startupTime = System.currentTimeMillis()
override fun run(node: Node) = startup.startNode(node, startupTime)
})
}
}
@Mixin
val cmdLineOptions = NodeCmdLineOptions()
}
/** This class provides a common set of functionality for starting a Node from command line arguments. */
open class NodeStartup : NodeStartupLogging {
companion object {
private val logger by lazy { loggerFor<Node>() } // I guess this is lazy to allow for logging init, but why Node?
const val LOGS_DIRECTORY_NAME = "logs"
const val LOGS_CAN_BE_FOUND_IN_STRING = "Logs can be found in"
}
lateinit var cmdLineOptions: SharedNodeCmdLineOptions
fun initialiseAndRun(cmdLineOptions: SharedNodeCmdLineOptions, afterNodeInitialisation: RunAfterNodeInitialisation): Int {
this.cmdLineOptions = cmdLineOptions
/**
* @return exit code based on the success of the node startup. This value is intended to be the exit code of the process.
*/
override fun runProgram(): Int {
val startTime = System.currentTimeMillis()
// Step 1. Check for supported Java version.
if (!isValidJavaVersion()) return ExitCodes.FAILURE
if (isInvalidJavaVersion()) return ExitCodes.FAILURE
// Step 2. We do the single node check before we initialise logging so that in case of a double-node start it
// doesn't mess with the running node's logs.
enforceSingleNodeIsRunning(cmdLineOptions.baseDirectory)
// Step 3. Initialise logging.
initLogging()
// Step 4. Register all cryptography [Provider]s.
// Step 3. Register all cryptography [Provider]s.
// Required to install our [SecureRandom] before e.g., UUID asks for one.
// This needs to go after initLogging(netty clashes with our logging).
// This needs to go after initLogging(netty clashes with our logging)
Crypto.registerProviders()
// Step 5. Print banner and basic node info.
// Step 4. Print banner and basic node info.
val versionInfo = getVersionInfo()
drawBanner(versionInfo)
Node.printBasicNodeInfo(LOGS_CAN_BE_FOUND_IN_STRING, System.getProperty("log-path"))
// Step 6. Load and validate node configuration.
val configuration = (attempt { loadConfiguration() }.doOnException(handleConfigurationLoadingError(cmdLineOptions.configFile)) as? Try.Success)?.let(Try.Success<NodeConfiguration>::value) ?: return ExitCodes.FAILURE
// Step 5. Load and validate node configuration.
val configuration = (attempt { cmdLineOptions.loadConfig() }.doOnException(handleConfigurationLoadingError(cmdLineOptions.configFile)) as? Try.Success)?.let(Try.Success<NodeConfiguration>::value)
?: return ExitCodes.FAILURE
val errors = configuration.validate()
if (errors.isNotEmpty()) {
logger.error("Invalid node configuration. Errors were:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
return ExitCodes.FAILURE
}
// Step 7. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization.
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success ?: return ExitCodes.FAILURE
// Step 6. Configuring special serialisation requirements, i.e., bft-smart relies on Java serialization.
attempt { banJavaSerialisation(configuration) }.doOnException { error -> error.logAsUnexpected("Exception while configuring serialisation") } as? Try.Success
?: return ExitCodes.FAILURE
// Step 8. Any actions required before starting up the Corda network layer.
attempt { preNetworkRegistration(configuration) }.doOnException(handleRegistrationError) as? Try.Success ?: return ExitCodes.FAILURE
// Step 7. Any actions required before starting up the Corda network layer.
attempt { preNetworkRegistration(configuration) }.doOnException(::handleRegistrationError) as? Try.Success
?: return ExitCodes.FAILURE
// Step 9. Check if in registration mode.
checkAndRunRegistrationMode(configuration, versionInfo)?.let {
return if (it) ExitCodes.SUCCESS
else ExitCodes.FAILURE
}
// Step 10. Log startup info.
// Step 8. Log startup info.
logStartupInfo(versionInfo, configuration)
// Step 11. Start node: create the node, check for other command-line options, add extra logging etc.
attempt { startNode(configuration, versionInfo, startTime) }.doOnSuccess { logger.info("Node exiting successfully") }.doOnException(handleStartError) as? Try.Success ?: return ExitCodes.FAILURE
// Step 9. Start node: create the node, check for other command-line options, add extra logging etc.
attempt {
cmdLineOptions.baseDirectory.createDirectories()
afterNodeInitialisation.run(createNode(configuration, versionInfo))
}.doOnException(::handleStartError) as? Try.Success ?: return ExitCodes.FAILURE
return ExitCodes.SUCCESS
}
private fun checkAndRunRegistrationMode(configuration: NodeConfiguration, versionInfo: VersionInfo): Boolean? {
checkUnfinishedRegistration()
cmdLineOptions.nodeRegistrationOption?.let {
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig]
attempt { registerWithNetwork(configuration, versionInfo, it) }.doOnException(handleRegistrationError) as? Try.Success
?: return false
// At this point the node registration was successful. We can delete the marker file.
deleteNodeRegistrationMarker(cmdLineOptions.baseDirectory)
return true
}
return null
}
// TODO: Reconsider if automatic re-registration should be applied when something failed during initial registration.
// There might be cases where the node user should investigate what went wrong before registering again.
private fun checkUnfinishedRegistration() {
if (checkRegistrationMode() && !cmdLineOptions.isRegistration) {
println("Node was started before with `--initial-registration`, but the registration was not completed.\nResuming registration.")
// Pretend that the node was started with `--initial-registration` to help prevent user error.
cmdLineOptions.isRegistration = true
}
}
private fun <RESULT> attempt(action: () -> RESULT): Try<RESULT> = Try.on(action)
private fun Exception.isExpectedWhenStartingNode() = startNodeExpectedErrors.any { error -> error.isInstance(this) }
private val startNodeExpectedErrors = setOf(MultipleCordappsForFlowException::class, CheckpointIncompatibleException::class, AddressBindingException::class, NetworkParametersReader::class, DatabaseIncompatibleException::class)
private fun Exception.logAsExpected(message: String? = this.message, print: (String?) -> Unit = logger::error) = print(message)
private fun Exception.logAsUnexpected(message: String? = this.message, error: Exception = this, print: (String?, Throwable) -> Unit = logger::error) = print("$message${this.message?.let { ": $it" } ?: ""}", error)
private fun Exception.isOpenJdkKnownIssue() = message?.startsWith("Unknown named curve:") == true
private val handleRegistrationError = { error: Exception ->
when (error) {
is NodeRegistrationException -> error.logAsExpected("Node registration service is unavailable. Perhaps try to perform the initial registration again after a while.")
else -> error.logAsUnexpected("Exception during node registration")
}
}
private val handleStartError = { error: Exception ->
when {
error.isExpectedWhenStartingNode() -> error.logAsExpected()
error is CouldNotCreateDataSourceException -> error.logAsUnexpected()
error is Errors.NativeIoException && error.message?.contains("Address already in use") == true -> error.logAsExpected("One of the ports required by the Corda node is already in use.")
error.isOpenJdkKnownIssue() -> error.logAsExpected("Exception during node startup - ${error.message}. This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.")
else -> error.logAsUnexpected("Exception during node startup")
}
}
private fun handleConfigurationLoadingError(configFile: Path) = { error: Exception ->
when (error) {
is UnknownConfigurationKeysException -> error.logAsExpected()
is ConfigException.IO -> error.logAsExpected(configFileNotFoundMessage(configFile), ::println)
else -> error.logAsUnexpected("Unexpected error whilst reading node configuration")
}
}
private fun configFileNotFoundMessage(configFile: Path): String {
return """
Unable to load the node config file from '$configFile'.
Try setting the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly.
""".trimIndent()
}
private fun loadConfiguration(): NodeConfiguration {
val (rawConfig, configurationResult) = loadConfigFile()
if (cmdLineOptions.devMode == true) {
println("Config:\n${rawConfig.root().render(ConfigRenderOptions.defaults())}")
}
val configuration = configurationResult.getOrThrow()
return if (cmdLineOptions.bootstrapRaftCluster) {
println("Bootstrapping raft cluster (starting up as seed node).")
// Ignore the configured clusterAddresses to make the node bootstrap a cluster instead of joining.
(configuration as NodeConfigurationImpl).copy(notary = configuration.notary?.copy(raft = configuration.notary?.raft?.copy(clusterAddresses = emptyList())))
} else {
configuration
}
}
private fun checkRegistrationMode(): Boolean {
// If the node was started with `--initial-registration`, create marker file.
// We do this here to ensure the marker is created even if parsing the args with NodeArgsParser fails.
val marker = cmdLineOptions.baseDirectory / INITIAL_REGISTRATION_MARKER
if (!cmdLineOptions.isRegistration && !marker.exists()) {
return false
}
try {
marker.createFile()
} catch (e: Exception) {
logger.warn("Could not create marker file for `--initial-registration`.", e)
}
return true
}
private fun deleteNodeRegistrationMarker(baseDir: Path) {
try {
val marker = File((baseDir / INITIAL_REGISTRATION_MARKER).toUri())
if (marker.exists()) {
marker.delete()
}
} catch (e: Exception) {
e.logAsUnexpected("Could not delete the marker file that was created for `--initial-registration`.", print = logger::warn)
}
}
protected open fun preNetworkRegistration(conf: NodeConfiguration) = Unit
protected open fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
open fun createNode(conf: NodeConfiguration, versionInfo: VersionInfo): Node = Node(conf, versionInfo)
protected open fun startNode(conf: NodeConfiguration, versionInfo: VersionInfo, startTime: Long) {
cmdLineOptions.baseDirectory.createDirectories()
val node = createNode(conf, versionInfo)
if (cmdLineOptions.clearNetworkMapCache) {
node.clearNetworkMapCache()
return
}
if (cmdLineOptions.justGenerateNodeInfo) {
// Perform the minimum required start-up logic to be able to write a nodeInfo to disk
node.generateAndSaveNodeInfo()
return
}
if (cmdLineOptions.justGenerateRpcSslCerts) {
generateRpcSslCertificates(conf)
return
}
if (conf.devMode) {
fun startNode(node: Node, startTime: Long) {
if (node.configuration.devMode) {
Emoji.renderIfSupported {
Node.printWarning("This node is running in developer mode! ${Emoji.developer} This is not safe for production deployment.")
Node.printWarning("This node is running in development mode! ${Emoji.developer} This is not safe for production deployment.")
}
} else {
logger.info("The Corda node is running in production mode. If this is a developer environment you can set 'devMode=true' in the node.conf file.")
}
val nodeInfo = node.start()
logLoadedCorDapps(node.services.cordappProvider.cordapps)
val loadedCodapps = node.services.cordappProvider.cordapps.filter { it.isLoaded }
logLoadedCorDapps(loadedCodapps)
node.nodeReadyFuture.thenMatch({
// Elapsed time in seconds. We used 10 / 100.0 and not directly / 1000.0 to only keep two decimal digits.
@ -265,7 +191,7 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
Node.printBasicNodeInfo("Node for \"$name\" started up and registered in $elapsed sec")
// Don't start the shell if there's no console attached.
if (conf.shouldStartLocalShell()) {
if (node.configuration.shouldStartLocalShell()) {
node.startupComplete.then {
try {
InteractiveShell.runLocalShell(node::stop)
@ -274,8 +200,8 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
}
}
}
if (conf.shouldStartSSHDaemon()) {
Node.printBasicNodeInfo("SSH server listening on port", conf.sshd!!.port.toString())
if (node.configuration.shouldStartSSHDaemon()) {
Node.printBasicNodeInfo("SSH server listening on port", node.configuration.sshd!!.port.toString())
}
},
{ th ->
@ -284,82 +210,6 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
node.run()
}
private fun generateRpcSslCertificates(conf: NodeConfiguration) {
val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(conf.myLegalName.x500Principal)
val keyStorePath = conf.baseDirectory / "certificates" / "rpcsslkeystore.jks"
val trustStorePath = conf.baseDirectory / "certificates" / "export" / "rpcssltruststore.jks"
if (keyStorePath.exists() || trustStorePath.exists()) {
println("Found existing RPC SSL keystores. Command was already run. Exiting..")
exitProcess(0)
}
val console: Console? = System.console()
when (console) {
// In this case, the JVM is not connected to the console so we need to exit.
null -> {
println("Not connected to console. Exiting")
exitProcess(1)
}
// Otherwise we can proceed normally.
else -> {
while (true) {
val keystorePassword1 = console.readPassword("Enter the RPC keystore password => ")
// TODO: consider adding a password strength policy.
if (keystorePassword1.isEmpty()) {
println("The RPC keystore password cannot be an empty String.")
continue
}
val keystorePassword2 = console.readPassword("Re-enter the RPC keystore password => ")
if (!keystorePassword1.contentEquals(keystorePassword2)) {
println("The RPC keystore passwords don't match.")
continue
}
saveToKeyStore(keyStorePath, keyPair, cert, String(keystorePassword1), "rpcssl")
println("The RPC keystore was saved to: $keyStorePath .")
break
}
while (true) {
val trustStorePassword1 = console.readPassword("Enter the RPC truststore password => ")
// TODO: consider adding a password strength policy.
if (trustStorePassword1.isEmpty()) {
println("The RPC truststore password cannot be an empty String.")
continue
}
val trustStorePassword2 = console.readPassword("Re-enter the RPC truststore password => ")
if (!trustStorePassword1.contentEquals(trustStorePassword2)) {
println("The RPC truststore passwords don't match.")
continue
}
saveToTrustStore(trustStorePath, cert, String(trustStorePassword1), "rpcssl")
println("The RPC truststore was saved to: $trustStorePath .")
println("You need to distribute this file along with the password in a secure way to all RPC clients.")
break
}
val dollar = '$'
println("""
|
|The SSL certificates for RPC were generated successfully.
|
|Add this snippet to the "rpcSettings" section of your node.conf:
| useSsl=true
| ssl {
| keyStorePath=$dollar{baseDirectory}/certificates/rpcsslkeystore.jks
| keyStorePassword=the_above_password
| }
|""".trimMargin())
}
}
}
protected open fun logStartupInfo(versionInfo: VersionInfo, conf: NodeConfiguration) {
logger.info("Vendor: ${versionInfo.vendor}")
logger.info("Release: ${versionInfo.releaseVersion}")
@ -378,36 +228,19 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
if (agentProperties.containsKey("sun.jdwp.listenerAddress")) {
logger.info("Debug port: ${agentProperties.getProperty("sun.jdwp.listenerAddress")}")
}
logger.info("Starting as node on ${conf.p2pAddress}")
var nodeStartedMessage = "Starting as node on ${conf.p2pAddress}"
if (conf.extraNetworkMapKeys.isNotEmpty()) {
nodeStartedMessage = "$nodeStartedMessage with additional Network Map keys ${conf.extraNetworkMapKeys.joinToString(prefix = "[", postfix = "]", separator = ", ")}"
}
logger.info(nodeStartedMessage)
}
protected open fun registerWithNetwork(conf: NodeConfiguration, versionInfo: VersionInfo, nodeRegistrationConfig: NodeRegistrationOption) {
val compatibilityZoneURL = conf.networkServices?.doormanURL ?: throw RuntimeException(
"compatibilityZoneURL or networkServices must be configured!")
println()
println("******************************************************************")
println("* *")
println("* Registering as a new participant with Corda network *")
println("* *")
println("******************************************************************")
NodeRegistrationHelper(conf, HTTPNetworkRegistrationService(compatibilityZoneURL, versionInfo), nodeRegistrationConfig).buildKeystore()
// Minimal changes to make registration tool create node identity.
// TODO: Move node identity generation logic from node to registration helper.
createNode(conf, getVersionInfo()).generateAndSaveNodeInfo()
println("Successfully registered Corda node with compatibility zone, node identity keys and certificates are stored in '${conf.certificatesDirectory}', it is advised to backup the private keys and certificates.")
println("Corda node will now terminate.")
}
protected open fun loadConfigFile(): Pair<Config, Try<NodeConfiguration>> = cmdLineOptions.loadConfig()
protected open fun banJavaSerialisation(conf: NodeConfiguration) {
SerialFilter.install(if (conf.notary?.bftSMaRt != null) ::bftSMaRtSerialFilter else ::defaultSerialFilter)
// Note that in dev mode this filter can be overridden by a notary service implementation.
SerialFilter.install(::defaultSerialFilter)
}
protected open fun getVersionInfo(): VersionInfo {
open fun getVersionInfo(): VersionInfo {
return VersionInfo(
PLATFORM_VERSION,
CordaVersionProvider.releaseVersion,
@ -460,18 +293,6 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
}
}
override fun initLogging() {
val loggingLevel = loggingLevel.name.toLowerCase(Locale.ENGLISH)
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
if (verbose) {
System.setProperty("consoleLogLevel", loggingLevel)
Node.renderBasicInfoToConsole = false
}
System.setProperty("log-path", (cmdLineOptions.baseDirectory / LOGS_DIRECTORY_NAME).toString())
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
SLF4JBridgeHandler.install()
}
private fun lookupMachineNameAndMaybeWarn(): String {
val start = System.currentTimeMillis()
val hostName: String = InetAddress.getLocalHost().hostName
@ -575,3 +396,66 @@ open class NodeStartup : CordaCliWrapper("corda", "Runs a Corda Node") {
}
}
/** Provide some common logging methods for node startup commands. */
interface NodeStartupLogging {
companion object {
val logger by lazy { contextLogger() }
val startupErrors = setOf(MultipleCordappsForFlowException::class, CheckpointIncompatibleException::class, AddressBindingException::class, NetworkParametersReader::class, DatabaseIncompatibleException::class)
}
fun <RESULT> attempt(action: () -> RESULT): Try<RESULT> = Try.on(action)
fun Exception.logAsExpected(message: String? = this.message, print: (String?) -> Unit = logger::error) = print(message)
fun Exception.logAsUnexpected(message: String? = this.message, error: Exception = this, print: (String?, Throwable) -> Unit = logger::error) = print("$message${this.message?.let { ": $it" } ?: ""}", error)
fun handleRegistrationError(error: Exception) {
when (error) {
is NodeRegistrationException -> error.logAsExpected("Issue with Node registration: ${error.message}")
else -> error.logAsUnexpected("Exception during node registration")
}
}
fun Exception.isOpenJdkKnownIssue() = message?.startsWith("Unknown named curve:") == true
fun Exception.isExpectedWhenStartingNode() = startupErrors.any { error -> error.isInstance(this) }
fun handleStartError(error: Exception) {
when {
error.isExpectedWhenStartingNode() -> error.logAsExpected()
error is CouldNotCreateDataSourceException -> error.logAsUnexpected()
error is Errors.NativeIoException && error.message?.contains("Address already in use") == true -> error.logAsExpected("One of the ports required by the Corda node is already in use.")
error.isOpenJdkKnownIssue() -> error.logAsExpected("Exception during node startup - ${error.message}. This is a known OpenJDK issue on some Linux distributions, please use OpenJDK from zulu.org or Oracle JDK.")
else -> error.logAsUnexpected("Exception during node startup")
}
}
fun handleConfigurationLoadingError(configFile: Path) = { error: Exception ->
when (error) {
is UnknownConfigurationKeysException -> error.logAsExpected()
is ConfigException.IO -> error.logAsExpected(configFileNotFoundMessage(configFile), ::println)
else -> error.logAsUnexpected("Unexpected error whilst reading node configuration")
}
}
private fun configFileNotFoundMessage(configFile: Path): String {
return """
Unable to load the node config file from '$configFile'.
Try setting the --base-directory flag to change which directory the node
is looking in, or use the --config-file flag to specify it explicitly.
""".trimIndent()
}
}
fun CliWrapperBase.initLogging(baseDirectory: Path) {
val loggingLevel = loggingLevel.name.toLowerCase(Locale.ENGLISH)
System.setProperty("defaultLogLevel", loggingLevel) // These properties are referenced from the XML config file.
if (verbose) {
System.setProperty("consoleLogLevel", loggingLevel)
Node.renderBasicInfoToConsole = false
}
System.setProperty("log-path", (baseDirectory / NodeCliCommand.LOGS_DIRECTORY_NAME).toString())
SLF4JBridgeHandler.removeHandlersForRootLogger() // The default j.u.l config adds a ConsoleHandler.
SLF4JBridgeHandler.install()
}

View File

@ -5,30 +5,26 @@ import com.typesafe.config.ConfigFactory
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.internal.isDirectory
import net.corda.core.internal.noneOrSingle
import net.corda.core.utilities.contextLogger
import java.nio.file.Path
import java.nio.file.Paths
class CordappConfigFileProvider(private val configDir: Path = DEFAULT_CORDAPP_CONFIG_DIR) : CordappConfigProvider {
class CordappConfigFileProvider(cordappDirectories: List<Path>) : CordappConfigProvider {
companion object {
val DEFAULT_CORDAPP_CONFIG_DIR = Paths.get("cordapps") / "config"
const val CONFIG_EXT = ".conf"
val logger = contextLogger()
private val logger = contextLogger()
}
init {
configDir.createDirectories()
}
private val configDirectories = cordappDirectories.map { (it / "config").createDirectories() }
override fun getConfigByName(name: String): Config {
val configFile = configDir / "$name$CONFIG_EXT"
return if (configFile.exists()) {
check(!configFile.isDirectory()) { "${configFile.toAbsolutePath()} is a directory, expected a config file" }
logger.info("Found config for cordapp $name in ${configFile.toAbsolutePath()}")
// TODO There's nothing stopping the same CorDapp jar from occuring in different directories and thus causing
// conflicts. The cordappDirectories list config option should just be a single cordappDirectory
val configFile = configDirectories.map { it / "$name.conf" }.noneOrSingle { it.exists() }
return if (configFile != null) {
logger.info("Found config for cordapp $name in $configFile")
ConfigFactory.parseFile(configFile.toFile())
} else {
logger.info("No config found for cordapp $name in ${configFile.toAbsolutePath()}")
logger.info("No config found for cordapp $name in $configDirectories")
ConfigFactory.empty()
}
}

View File

@ -1,8 +1,7 @@
package net.corda.node.internal.cordapp
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner
import io.github.lukehutch.fastclasspathscanner.scanner.ScanResult
import net.corda.core.contracts.warnContractWithoutConstraintPropagation
import io.github.classgraph.ClassGraph
import io.github.classgraph.ScanResult
import net.corda.core.cordapp.Cordapp
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
@ -10,6 +9,8 @@ import net.corda.core.flows.*
import net.corda.core.internal.*
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappInfoResolver
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.node.services.CordaService
import net.corda.core.schemas.MappedSchema
import net.corda.core.serialization.SerializationCustomSerializer
@ -18,7 +19,6 @@ import net.corda.core.serialization.SerializeAsToken
import net.corda.core.utilities.contextLogger
import net.corda.node.VersionInfo
import net.corda.node.cordapp.CordappLoader
import net.corda.node.internal.classloading.requireAnnotation
import net.corda.nodeapi.internal.coreContractClasses
import net.corda.serialization.internal.DefaultWhitelist
import org.apache.commons.collections4.map.LRUMap
@ -26,6 +26,7 @@ import java.lang.reflect.Modifier
import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Path
import java.security.cert.X509Certificate
import java.util.*
import java.util.jar.JarInputStream
import kotlin.reflect.KClass
@ -37,9 +38,13 @@ import kotlin.streams.toList
* @property cordappJarPaths The classpath of cordapp JARs
*/
class JarScanningCordappLoader private constructor(private val cordappJarPaths: List<RestrictedURL>,
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN) : CordappLoaderTemplate() {
private val versionInfo: VersionInfo = VersionInfo.UNKNOWN,
extraCordapps: List<CordappImpl>,
private val blacklistedCordappSigners: List<X509Certificate> = emptyList()) : CordappLoaderTemplate() {
override val cordapps: List<CordappImpl> by lazy { loadCordapps() + coreCordapp }
override val cordapps: List<CordappImpl> by lazy {
loadCordapps() + extraCordapps
}
override val appClassLoader: ClassLoader = URLClassLoader(cordappJarPaths.stream().map { it.url }.toTypedArray(), javaClass.classLoader)
@ -57,11 +62,15 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
/**
* Creates a CordappLoader from multiple directories.
*
* @param corDappDirectories Directories used to scan for CorDapp JARs.
* @param cordappDirs Directories used to scan for CorDapp JARs.
*/
fun fromDirectories(corDappDirectories: Iterable<Path>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader {
logger.info("Looking for CorDapps in ${corDappDirectories.distinct().joinToString(", ", "[", "]")}")
return JarScanningCordappLoader(corDappDirectories.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }, versionInfo)
fun fromDirectories(cordappDirs: Collection<Path>,
versionInfo: VersionInfo = VersionInfo.UNKNOWN,
extraCordapps: List<CordappImpl> = emptyList(),
blacklistedCerts: List<X509Certificate> = emptyList()): JarScanningCordappLoader {
logger.info("Looking for CorDapps in ${cordappDirs.distinct().joinToString(", ", "[", "]")}")
val paths = cordappDirs.distinct().flatMap(this::jarUrlsInDirectory).map { it.restricted() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, blacklistedCerts)
}
/**
@ -69,8 +78,9 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
*
* @param scanJars Uses the JAR URLs provided for classpath scanning and Cordapp detection.
*/
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN): JarScanningCordappLoader {
return JarScanningCordappLoader(scanJars.map { it.restricted() }, versionInfo)
fun fromJarUrls(scanJars: List<URL>, versionInfo: VersionInfo = VersionInfo.UNKNOWN, extraCordapps: List<CordappImpl> = emptyList(), blacklistedCerts: List<X509Certificate> = emptyList()): JarScanningCordappLoader {
val paths = scanJars.map { it.restricted() }
return JarScanningCordappLoader(paths, versionInfo, extraCordapps, blacklistedCerts)
}
private fun URL.restricted(rootPackageName: String? = null) = RestrictedURL(this, rootPackageName)
@ -86,48 +96,38 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
}
}
}
/** A list of the core RPC flows present in Corda */
private val coreRPCFlows = listOf(
ContractUpgradeFlow.Initiate::class.java,
ContractUpgradeFlow.Authorise::class.java,
ContractUpgradeFlow.Deauthorise::class.java)
}
/** A Cordapp representing the core package which is not scanned automatically. */
@VisibleForTesting
internal val coreCordapp = CordappImpl(
contractClassNames = listOf(),
initiatedFlows = listOf(),
rpcFlows = coreRPCFlows,
serviceFlows = listOf(),
schedulableFlows = listOf(),
services = listOf(),
serializationWhitelists = listOf(),
serializationCustomSerializers = listOf(),
customSchemas = setOf(),
info = CordappImpl.Info("corda-core", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion),
allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash
)
private fun loadCordapps(): List<CordappImpl> {
val cordapps = cordappJarPaths.map { scanCordapp(it).toCordapp(it) }
val cordapps = cordappJarPaths
.map { url -> scanCordapp(url).use { it.toCordapp(url) } }
.filter {
if (it.info.minimumPlatformVersion > versionInfo.platformVersion) {
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum platform version ${it.info.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).")
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it requires minimum " +
"platform version ${it.info.minimumPlatformVersion} (This node is running version ${versionInfo.platformVersion}).")
false
} else {
true
}
}
.filter {
if (blacklistedCordappSigners.isEmpty()) {
true //Nothing blacklisted, no need to check
} else {
val certificates = it.jarPath.openStream().let(::JarInputStream).use(JarSignatureCollector::collectCertificates)
if (certificates.isEmpty() || (certificates - blacklistedCordappSigners).isNotEmpty())
true // Cordapp is not signed or it is signed by at least one non-blacklisted certificate
else {
logger.warn("Not loading CorDapp ${it.info.shortName} (${it.info.vendor}) as it is signed by development key(s) only: " +
"${certificates.intersect(blacklistedCordappSigners).map { it.publicKey }}.")
false
}
}
}
cordapps.forEach { CordappInfoResolver.register(it.cordappClasses, it.info) }
return cordapps
}
private fun RestrictedScanResult.toCordapp(url: RestrictedURL): CordappImpl {
val info = url.url.openStream().let(::JarInputStream).use { it.manifest }.toCordappInfo(CordappImpl.jarName(url.url))
val info = url.url.openStream().let(::JarInputStream).use { it.manifest?.toCordappInfo(CordappImpl.jarName(url.url)) ?: CordappImpl.Info.UNKNOWN }
return CordappImpl(
findContractClassNames(this),
findInitiatedFlows(this),
@ -141,10 +141,21 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
findAllFlows(this),
url.url,
info,
getJarHash(url.url)
getJarHash(url.url),
findNotaryService(this)
)
}
private fun findNotaryService(scanResult: RestrictedScanResult): Class<out NotaryService>? {
// Note: we search for implementations of both NotaryService and TrustedAuthorityNotaryService as
// the scanner won't find subclasses deeper down the hierarchy if any intermediate class is not
// present in the CorDapp.
val result = scanResult.getClassesWithSuperclass(NotaryService::class) +
scanResult.getClassesWithSuperclass(TrustedAuthorityNotaryService::class)
logger.info("Found notary service CorDapp implementations: " + result.joinToString(", "))
return result.firstOrNull()
}
private fun getJarHash(url: URL): SecureHash.SHA256 = url.openStream().readFully().sha256()
private fun findServices(scanResult: RestrictedScanResult): List<Class<out SerializeAsToken>> {
@ -153,17 +164,6 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
private fun findInitiatedFlows(scanResult: RestrictedScanResult): List<Class<out FlowLogic<*>>> {
return scanResult.getClassesWithAnnotation(FlowLogic::class, InitiatedBy::class)
// First group by the initiating flow class in case there are multiple mappings
.groupBy { it.requireAnnotation<InitiatedBy>().value.java }
.map { (initiatingFlow, initiatedFlows) ->
val sorted = initiatedFlows.sortedWith(FlowTypeHierarchyComparator(initiatingFlow))
if (sorted.size > 1) {
logger.warn("${initiatingFlow.name} has been specified as the inititating flow by multiple flows " +
"in the same type hierarchy: ${sorted.joinToString { it.name }}. Choosing the most " +
"specific sub-type for registration: ${sorted[0].name}.")
}
sorted[0]
}
}
private fun Class<out FlowLogic<*>>.isUserInvokable(): Boolean {
@ -205,7 +205,7 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
}
private fun findCustomSchemas(scanResult: RestrictedScanResult): Set<MappedSchema> {
return scanResult.getClassesWithSuperclass(MappedSchema::class).toSet()
return scanResult.getClassesWithSuperclass(MappedSchema::class).instances().toSet()
}
private val cachedScanResult = LRUMap<RestrictedURL, RestrictedScanResult>(1000)
@ -213,21 +213,12 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
private fun scanCordapp(cordappJarPath: RestrictedURL): RestrictedScanResult {
logger.info("Scanning CorDapp in ${cordappJarPath.url}")
return cachedScanResult.computeIfAbsent(cordappJarPath) {
RestrictedScanResult(FastClasspathScanner().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).scan(), cordappJarPath.qualifiedNamePrefix)
val scanResult = ClassGraph().addClassLoader(appClassLoader).overrideClasspath(cordappJarPath.url).enableAllInfo().scan()
RestrictedScanResult(scanResult, cordappJarPath.qualifiedNamePrefix)
}
}
private class FlowTypeHierarchyComparator(val initiatingFlow: Class<out FlowLogic<*>>) : Comparator<Class<out FlowLogic<*>>> {
override fun compare(o1: Class<out FlowLogic<*>>, o2: Class<out FlowLogic<*>>): Int {
return when {
o1 == o2 -> 0
o1.isAssignableFrom(o2) -> 1
o2.isAssignableFrom(o1) -> -1
else -> throw IllegalArgumentException("${initiatingFlow.name} has been specified as the initiating flow by " +
"both ${o1.name} and ${o2.name}")
}
}
}
private fun <T : Any> loadClass(className: String, type: KClass<T>): Class<out T>? {
return try {
@ -246,41 +237,53 @@ class JarScanningCordappLoader private constructor(private val cordappJarPaths:
val qualifiedNamePrefix: String get() = rootPackageName?.let { "$it." } ?: ""
}
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) {
private fun <T : Any> List<Class<out T>>.instances(): List<T> {
return map { it.kotlin.objectOrNewInstance() }
}
private inner class RestrictedScanResult(private val scanResult: ScanResult, private val qualifiedNamePrefix: String) : AutoCloseable {
fun getNamesOfClassesImplementing(type: KClass<*>): List<String> {
return scanResult.getNamesOfClassesImplementing(type.java)
.filter { it.startsWith(qualifiedNamePrefix) }
return scanResult.getClassesImplementing(type.java.name).names.filter { it.startsWith(qualifiedNamePrefix) }
}
fun <T : Any> getClassesWithSuperclass(type: KClass<T>): List<T> {
return scanResult.getNamesOfSubclassesOf(type.java)
fun <T : Any> getClassesWithSuperclass(type: KClass<T>): List<Class<out T>> {
return scanResult
.getSubclasses(type.java.name)
.names
.filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) }
.map { it.kotlin.objectOrNewInstance() }
.filterNot { it.isAbstractClass }
}
fun <T : Any> getClassesImplementing(type: KClass<T>): List<T> {
return scanResult.getNamesOfClassesImplementing(type.java)
return scanResult
.getClassesImplementing(type.java.name)
.names
.filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) }
.filterNot { it.isAbstractClass }
.map { it.kotlin.objectOrNewInstance() }
}
fun <T : Any> getClassesWithAnnotation(type: KClass<T>, annotation: KClass<out Annotation>): List<Class<out T>> {
return scanResult.getNamesOfClassesWithAnnotation(annotation.java)
return scanResult
.getClassesWithAnnotation(annotation.java.name)
.names
.filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) }
}
fun <T : Any> getConcreteClassesOfType(type: KClass<T>): List<Class<out T>> {
return scanResult.getNamesOfSubclassesOf(type.java)
return scanResult
.getSubclasses(type.java.name)
.names
.filter { it.startsWith(qualifiedNamePrefix) }
.mapNotNull { loadClass(it, type) }
.filterNot { Modifier.isAbstract(it.modifiers) }
.filterNot { it.isAbstractClass }
}
override fun close() = scanResult.close()
}
}

View File

@ -1,10 +1,11 @@
package net.corda.node.internal.cordapp
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.cordapp.CordappImpl.Info.Companion.UNKNOWN_VALUE
import java.util.jar.Attributes
import java.util.jar.Manifest
fun createTestManifest(name: String, title: String, version: String, vendor: String): Manifest {
fun createTestManifest(name: String, title: String, version: String, vendor: String, targetVersion: Int): Manifest {
val manifest = Manifest()
// Mandatory manifest attribute. If not present, all other entries are silently skipped.
@ -19,27 +20,28 @@ fun createTestManifest(name: String, title: String, version: String, vendor: Str
manifest["Implementation-Title"] = title
manifest["Implementation-Version"] = version
manifest["Implementation-Vendor"] = vendor
manifest["Target-Platform-Version"] = targetVersion.toString()
return manifest
}
operator fun Manifest.set(key: String, value: String) {
mainAttributes.putValue(key, value)
operator fun Manifest.set(key: String, value: String): String? {
return mainAttributes.putValue(key, value)
}
fun Manifest?.toCordappInfo(defaultShortName: String): CordappImpl.Info {
var info = CordappImpl.Info.UNKNOWN
(this?.mainAttributes?.getValue("Name") ?: defaultShortName).let { shortName ->
info = info.copy(shortName = shortName)
}
this?.mainAttributes?.getValue("Implementation-Vendor")?.let { vendor ->
info = info.copy(vendor = vendor)
}
this?.mainAttributes?.getValue("Implementation-Version")?.let { version ->
info = info.copy(version = version)
}
val minPlatformVersion = this?.mainAttributes?.getValue("Min-Platform-Version")?.toInt() ?: 1
val targetPlatformVersion = this?.mainAttributes?.getValue("Target-Platform-Version")?.toInt() ?: minPlatformVersion
info = info.copy(minimumPlatformVersion = minPlatformVersion, targetPlatformVersion = targetPlatformVersion)
return info
}
operator fun Manifest.get(key: String): String? = mainAttributes.getValue(key)
fun Manifest.toCordappInfo(defaultShortName: String): CordappImpl.Info {
val shortName = this["Name"] ?: defaultShortName
val vendor = this["Implementation-Vendor"] ?: UNKNOWN_VALUE
val version = this["Implementation-Version"] ?: UNKNOWN_VALUE
val minPlatformVersion = this["Min-Platform-Version"]?.toIntOrNull() ?: 1
val targetPlatformVersion = this["Target-Platform-Version"]?.toIntOrNull() ?: minPlatformVersion
return CordappImpl.Info(
shortName = shortName,
vendor = vendor,
version = version,
minimumPlatformVersion = minPlatformVersion,
targetPlatformVersion = targetPlatformVersion
)
}

View File

@ -0,0 +1,60 @@
package net.corda.node.internal.cordapp
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.ContractUpgradeFlow
import net.corda.core.internal.cordapp.CordappImpl
import net.corda.core.internal.location
import net.corda.node.VersionInfo
import net.corda.node.services.transactions.NodeNotarySchemaV1
import net.corda.node.services.transactions.SimpleNotaryService
internal object VirtualCordapp {
/** A list of the core RPC flows present in Corda */
private val coreRpcFlows = listOf(
ContractUpgradeFlow.Initiate::class.java,
ContractUpgradeFlow.Authorise::class.java,
ContractUpgradeFlow.Deauthorise::class.java
)
/** A Cordapp representing the core package which is not scanned automatically. */
fun generateCoreCordapp(versionInfo: VersionInfo): CordappImpl {
return CordappImpl(
contractClassNames = listOf(),
initiatedFlows = listOf(),
rpcFlows = coreRpcFlows,
serviceFlows = listOf(),
schedulableFlows = listOf(),
services = listOf(),
serializationWhitelists = listOf(),
serializationCustomSerializers = listOf(),
customSchemas = setOf(),
info = CordappImpl.Info("corda-core", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion),
allFlows = listOf(),
jarPath = ContractUpgradeFlow.javaClass.location, // Core JAR location
jarHash = SecureHash.allOnesHash,
notaryService = null,
isLoaded = false
)
}
/** A Cordapp for the built-in notary service implementation. */
fun generateSimpleNotaryCordapp(versionInfo: VersionInfo): CordappImpl {
return CordappImpl(
contractClassNames = listOf(),
initiatedFlows = listOf(),
rpcFlows = listOf(),
serviceFlows = listOf(),
schedulableFlows = listOf(),
services = listOf(),
serializationWhitelists = listOf(),
serializationCustomSerializers = listOf(),
customSchemas = setOf(NodeNotarySchemaV1),
info = CordappImpl.Info("corda-notary", versionInfo.vendor, versionInfo.releaseVersion, 1, versionInfo.platformVersion),
allFlows = listOf(),
jarPath = SimpleNotaryService::class.java.location,
jarHash = SecureHash.allOnesHash,
notaryService = SimpleNotaryService::class.java,
isLoaded = false
)
}
}

View File

@ -4,14 +4,13 @@ package net.corda.node.internal.security
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.google.common.primitives.Ints
import net.corda.core.context.AuthServiceId
import net.corda.core.internal.buildNamed
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.uncheckedCast
import net.corda.core.utilities.loggerFor
import net.corda.node.internal.DataSourceFactory
import net.corda.node.services.config.AuthDataSourceType
import net.corda.node.services.config.PasswordEncryption
import net.corda.node.services.config.SecurityConfiguration
import net.corda.node.services.config.AuthDataSourceType
import net.corda.nodeapi.internal.config.User
import org.apache.shiro.authc.*
import org.apache.shiro.authc.credential.PasswordMatcher
@ -28,22 +27,22 @@ import org.apache.shiro.realm.jdbc.JdbcRealm
import org.apache.shiro.subject.PrincipalCollection
import org.apache.shiro.subject.SimplePrincipalCollection
import java.io.Closeable
import javax.security.auth.login.FailedLoginException
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit
import javax.security.auth.login.FailedLoginException
private typealias AuthServiceConfig = SecurityConfiguration.AuthService
/**
* Default implementation of [RPCSecurityManager] adapting
* [org.apache.shiro.mgt.SecurityManager]
*/
class RPCSecurityManagerImpl(config: AuthServiceConfig) : RPCSecurityManager {
class RPCSecurityManagerImpl(config: AuthServiceConfig, cacheFactory: NamedCacheFactory) : RPCSecurityManager {
override val id = config.id
private val manager: DefaultSecurityManager
init {
manager = buildImpl(config)
manager = buildImpl(config, cacheFactory)
}
@Throws(FailedLoginException::class)
@ -75,14 +74,8 @@ class RPCSecurityManagerImpl(config: AuthServiceConfig) : RPCSecurityManager {
private val logger = loggerFor<RPCSecurityManagerImpl>()
/**
* Instantiate RPCSecurityManager initialised with users data from a list of [User]
*/
fun fromUserList(id: AuthServiceId, users: List<User>) =
RPCSecurityManagerImpl(AuthServiceConfig.fromUsers(users).copy(id = id))
// Build internal Shiro securityManager instance
private fun buildImpl(config: AuthServiceConfig): DefaultSecurityManager {
private fun buildImpl(config: AuthServiceConfig, cacheFactory: NamedCacheFactory): DefaultSecurityManager {
val realm = when (config.dataSource.type) {
AuthDataSourceType.DB -> {
logger.info("Constructing DB-backed security data source: ${config.dataSource.connection}")
@ -98,7 +91,8 @@ class RPCSecurityManagerImpl(config: AuthServiceConfig) : RPCSecurityManager {
it.cacheManager = config.options?.cache?.let {
CaffeineCacheManager(
timeToLiveSeconds = it.expireAfterSecs,
maxSize = it.maxEntries)
maxSize = it.maxEntries,
cacheFactory = cacheFactory)
}
}
}
@ -294,7 +288,8 @@ private fun <K : Any, V> Cache<K, V>.toShiroCache() = object : ShiroCache<K, V>
* cache implementation in [com.github.benmanes.caffeine.cache.Cache]
*/
private class CaffeineCacheManager(val maxSize: Long,
val timeToLiveSeconds: Long) : CacheManager {
val timeToLiveSeconds: Long,
val cacheFactory: NamedCacheFactory) : CacheManager {
private val instances = ConcurrentHashMap<String, ShiroCache<*, *>>()
@ -306,11 +301,7 @@ private class CaffeineCacheManager(val maxSize: Long,
private fun <K : Any, V> buildCache(name: String): ShiroCache<K, V> {
logger.info("Constructing cache '$name' with maximumSize=$maxSize, TTL=${timeToLiveSeconds}s")
return Caffeine.newBuilder()
.expireAfterWrite(timeToLiveSeconds, TimeUnit.SECONDS)
.maximumSize(maxSize)
.buildNamed<K, V>("RPCSecurityManagerShiroCache_$name")
.toShiroCache()
return cacheFactory.buildNamed<K, V>(Caffeine.newBuilder(), "RPCSecurityManagerShiroCache_$name").toShiroCache()
}
companion object {

View File

@ -0,0 +1,14 @@
package net.corda.node.internal.subcommands
import net.corda.node.internal.Node
import net.corda.node.internal.NodeCliCommand
import net.corda.node.internal.NodeStartup
import net.corda.node.internal.RunAfterNodeInitialisation
class ClearNetworkCacheCli(startup: NodeStartup): NodeCliCommand("clear-network-cache", "Clears local copy of network map, on node startup it will be restored from server or file system.", startup) {
override fun runProgram(): Int {
return startup.initialiseAndRun(cmdLineOptions, object: RunAfterNodeInitialisation {
override fun run(node: Node) = node.clearNetworkMapCache()
})
}
}

View File

@ -0,0 +1,16 @@
package net.corda.node.internal.subcommands
import net.corda.node.internal.Node
import net.corda.node.internal.NodeCliCommand
import net.corda.node.internal.NodeStartup
import net.corda.node.internal.RunAfterNodeInitialisation
class GenerateNodeInfoCli(startup: NodeStartup): NodeCliCommand("generate-node-info", "Performs the node start-up tasks necessary to generate the nodeInfo file, saves it to disk, then exits.", startup) {
override fun runProgram(): Int {
return startup.initialiseAndRun(cmdLineOptions, object : RunAfterNodeInitialisation {
override fun run(node: Node) {
node.generateAndSaveNodeInfo()
}
})
}
}

View File

@ -0,0 +1,103 @@
package net.corda.node.internal.subcommands
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.node.internal.Node
import net.corda.node.internal.NodeCliCommand
import net.corda.node.internal.NodeStartup
import net.corda.node.internal.RunAfterNodeInitialisation
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.createKeyPairAndSelfSignedTLSCertificate
import net.corda.node.utilities.saveToKeyStore
import net.corda.node.utilities.saveToTrustStore
import java.io.Console
import kotlin.system.exitProcess
class GenerateRpcSslCertsCli(startup: NodeStartup): NodeCliCommand("generate-rpc-ssl-settings", "Generates the SSL key and trust stores for a secure RPC connection.", startup) {
override fun runProgram(): Int {
return startup.initialiseAndRun(cmdLineOptions, GenerateRpcSslCerts())
}
}
class GenerateRpcSslCerts: RunAfterNodeInitialisation {
override fun run(node: Node) {
generateRpcSslCertificates(node.configuration)
}
private fun generateRpcSslCertificates(conf: NodeConfiguration) {
val (keyPair, cert) = createKeyPairAndSelfSignedTLSCertificate(conf.myLegalName.x500Principal)
val keyStorePath = conf.baseDirectory / "certificates" / "rpcsslkeystore.jks"
val trustStorePath = conf.baseDirectory / "certificates" / "export" / "rpcssltruststore.jks"
if (keyStorePath.exists() || trustStorePath.exists()) {
println("Found existing RPC SSL keystores. Command was already run. Exiting.")
exitProcess(0)
}
val console: Console? = System.console()
when (console) {
// In this case, the JVM is not connected to the console so we need to exit.
null -> {
println("Not connected to console. Exiting.")
exitProcess(1)
}
// Otherwise we can proceed normally.
else -> {
while (true) {
val keystorePassword1 = console.readPassword("Enter the RPC keystore password:")
// TODO: consider adding a password strength policy.
if (keystorePassword1.isEmpty()) {
println("The RPC keystore password cannot be an empty String.")
continue
}
val keystorePassword2 = console.readPassword("Re-enter the RPC keystore password:")
if (!keystorePassword1.contentEquals(keystorePassword2)) {
println("The RPC keystore passwords don't match.")
continue
}
saveToKeyStore(keyStorePath, keyPair, cert, String(keystorePassword1), "rpcssl")
println("The RPC keystore was saved to: $keyStorePath .")
break
}
while (true) {
val trustStorePassword1 = console.readPassword("Enter the RPC truststore password:")
// TODO: consider adding a password strength policy.
if (trustStorePassword1.isEmpty()) {
println("The RPC truststore password cannot be an empty string.")
continue
}
val trustStorePassword2 = console.readPassword("Re-enter the RPC truststore password:")
if (!trustStorePassword1.contentEquals(trustStorePassword2)) {
println("The RPC truststore passwords don't match.")
continue
}
saveToTrustStore(trustStorePath, cert, String(trustStorePassword1), "rpcssl")
println("The RPC truststore was saved to: $trustStorePath.")
println("You need to distribute this file along with the password in a secure way to all RPC clients.")
break
}
val dollar = '$'
println("""
|
|The SSL certificates for RPC were generated successfully.
|
|Add this snippet to the "rpcSettings" section of your node.conf:
| useSsl=true
| ssl {
| keyStorePath=$dollar{baseDirectory}/certificates/rpcsslkeystore.jks
| keyStorePassword=the_above_password
| }
|""".trimMargin())
}
}
}
}

View File

@ -0,0 +1,110 @@
package net.corda.node.internal.subcommands
import net.corda.cliutils.CliWrapperBase
import net.corda.core.internal.createFile
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.utilities.Try
import net.corda.node.InitialRegistrationCmdLineOptions
import net.corda.node.NodeRegistrationOption
import net.corda.node.internal.*
import net.corda.node.internal.NodeStartupLogging.Companion.logger
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationHelper
import picocli.CommandLine.Mixin
import picocli.CommandLine.Option
import java.io.File
import java.nio.file.Path
class InitialRegistrationCli(val startup: NodeStartup): CliWrapperBase("initial-registration", "Starts initial node registration with Corda network to obtain certificate from the permissioning server.") {
@Option(names = ["-t", "--network-root-truststore"], description = ["Network root trust store obtained from network operator."])
var networkRootTrustStorePathParameter: Path? = null
@Option(names = ["-p", "--network-root-truststore-password"], description = ["Network root trust store password obtained from network operator."], required = true)
var networkRootTrustStorePassword: String = ""
override fun runProgram() : Int {
val networkRootTrustStorePath: Path = networkRootTrustStorePathParameter ?: cmdLineOptions.baseDirectory / "certificates" / "network-root-truststore.jks"
return startup.initialiseAndRun(cmdLineOptions, InitialRegistration(cmdLineOptions.baseDirectory, networkRootTrustStorePath, networkRootTrustStorePassword, startup))
}
override fun initLogging() = this.initLogging(cmdLineOptions.baseDirectory)
@Mixin
val cmdLineOptions = InitialRegistrationCmdLineOptions()
}
class InitialRegistration(val baseDirectory: Path, private val networkRootTrustStorePath: Path, networkRootTrustStorePassword: String, private val startup: NodeStartup) : RunAfterNodeInitialisation, NodeStartupLogging {
companion object {
private const val INITIAL_REGISTRATION_MARKER = ".initialregistration"
fun checkRegistrationMode(baseDirectory: Path): Boolean {
// If the node was started with `--initial-registration`, create marker file.
// We do this here to ensure the marker is created even if parsing the args with NodeArgsParser fails.
val marker = baseDirectory / INITIAL_REGISTRATION_MARKER
if (!marker.exists()) {
return false
}
try {
marker.createFile()
} catch (e: Exception) {
logger.warn("Could not create marker file for `initial-registration`.", e)
}
return true
}
}
private val nodeRegistration = NodeRegistrationOption(networkRootTrustStorePath, networkRootTrustStorePassword)
private fun registerWithNetwork(conf: NodeConfiguration) {
val versionInfo = startup.getVersionInfo()
println("\n" +
"******************************************************************\n" +
"* *\n" +
"* Registering as a new participant with a Corda network *\n" +
"* *\n" +
"******************************************************************\n")
NodeRegistrationHelper(conf,
HTTPNetworkRegistrationService(
requireNotNull(conf.networkServices),
versionInfo),
nodeRegistration).buildKeystore()
// Minimal changes to make registration tool create node identity.
// TODO: Move node identity generation logic from node to registration helper.
startup.createNode(conf, versionInfo).generateAndSaveNodeInfo()
println("Successfully registered Corda node with compatibility zone, node identity keys and certificates are stored in '${conf.certificatesDirectory}', it is advised to backup the private keys and certificates.")
println("Corda node will now terminate.")
}
private fun initialRegistration(config: NodeConfiguration) {
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig]
attempt { registerWithNetwork(config) }.doOnException(this::handleRegistrationError) as? Try.Success
// At this point the node registration was successful. We can delete the marker file.
deleteNodeRegistrationMarker(baseDirectory)
}
private fun deleteNodeRegistrationMarker(baseDir: Path) {
try {
val marker = File((baseDir / INITIAL_REGISTRATION_MARKER).toUri())
if (marker.exists()) {
marker.delete()
}
} catch (e: Exception) {
e.logAsUnexpected( "Could not delete the marker file that was created for `initial-registration`.", print = logger::warn)
}
}
override fun run(node: Node) {
require(networkRootTrustStorePath.exists()) { "Network root trust store path: '$networkRootTrustStorePath' doesn't exist" }
if (checkRegistrationMode(baseDirectory)) {
println("Node was started before with `--initial-registration`, but the registration was not completed.\nResuming registration.")
}
initialRegistration(node.configuration)
}
}

View File

@ -12,10 +12,9 @@ import com.esotericsoftware.kryo.serializers.ClosureSerializer
import net.corda.core.internal.uncheckedCast
import net.corda.core.serialization.*
import net.corda.core.serialization.internal.CheckpointSerializationContext
import net.corda.core.serialization.internal.CheckpointSerializationScheme
import net.corda.core.serialization.internal.CheckpointSerializer
import net.corda.core.utilities.ByteSequence
import net.corda.serialization.internal.*
import java.security.PublicKey
import java.util.concurrent.ConcurrentHashMap
val kryoMagic = CordaSerializationMagic("corda".toByteArray() + byteArrayOf(0, 0))
@ -31,7 +30,7 @@ private object AutoCloseableSerialisationDetector : Serializer<AutoCloseable>()
override fun read(kryo: Kryo, input: Input, type: Class<AutoCloseable>) = throw IllegalStateException("Should not reach here!")
}
object KryoSerializationScheme : CheckpointSerializationScheme {
object KryoCheckpointSerializer : CheckpointSerializer {
private val kryoPoolsForContexts = ConcurrentHashMap<Pair<ClassWhitelist, ClassLoader>, KryoPool>()
private fun getPool(context: CheckpointSerializationContext): KryoPool {

View File

@ -20,6 +20,8 @@ class FinalityHandler(private val sender: FlowSession) : FlowLogic<Unit>() {
override fun call() {
subFlow(ReceiveTransactionFlow(sender, true, StatesToRecord.ONLY_RELEVANT))
}
internal fun sender(): Party = sender.counterparty
}
class NotaryChangeHandler(otherSideSession: FlowSession) : AbstractStateReplacementFlow.Acceptor<Party>(otherSideSession) {
@ -54,7 +56,7 @@ class ContractUpgradeHandler(otherSide: FlowSession) : AbstractStateReplacementF
// verify outputs matches the proposed upgrade.
val ourSTX = serviceHub.validatedTransactions.getTransaction(proposal.stateRef.txhash)
requireNotNull(ourSTX) { "We don't have a copy of the referenced state" }
val oldStateAndRef = ourSTX!!.tx.outRef<ContractState>(proposal.stateRef.index)
val oldStateAndRef = ourSTX!!.resolveBaseTransaction(serviceHub).outRef<ContractState>(proposal.stateRef.index)
val authorisedUpgrade = serviceHub.contractUpgradeService.getAuthorisedContractUpgrade(oldStateAndRef.ref) ?: throw IllegalStateException("Contract state upgrade is unauthorised. State hash : ${oldStateAndRef.ref}")
val proposedTx = stx.coreTransaction as ContractUpgradeWireTransaction
val expectedTx = ContractUpgradeUtils.assembleUpgradeTx(oldStateAndRef, proposal.modification, proposedTx.privacySalt, serviceHub)

View File

@ -6,6 +6,7 @@ import net.corda.core.crypto.SecureHash
import net.corda.core.flows.FlowLogic
import net.corda.core.flows.StateMachineRunId
import net.corda.core.internal.FlowStateMachine
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.messaging.DataFeed
import net.corda.core.messaging.StateMachineTransactionMapping
@ -25,7 +26,6 @@ import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.persistence.AttachmentStorageInternal
import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import java.security.PublicKey

View File

@ -86,9 +86,9 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N
}
if (keyStore.getOptional() == null || signingCertificateStore.getOptional() == null) {
val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.password).get(true).also { it.registerDevSigningCertificates(myLegalName) }
val signingKeyStore = FileBasedCertificateStoreSupplier(signingCertificateStore.path, signingCertificateStore.storePassword, signingCertificateStore.entryPassword).get(true).also { it.registerDevSigningCertificates(myLegalName) }
FileBasedCertificateStoreSupplier(keyStore.path, keyStore.password).get(true).also { it.registerDevP2pCertificates(myLegalName) }
FileBasedCertificateStoreSupplier(keyStore.path, keyStore.storePassword, keyStore.entryPassword).get(true).also { it.registerDevP2pCertificates(myLegalName) }
// Move distributed service composite key (generated by IdentityGenerator.generateToDisk) to keystore if exists.
val distributedServiceKeystore = certificatesDirectory / "distributedService.jks"
@ -97,7 +97,7 @@ fun MutualSslConfiguration.configureDevKeyAndTrustStores(myLegalName: CordaX500N
signingKeyStore.update {
serviceKeystore.aliases().forEach {
if (serviceKeystore.internal.isKeyEntry(it)) {
setPrivateKey(it, serviceKeystore.getPrivateKey(it, DEV_CA_PRIVATE_KEY_PASS), serviceKeystore.getCertificateChain(it))
setPrivateKey(it, serviceKeystore.getPrivateKey(it, DEV_CA_KEY_STORE_PASS), serviceKeystore.getCertificateChain(it), signingKeyStore.entryPassword)
} else {
setCertificate(it, serviceKeystore.getCertificate(it))
}

View File

@ -11,12 +11,7 @@ import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
import net.corda.node.services.config.rpc.NodeRpcOptions
import net.corda.nodeapi.BrokerRpcSslOptions
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.SslConfiguration
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import net.corda.nodeapi.internal.config.User
import net.corda.nodeapi.internal.config.parseAs
import net.corda.nodeapi.internal.config.*
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.tools.shell.SSHDConfiguration
import org.slf4j.Logger
@ -73,7 +68,7 @@ interface NodeConfiguration {
val flowMonitorPeriodMillis: Duration get() = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS
val flowMonitorSuspensionLoggingThresholdMillis: Duration get() = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS
val crlCheckSoftFail: Boolean
val jmxReporterType : JmxReporterType? get() = defaultJmxReporterType
val jmxReporterType: JmxReporterType? get() = defaultJmxReporterType
val baseDirectory: Path
val certificatesDirectory: Path
@ -81,6 +76,7 @@ interface NodeConfiguration {
val p2pSslOptions: MutualSslConfiguration
val cordappDirectories: List<Path>
val flowOverrides: FlowOverrideConfig?
fun validate(): List<String>
@ -102,6 +98,9 @@ interface NodeConfiguration {
}
}
data class FlowOverrideConfig(val overrides: List<FlowOverride> = listOf())
data class FlowOverride(val initiator: String, val responder: String)
/**
* Currently registered JMX Reporters
*/
@ -119,34 +118,16 @@ fun NodeConfiguration.shouldStartSSHDaemon() = this.sshd != null
fun NodeConfiguration.shouldStartLocalShell() = !this.noLocalShell && System.console() != null && this.devMode
fun NodeConfiguration.shouldInitCrashShell() = shouldStartLocalShell() || shouldStartSSHDaemon()
data class NotaryConfig(val validating: Boolean,
val raft: RaftConfig? = null,
val bftSMaRt: BFTSMaRtConfiguration? = null,
val custom: Boolean = false,
val serviceLegalName: CordaX500Name? = null
) {
init {
require(raft == null || bftSMaRt == null || !custom) {
"raft, bftSMaRt, and custom configs cannot be specified together"
}
}
val isClusterConfig: Boolean get() = raft != null || bftSMaRt != null
}
data class RaftConfig(val nodeAddress: NetworkHostAndPort, val clusterAddresses: List<NetworkHostAndPort>)
/** @param exposeRaces for testing only, so its default is not in reference.conf but here. */
data class BFTSMaRtConfiguration(
val replicaId: Int,
val clusterAddresses: List<NetworkHostAndPort>,
val debug: Boolean = false,
val exposeRaces: Boolean = false
) {
init {
require(replicaId >= 0) { "replicaId cannot be negative" }
}
}
data class NotaryConfig(
/** Specifies whether the notary validates transactions or not. */
val validating: Boolean,
/** The legal name of cluster in case of a distributed notary service. */
val serviceLegalName: CordaX500Name? = null,
/** The name of the notary service class to load. */
val className: String = "net.corda.node.services.transactions.SimpleNotaryService",
/** Notary implementation-specific configuration parameters. */
val extraConfig: Config? = null
)
/**
* Used as an alternative to the older compatibilityZoneURL to allow the doorman and network map
@ -156,6 +137,8 @@ data class BFTSMaRtConfiguration(
*
* @property doormanURL The URL of the tls certificate signing service.
* @property networkMapURL The URL of the Network Map service.
* @property pnm If the compatibility zone operator supports the private network map option, have the node
* at registration automatically join that private network.
* @property inferred Non user setting that indicates weather the Network Services configuration was
* set explicitly ([inferred] == false) or weather they have been inferred via the compatibilityZoneURL parameter
* ([inferred] == true) where both the network map and doorman are running on the same endpoint. Only one,
@ -164,7 +147,8 @@ data class BFTSMaRtConfiguration(
data class NetworkServicesConfig(
val doormanURL: URL,
val networkMapURL: URL,
val inferred : Boolean = false
val pnm: UUID? = null,
val inferred: Boolean = false
)
/**
@ -230,7 +214,8 @@ data class NodeConfigurationImpl(
override val flowMonitorPeriodMillis: Duration = DEFAULT_FLOW_MONITOR_PERIOD_MILLIS,
override val flowMonitorSuspensionLoggingThresholdMillis: Duration = DEFAULT_FLOW_MONITOR_SUSPENSION_LOGGING_THRESHOLD_MILLIS,
override val cordappDirectories: List<Path> = listOf(baseDirectory / CORDAPPS_DIR_NAME_DEFAULT),
override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA
override val jmxReporterType: JmxReporterType? = JmxReporterType.JOLOKIA,
override val flowOverrides: FlowOverrideConfig?
) : NodeConfiguration {
companion object {
private val logger = loggerFor<NodeConfigurationImpl>()
@ -257,12 +242,16 @@ data class NodeConfigurationImpl(
override val certificatesDirectory = baseDirectory / "certificates"
private val signingCertificateStorePath = certificatesDirectory / "nodekeystore.jks"
override val signingCertificateStore = FileBasedCertificateStoreSupplier(signingCertificateStorePath, keyStorePassword)
private val p2pKeystorePath: Path get() = certificatesDirectory / "sslkeystore.jks"
private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword)
// TODO: There are two implications here:
// 1. "signingCertificateStore" and "p2pKeyStore" have the same passwords. In the future we should re-visit this "rule" and see of they can be made different;
// 2. The passwords for store and for keys in this store are the same, this is due to limitations of Artemis.
override val signingCertificateStore = FileBasedCertificateStoreSupplier(signingCertificateStorePath, keyStorePassword, keyStorePassword)
private val p2pKeyStore = FileBasedCertificateStoreSupplier(p2pKeystorePath, keyStorePassword, keyStorePassword)
private val p2pTrustStoreFilePath: Path get() = certificatesDirectory / "truststore.jks"
private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword)
private val p2pTrustStore = FileBasedCertificateStoreSupplier(p2pTrustStoreFilePath, trustStorePassword, trustStorePassword)
override val p2pSslOptions: MutualSslConfiguration = SslConfiguration.mutual(p2pKeyStore, p2pTrustStore)
override val rpcOptions: NodeRpcOptions
@ -353,7 +342,7 @@ data class NodeConfigurationImpl(
override val effectiveH2Settings: NodeH2Settings?
get() = when {
h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host="localhost", port=h2port))
h2port != null -> NodeH2Settings(address = NetworkHostAndPort(host = "localhost", port = h2port))
else -> h2Settings
}
@ -365,14 +354,15 @@ data class NodeConfigurationImpl(
"Cannot specify both 'rpcUsers' and 'security' in configuration"
}
@Suppress("DEPRECATION")
if(certificateChainCheckPolicies.isNotEmpty()) {
if (certificateChainCheckPolicies.isNotEmpty()) {
logger.warn("""You are configuring certificateChainCheckPolicies. This is a setting that is not used, and will be removed in a future version.
|Please contact the R3 team on the public slack to discuss your use case.
""".trimMargin())
}
// Support the deprecated method of configuring network services with a single compatibilityZoneURL option
if (compatibilityZoneURL != null && networkServices == null) {
networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, true)
networkServices = NetworkServicesConfig(compatibilityZoneURL, compatibilityZoneURL, inferred = true)
}
require(h2port == null || h2Settings == null) { "Cannot specify both 'h2port' and 'h2Settings' in configuration" }
}

View File

@ -2,6 +2,7 @@ package net.corda.node.services.identity
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.*
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.hash
import net.corda.core.node.services.UnknownAnonymousPartyException
import net.corda.core.serialization.SingletonSerializeAsToken
@ -10,7 +11,6 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.services.api.IdentityServiceInternal
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.x509Certificates
import net.corda.nodeapi.internal.persistence.CordaPersistence

View File

@ -2,11 +2,11 @@ package net.corda.node.services.keys
import net.corda.core.crypto.*
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.utilities.MAX_HASH_HEX_SIZE
import net.corda.node.services.identity.PersistentIdentityService
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY

View File

@ -1,6 +1,7 @@
package net.corda.node.services.messaging
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.internal.nodeSerializationEnv
@ -20,7 +21,7 @@ class InternalRPCMessagingClient(val sslConfig: MutualSslConfiguration, val serv
private var locator: ServerLocator? = null
private var rpcServer: RPCServer? = null
fun init(rpcOps: RPCOps, securityManager: RPCSecurityManager) = synchronized(this) {
fun init(rpcOps: RPCOps, securityManager: RPCSecurityManager, cacheFactory: NamedCacheFactory) = synchronized(this) {
val tcpTransport = ArtemisTcpTransport.rpcInternalClientTcpTransport(serverAddress, sslConfig)
locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
@ -32,7 +33,7 @@ class InternalRPCMessagingClient(val sslConfig: MutualSslConfiguration, val serv
isUseGlobalPools = nodeSerializationEnv != null
}
rpcServer = RPCServer(rpcOps, NODE_RPC_USER, NODE_RPC_USER, locator!!, securityManager, nodeName, rpcServerConfiguration)
rpcServer = RPCServer(rpcOps, NODE_RPC_USER, NODE_RPC_USER, locator!!, securityManager, nodeName, rpcServerConfiguration, cacheFactory)
}
fun start(serverControl: ActiveMQServerControl) = synchronized(this) {

View File

@ -2,9 +2,9 @@ package net.corda.node.services.messaging
import net.corda.core.crypto.SecureHash
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.NamedCacheFactory
import net.corda.node.services.statemachine.DeduplicationId
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import java.time.Instant

View File

@ -4,6 +4,7 @@ import co.paralleluniverse.fibers.Suspendable
import com.codahale.metrics.MetricRegistry
import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.ThreadBox
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.MessageRecipients
@ -27,7 +28,6 @@ import net.corda.node.services.statemachine.DeduplicationId
import net.corda.node.services.statemachine.ExternalEvent
import net.corda.node.services.statemachine.SenderDeduplicationId
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.ArtemisMessagingComponent.*
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL

View File

@ -13,7 +13,7 @@ import net.corda.core.context.Trace
import net.corda.core.context.Trace.InvocationId
import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.LifeCycle
import net.corda.core.internal.buildNamed
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.messaging.RPCOps
import net.corda.core.serialization.SerializationContext
import net.corda.core.serialization.SerializationDefaults
@ -33,13 +33,8 @@ import net.corda.nodeapi.internal.persistence.contextDatabase
import net.corda.nodeapi.internal.persistence.contextDatabaseOrNull
import org.apache.activemq.artemis.api.core.Message
import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.*
import org.apache.activemq.artemis.api.core.client.ActiveMQClient.DEFAULT_ACK_BATCH_SIZE
import org.apache.activemq.artemis.api.core.client.ClientConsumer
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.api.core.client.ClientProducer
import org.apache.activemq.artemis.api.core.client.ClientSession
import org.apache.activemq.artemis.api.core.client.ClientSessionFactory
import org.apache.activemq.artemis.api.core.client.ServerLocator
import org.apache.activemq.artemis.api.core.management.ActiveMQServerControl
import org.apache.activemq.artemis.api.core.management.CoreNotificationType
import org.apache.activemq.artemis.api.core.management.ManagementHelper
@ -49,12 +44,7 @@ import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.time.Duration
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.Executors
import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.*
import kotlin.concurrent.thread
private typealias ObservableSubscriptionMap = Cache<InvocationId, ObservableSubscription>
@ -91,7 +81,8 @@ class RPCServer(
private val serverLocator: ServerLocator,
private val securityManager: RPCSecurityManager,
private val nodeLegalName: CordaX500Name,
private val rpcConfiguration: RPCServerConfiguration
private val rpcConfiguration: RPCServerConfiguration,
private val cacheFactory: NamedCacheFactory
) {
private companion object {
private val log = contextLogger()
@ -136,7 +127,7 @@ class RPCServer(
private val responseMessageBuffer = ConcurrentHashMap<SimpleString, BufferOrNone>()
private val sendJobQueue = LinkedBlockingQueue<RpcSendJob>()
private val deduplicationChecker = DeduplicationChecker(rpcConfiguration.deduplicationCacheExpiry)
private val deduplicationChecker = DeduplicationChecker(rpcConfiguration.deduplicationCacheExpiry, cacheFactory = cacheFactory)
private var deduplicationIdentity: String? = null
init {
@ -154,7 +145,7 @@ class RPCServer(
log.debug { "Unsubscribing from Observable with id $key because of $cause" }
value!!.subscription.unsubscribe()
}
return Caffeine.newBuilder().removalListener(onObservableRemove).executor(SameThreadExecutor.getExecutor()).buildNamed("RPCServer_observableSubscription")
return cacheFactory.buildNamed(Caffeine.newBuilder().removalListener(onObservableRemove).executor(SameThreadExecutor.getExecutor()), "RPCServer_observableSubscription")
}
fun start(activeMqServerControl: ActiveMQServerControl) {

View File

@ -6,6 +6,7 @@ import net.corda.core.identity.AbstractParty
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.bufferUntilSubscribed
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.openFuture
@ -23,7 +24,6 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.internal.schemas.NodeInfoSchemaV1
import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.utilities.NamedCacheFactory
import net.corda.node.utilities.NonInvalidatingCache
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.bufferUntilDatabaseCommit

View File

@ -3,6 +3,7 @@ package net.corda.node.services.persistence
import net.corda.core.concurrent.CordaFuture
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.TransactionSignature
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.ThreadBox
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.bufferUntilSubscribed
@ -15,7 +16,6 @@ import net.corda.core.transactions.SignedTransaction
import net.corda.node.services.api.WritableTransactionStorage
import net.corda.node.services.statemachine.FlowStateMachineImpl
import net.corda.node.utilities.AppendOnlyPersistentMapBase
import net.corda.node.utilities.NamedCacheFactory
import net.corda.node.utilities.WeightBasedAppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX

View File

@ -22,7 +22,6 @@ import net.corda.core.node.services.vault.AttachmentSort
import net.corda.core.serialization.*
import net.corda.core.utilities.contextLogger
import net.corda.node.services.vault.HibernateAttachmentQueryCriteriaParser
import net.corda.node.utilities.NamedCacheFactory
import net.corda.node.utilities.NonInvalidatingCache
import net.corda.node.utilities.NonInvalidatingWeightBasedCache
import net.corda.nodeapi.exceptions.DuplicateAttachmentException

View File

@ -1,5 +1,6 @@
package net.corda.node.services.persistence
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.services.api.NodePropertiesStore
@ -17,12 +18,12 @@ import javax.persistence.Table
/**
* Simple node properties key value store in DB.
*/
class NodePropertiesPersistentStore(readPhysicalNodeId: () -> String, database: CordaPersistence) : NodePropertiesStore {
class NodePropertiesPersistentStore(readPhysicalNodeId: () -> String, database: CordaPersistence, cacheFactory: NamedCacheFactory) : NodePropertiesStore {
private companion object {
val logger = contextLogger()
}
override val flowsDrainingMode = FlowsDrainingModeOperationsImpl(readPhysicalNodeId, database, logger)
override val flowsDrainingMode = FlowsDrainingModeOperationsImpl(readPhysicalNodeId, database, logger, cacheFactory)
fun start() {
flowsDrainingMode.map.preload()
@ -40,7 +41,7 @@ class NodePropertiesPersistentStore(readPhysicalNodeId: () -> String, database:
)
}
class FlowsDrainingModeOperationsImpl(readPhysicalNodeId: () -> String, private val persistence: CordaPersistence, logger: Logger) : FlowsDrainingModeOperations {
class FlowsDrainingModeOperationsImpl(readPhysicalNodeId: () -> String, private val persistence: CordaPersistence, logger: Logger, cacheFactory: NamedCacheFactory) : FlowsDrainingModeOperations {
private val nodeSpecificFlowsExecutionModeKey = "${readPhysicalNodeId()}_flowsExecutionMode"
init {
@ -52,7 +53,8 @@ class FlowsDrainingModeOperationsImpl(readPhysicalNodeId: () -> String, private
{ key -> key },
{ entity -> entity.key to entity.value!! },
NodePropertiesPersistentStore::DBNodeProperty,
NodePropertiesPersistentStore.DBNodeProperty::class.java
NodePropertiesPersistentStore.DBNodeProperty::class.java,
cacheFactory
)
override val values = PublishSubject.create<Pair<Boolean, Boolean>>()!!

View File

@ -2,6 +2,7 @@ package net.corda.node.services.schema
import net.corda.core.contracts.ContractState
import net.corda.core.contracts.FungibleAsset
import net.corda.core.contracts.FungibleState
import net.corda.core.contracts.LinearState
import net.corda.core.schemas.*
import net.corda.core.schemas.MappedSchemaValidator.crossReferencesToOtherMappedSchema
@ -16,9 +17,7 @@ import net.corda.node.services.messaging.P2PMessageDeduplicator
import net.corda.node.services.persistence.DBCheckpointStorage
import net.corda.node.services.persistence.DBTransactionStorage
import net.corda.node.services.persistence.NodeAttachmentService
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.transactions.RaftUniquenessProvider
import net.corda.node.services.upgrade.ContractUpgradeServiceImpl
import net.corda.node.services.vault.VaultSchemaV1
@ -29,7 +28,7 @@ import net.corda.node.services.vault.VaultSchemaV1
* TODO: support plugins for schema version upgrading or custom mapping not supported by original [QueryableState].
* TODO: create whitelisted tables when a CorDapp is first installed
*/
class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet(), includeNotarySchemas: Boolean = false) : SchemaService, SingletonSerializeAsToken() {
class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()) : SchemaService, SingletonSerializeAsToken() {
// Core Entities used by a Node
object NodeCore
@ -47,26 +46,12 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
override val migrationResource = "node-core.changelog-master"
}
// Entities used by a Notary
object NodeNotary
object NodeNotaryV1 : MappedSchema(schemaFamily = NodeNotary.javaClass, version = 1,
mappedTypes = listOf(PersistentUniquenessProvider.BaseComittedState::class.java,
PersistentUniquenessProvider.Request::class.java,
PersistentUniquenessProvider.CommittedState::class.java,
RaftUniquenessProvider.CommittedState::class.java,
BFTNonValidatingNotaryService.CommittedState::class.java
)) {
override val migrationResource = "node-notary.changelog-master"
}
// Required schemas are those used by internal Corda services
private val requiredSchemas: Map<MappedSchema, SchemaService.SchemaOptions> =
mapOf(Pair(CommonSchemaV1, SchemaOptions()),
Pair(VaultSchemaV1, SchemaOptions()),
Pair(NodeInfoSchemaV1, SchemaOptions()),
Pair(NodeCoreV1, SchemaOptions())) +
if (includeNotarySchemas) mapOf(Pair(NodeNotaryV1, SchemaOptions())) else emptyMap()
Pair(NodeCoreV1, SchemaOptions()))
fun internalSchemas() = requiredSchemas.keys + extraSchemas.filter { schema -> // when mapped schemas from the finance module are present, they are considered as internal ones
schema::class.qualifiedName == "net.corda.finance.schemas.CashSchemaV1" || schema::class.qualifiedName == "net.corda.finance.schemas.CommercialPaperSchemaV1" }
@ -81,6 +66,8 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
if (state is LinearState)
schemas += VaultSchemaV1 // VaultLinearStates
if (state is FungibleAsset<*>)
schemas += VaultSchemaV1 // VaultFungibleAssets
if (state is FungibleState<*>)
schemas += VaultSchemaV1 // VaultFungibleStates
return schemas
@ -92,6 +79,14 @@ class NodeSchemaService(private val extraSchemas: Set<MappedSchema> = emptySet()
return VaultSchemaV1.VaultLinearStates(state.linearId, state.participants)
if ((schema === VaultSchemaV1) && (state is FungibleAsset<*>))
return VaultSchemaV1.VaultFungibleStates(state.owner, state.amount.quantity, state.amount.token.issuer.party, state.amount.token.issuer.reference, state.participants)
if ((schema === VaultSchemaV1) && (state is FungibleState<*>))
return VaultSchemaV1.VaultFungibleStates(
participants = state.participants.toMutableSet(),
owner = null,
quantity = state.amount.quantity,
issuer = null,
issuerRef = null
)
return (state as QueryableState).generateMappedObject(schema)
}

View File

@ -124,7 +124,7 @@ sealed class Action {
/**
* Execute the specified [operation].
*/
data class ExecuteAsyncOperation(val operation: FlowAsyncOperation<*>) : Action()
data class ExecuteAsyncOperation(val deduplicationId: String, val operation: FlowAsyncOperation<*>) : Action()
/**
* Release soft locks associated with given ID (currently the flow ID).

View File

@ -221,7 +221,7 @@ class ActionExecutorImpl(
@Suspendable
private fun executeAsyncOperation(fiber: FlowFiber, action: Action.ExecuteAsyncOperation) {
val operationFuture = action.operation.execute()
val operationFuture = action.operation.execute(action.deduplicationId)
operationFuture.thenMatch(
success = { result ->
fiber.scheduleEvent(Event.AsyncOperationCompletion(result))

View File

@ -14,6 +14,7 @@ import net.corda.core.identity.Party
import net.corda.core.internal.*
import net.corda.core.serialization.internal.CheckpointSerializationContext
import net.corda.core.serialization.internal.checkpointSerialize
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.Try
import net.corda.core.utilities.debug
import net.corda.core.utilities.trace
@ -205,6 +206,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
@Suspendable
override fun run() {
logic.progressTracker?.currentStep = ProgressTracker.STARTING
logic.stateMachine = this
setLoggingContext()
@ -218,7 +220,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
suspend(FlowIORequest.WaitForSessionConfirmations, maySkipCheckpoint = true)
Try.Success(result)
} catch (throwable: Throwable) {
logger.info("Flow threw exception... sending to flow hospital", throwable)
logger.info("Flow threw exception... sending it to flow hospital", throwable)
Try.Failure<R>(throwable)
}
val softLocksId = if (hasSoftLockedStates) logic.runId.uuid else null
@ -263,7 +265,7 @@ class FlowStateMachineImpl<R>(override val id: StateMachineRunId,
processEventImmediately(
Event.EnterSubFlow(subFlow.javaClass,
createSubFlowVersion(
serviceHub.cordappProvider.getCordappForFlow(subFlow), serviceHub.myInfo.platformVersion
serviceHub.cordappProvider.getCordappForFlow(subFlow), serviceHub.myInfo.platformVersion
)
),
isDbTransactionOpenOnEntry = true,
@ -435,7 +437,7 @@ val Class<out FlowLogic<*>>.flowVersionAndInitiatingClass: Pair<Int, Class<out F
current = current.superclass
?: return found
?: throw IllegalArgumentException("$name, as a flow that initiates other flows, must be annotated with " +
"${InitiatingFlow::class.java.name}. See https://docs.corda.net/api-flows.html#flowlogic-annotations.")
"${InitiatingFlow::class.java.name}. See https://docs.corda.net/api-flows.html#flowlogic-annotations.")
}
}

View File

@ -127,7 +127,7 @@ class SingleThreadedStateMachineManager(
override fun start(tokenizableServices: List<Any>) {
checkQuasarJavaAgentPresence()
val checkpointSerializationContext = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withTokenContext(
CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZATION_FACTORY, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub)
CheckpointSerializeAsTokenContextImpl(tokenizableServices, CheckpointSerializationDefaults.CHECKPOINT_SERIALIZER, CheckpointSerializationDefaults.CHECKPOINT_CONTEXT, serviceHub)
)
this.checkpointSerializationContext = checkpointSerializationContext
this.actionExecutor = makeActionExecutor(checkpointSerializationContext)

View File

@ -217,7 +217,11 @@ class StaffedFlowHospital {
*/
object FinalityDoctor : Staff {
override fun consult(flowFiber: FlowFiber, currentState: StateMachineState, newError: Throwable, history: MedicalHistory): Diagnosis {
return if (currentState.flowLogic is FinalityHandler) Diagnosis.OVERNIGHT_OBSERVATION else Diagnosis.NOT_MY_SPECIALTY
return (currentState.flowLogic as? FinalityHandler)?.let { logic -> Diagnosis.OVERNIGHT_OBSERVATION.also { warn(logic, flowFiber, currentState) } } ?: Diagnosis.NOT_MY_SPECIALTY
}
private fun warn(flowLogic: FinalityHandler, flowFiber: FlowFiber, currentState: StateMachineState) {
log.warn("Flow ${flowFiber.id} failed to be finalised. Manual intervention may be required before retrying the flow by re-starting the node. State machine state: $currentState, initiating party was: ${flowLogic.sender().name}")
}
}
}

View File

@ -411,7 +411,10 @@ class StartedFlowTransition(
private fun executeAsyncOperation(flowIORequest: FlowIORequest.ExecuteAsyncOperation<*>): TransitionResult {
return builder {
actions.add(Action.ExecuteAsyncOperation(flowIORequest.operation))
// The `numberOfSuspends` is added to the deduplication ID in case an async
// operation is executed multiple times within the same flow.
val deduplicationId = context.id.toString() + ":" + currentState.checkpoint.numberOfSuspends.toString()
actions.add(Action.ExecuteAsyncOperation(deduplicationId, flowIORequest.operation))
FlowContinuation.ProcessEvents
}
}

View File

@ -1,174 +0,0 @@
package net.corda.node.services.transactions
import co.paralleluniverse.fibers.Suspendable
import com.google.common.util.concurrent.SettableFuture
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.SignedData
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.internal.notary.NotaryInternalException
import net.corda.core.internal.notary.NotaryService
import net.corda.core.internal.notary.verifySignature
import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.transactions.CoreTransaction
import net.corda.core.transactions.FilteredTransaction
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.unwrap
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.config.BFTSMaRtConfiguration
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import java.security.PublicKey
import javax.persistence.Entity
import javax.persistence.Table
import kotlin.concurrent.thread
/**
* A non-validating notary service operated by a group of parties that don't necessarily trust each other.
*
* A transaction is notarised when the consensus is reached by the cluster on its uniqueness, and time-window validity.
*/
class BFTNonValidatingNotaryService(
override val services: ServiceHubInternal,
override val notaryIdentityKey: PublicKey,
private val bftSMaRtConfig: BFTSMaRtConfiguration,
cluster: BFTSMaRt.Cluster
) : NotaryService() {
companion object {
private val log = contextLogger()
}
private val client: BFTSMaRt.Client
private val replicaHolder = SettableFuture.create<Replica>()
init {
client = BFTSMaRtConfig(bftSMaRtConfig.clusterAddresses, bftSMaRtConfig.debug, bftSMaRtConfig.exposeRaces).use {
val replicaId = bftSMaRtConfig.replicaId
val configHandle = it.handle()
// Replica startup must be in parallel with other replicas, otherwise the constructor may not return:
thread(name = "BFT SMaRt replica $replicaId init", isDaemon = true) {
configHandle.use {
val replica = Replica(it, replicaId, { createMap() }, services, notaryIdentityKey)
replicaHolder.set(replica)
log.info("BFT SMaRt replica $replicaId is running.")
}
}
BFTSMaRt.Client(it, replicaId, cluster, this)
}
}
fun waitUntilReplicaHasInitialized() {
log.debug { "Waiting for replica ${bftSMaRtConfig.replicaId} to initialize." }
replicaHolder.getOrThrow() // It's enough to wait for the ServiceReplica constructor to return.
}
fun commitTransaction(payload: NotarisationPayload, otherSide: Party) = client.commitTransaction(payload, otherSide)
override fun createServiceFlow(otherPartySession: FlowSession): FlowLogic<Void?> = ServiceFlow(otherPartySession, this)
private class ServiceFlow(val otherSideSession: FlowSession, val service: BFTNonValidatingNotaryService) : FlowLogic<Void?>() {
@Suspendable
override fun call(): Void? {
val payload = otherSideSession.receive<NotarisationPayload>().unwrap { it }
val response = commit(payload)
otherSideSession.send(response)
return null
}
private fun commit(payload: NotarisationPayload): NotarisationResponse {
val response = service.commitTransaction(payload, otherSideSession.counterparty)
when (response) {
is BFTSMaRt.ClusterResponse.Error -> {
// TODO: here we assume that all error will be the same, but there might be invalid onces from mailicious nodes
val responseError = response.errors.first().verified()
throw NotaryException(responseError, payload.coreTransaction.id)
}
is BFTSMaRt.ClusterResponse.Signatures -> {
log.debug("All input states of transaction ${payload.coreTransaction.id} have been committed")
return NotarisationResponse(response.txSignatures)
}
}
}
}
@Entity
@Table(name = "${NODE_DATABASE_PREFIX}bft_committed_states")
class CommittedState(id: PersistentStateRef, consumingTxHash: String) : PersistentUniquenessProvider.BaseComittedState(id, consumingTxHash)
private fun createMap(): AppendOnlyPersistentMap<StateRef, SecureHash, CommittedState, PersistentStateRef> {
return AppendOnlyPersistentMap(
cacheFactory = services.cacheFactory,
name = "BFTNonValidatingNotaryService_transactions",
toPersistentEntityKey = { PersistentStateRef(it.txhash.toString(), it.index) },
fromPersistentEntity = {
//TODO null check will become obsolete after making DB/JPA columns not nullable
val txId = it.id.txId
val index = it.id.index
Pair(
StateRef(txhash = SecureHash.parse(txId), index = index),
SecureHash.parse(it.consumingTxHash)
)
},
toPersistentEntity = { (txHash, index): StateRef, id: SecureHash ->
CommittedState(
id = PersistentStateRef(txHash.toString(), index),
consumingTxHash = id.toString()
)
},
persistentEntityClass = CommittedState::class.java
)
}
private class Replica(config: BFTSMaRtConfig,
replicaId: Int,
createMap: () -> AppendOnlyPersistentMap<StateRef, SecureHash, CommittedState, PersistentStateRef>,
services: ServiceHubInternal,
notaryIdentityKey: PublicKey) : BFTSMaRt.Replica(config, replicaId, createMap, services, notaryIdentityKey) {
override fun executeCommand(command: ByteArray): ByteArray {
val commitRequest = command.deserialize<BFTSMaRt.CommitRequest>()
verifyRequest(commitRequest)
val response = verifyAndCommitTx(commitRequest.payload.coreTransaction, commitRequest.callerIdentity, commitRequest.payload.requestSignature)
return response.serialize().bytes
}
private fun verifyAndCommitTx(transaction: CoreTransaction, callerIdentity: Party, requestSignature: NotarisationRequestSignature): BFTSMaRt.ReplicaResponse {
return try {
val id = transaction.id
val inputs = transaction.inputs
val references = transaction.references
val notary = transaction.notary
val timeWindow = (transaction as? FilteredTransaction)?.timeWindow
if (notary !in services.myInfo.legalIdentities) throw NotaryInternalException(NotaryError.WrongNotary)
commitInputStates(inputs, id, callerIdentity.name, requestSignature, timeWindow, references)
log.debug { "Inputs committed successfully, signing $id" }
BFTSMaRt.ReplicaResponse.Signature(sign(id))
} catch (e: NotaryInternalException) {
log.debug { "Error processing transaction: ${e.error}" }
val serializedError = e.error.serialize()
val errorSignature = sign(serializedError.bytes)
val signedError = SignedData(serializedError, errorSignature)
BFTSMaRt.ReplicaResponse.Error(signedError)
}
}
private fun verifyRequest(commitRequest: BFTSMaRt.CommitRequest) {
val transaction = commitRequest.payload.coreTransaction
val notarisationRequest = NotarisationRequest(transaction.inputs, transaction.id)
notarisationRequest.verifySignature(commitRequest.payload.requestSignature, commitRequest.callerIdentity)
}
}
override fun start() {
}
override fun stop() {
replicaHolder.getOrThrow().dispose()
client.dispose()
}
}

View File

@ -1,318 +0,0 @@
package net.corda.node.services.transactions
import bftsmart.communication.ServerCommunicationSystem
import bftsmart.communication.client.netty.NettyClientServerCommunicationSystemClientSide
import bftsmart.communication.client.netty.NettyClientServerSession
import bftsmart.statemanagement.strategy.StandardStateManager
import bftsmart.tom.MessageContext
import bftsmart.tom.ServiceProxy
import bftsmart.tom.ServiceReplica
import bftsmart.tom.core.TOMLayer
import bftsmart.tom.core.messages.TOMMessage
import bftsmart.tom.server.defaultservices.DefaultRecoverable
import bftsmart.tom.server.defaultservices.DefaultReplier
import bftsmart.tom.util.Extractor
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.*
import net.corda.core.flows.NotarisationPayload
import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.flows.NotaryError
import net.corda.core.flows.StateConsumptionDetails
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.Party
import net.corda.core.internal.declaredField
import net.corda.core.internal.notary.NotaryInternalException
import net.corda.core.internal.notary.isConsumedByTheSameTx
import net.corda.core.internal.notary.validateTimeWindow
import net.corda.core.internal.toTypedArray
import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.CordaSerializable
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.transactions.BFTSMaRt.Client
import net.corda.node.services.transactions.BFTSMaRt.Replica
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.currentDBSession
import java.nio.file.Path
import java.security.PublicKey
import java.util.*
/**
* Implements a replicated transaction commit log based on the [BFT-SMaRt](https://github.com/bft-smart/library)
* consensus algorithm. Every replica in the cluster is running a [Replica] maintaining the state, and a [Client] is used
* to relay state modification requests to all [Replica]s.
*/
// TODO: Define and document the configuration of the bft-smart cluster.
// TODO: Potentially update the bft-smart API for our use case or rebuild client and server from lower level building
// blocks bft-smart provides.
// TODO: Support cluster membership changes. This requires reading about reconfiguration of bft-smart clusters and
// perhaps a design doc. In general, it seems possible to use the state machine to reconfigure the cluster (reaching
// consensus about membership changes). Nodes that join the cluster for the first time or re-join can go through
// a "recovering" state and request missing data from their peers.
object BFTSMaRt {
/** Sent from [Client] to [Replica]. */
@CordaSerializable
data class CommitRequest(val payload: NotarisationPayload, val callerIdentity: Party)
/** Sent from [Replica] to [Client]. */
@CordaSerializable
sealed class ReplicaResponse {
data class Error(val error: SignedData<NotaryError>) : ReplicaResponse()
data class Signature(val txSignature: TransactionSignature) : ReplicaResponse()
}
/** An aggregate response from all replica ([Replica]) replies sent from [Client] back to the calling application. */
@CordaSerializable
sealed class ClusterResponse {
data class Error(val errors: List<SignedData<NotaryError>>) : ClusterResponse()
data class Signatures(val txSignatures: List<TransactionSignature>) : ClusterResponse()
}
interface Cluster {
/** Avoid bug where a replica fails to start due to a consensus change during the BFT startup sequence. */
fun waitUntilAllReplicasHaveInitialized()
}
class Client(config: BFTSMaRtConfig, private val clientId: Int, private val cluster: Cluster, private val notaryService: BFTNonValidatingNotaryService) : SingletonSerializeAsToken() {
companion object {
private val log = contextLogger()
}
/** A proxy for communicating with the BFT cluster */
private val proxy = ServiceProxy(clientId, config.path.toString(), buildResponseComparator(), buildExtractor())
private val sessionTable = (proxy.communicationSystem as NettyClientServerCommunicationSystemClientSide).declaredField<Map<Int, NettyClientServerSession>>("sessionTable").value
fun dispose() {
proxy.close() // XXX: Does this do enough?
}
private fun awaitClientConnectionToCluster() {
// TODO: Hopefully we only need to wait for the client's initial connection to the cluster, and this method can be moved to some startup code.
// TODO: Investigate ConcurrentModificationException in this method.
while (true) {
val inactive = sessionTable.entries.mapNotNull { if (it.value.channel.isActive) null else it.key }
if (inactive.isEmpty()) break
log.info("Client-replica channels not yet active: $clientId to $inactive")
Thread.sleep((inactive.size * 100).toLong())
}
}
/**
* Sends a transaction commit request to the BFT cluster. The [proxy] will deliver the request to every
* replica, and block until a sufficient number of replies are received.
*/
fun commitTransaction(payload: NotarisationPayload, otherSide: Party): ClusterResponse {
awaitClientConnectionToCluster()
cluster.waitUntilAllReplicasHaveInitialized()
val requestBytes = CommitRequest(payload, otherSide).serialize().bytes
val responseBytes = proxy.invokeOrdered(requestBytes)
return responseBytes.deserialize()
}
/** A comparator to check if replies from two replicas are the same. */
private fun buildResponseComparator(): Comparator<ByteArray> {
return Comparator { o1, o2 ->
val reply1 = o1.deserialize<ReplicaResponse>()
val reply2 = o2.deserialize<ReplicaResponse>()
if (reply1 is ReplicaResponse.Error && reply2 is ReplicaResponse.Error) {
// TODO: for now we treat all errors as equal, compare by error type as well
0
} else if (reply1 is ReplicaResponse.Signature && reply2 is ReplicaResponse.Signature) 0 else -1
}
}
/** An extractor to build the final response message for the client application from all received replica replies. */
private fun buildExtractor(): Extractor {
return Extractor { replies, _, lastReceived ->
val responses = replies.mapNotNull { it?.content?.deserialize<ReplicaResponse>() }
val accepted = responses.filterIsInstance<ReplicaResponse.Signature>()
val rejected = responses.filterIsInstance<ReplicaResponse.Error>()
log.debug { "BFT Client $clientId: number of replicas accepted the commit: ${accepted.size}, rejected: ${rejected.size}" }
// TODO: only return an aggregate if the majority of signatures are replies
// TODO: return an error reported by the majority and not just the first one
val aggregateResponse = if (accepted.isNotEmpty()) {
log.debug { "Cluster response - signatures: ${accepted.map { it.txSignature }}" }
ClusterResponse.Signatures(accepted.map { it.txSignature })
} else {
log.debug { "Cluster response - error: ${rejected.first().error}" }
ClusterResponse.Error(rejected.map { it.error })
}
val messageContent = aggregateResponse.serialize().bytes
// TODO: is it safe use the last message for sender/session/sequence info
val reply = replies[lastReceived]
TOMMessage(reply.sender, reply.session, reply.sequence, messageContent, reply.viewID)
}
}
}
/** ServiceReplica doesn't have any kind of shutdown method, so we add one in this subclass. */
private class CordaServiceReplica(replicaId: Int, configHome: Path, owner: DefaultRecoverable) : ServiceReplica(replicaId, configHome.toString(), owner, owner, null, DefaultReplier()) {
private val tomLayerField = declaredField<TOMLayer>(ServiceReplica::class, "tomLayer")
private val csField = declaredField<ServerCommunicationSystem>(ServiceReplica::class, "cs")
fun dispose() {
// Half of what restart does:
val tomLayer = tomLayerField.value
tomLayer.shutdown() // Non-blocking.
val cs = csField.value
cs.join()
cs.serversConn.join()
tomLayer.join()
tomLayer.deliveryThread.join()
// TODO: At the cluster level, join all Sender/Receiver threads.
}
}
/**
* Maintains the commit log and executes commit commands received from the [Client].
*
* The validation logic can be specified by implementing the [executeCommand] method.
*/
abstract class Replica(config: BFTSMaRtConfig,
replicaId: Int,
createMap: () -> AppendOnlyPersistentMap<StateRef, SecureHash,
BFTNonValidatingNotaryService.CommittedState, PersistentStateRef>,
protected val services: ServiceHubInternal,
protected val notaryIdentityKey: PublicKey) : DefaultRecoverable() {
companion object {
private val log = contextLogger()
}
private val stateManagerOverride = run {
// Mock framework shutdown is not in reverse order, and we need to stop the faulty replicas first:
val exposeStartupRace = config.exposeRaces && replicaId < maxFaultyReplicas(config.clusterSize)
object : StandardStateManager() {
override fun askCurrentConsensusId() {
if (exposeStartupRace) Thread.sleep(20000) // Must be long enough for the non-redundant replicas to reach a non-initial consensus.
super.askCurrentConsensusId()
}
}
}
override fun getStateManager() = stateManagerOverride
// Must be initialised before ServiceReplica is started
private val commitLog = services.database.transaction { createMap() }
private val replica = run {
config.waitUntilReplicaWillNotPrintStackTrace(replicaId)
@Suppress("LeakingThis")
CordaServiceReplica(replicaId, config.path, this)
}
fun dispose() {
replica.dispose()
}
override fun appExecuteUnordered(command: ByteArray, msgCtx: MessageContext): ByteArray? {
throw NotImplementedError("No unordered operations supported")
}
override fun appExecuteBatch(command: Array<ByteArray>, mcs: Array<MessageContext>): Array<ByteArray?> {
return Arrays.stream(command).map(this::executeCommand).toTypedArray()
}
/**
* Implement logic to execute the command and commit the transaction to the log.
* Helper methods are provided for transaction processing: [commitInputStates], and [sign].
*/
abstract fun executeCommand(command: ByteArray): ByteArray?
private fun checkConflict(
conflictingStates: LinkedHashMap<StateRef, StateConsumptionDetails>,
states: List<StateRef>,
type: StateConsumptionDetails.ConsumedStateType
) {
states.forEach { stateRef ->
commitLog[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.sha256(), type) }
}
}
protected fun commitInputStates(
states: List<StateRef>,
txId: SecureHash,
callerName: CordaX500Name,
requestSignature: NotarisationRequestSignature,
timeWindow: TimeWindow?,
references: List<StateRef> = emptyList()
) {
log.debug { "Attempting to commit inputs for transaction: $txId" }
services.database.transaction {
logRequest(txId, callerName, requestSignature)
val conflictingStates = LinkedHashMap<StateRef, StateConsumptionDetails>()
checkConflict(conflictingStates, states, StateConsumptionDetails.ConsumedStateType.INPUT_STATE)
checkConflict(conflictingStates, references, StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE)
if (conflictingStates.isNotEmpty()) {
if (!isConsumedByTheSameTx(txId.sha256(), conflictingStates)) {
log.debug { "Failure, input states or references already committed: ${conflictingStates.keys}" }
throw NotaryInternalException(NotaryError.Conflict(txId, conflictingStates))
}
} else {
val outsideTimeWindowError = validateTimeWindow(services.clock.instant(), timeWindow)
if (outsideTimeWindowError == null) {
states.forEach { commitLog[it] = txId }
log.debug { "Successfully committed all input states: $states" }
} else {
throw NotaryInternalException(outsideTimeWindowError)
}
}
}
}
private fun logRequest(txId: SecureHash, callerName: CordaX500Name, requestSignature: NotarisationRequestSignature) {
val request = PersistentUniquenessProvider.Request(
consumingTxHash = txId.toString(),
partyName = callerName.toString(),
requestSignature = requestSignature.serialize().bytes,
requestDate = services.clock.instant()
)
val session = currentDBSession()
session.persist(request)
}
/** Generates a signature over an arbitrary array of bytes. */
protected fun sign(bytes: ByteArray): DigitalSignature.WithKey {
return services.database.transaction { services.keyManagementService.sign(bytes, notaryIdentityKey) }
}
/** Generates a transaction signature over the specified transaction [txId]. */
protected fun sign(txId: SecureHash): TransactionSignature {
val signableData = SignableData(txId, SignatureMetadata(services.myInfo.platformVersion, Crypto.findSignatureScheme(notaryIdentityKey).schemeNumberID))
return services.database.transaction { services.keyManagementService.sign(signableData, notaryIdentityKey) }
}
// TODO:
// - Test snapshot functionality with different bft-smart cluster configurations.
// - Add streaming to support large data sets.
override fun getSnapshot(): ByteArray {
// LinkedHashMap for deterministic serialisation
val committedStates = LinkedHashMap<StateRef, SecureHash>()
val requests = services.database.transaction {
commitLog.allPersisted().forEach { committedStates[it.first] = it.second }
val criteriaQuery = session.criteriaBuilder.createQuery(PersistentUniquenessProvider.Request::class.java)
criteriaQuery.select(criteriaQuery.from(PersistentUniquenessProvider.Request::class.java))
session.createQuery(criteriaQuery).resultList
}
return (committedStates to requests).serialize().bytes
}
override fun installSnapshot(bytes: ByteArray) {
val (committedStates, requests) = bytes.deserialize<Pair<LinkedHashMap<StateRef, SecureHash>, List<PersistentUniquenessProvider.Request>>>()
services.database.transaction {
commitLog.clear()
commitLog.putAll(committedStates)
val deleteQuery = session.criteriaBuilder.createCriteriaDelete(PersistentUniquenessProvider.Request::class.java)
deleteQuery.from(PersistentUniquenessProvider.Request::class.java)
session.createQuery(deleteQuery).executeUpdate()
requests.forEach { session.persist(it) }
}
}
}
}

View File

@ -1,100 +0,0 @@
package net.corda.node.services.transactions
import net.corda.core.internal.div
import net.corda.core.internal.writer
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import java.io.PrintWriter
import java.net.InetAddress
import java.net.Socket
import java.net.SocketException
import java.nio.file.Files
import java.util.concurrent.TimeUnit.MILLISECONDS
/**
* BFT SMaRt can only be configured via files in a configHome directory.
* Each instance of this class creates such a configHome, accessible via [path].
* The files are deleted on [close] typically via [use], see [PathManager] for details.
*/
class BFTSMaRtConfig(private val replicaAddresses: List<NetworkHostAndPort>, debug: Boolean, val exposeRaces: Boolean) : PathManager<BFTSMaRtConfig>(Files.createTempDirectory("bft-smart-config")) {
companion object {
private val log = contextLogger()
internal const val portIsClaimedFormat = "Port %s is claimed by another replica: %s"
}
val clusterSize get() = replicaAddresses.size
init {
val claimedPorts = mutableSetOf<NetworkHostAndPort>()
val n = clusterSize
(0 until n).forEach { replicaId ->
// Each replica claims the configured port and the next one:
replicaPorts(replicaId).forEach { port ->
claimedPorts.add(port) || throw IllegalArgumentException(portIsClaimedFormat.format(port, claimedPorts))
}
}
configWriter("hosts.config") {
replicaAddresses.forEachIndexed { index, (host, port) ->
// The documentation strongly recommends IP addresses:
println("$index ${InetAddress.getByName(host).hostAddress} $port")
}
}
val systemConfig = String.format(javaClass.getResource("system.config.printf").readText(), n, maxFaultyReplicas(n), if (debug) 1 else 0, (0 until n).joinToString(","))
configWriter("system.config") {
print(systemConfig)
}
}
private fun configWriter(name: String, block: PrintWriter.() -> Unit) {
// Default charset, consistent with loaders:
(path / name).writer().use {
PrintWriter(it).use {
it.run(block)
}
}
}
fun waitUntilReplicaWillNotPrintStackTrace(contextReplicaId: Int) {
// A replica will printStackTrace until all lower-numbered replicas are listening.
// But we can't probe a replica without it logging EOFException when our probe succeeds.
// So to keep logging to a minimum we only check the previous replica:
val peerId = contextReplicaId - 1
if (peerId < 0) return
// The printStackTrace we want to avoid is in replica-replica communication code:
val address = BFTSMaRtPort.FOR_REPLICAS.ofReplica(replicaAddresses[peerId])
log.debug { "Waiting for replica $peerId to start listening on: $address" }
while (!address.isListening()) MILLISECONDS.sleep(200)
log.debug { "Replica $peerId is ready for P2P." }
}
private fun replicaPorts(replicaId: Int): List<NetworkHostAndPort> {
val base = replicaAddresses[replicaId]
return BFTSMaRtPort.values().map { it.ofReplica(base) }
}
}
private enum class BFTSMaRtPort(private val off: Int) {
FOR_CLIENTS(0),
FOR_REPLICAS(1);
fun ofReplica(base: NetworkHostAndPort) = NetworkHostAndPort(base.host, base.port + off)
}
private fun NetworkHostAndPort.isListening() = try {
Socket(host, port).use { true } // Will cause one error to be logged in the replica on success.
} catch (e: SocketException) {
false
}
fun maxFaultyReplicas(clusterSize: Int) = (clusterSize - 1) / 3
fun minCorrectReplicas(clusterSize: Int) = (2 * clusterSize + 3) / 3
fun minClusterSize(maxFaultyReplicas: Int) = maxFaultyReplicas * 3 + 1
fun bftSMaRtSerialFilter(clazz: Class<*>): Boolean = clazz.name.let {
it.startsWith("bftsmart.")
|| it.startsWith("java.security.")
|| it.startsWith("java.util.")
|| it.startsWith("java.lang.")
|| it.startsWith("java.net.")
}

View File

@ -9,6 +9,7 @@ import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.flows.NotaryError
import net.corda.core.flows.StateConsumptionDetails
import net.corda.core.identity.Party
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.internal.concurrent.OpenFuture
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.notary.AsyncUniquenessProvider
@ -22,7 +23,6 @@ import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import net.corda.nodeapi.internal.persistence.currentDBSession

View File

@ -1,26 +0,0 @@
package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.node.ServiceHub
import java.security.PublicKey
/** A non-validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
class RaftNonValidatingNotaryService(
override val services: ServiceHub,
override val notaryIdentityKey: PublicKey,
override val uniquenessProvider: RaftUniquenessProvider
) : TrustedAuthorityNotaryService() {
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
return NonValidatingNotaryFlow(otherPartySession, this)
}
override fun start() {
uniquenessProvider.start()
}
override fun stop() {
uniquenessProvider.stop()
}
}

View File

@ -1,220 +0,0 @@
package net.corda.node.services.transactions
import io.atomix.catalyst.buffer.BufferInput
import io.atomix.catalyst.buffer.BufferOutput
import io.atomix.catalyst.serializer.Serializer
import io.atomix.catalyst.serializer.TypeSerializer
import io.atomix.copycat.Command
import io.atomix.copycat.Query
import io.atomix.copycat.server.Commit
import io.atomix.copycat.server.Snapshottable
import io.atomix.copycat.server.StateMachine
import io.atomix.copycat.server.storage.snapshot.SnapshotReader
import io.atomix.copycat.server.storage.snapshot.SnapshotWriter
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.sha256
import net.corda.core.flows.NotaryError
import net.corda.core.flows.StateConsumptionDetails
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.notary.isConsumedByTheSameTx
import net.corda.core.internal.notary.validateTimeWindow
import net.corda.core.serialization.*
import net.corda.core.serialization.internal.CheckpointSerializationDefaults
import net.corda.core.serialization.internal.CheckpointSerializationFactory
import net.corda.core.serialization.internal.checkpointSerialize
import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.currentDBSession
import net.corda.serialization.internal.CordaSerializationEncoding
import java.time.Clock
/**
* Notarised contract state commit log, replicated across a Copycat Raft cluster.
*
* Copycat ony supports in-memory state machines, so we back the state with JDBC tables.
* State re-synchronisation is achieved by replaying the command log to the new (or re-joining) cluster member.
*/
class RaftTransactionCommitLog<E, EK>(
private val db: CordaPersistence,
private val nodeClock: Clock,
createMap: () -> AppendOnlyPersistentMap<StateRef, Pair<Long, SecureHash>, E, EK>
) : StateMachine(), Snapshottable {
object Commands {
class CommitTransaction @JvmOverloads constructor(
val states: List<StateRef>,
val txId: SecureHash,
val requestingParty: String,
val requestSignature: ByteArray,
val timeWindow: TimeWindow? = null,
val references: List<StateRef> = emptyList()
) : Command<NotaryError?> {
override fun compaction(): Command.CompactionMode {
// The FULL compaction mode retains the command in the log until it has been stored and applied on all
// servers in the cluster. Once the commit has been applied to a state machine and closed it may be
// removed from the log during minor or major compaction.
//
// Note that we are not closing the commits, thus our log grows without bounds. We let the log grow on
// purpose to be able to increase the size of a running cluster, e.g. to add and decommission nodes.
// TODO: Cluster membership changes need testing.
// TODO: I'm wondering if we should support resizing notary clusters, or if we could require users to
// setup a new cluster of the desired size and transfer the data.
return Command.CompactionMode.FULL
}
}
class Get(val key: StateRef) : Query<SecureHash?>
}
private val map = db.transaction { createMap() }
/** Commits the input states for the transaction as specified in the given [Commands.CommitTransaction]. */
fun commitTransaction(raftCommit: Commit<Commands.CommitTransaction>): NotaryError? {
val conflictingStates = LinkedHashMap<StateRef, StateConsumptionDetails>()
fun checkConflict(states: List<StateRef>, type: StateConsumptionDetails.ConsumedStateType) = states.forEach { stateRef ->
map[stateRef]?.let { conflictingStates[stateRef] = StateConsumptionDetails(it.second.sha256(), type) }
}
raftCommit.use {
val index = it.index()
return db.transaction {
val commitCommand = raftCommit.command()
logRequest(commitCommand)
val txId = commitCommand.txId
log.debug("State machine commit: attempting to store entries with keys (${commitCommand.states.joinToString()})")
checkConflict(commitCommand.states, StateConsumptionDetails.ConsumedStateType.INPUT_STATE)
checkConflict(commitCommand.references, StateConsumptionDetails.ConsumedStateType.REFERENCE_INPUT_STATE)
if (conflictingStates.isNotEmpty()) {
if (isConsumedByTheSameTx(commitCommand.txId.sha256(), conflictingStates)) {
null
} else {
log.debug { "Failure, input states already committed: ${conflictingStates.keys}" }
NotaryError.Conflict(txId, conflictingStates)
}
} else {
val outsideTimeWindowError = validateTimeWindow(clock.instant(), commitCommand.timeWindow)
if (outsideTimeWindowError == null) {
val entries = commitCommand.states.map { it to Pair(index, txId) }.toMap()
map.putAll(entries)
log.debug { "Successfully committed all input states: ${commitCommand.states}" }
null
} else {
outsideTimeWindowError
}
}
}
}
}
private fun logRequest(commitCommand: RaftTransactionCommitLog.Commands.CommitTransaction) {
val request = PersistentUniquenessProvider.Request(
consumingTxHash = commitCommand.txId.toString(),
partyName = commitCommand.requestingParty,
requestSignature = commitCommand.requestSignature,
requestDate = nodeClock.instant()
)
val session = currentDBSession()
session.persist(request)
}
/** Gets the consuming transaction id for a given state reference. */
fun get(commit: Commit<Commands.Get>): SecureHash? {
commit.use {
val key = it.operation().key
return db.transaction { map[key]?.second }
}
}
/**
* Writes out all committed state and notarisation request entries to disk. Note that this operation does not
* load all entries into memory, as the [SnapshotWriter] is using a disk-backed buffer internally, and iterating
* map entries results in only a fixed number of recently accessed entries to ever be kept in memory.
*/
override fun snapshot(writer: SnapshotWriter) {
db.transaction {
writer.writeInt(map.size)
map.allPersisted().forEach {
val bytes = it.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes
writer.writeUnsignedShort(bytes.size)
writer.writeObject(bytes)
}
val criteriaQuery = session.criteriaBuilder.createQuery(PersistentUniquenessProvider.Request::class.java)
criteriaQuery.select(criteriaQuery.from(PersistentUniquenessProvider.Request::class.java))
val results = session.createQuery(criteriaQuery).resultList
writer.writeInt(results.size)
results.forEach {
val bytes = it.serialize(context = SerializationDefaults.STORAGE_CONTEXT).bytes
writer.writeUnsignedShort(bytes.size)
writer.writeObject(bytes)
}
}
}
/** Reads entries from disk and populates the committed state and notarisation request tables. */
override fun install(reader: SnapshotReader) {
val size = reader.readInt()
db.transaction {
map.clear()
// TODO: read & put entries in batches
for (i in 1..size) {
val bytes = ByteArray(reader.readUnsignedShort())
reader.read(bytes)
val (key, value) = bytes.deserialize<Pair<StateRef, Pair<Long, SecureHash>>>()
map[key] = value
}
// Clean notarisation request log
val deleteQuery = session.criteriaBuilder.createCriteriaDelete(PersistentUniquenessProvider.Request::class.java)
deleteQuery.from(PersistentUniquenessProvider.Request::class.java)
session.createQuery(deleteQuery).executeUpdate()
// Load and populate request log
for (i in 1..reader.readInt()) {
val bytes = ByteArray(reader.readUnsignedShort())
reader.read(bytes)
val request = bytes.deserialize<PersistentUniquenessProvider.Request>()
session.persist(request)
}
}
}
companion object {
private val log = contextLogger()
@VisibleForTesting
val serializer: Serializer by lazy {
Serializer().apply {
registerAbstract(SecureHash::class.java, CordaKryoSerializer::class.java)
registerAbstract(TimeWindow::class.java, CordaKryoSerializer::class.java)
registerAbstract(NotaryError::class.java, CordaKryoSerializer::class.java)
register(RaftTransactionCommitLog.Commands.CommitTransaction::class.java, CordaKryoSerializer::class.java)
register(RaftTransactionCommitLog.Commands.Get::class.java, CordaKryoSerializer::class.java)
register(StateRef::class.java, CordaKryoSerializer::class.java)
register(LinkedHashMap::class.java, CordaKryoSerializer::class.java)
}
}
class CordaKryoSerializer<T : Any> : TypeSerializer<T> {
private val context = CheckpointSerializationDefaults.CHECKPOINT_CONTEXT.withEncoding(CordaSerializationEncoding.SNAPPY)
private val factory = CheckpointSerializationFactory.defaultFactory
override fun write(obj: T, buffer: BufferOutput<*>, serializer: Serializer) {
val serialized = obj.checkpointSerialize(context = context)
buffer.writeInt(serialized.size)
buffer.write(serialized.bytes)
}
override fun read(type: Class<T>, buffer: BufferInput<*>, serializer: Serializer): T {
val size = buffer.readInt()
val serialized = ByteArray(size)
buffer.read(serialized)
return factory.deserialize(ByteSequence.of(serialized), type, context)
}
}
}
}

View File

@ -1,213 +0,0 @@
package net.corda.node.services.transactions
import com.codahale.metrics.Gauge
import com.codahale.metrics.MetricRegistry
import io.atomix.catalyst.transport.Address
import io.atomix.catalyst.transport.Transport
import io.atomix.catalyst.transport.netty.NettyTransport
import io.atomix.catalyst.transport.netty.SslProtocol
import io.atomix.copycat.client.ConnectionStrategies
import io.atomix.copycat.client.CopycatClient
import io.atomix.copycat.client.RecoveryStrategies
import io.atomix.copycat.server.CopycatServer
import io.atomix.copycat.server.cluster.Member
import io.atomix.copycat.server.storage.Storage
import io.atomix.copycat.server.storage.StorageLevel
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.TimeWindow
import net.corda.core.crypto.SecureHash
import net.corda.core.flows.NotarisationRequestSignature
import net.corda.core.identity.Party
import net.corda.core.internal.notary.NotaryInternalException
import net.corda.core.internal.notary.UniquenessProvider
import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.serialize
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.debug
import net.corda.node.services.config.RaftConfig
import net.corda.node.services.transactions.RaftTransactionCommitLog.Commands.CommitTransaction
import net.corda.node.utilities.AppendOnlyPersistentMap
import net.corda.node.utilities.NamedCacheFactory
import net.corda.nodeapi.internal.config.MutualSslConfiguration
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import java.nio.file.Path
import java.time.Clock
import java.util.concurrent.CompletableFuture
import javax.annotation.concurrent.ThreadSafe
import javax.persistence.Column
import javax.persistence.EmbeddedId
import javax.persistence.Entity
import javax.persistence.Table
/**
* A uniqueness provider that records committed input states in a distributed collection replicated and
* persisted in a Raft cluster, using the Copycat framework (http://atomix.io/copycat/).
*
* The uniqueness provider maintains both a Copycat cluster node (server) and a client through which it can submit
* requests to the cluster. In Copycat, a client request is first sent to the server it's connected to and then redirected
* to the cluster leader to be actioned.
*/
@ThreadSafe
class RaftUniquenessProvider(
private val storagePath: Path,
private val transportConfiguration: MutualSslConfiguration,
private val db: CordaPersistence,
private val clock: Clock,
private val metrics: MetricRegistry,
private val cacheFactory: NamedCacheFactory,
private val raftConfig: RaftConfig
) : UniquenessProvider, SingletonSerializeAsToken() {
companion object {
private val log = contextLogger()
fun createMap(cacheFactory: NamedCacheFactory): AppendOnlyPersistentMap<StateRef, Pair<Long, SecureHash>, CommittedState, PersistentStateRef> =
AppendOnlyPersistentMap(
cacheFactory = cacheFactory,
name = "RaftUniquenessProvider_transactions",
toPersistentEntityKey = { PersistentStateRef(it) },
fromPersistentEntity = {
val txId = it.id.txId
val index = it.id.index
Pair(
StateRef(txhash = SecureHash.parse(txId), index = index),
Pair(it.index, SecureHash.parse(it.value) as SecureHash))
},
toPersistentEntity = { k: StateRef, (first, second) ->
CommittedState(
PersistentStateRef(k),
second.toString(),
first)
},
persistentEntityClass = CommittedState::class.java
)
fun StateRef.encoded() = "$txhash:$index"
fun String.parseStateRef() = split(":").let { StateRef(SecureHash.parse(it[0]), it[1].toInt()) }
}
@Entity
@Table(name = "${NODE_DATABASE_PREFIX}raft_committed_states")
class CommittedState(
@EmbeddedId
val id: PersistentStateRef,
@Column(name = "consuming_transaction_id", nullable = true)
var value: String? = "",
@Column(name = "raft_log_index", nullable = false)
var index: Long = 0
)
private lateinit var _clientFuture: CompletableFuture<CopycatClient>
private lateinit var server: CopycatServer
/**
* Copycat clients are responsible for connecting to the cluster and submitting commands and queries that operate
* on the cluster's replicated state machine.
*/
private val client: CopycatClient
get() = _clientFuture.get()
fun start() {
log.info("Creating Copycat server, log stored in: ${storagePath.toAbsolutePath()}")
val stateMachineFactory = {
RaftTransactionCommitLog(db, clock, { createMap(cacheFactory) })
}
val address = raftConfig.nodeAddress.let { Address(it.host, it.port) }
val storage = buildStorage(storagePath)
val transport = buildTransport(transportConfiguration)
server = CopycatServer.builder(address)
.withStateMachine(stateMachineFactory)
.withStorage(storage)
.withServerTransport(transport)
.withSerializer(RaftTransactionCommitLog.serializer)
.build()
val serverFuture = if (raftConfig.clusterAddresses.isNotEmpty()) {
log.info("Joining an existing Copycat cluster at ${raftConfig.clusterAddresses}")
val cluster = raftConfig.clusterAddresses.map { Address(it.host, it.port) }
server.join(cluster)
} else {
log.info("Bootstrapping a Copycat cluster at $address")
server.bootstrap()
}
registerMonitoring()
val client = CopycatClient.builder(address)
.withTransport(transport) // TODO: use local transport for client-server communications
.withConnectionStrategy(ConnectionStrategies.EXPONENTIAL_BACKOFF)
.withSerializer(RaftTransactionCommitLog.serializer)
.withRecoveryStrategy(RecoveryStrategies.RECOVER)
.build()
_clientFuture = serverFuture.thenCompose { client.connect(address) }
}
fun stop() {
server.shutdown()
}
private fun buildStorage(storagePath: Path): Storage? {
return Storage.builder()
.withDirectory(storagePath.toFile())
.withStorageLevel(StorageLevel.DISK)
.build()
}
private fun buildTransport(config: MutualSslConfiguration): Transport? {
return NettyTransport.builder()
.withSsl()
.withSslProtocol(SslProtocol.TLSv1_2)
.withKeyStorePath(config.keyStore.path.toString())
.withKeyStorePassword(config.keyStore.password)
.withTrustStorePath(config.trustStore.path.toString())
.withTrustStorePassword(config.trustStore.password)
.build()
}
private fun registerMonitoring() {
metrics.register("RaftCluster.ThisServerStatus", Gauge<String> {
server.state().name
})
metrics.register("RaftCluster.MembersCount", Gauge<Int> {
server.cluster().members().size
})
metrics.register("RaftCluster.Members", Gauge<List<String>> {
server.cluster().members().map { it.address().toString() }
})
metrics.register("RaftCluster.AvailableMembers", Gauge<List<String>> {
server.cluster().members().filter { it.status() == Member.Status.AVAILABLE }.map { it.address().toString() }
})
metrics.register("RaftCluster.AvailableMembersCount", Gauge<Int> {
server.cluster().members().filter { it.status() == Member.Status.AVAILABLE }.size
})
}
override fun commit(
states: List<StateRef>,
txId: SecureHash,
callerIdentity: Party,
requestSignature: NotarisationRequestSignature,
timeWindow: TimeWindow?,
references: List<StateRef>
) {
log.debug { "Attempting to commit input states: ${states.joinToString()}" }
val commitCommand = CommitTransaction(
states,
txId,
callerIdentity.name.toString(),
requestSignature.serialize().bytes,
timeWindow,
references
)
val commitError = client.submit(commitCommand).get()
if (commitError != null) throw NotaryInternalException(commitError)
log.debug { "All input states of transaction $txId have been committed" }
}
}

View File

@ -1,26 +0,0 @@
package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.node.ServiceHub
import java.security.PublicKey
/** A validating notary service operated by a group of mutually trusting parties, uses the Raft algorithm to achieve consensus. */
class RaftValidatingNotaryService(
override val services: ServiceHub,
override val notaryIdentityKey: PublicKey,
override val uniquenessProvider: RaftUniquenessProvider
) : TrustedAuthorityNotaryService() {
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
return ValidatingNotaryFlow(otherPartySession, this)
}
override fun start() {
uniquenessProvider.start()
}
override fun stop() {
uniquenessProvider.stop()
}
}

View File

@ -3,15 +3,38 @@ package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.core.schemas.MappedSchema
import net.corda.node.services.api.ServiceHubInternal
import java.security.PublicKey
/** A simple Notary service that does not perform transaction validation */
/** An embedded notary service that uses the node's database to store committed states. */
class SimpleNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
private val notaryConfig = services.configuration.notary
?: throw IllegalArgumentException("Failed to register ${this::class.java}: notary configuration not present")
override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = NonValidatingNotaryFlow(otherPartySession, this)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow {
return if (notaryConfig.validating) {
log.info("Starting in validating mode")
ValidatingNotaryFlow(otherPartySession, this)
} else {
log.info("Starting in non-validating mode")
NonValidatingNotaryFlow(otherPartySession, this)
}
}
override fun start() {}
override fun stop() {}
}
// Entities used by a Notary
object NodeNotarySchema
object NodeNotarySchemaV1 : MappedSchema(schemaFamily = NodeNotarySchema.javaClass, version = 1,
mappedTypes = listOf(PersistentUniquenessProvider.BaseComittedState::class.java,
PersistentUniquenessProvider.Request::class.java,
PersistentUniquenessProvider.CommittedState::class.java
)) {
override val migrationResource = "node-notary.changelog-master"
}

View File

@ -1,17 +0,0 @@
package net.corda.node.services.transactions
import net.corda.core.flows.FlowSession
import net.corda.core.internal.notary.NotaryServiceFlow
import net.corda.core.internal.notary.TrustedAuthorityNotaryService
import net.corda.node.services.api.ServiceHubInternal
import java.security.PublicKey
/** A Notary service that validates the transaction chain of the submitted transaction before committing it */
class ValidatingNotaryService(override val services: ServiceHubInternal, override val notaryIdentityKey: PublicKey) : TrustedAuthorityNotaryService() {
override val uniquenessProvider = PersistentUniquenessProvider(services.clock, services.database, services.cacheFactory)
override fun createServiceFlow(otherPartySession: FlowSession): NotaryServiceFlow = ValidatingNotaryFlow(otherPartySession, this)
override fun start() {}
override fun stop() {}
}

View File

@ -2,6 +2,7 @@ package net.corda.node.services.upgrade
import net.corda.core.contracts.StateRef
import net.corda.core.contracts.UpgradedContract
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.node.services.ContractUpgradeService
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.node.utilities.PersistentMap
@ -11,7 +12,7 @@ import javax.persistence.Entity
import javax.persistence.Id
import javax.persistence.Table
class ContractUpgradeServiceImpl : ContractUpgradeService, SingletonSerializeAsToken() {
class ContractUpgradeServiceImpl(cacheFactory: NamedCacheFactory) : ContractUpgradeService, SingletonSerializeAsToken() {
@Entity
@Table(name = "${NODE_DATABASE_PREFIX}contract_upgrades")
@ -26,7 +27,7 @@ class ContractUpgradeServiceImpl : ContractUpgradeService, SingletonSerializeAsT
)
private companion object {
fun createContractUpgradesMap(): PersistentMap<String, String, DBContractUpgrade, String> {
fun createContractUpgradesMap(cacheFactory: NamedCacheFactory): PersistentMap<String, String, DBContractUpgrade, String> {
return PersistentMap(
"ContractUpgradeService_upgrades",
toPersistentEntityKey = { it },
@ -37,12 +38,13 @@ class ContractUpgradeServiceImpl : ContractUpgradeService, SingletonSerializeAsT
upgradedContractClassName = value
}
},
persistentEntityClass = DBContractUpgrade::class.java
persistentEntityClass = DBContractUpgrade::class.java,
cacheFactory = cacheFactory
)
}
}
private val authorisedUpgrade = createContractUpgradesMap()
private val authorisedUpgrade = createContractUpgradesMap(cacheFactory)
fun start() {
authorisedUpgrade.preload()

View File

@ -225,6 +225,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
private val rootEntities = mutableMapOf<Class<out PersistentState>, Root<*>>(Pair(VaultSchemaV1.VaultStates::class.java, vaultStates))
private val aggregateExpressions = mutableListOf<Expression<*>>()
private val commonPredicates = mutableMapOf<Pair<String, Operator>, Predicate>() // schema attribute Name, operator -> predicate
private val constraintPredicates = mutableSetOf<Predicate>()
var stateTypes: Vault.StateStatus = Vault.StateStatus.UNCONSUMED
@ -508,7 +509,7 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
else
aggregateExpressions
criteriaQuery.multiselect(selections)
val combinedPredicates = commonPredicates.values.plus(predicateSet)
val combinedPredicates = commonPredicates.values.plus(predicateSet).plus(constraintPredicates)
criteriaQuery.where(*combinedPredicates.toTypedArray())
return predicateSet
@ -561,6 +562,38 @@ class HibernateQueryCriteriaParser(val contractStateType: Class<out ContractStat
}
}
// contract constraint types
if (criteria.constraintTypes.isNotEmpty()) {
val predicateID = Pair(VaultSchemaV1.VaultStates::constraintType.name, IN)
if (commonPredicates.containsKey(predicateID)) {
val existingTypes = (commonPredicates[predicateID]!!.expressions[0] as InPredicate<*>).values.map { (it as LiteralExpression).literal }.toSet()
if (existingTypes != criteria.constraintTypes) {
log.warn("Enriching previous attribute [${VaultSchemaV1.VaultStates::constraintType.name}] values [$existingTypes] with [${criteria.constraintTypes}]")
commonPredicates.replace(predicateID, criteriaBuilder.and(vaultStates.get<Vault.ConstraintInfo.Type>(VaultSchemaV1.VaultStates::constraintType.name).`in`(criteria.constraintTypes.plus(existingTypes))))
}
} else {
commonPredicates[predicateID] = criteriaBuilder.and(vaultStates.get<Vault.ConstraintInfo.Type>(VaultSchemaV1.VaultStates::constraintType.name).`in`(criteria.constraintTypes))
}
}
// contract constraint information (type and data)
if (criteria.constraints.isNotEmpty()) {
criteria.constraints.forEach { constraint ->
val predicateConstraintType = criteriaBuilder.equal(vaultStates.get<Vault.ConstraintInfo>(VaultSchemaV1.VaultStates::constraintType.name), constraint.type())
if (constraint.data() != null) {
val predicateConstraintData = criteriaBuilder.equal(vaultStates.get<Vault.ConstraintInfo>(VaultSchemaV1.VaultStates::constraintData.name), constraint.data())
val compositePredicate = criteriaBuilder.and(predicateConstraintType, predicateConstraintData)
if (constraintPredicates.isNotEmpty()) {
val previousPredicate = constraintPredicates.last()
constraintPredicates.clear()
constraintPredicates.add(criteriaBuilder.or(previousPredicate, compositePredicate))
}
else constraintPredicates.add(compositePredicate)
}
else constraintPredicates.add(criteriaBuilder.or(predicateConstraintType))
}
}
return emptySet()
}

View File

@ -10,6 +10,7 @@ import net.corda.core.messaging.DataFeed
import net.corda.core.node.ServicesForResolution
import net.corda.core.node.StatesToRecord
import net.corda.core.node.services.*
import net.corda.core.node.services.Vault.ConstraintInfo.Companion.constraintInfo
import net.corda.core.node.services.vault.*
import net.corda.core.schemas.PersistentStateRef
import net.corda.core.serialization.SingletonSerializeAsToken
@ -132,12 +133,15 @@ class NodeVaultService(
// Adding a new column in the "VaultStates" table was considered the best approach.
val keys = stateOnly.participants.map { it.owningKey }
val isRelevant = isRelevant(stateOnly, keyManagementService.filterMyKeys(keys).toSet())
val constraintInfo = Vault.ConstraintInfo(stateAndRef.value.state.constraint)
val stateToAdd = VaultSchemaV1.VaultStates(
notary = stateAndRef.value.state.notary,
contractStateClassName = stateAndRef.value.state.data.javaClass.name,
stateStatus = Vault.StateStatus.UNCONSUMED,
recordedTime = clock.instant(),
relevancyStatus = if (isRelevant) Vault.RelevancyStatus.RELEVANT else Vault.RelevancyStatus.NOT_RELEVANT
relevancyStatus = if (isRelevant) Vault.RelevancyStatus.RELEVANT else Vault.RelevancyStatus.NOT_RELEVANT,
constraintType = constraintInfo.type(),
constraintData = constraintInfo.data()
)
stateToAdd.stateRef = PersistentStateRef(stateAndRef.key)
session.save(stateToAdd)
@ -271,7 +275,10 @@ class NodeVaultService(
val uuid = (Strand.currentStrand() as? FlowStateMachineImpl<*>)?.id?.uuid
val vaultUpdate = if (uuid != null) netUpdate.copy(flowId = uuid) else netUpdate
if (uuid != null) {
val fungible = netUpdate.produced.filter { it.state.data is FungibleAsset<*> }
val fungible = netUpdate.produced.filter { stateAndRef ->
val state = stateAndRef.state.data
state is FungibleAsset<*> || state is FungibleState<*>
}
if (fungible.isNotEmpty()) {
val stateRefs = fungible.map { it.ref }.toNonEmptySet()
log.trace { "Reserving soft locks for flow id $uuid and states $stateRefs" }
@ -390,14 +397,27 @@ class NodeVaultService(
@Suspendable
@Throws(StatesNotAvailableException::class)
override fun <T : FungibleAsset<U>, U : Any> tryLockFungibleStatesForSpending(lockId: UUID,
eligibleStatesQuery: QueryCriteria,
amount: Amount<U>,
contractStateType: Class<out T>): List<StateAndRef<T>> {
override fun <T : FungibleState<*>> tryLockFungibleStatesForSpending(
lockId: UUID,
eligibleStatesQuery: QueryCriteria,
amount: Amount<*>,
contractStateType: Class<out T>
): List<StateAndRef<T>> {
if (amount.quantity == 0L) {
return emptyList()
}
// Helper to unwrap the token from the Issued object if one exists.
fun unwrapIssuedAmount(amount: Amount<*>): Any {
val token = amount.token
return when (token) {
is Issued<*> -> token.product
else -> token
}
}
val unwrappedToken = unwrapIssuedAmount(amount)
// Enrich QueryCriteria with additional default attributes (such as soft locks).
// We only want to return RELEVANT states here.
val sortAttribute = SortAttribute.Standard(Sort.CommonStateAttribute.STATE_REF)
@ -412,8 +432,10 @@ class NodeVaultService(
var claimedAmount = 0L
val claimedStates = mutableListOf<StateAndRef<T>>()
for (state in results.states) {
val issuedAssetToken = state.state.data.amount.token
if (issuedAssetToken.product == amount.token) {
// This method handles Amount<Issued<T>> in FungibleAsset and Amount<T> in FungibleState.
val issuedAssetToken = unwrapIssuedAmount(state.state.data.amount)
if (issuedAssetToken == unwrappedToken) {
claimedStates += state
claimedAmount += state.state.data.amount.quantity
if (claimedAmount > amount.quantity) {
@ -514,7 +536,9 @@ class NodeVaultService(
vaultState.notary,
vaultState.lockId,
vaultState.lockUpdateTime,
vaultState.relevancyStatus))
vaultState.relevancyStatus,
constraintInfo(vaultState.constraintType, vaultState.constraintData)
))
} else {
// TODO: improve typing of returned other results
log.debug { "OtherResults: ${Arrays.toString(result.toArray())}" }

View File

@ -5,6 +5,7 @@ import net.corda.core.contracts.MAX_ISSUER_REF_SIZE
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.identity.AbstractParty
import net.corda.core.identity.Party
import net.corda.core.node.services.MAX_CONSTRAINT_DATA_SIZE
import net.corda.core.node.services.Vault
import net.corda.core.schemas.MappedSchema
import net.corda.core.schemas.PersistentState
@ -66,7 +67,16 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
/** refers to the last time a lock was taken (reserved) or updated (released, re-reserved) */
@Column(name = "lock_timestamp", nullable = true)
var lockUpdateTime: Instant? = null
var lockUpdateTime: Instant? = null,
/** refers to constraint type (none, hash, whitelisted, signature) associated with a contract state */
@Column(name = "constraint_type", nullable = false)
var constraintType: Vault.ConstraintInfo.Type,
/** associated constraint type data (if any) */
@Column(name = "constraint_data", length = MAX_CONSTRAINT_DATA_SIZE, nullable = true)
@Type(type = "corda-wrapper-binary")
var constraintData: ByteArray? = null
) : PersistentState()
@Entity
@ -135,9 +145,9 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
@Column(name = "issuer_name", nullable = true)
var issuer: AbstractParty?,
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE, nullable = false)
@Column(name = "issuer_ref", length = MAX_ISSUER_REF_SIZE, nullable = true)
@Type(type = "corda-wrapper-binary")
var issuerRef: ByteArray
var issuerRef: ByteArray?
) : PersistentState() {
constructor(_owner: AbstractParty, _quantity: Long, _issuerParty: AbstractParty, _issuerRef: OpaqueBytes, _participants: List<AbstractParty>) :
this(owner = _owner,

View File

@ -2,6 +2,7 @@ package net.corda.node.utilities
import com.github.benmanes.caffeine.cache.LoadingCache
import com.github.benmanes.caffeine.cache.Weigher
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.persistence.DatabaseTransaction
import net.corda.nodeapi.internal.persistence.contextTransaction
@ -12,7 +13,6 @@ import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicReference
/**
* Implements a caching layer on top of an *append-only* table accessed via Hibernate mapping. Note that if the same key is [set] twice,
* typically this will result in a duplicate insert if this is racing with another transaction. The flow framework will then retry.

View File

@ -5,50 +5,77 @@ import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.CacheLoader
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.LoadingCache
import net.corda.core.internal.buildNamed
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.node.services.config.NodeConfiguration
import java.util.concurrent.TimeUnit
/**
* Allow passing metrics and config to caching implementations.
* Allow passing metrics and config to caching implementations. This is needs to be distinct from [NamedCacheFactory]
* to avoid deterministic serialization from seeing metrics and config on method signatures.
*/
interface NamedCacheFactory : SerializeAsToken {
interface BindableNamedCacheFactory : NamedCacheFactory, SerializeAsToken {
/**
* Build a new cache factory of the same type that incorporates metrics.
*/
fun bindWithMetrics(metricRegistry: MetricRegistry): NamedCacheFactory
fun bindWithMetrics(metricRegistry: MetricRegistry): BindableNamedCacheFactory
/**
* Build a new cache factory of the same type that incorporates the associated configuration.
*/
fun bindWithConfig(nodeConfiguration: NodeConfiguration): NamedCacheFactory
fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String): Cache<K, V>
fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String, loader: CacheLoader<K, V>): LoadingCache<K, V>
fun bindWithConfig(nodeConfiguration: NodeConfiguration): BindableNamedCacheFactory
}
class DefaultNamedCacheFactory private constructor(private val metricRegistry: MetricRegistry?, private val nodeConfiguration: NodeConfiguration?) : NamedCacheFactory, SingletonSerializeAsToken() {
open class DefaultNamedCacheFactory protected constructor(private val metricRegistry: MetricRegistry?, private val nodeConfiguration: NodeConfiguration?) : BindableNamedCacheFactory, SingletonSerializeAsToken() {
constructor() : this(null, null)
override fun bindWithMetrics(metricRegistry: MetricRegistry): NamedCacheFactory = DefaultNamedCacheFactory(metricRegistry, this.nodeConfiguration)
override fun bindWithConfig(nodeConfiguration: NodeConfiguration): NamedCacheFactory = DefaultNamedCacheFactory(this.metricRegistry, nodeConfiguration)
override fun bindWithMetrics(metricRegistry: MetricRegistry): BindableNamedCacheFactory = DefaultNamedCacheFactory(metricRegistry, this.nodeConfiguration)
override fun bindWithConfig(nodeConfiguration: NodeConfiguration): BindableNamedCacheFactory = DefaultNamedCacheFactory(this.metricRegistry, nodeConfiguration)
override fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String): Cache<K, V> {
open protected fun <K, V> configuredForNamed(caffeine: Caffeine<K, V>, name: String): Caffeine<K, V> {
return with(nodeConfiguration!!) {
when {
name.startsWith("RPCSecurityManagerShiroCache_") -> with(security?.authService?.options?.cache!!) { caffeine.maximumSize(maxEntries).expireAfterWrite(expireAfterSecs, TimeUnit.SECONDS) }
name == "RPCServer_observableSubscription" -> caffeine
name == "RpcClientProxyHandler_rpcObservable" -> caffeine
name == "SerializationScheme_attachmentClassloader" -> caffeine
name == "HibernateConfiguration_sessionFactories" -> caffeine.maximumSize(database.mappedSchemaCacheSize)
name == "DBTransactionStorage_transactions" -> caffeine.maximumWeight(transactionCacheSizeBytes)
name == "NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(attachmentContentCacheSizeBytes)
name == "NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(attachmentCacheBound)
name == "PersistentIdentityService_partyByKey" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentIdentityService_partyByName" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentNetworkMap_nodesByKey" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentNetworkMap_idByLegalName" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentKeyManagementService_keys" -> caffeine.maximumSize(defaultCacheSize)
name == "FlowDrainingMode_nodeProperties" -> caffeine.maximumSize(defaultCacheSize)
name == "ContractUpgradeService_upgrades" -> caffeine.maximumSize(defaultCacheSize)
name == "PersistentUniquenessProvider_transactions" -> caffeine.maximumSize(defaultCacheSize)
name == "P2PMessageDeduplicator_processedMessages" -> caffeine.maximumSize(defaultCacheSize)
name == "DeduplicationChecker_watermark" -> caffeine
name == "BFTNonValidatingNotaryService_transactions" -> caffeine.maximumSize(defaultCacheSize)
name == "RaftUniquenessProvider_transactions" -> caffeine.maximumSize(defaultCacheSize)
else -> throw IllegalArgumentException("Unexpected cache name $name. Did you add a new cache?")
}
}
}
protected fun checkState(name: String) {
checkCacheName(name)
checkNotNull(metricRegistry)
checkNotNull(nodeConfiguration)
return caffeine.maximumSize(1024).buildNamed<K, V>(name)
}
override fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String): Cache<K, V> {
checkState(name)
return configuredForNamed(caffeine, name).build<K, V>()
}
override fun <K, V> buildNamed(caffeine: Caffeine<in K, in V>, name: String, loader: CacheLoader<K, V>): LoadingCache<K, V> {
checkNotNull(metricRegistry)
checkNotNull(nodeConfiguration)
val configuredCaffeine = when (name) {
"DBTransactionStorage_transactions" -> caffeine.maximumWeight(nodeConfiguration!!.transactionCacheSizeBytes)
"NodeAttachmentService_attachmentContent" -> caffeine.maximumWeight(nodeConfiguration!!.attachmentContentCacheSizeBytes)
"NodeAttachmentService_attachmentPresence" -> caffeine.maximumSize(nodeConfiguration!!.attachmentCacheBound)
else -> caffeine.maximumSize(1024)
}
return configuredCaffeine.buildNamed<K, V>(name, loader)
checkState(name)
return configuredForNamed(caffeine, name).build<K, V>(loader)
}
open protected val defaultCacheSize = 1024L
}

View File

@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.CacheLoader
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.LoadingCache
import com.github.benmanes.caffeine.cache.Weigher
import net.corda.core.internal.NamedCacheFactory
class NonInvalidatingCache<K, V> private constructor(
val cache: LoadingCache<K, V>

View File

@ -5,21 +5,21 @@ import com.github.benmanes.caffeine.cache.CacheLoader
import com.github.benmanes.caffeine.cache.Caffeine
import com.github.benmanes.caffeine.cache.LoadingCache
import com.github.benmanes.caffeine.cache.RemovalListener
import net.corda.core.internal.buildNamed
import net.corda.core.internal.NamedCacheFactory
class NonInvalidatingUnboundCache<K, V> private constructor(
val cache: LoadingCache<K, V>
) : LoadingCache<K, V> by cache {
constructor(name: String, loadFunction: (K) -> V, removalListener: RemovalListener<K, V> = RemovalListener { _, _, _ -> },
constructor(name: String, cacheFactory: NamedCacheFactory, loadFunction: (K) -> V, removalListener: RemovalListener<K, V> = RemovalListener { _, _, _ -> },
keysToPreload: () -> Iterable<K> = { emptyList() }) :
this(buildCache(name, loadFunction, removalListener, keysToPreload))
this(buildCache(name, cacheFactory, loadFunction, removalListener, keysToPreload))
private companion object {
private fun <K, V> buildCache(name: String, loadFunction: (K) -> V, removalListener: RemovalListener<K, V>,
private fun <K, V> buildCache(name: String, cacheFactory: NamedCacheFactory, loadFunction: (K) -> V, removalListener: RemovalListener<K, V>,
keysToPreload: () -> Iterable<K>): LoadingCache<K, V> {
val builder = Caffeine.newBuilder().removalListener(removalListener).executor(SameThreadExecutor.getExecutor())
return builder.buildNamed(name, NonInvalidatingCacheLoader(loadFunction)).apply {
return cacheFactory.buildNamed(builder, name, NonInvalidatingCacheLoader(loadFunction)).apply {
getAll(keysToPreload())
}
}

View File

@ -2,6 +2,7 @@ package net.corda.node.utilities
import com.github.benmanes.caffeine.cache.RemovalCause
import com.github.benmanes.caffeine.cache.RemovalListener
import net.corda.core.internal.NamedCacheFactory
import net.corda.core.utilities.contextLogger
import net.corda.nodeapi.internal.persistence.currentDBSession
import java.util.*
@ -14,7 +15,8 @@ class PersistentMap<K : Any, V, E, out EK>(
val toPersistentEntityKey: (K) -> EK,
val fromPersistentEntity: (E) -> Pair<K, V>,
val toPersistentEntity: (key: K, value: V) -> E,
val persistentEntityClass: Class<E>
val persistentEntityClass: Class<E>,
cacheFactory: NamedCacheFactory
) : MutableMap<K, V>, AbstractMap<K, V>() {
private companion object {
@ -24,7 +26,8 @@ class PersistentMap<K : Any, V, E, out EK>(
private val cache = NonInvalidatingUnboundCache(
name,
loadFunction = { key -> Optional.ofNullable(loadValue(key)) },
removalListener = ExplicitRemoval(toPersistentEntityKey, persistentEntityClass)
removalListener = ExplicitRemoval(toPersistentEntityKey, persistentEntityClass),
cacheFactory = cacheFactory
)
/** Preload to allow [all] to take data only from the cache (cache is unbound) */

View File

@ -6,6 +6,7 @@ import net.corda.core.internal.post
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.seconds
import net.corda.node.VersionInfo
import net.corda.node.services.config.NetworkServicesConfig
import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import okhttp3.CacheControl
import okhttp3.Headers
@ -19,8 +20,11 @@ import java.util.*
import java.util.zip.ZipInputStream
import javax.naming.ServiceUnavailableException
class HTTPNetworkRegistrationService(compatibilityZoneURL: URL, val versionInfo: VersionInfo) : NetworkRegistrationService {
private val registrationURL = URL("$compatibilityZoneURL/certificate")
class HTTPNetworkRegistrationService(
val config : NetworkServicesConfig,
val versionInfo: VersionInfo
) : NetworkRegistrationService {
private val registrationURL = URL("${config.doormanURL}/certificate")
companion object {
private val TRANSIENT_ERROR_STATUS_CODES = setOf(HTTP_BAD_GATEWAY, HTTP_UNAVAILABLE, HTTP_GATEWAY_TIMEOUT)
@ -54,7 +58,8 @@ class HTTPNetworkRegistrationService(compatibilityZoneURL: URL, val versionInfo:
override fun submitRequest(request: PKCS10CertificationRequest): String {
return String(registrationURL.post(OpaqueBytes(request.encoded),
"Platform-Version" to "${versionInfo.platformVersion}",
"Client-Version" to versionInfo.releaseVersion))
"Client-Version" to versionInfo.releaseVersion,
"Private-Network-Map" to (config.pnm?.toString() ?: "")))
}
}

View File

@ -96,10 +96,9 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
val requestId = try {
submitOrResumeCertificateSigningRequest(keyPair)
} catch (e: Exception) {
if (e is ConnectException || e is ServiceUnavailableException || e is IOException) {
throw NodeRegistrationException(e)
}
throw e
throw if (e is ConnectException || e is ServiceUnavailableException || e is IOException) {
NodeRegistrationException(e.message, e)
} else e
}
val certificates = try {
@ -162,7 +161,7 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
println("Private key '$keyAlias' and certificate stored in node signing keystore.")
}
private fun CertificateStore.loadOrCreateKeyPair(alias: String, privateKeyPassword: String = password): KeyPair {
private fun CertificateStore.loadOrCreateKeyPair(alias: String, entryPassword: String = password): KeyPair {
// Create or load self signed keypair from the key store.
// We use the self sign certificate to store the key temporarily in the keystore while waiting for the request approval.
if (alias !in this) {
@ -171,11 +170,11 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
val selfSignCert = X509Utilities.createSelfSignedCACertificate(myLegalName.x500Principal, keyPair)
// Save to the key store.
with(value) {
setPrivateKey(alias, keyPair.private, listOf(selfSignCert), keyPassword = privateKeyPassword)
setPrivateKey(alias, keyPair.private, listOf(selfSignCert), keyPassword = entryPassword)
save()
}
}
return query { getCertificateAndKeyPair(alias, privateKeyPassword) }.keyPair
return query { getCertificateAndKeyPair(alias, entryPassword) }.keyPair
}
/**
@ -200,7 +199,8 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
if (idlePeriodDuration != null) {
Thread.sleep(idlePeriodDuration.toMillis())
} else {
throw NodeRegistrationException(e)
throw NodeRegistrationException("Compatibility Zone registration service is currently unavailable, "
+ "try again later!.", e)
}
}
}
@ -249,10 +249,17 @@ open class NetworkRegistrationHelper(private val certificatesDirectory: Path,
protected open fun isTlsCrlIssuerCertRequired(): Boolean = false
}
class NodeRegistrationException(cause: Throwable?) : IOException("Unable to contact node registration service", cause)
class NodeRegistrationException(
message: String?,
cause: Throwable?
) : IOException(message ?: "Unable to contact node registration service", cause)
class NodeRegistrationHelper(private val config: NodeConfiguration, certService: NetworkRegistrationService, regConfig: NodeRegistrationOption, computeNextIdleDoormanConnectionPollInterval: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))) :
NetworkRegistrationHelper(
class NodeRegistrationHelper(
private val config: NodeConfiguration,
certService: NetworkRegistrationService,
regConfig: NodeRegistrationOption,
computeNextIdleDoormanConnectionPollInterval: (Duration?) -> Duration? = FixedPeriodLimitedRetrialStrategy(10, Duration.ofMinutes(1))
) : NetworkRegistrationHelper(
config.certificatesDirectory,
config.signingCertificateStore,
config.myLegalName,
@ -274,7 +281,9 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
}
private fun createSSLKeystore(nodeCAKeyPair: KeyPair, certificates: List<X509Certificate>, tlsCertCrlIssuer: X500Name?) {
config.p2pSslOptions.keyStore.get(createNew = true).update {
val keyStore = config.p2pSslOptions.keyStore
val certificateStore = keyStore.get(createNew = true)
certificateStore.update {
println("Generating SSL certificate for node messaging service.")
val sslKeyPair = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
val sslCert = X509Utilities.createCertificate(
@ -286,9 +295,9 @@ class NodeRegistrationHelper(private val config: NodeConfiguration, certService:
crlDistPoint = config.tlsCertCrlDistPoint?.toString(),
crlIssuer = tlsCertCrlIssuer)
logger.info("Generated TLS certificate: $sslCert")
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates)
setPrivateKey(CORDA_CLIENT_TLS, sslKeyPair.private, listOf(sslCert) + certificates, certificateStore.entryPassword)
}
println("SSL private key and certificate stored in ${config.p2pSslOptions.keyStore.path}.")
println("SSL private key and certificate stored in ${keyStore.path}.")
}
private fun createTruststore(rootCertificate: X509Certificate) {

View File

@ -5,17 +5,6 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"
logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="1511451595465-6">
<createTable tableName="node_bft_committed_states">
<column name="output_index" type="INT">
<constraints nullable="false"/>
</column>
<column name="transaction_id" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="consuming_transaction_id" type="NVARCHAR(64)"/>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1511451595465-16">
<createTable tableName="node_notary_committed_states">
<column name="output_index" type="INT">
@ -27,18 +16,6 @@
<column name="consuming_transaction_id" type="NVARCHAR(64)"/>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1511451595465-18">
<createTable tableName="node_raft_committed_states">
<column name="transaction_id" type="NVARCHAR(64)">
<constraints nullable="false"/>
</column>
<column name="output_index" type="INT">
<constraints nullable="false"/>
</column>
<column name="raft_log_index" type="BIGINT"/>
<column name="consuming_transaction_id" type="NVARCHAR(64)"/>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1521131680317-17">
<createTable tableName="node_notary_request_log">
<column name="id" type="INT">
@ -58,18 +35,11 @@
</column>
</createTable>
</changeSet>
<changeSet author="R3.Corda" id="1511451595465-31">
<addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_bft_states_pkey"
tableName="node_bft_committed_states"/>
</changeSet>
<changeSet author="R3.Corda" id="1511451595465-41">
<addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_notary_states_pkey"
tableName="node_notary_committed_states"/>
</changeSet>
<changeSet author="R3.Corda" id="1511451595465-43">
<addPrimaryKey columnNames="output_index, transaction_id" constraintName="node_raft_state_pkey"
tableName="node_raft_committed_states"/>
</changeSet>
<changeSet author="R3.Corda" id="1521131680317-48">
<addPrimaryKey columnNames="id" constraintName="node_notary_request_log_pkey"
tableName="node_notary_request_log"/>

View File

@ -8,14 +8,4 @@
<addPrimaryKey tableName="node_notary_committed_states" columnNames="output_index, transaction_id"
constraintName="node_notary_states_pkey" clustered="false"/>
</changeSet>
<changeSet id="non-clustered_pk-bft_stae" author="R3.Corda" onValidationFail="MARK_RAN">
<dropPrimaryKey tableName="node_bft_committed_states" constraintName="node_bft_states_pkey"/>
<addPrimaryKey tableName="node_bft_committed_states" columnNames="output_index, transaction_id"
constraintName="node_bft_states_pkey" clustered="false"/>
</changeSet>
<changeSet id="non-clustered_pk-raft_state" author="R3.Corda" onValidationFail="MARK_RAN">
<dropPrimaryKey tableName="node_raft_committed_states" constraintName="node_raft_state_pkey"/>
<addPrimaryKey tableName="node_raft_committed_states" columnNames="output_index, transaction_id"
constraintName="node_raft_state_pkey" clustered="false"/>
</changeSet>
</databaseChangeLog>

View File

@ -6,11 +6,6 @@
logicalFilePath="migration/node-services.changelog-init.xml">
<changeSet author="R3.Corda" id="nullability">
<addNotNullConstraint tableName="node_bft_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/>
<addNotNullConstraint tableName="node_notary_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/>
<addNotNullConstraint tableName="node_raft_committed_states" columnName="raft_log_index" columnDataType="BIGINT"/>
<addNotNullConstraint tableName="node_raft_committed_states" columnName="consuming_transaction_id" columnDataType="NVARCHAR(64)"/>
</changeSet>
</databaseChangeLog>

View File

@ -9,4 +9,6 @@
<include file="migration/vault-schema.changelog-v4.xml"/>
<include file="migration/vault-schema.changelog-pkey.xml"/>
<include file="migration/vault-schema.changelog-v5.xml"/>
<include file="migration/vault-schema.changelog-v6.xml"/>
<include file="migration/vault-schema.changelog-v7.xml"/>
</databaseChangeLog>

View File

@ -0,0 +1,17 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="R3.Corda" id="add_is_constraint_information_columns">
<addColumn tableName="vault_states">
<column name="constraint_type" type="INT" defaultValue="0">
<constraints nullable="false"/>
</column>
</addColumn>
<addColumn tableName="vault_states">
<column name="constraint_data" type="varbinary(563)">
<constraints nullable="true"/>
</column>
</addColumn>
</changeSet>
</databaseChangeLog>

View File

@ -0,0 +1,8 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="R3.Corda" id="make_issuer_ref_nullable">
<dropNotNullConstraint columnDataType="varbinary(512)" columnName="issuer_ref" tableName="vault_fungible_states"/>
</changeSet>
</databaseChangeLog>

View File

@ -6,11 +6,6 @@
required: false
multiParam: true
acceptableValues: []
- parameterName: "--bootstrap-raft-cluster"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "--clear-network-map-cache"
parameterType: "boolean"
required: false
@ -31,11 +26,6 @@
required: false
multiParam: false
acceptableValues: []
- parameterName: "--install-shell-extensions"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "--just-generate-node-info"
parameterType: "boolean"
required: false
@ -104,11 +94,6 @@
required: false
multiParam: true
acceptableValues: []
- parameterName: "-c"
parameterType: "boolean"
required: false
multiParam: false
acceptableValues: []
- parameterName: "-d"
parameterType: "java.lang.Boolean"
required: false

View File

@ -1,118 +0,0 @@
# Copyright (c) 2007-2013 Alysson Bessani, Eduardo Alchieri, Paulo Sousa, and the authors indicated in the @author tags
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
############################################
####### Communication Configurations #######
############################################
#HMAC algorithm used to authenticate messages between processes (HmacMD5 is the default value)
#This parameter is not currently being used being used
#system.authentication.hmacAlgorithm = HmacSHA1
#Specify if the communication system should use a thread to send data (true or false)
system.communication.useSenderThread = true
#Force all processes to use the same public/private keys pair and secret key. This is useful when deploying experiments
#and benchmarks, but must not be used in production systems.
system.communication.defaultkeys = true
############################################
### Replication Algorithm Configurations ###
############################################
#Number of servers in the group
system.servers.num = %s
#Maximum number of faulty replicas
system.servers.f = %s
#Timeout to asking for a client request
system.totalordermulticast.timeout = 2000
#Maximum batch size (in number of messages)
system.totalordermulticast.maxbatchsize = 400
#Number of nonces (for non-determinism actions) generated
system.totalordermulticast.nonces = 10
#if verification of leader-generated timestamps are increasing
#it can only be used on systems in which the network clocks
#are synchronized
system.totalordermulticast.verifyTimestamps = false
#Quantity of messages that can be stored in the receive queue of the communication system
system.communication.inQueueSize = 500000
# Quantity of messages that can be stored in the send queue of each replica
system.communication.outQueueSize = 500000
#Set to 1 if SMaRt should use signatures, set to 0 if otherwise
system.communication.useSignatures = 0
#Set to 1 if SMaRt should use MAC's, set to 0 if otherwise
system.communication.useMACs = 1
#Set to 1 if SMaRt should use the standard output to display debug messages, set to 0 if otherwise
system.debug = %s
#Print information about the replica when it is shutdown
system.shutdownhook = true
############################################
###### State Transfer Configurations #######
############################################
#Activate the state transfer protocol ('true' to activate, 'false' to de-activate)
system.totalordermulticast.state_transfer = false
#Maximum ahead-of-time message not discarded
system.totalordermulticast.highMark = 10000
#Maximum ahead-of-time message not discarded when the replica is still on EID 0 (after which the state transfer is triggered)
system.totalordermulticast.revival_highMark = 10
#Number of ahead-of-time messages necessary to trigger the state transfer after a request timeout occurs
system.totalordermulticast.timeout_highMark = 200
############################################
###### Log and Checkpoint Configurations ###
############################################
system.totalordermulticast.log = false
system.totalordermulticast.log_parallel = false
system.totalordermulticast.log_to_disk = false
system.totalordermulticast.sync_log = false
#Period at which BFT-SMaRt requests the state to the application (for the state transfer state protocol)
system.totalordermulticast.checkpoint_period = 1
system.totalordermulticast.global_checkpoint_period = 1
system.totalordermulticast.checkpoint_to_disk = false
system.totalordermulticast.sync_ckp = false
############################################
###### Reconfiguration Configurations ######
############################################
#Replicas ID for the initial view, separated by a comma.
# The number of replicas in this parameter should be equal to that specified in 'system.servers.num'
system.initial.view = %s
#The ID of the trust third party (TTP)
system.ttp.id = 7002
#This sets if the system will function in Byzantine or crash-only mode. Set to "true" to support Byzantine faults
system.bft = true

View File

@ -19,7 +19,7 @@ import net.corda.core.node.services.vault.QueryCriteria.VaultQueryCriteria;
import net.corda.finance.contracts.DealState;
import net.corda.finance.contracts.asset.Cash;
import net.corda.finance.schemas.CashSchemaV1;
import net.corda.finance.schemas.SampleCashSchemaV2;
import net.corda.finance.schemas.test.SampleCashSchemaV2;
import net.corda.node.services.api.IdentityServiceInternal;
import net.corda.nodeapi.internal.persistence.CordaPersistence;
import net.corda.nodeapi.internal.persistence.DatabaseTransaction;

View File

@ -38,10 +38,11 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.expect
import net.corda.testing.core.expectEvents
import net.corda.testing.core.sequence
import net.corda.testing.node.internal.cordappsForPackages
import net.corda.testing.internal.fromUserList
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.cordappsForPackages
import net.corda.testing.node.testActor
import org.apache.commons.io.IOUtils
import org.assertj.core.api.Assertions.*

View File

@ -7,7 +7,7 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.getOrThrow
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.common.internal.relaxedThoroughness
import net.corda.testing.internal.rigorousMock
import net.corda.testing.internal.configureDatabase
import net.corda.testing.node.internal.ProcessUtilities.startJavaProcess
import org.junit.Rule
import org.junit.Test

View File

@ -12,7 +12,6 @@ import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.StartedMockNode
import org.assertj.core.api.Assertions.assertThatIllegalStateException
import org.junit.After
import org.junit.Before
import org.junit.Test
@ -39,15 +38,15 @@ class FlowRegistrationTest {
}
@Test
fun `startup fails when two flows initiated by the same flow are registered`() {
fun `succeeds when a subclass of a flow initiated by the same flow is registered`() {
// register the same flow twice to invoke the error without causing errors in other tests
responder.registerInitiatedFlow(Responder::class.java)
assertThatIllegalStateException().isThrownBy { responder.registerInitiatedFlow(Responder::class.java) }
responder.registerInitiatedFlow(Responder1::class.java)
responder.registerInitiatedFlow(Responder1Subclassed::class.java)
}
@Test
fun `a single initiated flow can be registered without error`() {
responder.registerInitiatedFlow(Responder::class.java)
responder.registerInitiatedFlow(Responder1::class.java)
val result = initiator.startFlow(Initiator(responder.info.singleIdentity()))
mockNetwork.runNetwork()
assertNotNull(result.get())
@ -63,7 +62,38 @@ class Initiator(val party: Party) : FlowLogic<String>() {
}
@InitiatedBy(Initiator::class)
private class Responder(val session: FlowSession) : FlowLogic<Unit>() {
private open class Responder1(val session: FlowSession) : FlowLogic<Unit>() {
open fun getPayload(): String {
return "whats up"
}
@Suspendable
override fun call() {
session.receive<String>().unwrap { it }
session.send("What's up")
}
}
@InitiatedBy(Initiator::class)
private open class Responder2(val session: FlowSession) : FlowLogic<Unit>() {
open fun getPayload(): String {
return "whats up"
}
@Suspendable
override fun call() {
session.receive<String>().unwrap { it }
session.send("What's up")
}
}
@InitiatedBy(Initiator::class)
private class Responder1Subclassed(session: FlowSession) : Responder1(session) {
override fun getPayload(): String {
return "im subclassed! that's what's up!"
}
@Suspendable
override fun call() {
session.receive<String>().unwrap { it }

View File

@ -0,0 +1,110 @@
package net.corda.node.internal
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.node.services.config.FlowOverride
import net.corda.node.services.config.FlowOverrideConfig
import org.hamcrest.CoreMatchers.`is`
import org.hamcrest.CoreMatchers.instanceOf
import org.junit.Assert
import org.junit.Test
import org.mockito.Mockito
import java.lang.IllegalStateException
private val marker = "This is a special marker"
class NodeFlowManagerTest {
@InitiatingFlow
class Init : FlowLogic<Unit>() {
override fun call() {
TODO("not implemented")
}
}
@InitiatedBy(Init::class)
open class Resp(val otherSesh: FlowSession) : FlowLogic<Unit>() {
override fun call() {
TODO("not implemented")
}
}
@InitiatedBy(Init::class)
class Resp2(val otherSesh: FlowSession) : FlowLogic<Unit>() {
override fun call() {
TODO("not implemented")
}
}
@InitiatedBy(Init::class)
open class RespSub(sesh: FlowSession) : Resp(sesh) {
override fun call() {
TODO("not implemented")
}
}
@InitiatedBy(Init::class)
class RespSubSub(sesh: FlowSession) : RespSub(sesh) {
override fun call() {
TODO("not implemented")
}
}
@Test(expected = IllegalStateException::class)
fun `should fail to validate if more than one registration with equal weight`() {
val nodeFlowManager = NodeFlowManager()
nodeFlowManager.registerInitiatedFlow(Init::class.java, Resp::class.java)
nodeFlowManager.registerInitiatedFlow(Init::class.java, Resp2::class.java)
nodeFlowManager.validateRegistrations()
}
@Test()
fun `should allow registration of flows with different weights`() {
val nodeFlowManager = NodeFlowManager()
nodeFlowManager.registerInitiatedFlow(Init::class.java, Resp::class.java)
nodeFlowManager.registerInitiatedFlow(Init::class.java, RespSub::class.java)
nodeFlowManager.validateRegistrations()
val factory = nodeFlowManager.getFlowFactoryForInitiatingFlow(Init::class.java)!!
val flow = factory.createFlow(Mockito.mock(FlowSession::class.java))
Assert.assertThat(flow, `is`(instanceOf(RespSub::class.java)))
}
@Test()
fun `should allow updating of registered responder at runtime`() {
val nodeFlowManager = NodeFlowManager()
nodeFlowManager.registerInitiatedFlow(Init::class.java, Resp::class.java)
nodeFlowManager.registerInitiatedFlow(Init::class.java, RespSub::class.java)
nodeFlowManager.validateRegistrations()
var factory = nodeFlowManager.getFlowFactoryForInitiatingFlow(Init::class.java)!!
var flow = factory.createFlow(Mockito.mock(FlowSession::class.java))
Assert.assertThat(flow, `is`(instanceOf(RespSub::class.java)))
// update
nodeFlowManager.registerInitiatedFlow(Init::class.java, RespSubSub::class.java)
nodeFlowManager.validateRegistrations()
factory = nodeFlowManager.getFlowFactoryForInitiatingFlow(Init::class.java)!!
flow = factory.createFlow(Mockito.mock(FlowSession::class.java))
Assert.assertThat(flow, `is`(instanceOf(RespSubSub::class.java)))
}
@Test
fun `should allow an override to be specified`() {
val nodeFlowManager = NodeFlowManager(FlowOverrideConfig(listOf(FlowOverride(Init::class.qualifiedName!!, Resp::class.qualifiedName!!))))
nodeFlowManager.registerInitiatedFlow(Init::class.java, Resp::class.java)
nodeFlowManager.registerInitiatedFlow(Init::class.java, Resp2::class.java)
nodeFlowManager.registerInitiatedFlow(Init::class.java, RespSubSub::class.java)
nodeFlowManager.validateRegistrations()
val factory = nodeFlowManager.getFlowFactoryForInitiatingFlow(Init::class.java)!!
val flow = factory.createFlow(Mockito.mock(FlowSession::class.java))
Assert.assertThat(flow, `is`(instanceOf(Resp::class.java)))
}
}

View File

@ -2,4 +2,4 @@ package net.corda.node.internal
import net.corda.testing.CliBackwardsCompatibleTest
class NodeStartupCompatibilityTest : CliBackwardsCompatibleTest(NodeStartup::class.java)
class NodeStartupCompatibilityTest : CliBackwardsCompatibleTest(NodeStartupCli::class.java)

View File

@ -1,6 +1,8 @@
package net.corda.node.internal
import net.corda.core.internal.div
import net.corda.node.InitialRegistrationCmdLineOptions
import net.corda.node.internal.subcommands.InitialRegistrationCli
import net.corda.nodeapi.internal.config.UnknownConfigKeysPolicy
import org.assertj.core.api.Assertions.assertThat
import org.junit.BeforeClass
@ -11,7 +13,7 @@ import java.nio.file.Path
import java.nio.file.Paths
class NodeStartupTest {
private val startup = NodeStartup()
private val startup = NodeStartupCli()
companion object {
private lateinit var workingDirectory: Path
@ -30,16 +32,14 @@ class NodeStartupTest {
assertThat(startup.cmdLineOptions.configFile).isEqualTo(workingDirectory / "node.conf")
assertThat(startup.verbose).isEqualTo(false)
assertThat(startup.loggingLevel).isEqualTo(Level.INFO)
assertThat(startup.cmdLineOptions.nodeRegistrationOption).isEqualTo(null)
assertThat(startup.cmdLineOptions.noLocalShell).isEqualTo(false)
assertThat(startup.cmdLineOptions.sshdServer).isEqualTo(false)
assertThat(startup.cmdLineOptions.justGenerateNodeInfo).isEqualTo(false)
assertThat(startup.cmdLineOptions.justGenerateRpcSslCerts).isEqualTo(false)
assertThat(startup.cmdLineOptions.bootstrapRaftCluster).isEqualTo(false)
assertThat(startup.cmdLineOptions.unknownConfigKeysPolicy).isEqualTo(UnknownConfigKeysPolicy.FAIL)
assertThat(startup.cmdLineOptions.devMode).isEqualTo(null)
assertThat(startup.cmdLineOptions.clearNetworkMapCache).isEqualTo(false)
assertThat(startup.cmdLineOptions.networkRootTrustStorePath).isEqualTo(workingDirectory / "certificates" / "network-root-truststore.jks")
assertThat(startup.cmdLineOptions.networkRootTrustStorePathParameter).isEqualTo(null)
}
@Test
@ -47,6 +47,6 @@ class NodeStartupTest {
CommandLine.populateCommand(startup, "--base-directory", (workingDirectory / "another-base-dir").toString())
assertThat(startup.cmdLineOptions.baseDirectory).isEqualTo(workingDirectory / "another-base-dir")
assertThat(startup.cmdLineOptions.configFile).isEqualTo(workingDirectory / "another-base-dir" / "node.conf")
assertThat(startup.cmdLineOptions.networkRootTrustStorePath).isEqualTo(workingDirectory / "another-base-dir" / "certificates" / "network-root-truststore.jks")
assertThat(startup.cmdLineOptions.networkRootTrustStorePathParameter).isEqualTo(null)
}
}

View File

@ -18,6 +18,7 @@ import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.internal.configureDatabase
import net.corda.testing.internal.createNodeInfoAndSigned
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
@ -148,7 +149,7 @@ class NodeTest {
}
}
private fun createConfig(nodeName: CordaX500Name): NodeConfiguration {
private fun createConfig(nodeName: CordaX500Name): NodeConfigurationImpl {
val fakeAddress = NetworkHostAndPort("0.1.2.3", 456)
return NodeConfigurationImpl(
baseDirectory = temporaryFolder.root.toPath(),
@ -166,7 +167,8 @@ class NodeTest {
flowTimeout = FlowTimeoutConfiguration(timeout = Duration.ZERO, backoffBase = 1.0, maxRestartCount = 1),
rpcSettings = NodeRpcSettings(address = fakeAddress, adminAddress = null, ssl = null),
messagingServerAddress = null,
notary = null
notary = null,
flowOverrides = FlowOverrideConfig(listOf())
)
}

Some files were not shown because too many files have changed in this diff Show More